From 96f5586375fcf83437f195f74f51fbe352e1fb97 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 20 Feb 2025 02:59:30 +0900 Subject: [PATCH 001/663] WIP: wasm version does not handle password error correctly --- analysis_options.yaml | 2 +- example/viewer/pubspec.lock | 7 ++++ example/viewer/pubspec.yaml | 4 +-- lib/src/pdf_api.dart | 16 ++++----- lib/src/web/pdfrx_wasm.dart | 27 +++++++------- lib/src/widgets/pdf_text_searcher.dart | 2 +- test/pdf_document_test.dart | 12 +++---- test/pdf_viewer_test.dart | 38 ++++++++------------ test/setup.dart | 15 ++++---- wasm/pdfrx_wasm/analysis_options.yaml | 47 ------------------------- wasm/pdfrx_wasm/assets/pdfium_client.js | 2 +- wasm/pdfrx_wasm/assets/pdfium_worker.js | 17 ++++++--- 12 files changed, 73 insertions(+), 116 deletions(-) delete mode 100644 wasm/pdfrx_wasm/analysis_options.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml index 44ca9a1f..d85fd475 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,7 +23,7 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule prefer_single_quotes: true - comment_references: true + # comment_references: true prefer_relative_imports: true use_key_in_widget_constructors: true avoid_return_types_on_setters: true diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 5a5fe49b..03b64df2 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -316,6 +316,13 @@ packages: relative: true source: path version: "1.1.11" + pdfrx_wasm: + dependency: "direct main" + description: + path: "../../wasm/pdfrx_wasm" + relative: true + source: path + version: "1.1.6" platform: dependency: transitive description: diff --git a/example/viewer/pubspec.yaml b/example/viewer/pubspec.yaml index b19f2fe3..555536a2 100644 --- a/example/viewer/pubspec.yaml +++ b/example/viewer/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: pdfrx: path: ../../ - # pdfrx_wasm: - # path: ../../wasm/pdfrx_wasm/ + pdfrx_wasm: + path: ../../wasm/pdfrx_wasm/ cupertino_icons: ^1.0.8 rxdart: ^0.28.0 diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 67787683..349bef9a 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -158,8 +158,8 @@ abstract class PdfDocument { /// For Web, [filePath] can be relative path from `index.html` or any arbitrary URL but it may be restricted by CORS. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. static Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, @@ -173,8 +173,8 @@ abstract class PdfDocument { /// Opening the specified asset. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. static Future openAsset( String name, { PdfPasswordProvider? passwordProvider, @@ -219,8 +219,8 @@ abstract class PdfDocument { /// The default size is 1MB. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. /// /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. @@ -249,8 +249,8 @@ abstract class PdfDocument { /// For other platforms, it uses [pdfDocumentFromUri] that uses HTTP's range request to download the file. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. /// /// [progressCallback] is called when the download progress is updated (Not supported on Web). /// [reportCallback] is called when the download is completed (Not supported on Web). diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 470ee301..8cc6586b 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -181,24 +181,23 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactoryImpl { throw const PdfPasswordException('No password supplied by PasswordProvider.'); } } - try { - await _init(); - - return PdfDocumentWasm._( - await openDocument(password), - sourceName: sourceName, - disposeCallback: onDispose, - factory: factory, - ); - } catch (e) { - if (!_isPasswordError(e)) { - rethrow; + + await _init(); + + final result = await openDocument(password); + + const fpdfErrPassword = 4; + final errorCode = (result['errorCode'] as num?)?.toInt(); + if (errorCode != null) { + if (errorCode == fpdfErrPassword) { + continue; } + throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); } + + return PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose, factory: factory); } } - - static bool _isPasswordError(dynamic e) => e.toString().startsWith('PasswordException:'); } class PdfDocumentWasm extends PdfDocument { diff --git a/lib/src/widgets/pdf_text_searcher.dart b/lib/src/widgets/pdf_text_searcher.dart index 396c34b1..cee964d7 100644 --- a/lib/src/widgets/pdf_text_searcher.dart +++ b/lib/src/widgets/pdf_text_searcher.dart @@ -272,7 +272,7 @@ class PdfTextSearcher extends Listenable { void removeListener(VoidCallback listener) => _listeners.remove(listener); } -extension PatternExts on Pattern { +extension _PatternExts on Pattern { bool get isEmpty { switch (this) { case String s: diff --git a/test/pdf_document_test.dart b/test/pdf_document_test.dart index d27ede35..86b20a6c 100644 --- a/test/pdf_document_test.dart +++ b/test/pdf_document_test.dart @@ -13,18 +13,14 @@ final testPdfFile = File('example/viewer/assets/hello.pdf'); void main() { setUp(() => setup()); - test( - 'PdfDocument.openFile', - () async => - await testDocument(await PdfDocument.openFile(testPdfFile.path))); + test('PdfDocument.openFile', () async => await testDocument(await PdfDocument.openFile(testPdfFile.path))); test('PdfDocument.openData', () async { final data = await testPdfFile.readAsBytes(); await testDocument(await PdfDocument.openData(data)); }); test('PdfDocument.openUri', () async { - Pdfrx.createHttpClient = () => MockClient((request) async => - http.Response.bytes(await testPdfFile.readAsBytes(), 200)); - await testDocument( - await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf'))); + Pdfrx.createHttpClient = + () => MockClient((request) async => http.Response.bytes(await testPdfFile.readAsBytes(), 200)); + await testDocument(await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf'))); }); } diff --git a/test/pdf_viewer_test.dart b/test/pdf_viewer_test.dart index 5ec84678..2a5050b4 100644 --- a/test/pdf_viewer_test.dart +++ b/test/pdf_viewer_test.dart @@ -13,30 +13,22 @@ final binding = TestWidgetsFlutterBinding.ensureInitialized(); void main() { setUp(() => setup()); - Pdfrx.createHttpClient = () => MockClient( - (request) async { - return http.Response.bytes(await testPdfFile.readAsBytes(), 200); - }, - ); + Pdfrx.createHttpClient = + () => MockClient((request) async { + return http.Response.bytes(await testPdfFile.readAsBytes(), 200); + }); - testWidgets( - 'PdfViewer.uri', - (tester) async { - await binding.setSurfaceSize(Size(1080, 1920)); - await tester.pumpWidget( - MaterialApp( - // FIXME: Just a workaround for "A RenderFlex overflowed..." - home: SingleChildScrollView( - child: PdfViewer.uri( - Uri.parse('https://example.com/hello.pdf'), - ), - ), - ), - ); + testWidgets('PdfViewer.uri', (tester) async { + await binding.setSurfaceSize(Size(1080, 1920)); + await tester.pumpWidget( + MaterialApp( + // FIXME: Just a workaround for "A RenderFlex overflowed..." + home: SingleChildScrollView(child: PdfViewer.uri(Uri.parse('https://example.com/hello.pdf'))), + ), + ); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - expect(find.byType(PdfViewer), findsOneWidget); - }, - ); + expect(find.byType(PdfViewer), findsOneWidget); + }); } diff --git a/test/setup.dart b/test/setup.dart index cbdd6e25..ba9238f1 100644 --- a/test/setup.dart +++ b/test/setup.dart @@ -17,13 +17,16 @@ Future setup() async { TestWidgetsFlutterBinding.ensureInitialized(); const channel = MethodChannel('plugins.flutter.io/path_provider'); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, (methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( + methodCall, + ) async { return cacheRoot.path; }); try { await cacheRoot.delete(recursive: true); - } catch (e) {/**/} + } catch (e) { + /**/ + } } /// Downloads the pdfium module for the current platform and architecture. @@ -50,8 +53,7 @@ Future downloadAndGetPdfiumModulePath() async { } /// Downloads the pdfium module for the given platform and architecture. -Future _downloadPdfium( - String platform, String arch, String modulePath) async { +Future _downloadPdfium(String platform, String arch, String modulePath) async { final tmpDir = Directory('${tmpRoot.path}/$platform-$arch'); final targetPath = '${tmpDir.path}/$modulePath'; if (await File(targetPath).exists()) return targetPath; @@ -62,8 +64,7 @@ Future _downloadPdfium( if (tgz.statusCode != 200) { throw Exception('Failed to download pdfium: $uri'); } - final archive = - TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); + final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); try { await tmpDir.delete(recursive: true); } catch (_) {} diff --git a/wasm/pdfrx_wasm/analysis_options.yaml b/wasm/pdfrx_wasm/analysis_options.yaml deleted file mode 100644 index 44ca9a1f..00000000 --- a/wasm/pdfrx_wasm/analysis_options.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - prefer_single_quotes: true - comment_references: true - prefer_relative_imports: true - use_key_in_widget_constructors: true - avoid_return_types_on_setters: true - avoid_types_on_closure_parameters: true - eol_at_end_of_file: true - sort_child_properties_last: true - sort_unnamed_constructors_first: true - sort_constructors_first: true - always_put_required_named_parameters_first: true - invalid_runtime_check_with_js_interop_types: true - - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - -analyzer: - exclude: - - lib/src/pdfium/pdfium_bindings.dart - -formatter: - page_width: 120 diff --git a/wasm/pdfrx_wasm/assets/pdfium_client.js b/wasm/pdfrx_wasm/assets/pdfium_client.js index 2636dcec..7eec551e 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_client.js +++ b/wasm/pdfrx_wasm/assets/pdfium_client.js @@ -16,7 +16,7 @@ globalThis.pdfiumWasmSendCommand = function() { if (data.status === "success") { callback.resolve(data.result); } else { - callback.reject(new Error(data.error)); + callback.reject(new Error(data.error, data.cause != null ? { cause: data.cause } : undefined)); } callbacks.delete(data.id); } diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index 77e0204c..cade2f05 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -493,12 +493,12 @@ function loadDocumentFromData(params) { const sizeThreshold = 1024 * 1024; // 1MB if (data.byteLength < sizeThreshold) { - const passwordPtr = StringUtils.allocateUTF8(password); const buffer = Pdfium.wasmExports.malloc(data.byteLength); if (buffer === 0) { throw new Error("Failed to allocate memory for PDF data (${data.byteLength} bytes)"); } new Uint8Array(Pdfium.memory.buffer, buffer, data.byteLength).set(data); + const passwordPtr = StringUtils.allocateUTF8(password); const docHandle = Pdfium.wasmExports.FPDF_LoadMemDocument( buffer, data.byteLength, @@ -514,23 +514,32 @@ function loadDocumentFromData(params) { const fileNamePtr = StringUtils.allocateUTF8(tempFileName); const passwordPtr = StringUtils.allocateUTF8(password); const docHandle = Pdfium.wasmExports.FPDF_LoadDocument(fileNamePtr, passwordPtr); - StringUtils.freeUTF8(fileNamePtr); StringUtils.freeUTF8(passwordPtr); + StringUtils.freeUTF8(fileNamePtr); return _loadDocument(docHandle, () => fileSystem.unregisterFile(tempFileName)); + } /** @type {Object} */ const disposers = {}; +/** + * @typedef {{docHandle: number,permissions: number, securityHandlerRevision: number, pages: PdfPage[], formHandle: number, formInfo: number}} PdfDocument + * @typedef {{pageIndex: number, width: number, height: number, rotation: number}} PdfPage + * @typedef {{errorCode: number, errorCodeStr: string|undefined, message: string}} PdfError + */ + /** * @param {number} docHandle * @param {function():void} onDispose + * @returns {PdfDocument|PdfError} */ function _loadDocument(docHandle, onDispose) { try { if (!docHandle) { const error = Pdfium.wasmExports.FPDF_GetLastError(); - throw new Error(`Failed to load document from data (${_getErrorMessage(error)})`); + const errorStr = _errorMappings[error]; + return { errorCode: error, errorCodeStr: _errorMappings[error], message: `Failed to load document` }; } const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); @@ -543,7 +552,6 @@ function _loadDocument(docHandle, onDispose) { uint32[0] = 1; // version const formHandle = Pdfium.wasmExports.FPDFDOC_InitFormFillEnvironment(docHandle, formInfo); if (formHandle === 0) { - Pdfium.wasmExports.free(formInfo); formInfo = 0; } @@ -574,6 +582,7 @@ function _loadDocument(docHandle, onDispose) { formInfo: formInfo, }; } catch (e) { + Pdfium.wasmExports.free(formInfo); delete disposers[docHandle]; onDispose(); throw e; From b94b322f384875a49daed4fe4910497fca8fb40b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Mar 2025 03:16:13 +0900 Subject: [PATCH 002/663] WIP --- .gitignore | 2 ++ example/viewer/lib/main.dart | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f15d08a2..3f8f3a03 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ migrate_working_dir/ build/ /test/.tmp + +CLAUDE.md diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index d1fe7458..6f3451da 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -14,7 +14,7 @@ void main() { // NOTE: To enable bleeding-edge Pdfium WASM support on Flutter Web; // 1. add pdfrx_wasm to your pubspec.yaml's dependencies. // 2. uncomment the following line. - //Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; + Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; runApp(const MyApp()); } From 9a173bbfbeb86567595ded49812424d9cc783375 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Mar 2025 17:52:33 +0900 Subject: [PATCH 003/663] FIXED: WASM memory allocation issue --- wasm/pdfrx_wasm/assets/pdfium_worker.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index cade2f05..4f19476b 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -398,19 +398,20 @@ const emEnv = { _tzset_js: function() { }, emscripten_date_now: function() { return Date.now(); }, emscripten_errn: function() { _notImplemented('emscripten_errn'); }, - emscripten_resize_heap: function(requestedSize) { - const oldSize = Pdfium.memory.buffer.byteLength; - const maxHeapSize = 2 * 1024 * 1024 * 1024; // 2GB - const pageSize = 65536; - if (requestedSize > maxHeapSize) { - console.error(`emscripten_resize_heap: Cannot enlarge memory, asked for ${requestedSize} bytes but limit is ${maxHeapSize}`); + emscripten_resize_heap: function(requestedSizeInBytes) { + const maxHeapSizeInBytes = 2 * 1024 * 1024 * 1024; // 2GB + if (requestedSizeInBytes > maxHeapSizeInBytes) { + console.error(`emscripten_resize_heap: Cannot enlarge memory, asked for ${requestedPageCount} bytes but limit is ${maxHeapSizeInBytes}`); return false; } - let newSize = Math.max(oldSize * 1.5, requestedSize); - newSize = (newSize + pageSize - 1) & ~pageSize; + + const pageSize = 65536; + const oldPageCount = ((Pdfium.memory.buffer.byteLength + pageSize - 1) / pageSize)|0; + const requestedPageCount = ((requestedSizeInBytes + pageSize - 1) / pageSize)|0; + const newPageCount = Math.max(oldPageCount * 1.5, requestedPageCount) | 0; try { - Pdfium.memory.grow((newSize - oldSize) / pageSize); - console.log(`emscripten_resize_heap: ${oldSize} => ${newSize}`); + Pdfium.memory.grow(newPageCount - oldPageCount); + console.log(`emscripten_resize_heap: ${oldPageCount} => ${newPageCount}`); return true; } catch (e) { console.error(`emscripten_resize_heap: Failed to resize heap: ${_error(e)}`); @@ -709,7 +710,6 @@ function renderPage(params) { bufferPtr, width * 4 ); - if (!bitmap) { throw new Error("Failed to create bitmap for rendering"); } From 3b1e7eb5a7ebf2abbb1663d8f15053f007b734ec Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Mar 2025 21:14:01 +0900 Subject: [PATCH 004/663] FIXED: #326 --- wasm/pdfrx_wasm/assets/pdfium_worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index 4f19476b..bbb5348b 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -498,7 +498,7 @@ function loadDocumentFromData(params) { if (buffer === 0) { throw new Error("Failed to allocate memory for PDF data (${data.byteLength} bytes)"); } - new Uint8Array(Pdfium.memory.buffer, buffer, data.byteLength).set(data); + new Uint8Array(Pdfium.memory.buffer, buffer, data.byteLength).set(new Uint8Array(data)); const passwordPtr = StringUtils.allocateUTF8(password); const docHandle = Pdfium.wasmExports.FPDF_LoadMemDocument( buffer, From 1ca09a2b0afa51b8597217d0659368180b75d802 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Mar 2025 21:19:52 +0900 Subject: [PATCH 005/663] 1.1.12 --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6d8c10..c660e262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.12 + +- FIXED: WASM: could not open PDF files smaller than 1MB (#326) + # 1.1.11 - Color.withOpacity -> Color.withValues, Color.value -> Color.toARGB32() diff --git a/README.md b/README.md index d4a6b56d..4710ed2a 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.11 + pdfrx: ^1.1.12 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.11 + pdfrx: ^1.1.12 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 03b64df2..a7152108 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.11" + version: "1.1.12" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 791fcd56..f6ba3013 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.11 +version: 1.1.12 homepage: https://github.com/espresso3389/pdfrx environment: From e85bb769c4cd38dfa788a030a45cba2b5d7f0863 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Mar 2025 22:41:43 +0900 Subject: [PATCH 006/663] wasm 1.1.7 --- wasm/pdfrx_wasm/CHANGELOG.md | 5 +++++ wasm/pdfrx_wasm/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/wasm/pdfrx_wasm/CHANGELOG.md b/wasm/pdfrx_wasm/CHANGELOG.md index 29f648fd..8767693d 100644 --- a/wasm/pdfrx_wasm/CHANGELOG.md +++ b/wasm/pdfrx_wasm/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.7 + +* FIXED: WASM: could not open PDF files smaller than 1MB (#326) +* FIXED: WASM memory allocation issue + ## 0.0.1 * Initial release diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml index f92e1dca..8edcde3f 100644 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ b/wasm/pdfrx_wasm/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_wasm description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.6 +version: 1.1.7 homepage: https://github.com/espresso3389/pdfrx environment: From 76ce8395c352fd6859103eafcb37b4f69fa44f14 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 14 Mar 2025 11:12:19 +0900 Subject: [PATCH 007/663] For certain vital packages, remove explicit version specification. --- example/viewer/pubspec.lock | 10 +++++----- example/viewer/pubspec.yaml | 2 +- pubspec.yaml | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index a7152108..26968502 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -322,7 +322,7 @@ packages: path: "../../wasm/pdfrx_wasm" relative: true source: path - version: "1.1.6" + version: "1.1.7" platform: dependency: transitive description: @@ -388,10 +388,10 @@ packages: dependency: "direct main" description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.3.1" term_glyph: dependency: transitive description: @@ -500,10 +500,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" xdg_directories: dependency: transitive description: diff --git a/example/viewer/pubspec.yaml b/example/viewer/pubspec.yaml index 555536a2..d507dd6e 100644 --- a/example/viewer/pubspec.yaml +++ b/example/viewer/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: cupertino_icons: ^1.0.8 rxdart: ^0.28.0 url_launcher: ^6.3.1 - synchronized: ^3.3.0 + synchronized: ^3.3.1 file_selector: ^1.0.3 dev_dependencies: diff --git a/pubspec.yaml b/pubspec.yaml index f6ba3013..91d9a7f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,19 +8,19 @@ environment: flutter: ">=3.29.0" dependencies: - collection: '>=1.18.0 <1.20.0' + collection: crypto: ^3.0.6 - ffi: ^2.1.3 + ffi: flutter: sdk: flutter - http: ^1.2.2 - path: ^1.9.0 + http: + path: path_provider: ^2.1.5 - rxdart: '>=0.27.0 <0.29.0' - synchronized: ^3.3.0 + rxdart: + synchronized: ^3.3.1 url_launcher: ^6.3.1 vector_math: ^2.1.4 - web: ^1.1.0 + web: ^1.1.1 dev_dependencies: ffigen: ^16.1.0 From e738dccee80500de3ba8d9d74b6e52455d24ae95 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 17 Mar 2025 15:22:50 +0900 Subject: [PATCH 008/663] #311, #330 --- lib/src/pdfium/pdf_file_cache.dart | 50 ++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index 0cb8bab8..4216d26f 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; @@ -7,6 +8,7 @@ import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; +import 'package:synchronized/extension.dart'; import '../../pdfrx.dart'; import 'http_cache_control.dart'; @@ -98,7 +100,7 @@ class PdfFileCache { } static const header1Size = 16; - static const headerMagic = 23456; + static const headerMagic = 34567; static const dataStrSizeMax = 128; Future _saveCacheState() => _write(_cacheStatePosition!, _cacheState); @@ -235,6 +237,19 @@ class PdfFileCache { static Future Function() getCacheDirectory = getApplicationCacheDirectory; } +class _HttpClientWrapper { + _HttpClientWrapper(this.createHttpClient); + final http.Client Function() createHttpClient; + + http.Client? _client; + http.Client get client => _client ??= createHttpClient(); + + void reset() { + _client?.close(); + _client = null; + } +} + /// Open PDF file from [uri]. /// /// On web, unlike [PdfDocument.openUri], this function uses HTTP's range request to download the file and uses [PdfFileCache]. @@ -258,13 +273,13 @@ Future pdfDocumentFromUri( progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); - final httpClient = Pdfrx.createHttpClient?.call() ?? http.Client(); + final httpClientWrapper = _HttpClientWrapper(Pdfrx.createHttpClient ?? () => http.Client()); try { if (!cache.isInitialized) { cache.setBlockSize(blockSize ?? PdfFileCache.defaultBlockSize); final result = await _downloadBlock( - httpClient, + httpClientWrapper, uri, cache, progressCallback, @@ -285,7 +300,7 @@ Future pdfDocumentFromUri( // cache is valid; no need to download. } else { final result = await _downloadBlock( - httpClient, + httpClientWrapper, uri, cache, progressCallback, @@ -296,7 +311,7 @@ Future pdfDocumentFromUri( ); if (result.isFullDownload) { cache.close(); // close the cache file before opening it. - httpClient.close(); + httpClientWrapper.reset(); return await PdfDocument.openFile( cache.filePath, passwordProvider: passwordProvider, @@ -315,7 +330,7 @@ Future pdfDocumentFromUri( final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { - await _downloadBlock(httpClient, uri, cache, progressCallback, blockId, headers: headers); + await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers); } final readEnd = min(p + size, (blockId + 1) * cache.blockSize); final sizeToRead = readEnd - p; @@ -332,12 +347,12 @@ Future pdfDocumentFromUri( sourceName: uri.toString(), onDispose: () { cache!.close(); - httpClient.close(); + httpClientWrapper.reset(); }, ); } catch (e) { cache.close(); - httpClient.close(); + httpClientWrapper.reset(); rethrow; } finally { report(); @@ -363,7 +378,7 @@ class _DownloadResult { // Download blocks of the file and cache the data to file. Future<_DownloadResult> _downloadBlock( - http.Client httpClient, + _HttpClientWrapper httpClientWrapper, Uri uri, PdfFileCache cache, PdfDownloadProgressCallback? progressCallback, @@ -372,18 +387,26 @@ Future<_DownloadResult> _downloadBlock( bool addCacheControlHeaders = false, bool useRangeAccess = true, Map? headers, -}) async { +}) => httpClientWrapper.synchronized(() async { int? fileSize; final blockOffset = blockId * cache.blockSize; final end = blockOffset + cache.blockSize * blockCount; - final request = http.Request('GET', uri) ..headers.addAll({ if (useRangeAccess) 'Range': 'bytes=$blockOffset-${end - 1}', if (addCacheControlHeaders) ...cache.cacheControlState.getHeadersForFetch(), if (headers != null) ...headers, }); - final response = await httpClient.send(request); + late final http.StreamedResponse response; + try { + response = await httpClientWrapper.client.send(request).timeout(Duration(seconds: 5)); + } on TimeoutException { + httpClientWrapper.reset(); + rethrow; + } catch (e) { + httpClientWrapper.reset(); + throw PdfException('Failed to download PDF file: $e'); + } if (response.statusCode == 304) { return _DownloadResult(cache.fileSize, false, true); } @@ -422,10 +445,11 @@ Future<_DownloadResult> _downloadBlock( if (isFullDownload) { fileSize ??= cachedBytesSoFar; + await cache.initializeWithFileSize(fileSize, truncateExistingContent: false); await cache.setCached(0, lastBlock: cache.totalBlocks - 1); } else { await cache.setCached(blockId, lastBlock: blockId + blockCount - 1); } return _DownloadResult(fileSize!, isFullDownload, false); -} +}); From 80a7fc2560c1c2d05f92629d25ff877849a74335 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 17 Mar 2025 15:30:05 +0900 Subject: [PATCH 009/663] 1.1.13 --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c660e262..0ce7648b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.13 + +- Fix indefinite stack on loading PDF files from certain server; now it immediately return error (not actually fixed) (#311) +- FIXED: 2nd time loading of certain URL fails due to some cache error (#330) + # 1.1.12 - FIXED: WASM: could not open PDF files smaller than 1MB (#326) diff --git a/README.md b/README.md index 4710ed2a..5644355f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.12 + pdfrx: ^1.1.13 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.12 + pdfrx: ^1.1.13 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 26968502..150306db 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.12" + version: "1.1.13" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 91d9a7f3..d9b4070b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.12 +version: 1.1.13 homepage: https://github.com/espresso3389/pdfrx environment: From 9ccd5adbea0999c7f27cfc26e4fa34467f64a6bd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Mar 2025 00:57:16 +0900 Subject: [PATCH 010/663] WIP: #331 additional note for WASM module --- lib/src/pdf_api.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 349bef9a..a42a7c4a 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -14,7 +14,9 @@ import 'web/pdfrx_web.dart' if (dart.library.io) 'pdfium/pdfrx_pdfium.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. -abstract class Pdfrx { +class Pdfrx { + Pdfrx._(); + /// Explicitly specify pdfium module path for special purpose. /// /// It is not supported on Flutter Web. @@ -31,6 +33,11 @@ abstract class Pdfrx { static http.Client Function()? createHttpClient; /// Select the Web runtime type. + /// + /// To use PDFium (WASM) runtime, set this value to [PdfrxWebRuntimeType.pdfiumWasm] and you must add + /// [pdfrx_wasm](https://pub.dartlang.org/packages/pdfrx_wasm) to your `pubspec.yaml`'s `dependencies`. + /// + /// It is used only when on Flutter Web. static PdfrxWebRuntimeType webRuntimeType = PdfrxWebRuntimeType.pdfjs; /// To override the default pdfium WASM modules URL. From 399a409a21a4600b14bf8097af08343f442ac338 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Mar 2025 20:28:10 +0900 Subject: [PATCH 011/663] #331: Improve pdfium_worker.js/pdfium.wasm loading path resolution logic --- lib/src/pdf_api.dart | 3 +-- lib/src/web/pdfrx_wasm.dart | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index a42a7c4a..d8e4e250 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -40,9 +40,8 @@ class Pdfrx { /// It is used only when on Flutter Web. static PdfrxWebRuntimeType webRuntimeType = PdfrxWebRuntimeType.pdfjs; - /// To override the default pdfium WASM modules URL. + /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. /// - /// It should be full /// It is used only when on Flutter Web with [Pdfrx.webRuntimeType] is [PdfrxWebRuntimeType.pdfiumWasm]. static String? pdfiumWasmModulesUrl; } diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 8cc6586b..635d871d 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -56,10 +56,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactoryImpl { /// Ugly workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments String _getWorkerUrl() { - final moduleUrl = - Pdfrx.pdfiumWasmModulesUrl ?? '${_removeLastComponent(web.window.location.href)}$defaultWasmModulePath'; - final workerJsUrl = '${moduleUrl}pdfium_worker.js'; - final pdfiumWasmUrl = '${moduleUrl}pdfium.wasm'; + final moduleUrl = Pdfrx.pdfiumWasmModulesUrl ?? _appendComponents(web.window.location.href, defaultWasmModulePath); + final workerJsUrl = _appendComponents(moduleUrl, 'pdfium_worker.js'); + final pdfiumWasmUrl = _appendComponents(moduleUrl, 'pdfium.wasm'); final content = 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; final blob = web.Blob( [content].jsify() as JSArray, @@ -68,19 +67,15 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactoryImpl { return web.URL.createObjectURL(blob); } - /// Removes the last component from the URL (e.g. the file name) and adds a trailing slash if necessary. - /// - /// This is necessary to ensure that the URL points to a directory, which is required by the WASM loader. - /// - `https://example.com/path/to/file.pdf` -> `https://example.com/path/to/` - /// - `https://example.com/path/to/` -> `https://example.com/path/to/` - /// - `https://example.com/` -> `https://example.com/` - /// - `https://example.com` -> `https://example.com/` - static String _removeLastComponent(String url) { - final lastSlash = url.lastIndexOf('/'); + static String _appendComponents(String url, String additionalPath) { + var uri = Uri.parse(url); + final lastSlash = uri.path.lastIndexOf('/'); if (lastSlash == -1) { - return '$url/'; + uri = uri.replace(path: '${uri.path}/$additionalPath'); + } else { + uri = uri.replace(path: uri.path.substring(0, lastSlash + 1) + additionalPath); } - return url.substring(0, lastSlash + 1); + return uri.toString(); } Future> sendCommand(String command, {Map? parameters}) async { From 04f548105ddbc4e67280cce6711b2333cc2685c1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Mar 2025 20:29:42 +0900 Subject: [PATCH 012/663] 1.1.14 --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce7648b..cce4374d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.14 + +- Improve pdfium_worker.js/pdfium.wasm loading path resolution logic (#331) + # 1.1.13 - Fix indefinite stack on loading PDF files from certain server; now it immediately return error (not actually fixed) (#311) diff --git a/README.md b/README.md index 5644355f..e6690509 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.13 + pdfrx: ^1.1.14 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.13 + pdfrx: ^1.1.14 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 150306db..22f2de2c 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.13" + version: "1.1.14" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d9b4070b..6f4e6a9b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.13 +version: 1.1.14 homepage: https://github.com/espresso3389/pdfrx environment: From 554ab5c7cd6adcf4dad2c1eb513c1daa659ea2a9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 21 Mar 2025 22:33:55 +0900 Subject: [PATCH 013/663] #329 --- lib/src/widgets/pdf_viewer.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 26030a36..15e73e74 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -1157,14 +1157,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } Matrix4 _normalizeMatrix(Matrix4 newValue) { - final position = newValue.calcPosition(_viewSize!); + final layout = _layout; + final viewSize = _viewSize; + if (layout == null || viewSize == null) return newValue; + final position = newValue.calcPosition(viewSize); final newZoom = widget.params.boundaryMargin != null ? newValue.zoom : max(newValue.zoom, minScale); - final hw = _viewSize!.width / 2 / newZoom; - final hh = _viewSize!.height / 2 / newZoom; - final x = position.dx.range(hw, _layout!.documentSize.width - hw); - final y = position.dy.range(hh, _layout!.documentSize.height - hh); + final hw = viewSize.width / 2 / newZoom; + final hh = viewSize.height / 2 / newZoom; + final x = position.dx.range(hw, layout.documentSize.width - hw); + final y = position.dy.range(hh, layout.documentSize.height - hh); - return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: _viewSize!); + return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize); } /// Calculate matrix to center the specified position. From 4042f75302f4838a5cf37cac6d05b410246e0200 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 24 Mar 2025 00:39:01 +0900 Subject: [PATCH 014/663] 1.1.17 --- .pubignore | 2 +- CHANGELOG.md | 4 ++++ README.md | 4 ++-- example/{example.dart => README.md} | 8 ++++++++ example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 6 files changed, 17 insertions(+), 5 deletions(-) rename example/{example.dart => README.md} (83%) diff --git a/.pubignore b/.pubignore index 1d508c00..5a90ca91 100644 --- a/.pubignore +++ b/.pubignore @@ -1,2 +1,2 @@ wasm/ -example/ +example/viewer/ diff --git a/CHANGELOG.md b/CHANGELOG.md index cce4374d..32f2e3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.17 + +- FIXED: example is not shown on pub.dev + # 1.1.14 - Improve pdfium_worker.js/pdfium.wasm loading path resolution logic (#331) diff --git a/README.md b/README.md index e6690509..c434a93f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.14 + pdfrx: ^1.1.17 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.14 + pdfrx: ^1.1.17 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/example.dart b/example/README.md similarity index 83% rename from example/example.dart rename to example/README.md index 31334920..29a0495b 100644 --- a/example/example.dart +++ b/example/README.md @@ -1,3 +1,8 @@ +# Minimum Example + +The following code is a minimum example usage of pdfrx: + +```dart import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; @@ -32,3 +37,6 @@ class MainPage extends StatelessWidget { ); } } +``` + +For more advanced usage, see [[viewer/lib/main.dart]]. diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 22f2de2c..9bf83e74 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.14" + version: "1.1.17" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6f4e6a9b..4c1685ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.14 +version: 1.1.17 homepage: https://github.com/espresso3389/pdfrx environment: From f886a2e46ccd2736068485e55a96954f339543d6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 24 Mar 2025 01:35:36 +0900 Subject: [PATCH 015/663] WIP --- .vscode/settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d8f1a982..c079e946 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -277,5 +277,12 @@ "xloctime": "cpp", "xstring": "cpp" }, - "cmake.ignoreCMakeListsMissing": true + "cmake.ignoreCMakeListsMissing": true, + "[dart]": { + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.rulers": [ + 120 + ] + } } \ No newline at end of file From ad2a1af685d9f31c25f42f0cbc05a2b4dabdfc66 Mon Sep 17 00:00:00 2001 From: Mike Allen Date: Fri, 28 Mar 2025 11:55:44 -0700 Subject: [PATCH 016/663] If a cached pdf has expired and a http modification check returns that the file has not been modified, use the cached version. --- lib/src/pdfium/pdf_file_cache.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index 4216d26f..35085c0a 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -309,7 +309,9 @@ Future pdfDocumentFromUri( useRangeAccess: useRangeAccess, headers: headers, ); - if (result.isFullDownload) { + // cached file has expired + // if the file has fully downloaded again or has not been modified + if (result.isFullDownload || result.notModified) { cache.close(); // close the cache file before opening it. httpClientWrapper.reset(); return await PdfDocument.openFile( From ec4c608c32793f1d4d34b18845a3735c05f23d2d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 31 Mar 2025 00:12:14 +0900 Subject: [PATCH 017/663] 1.1.18 --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f2e3ed..240fc6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.18 + +- Merge PR #338 from mtallenca/cache_expired_not_modified_fix + # 1.1.17 - FIXED: example is not shown on pub.dev diff --git a/README.md b/README.md index c434a93f..278ae085 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.17 + pdfrx: ^1.1.18 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.17 + pdfrx: ^1.1.18 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 9bf83e74..203ee421 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.17" + version: "1.1.18" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 4c1685ba..c1171ee6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.17 +version: 1.1.18 homepage: https://github.com/espresso3389/pdfrx environment: From 18c099c6dac151390ced931b1121caf2fdc36187 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Apr 2025 08:44:02 +0900 Subject: [PATCH 018/663] ffigen update --- lib/src/pdfium/pdfium_bindings.dart | 10 +++++----- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/pdfium/pdfium_bindings.dart b/lib/src/pdfium/pdfium_bindings.dart index d9531c80..64e3e457 100644 --- a/lib/src/pdfium/pdfium_bindings.dart +++ b/lib/src/pdfium/pdfium_bindings.dart @@ -6308,7 +6308,7 @@ enum FPDF_TEXT_RENDERMODE { 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, 7 => FPDF_TEXTRENDERMODE_CLIP, _ => - throw ArgumentError("Unknown value for FPDF_TEXT_RENDERMODE: $value"), + throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), }; @override @@ -6459,7 +6459,7 @@ enum _FPDF_DUPLEXTYPE_ { 1 => Simplex, 2 => DuplexFlipShortEdge, 3 => DuplexFlipLongEdge, - _ => throw ArgumentError("Unknown value for _FPDF_DUPLEXTYPE_: $value"), + _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), }; } @@ -6587,7 +6587,7 @@ enum FPDF_RENDERER_TYPE { 0 => FPDF_RENDERERTYPE_AGG, 1 => FPDF_RENDERERTYPE_SKIA, _ => - throw ArgumentError("Unknown value for FPDF_RENDERER_TYPE: $value"), + throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), }; } @@ -7054,7 +7054,7 @@ enum FPDFANNOT_COLORTYPE { 0 => FPDFANNOT_COLORTYPE_Color, 1 => FPDFANNOT_COLORTYPE_InteriorColor, _ => - throw ArgumentError("Unknown value for FPDFANNOT_COLORTYPE: $value"), + throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), }; } @@ -7068,7 +7068,7 @@ enum FPDF_FILEIDTYPE { static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { 0 => FILEIDTYPE_PERMANENT, 1 => FILEIDTYPE_CHANGING, - _ => throw ArgumentError("Unknown value for FPDF_FILEIDTYPE: $value"), + _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), }; } diff --git a/pubspec.yaml b/pubspec.yaml index c1171ee6..0aad05ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: web: ^1.1.1 dev_dependencies: - ffigen: ^16.1.0 + ffigen: ^18.0.0 flutter_test: sdk: flutter flutter_lints: ^5.0.0 From bf9559580f4a2221e0831c76c90a84a980117522 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Apr 2025 08:53:56 +0900 Subject: [PATCH 019/663] disable dart format for pdfium_bindings.dart --- lib/src/pdfium/pdfium_bindings.dart | 1 + pubspec.yaml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/src/pdfium/pdfium_bindings.dart b/lib/src/pdfium/pdfium_bindings.dart index 64e3e457..1318c5d4 100644 --- a/lib/src/pdfium/pdfium_bindings.dart +++ b/lib/src/pdfium/pdfium_bindings.dart @@ -1,4 +1,5 @@ // ignore_for_file: unused_field +// dart format off // AUTO GENERATED FILE, DO NOT EDIT. // diff --git a/pubspec.yaml b/pubspec.yaml index 0aad05ce..71f3983d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,4 +62,6 @@ ffigen: - "example/viewer/build/windows/x64/.lib/latest/include/**" preamble: | // ignore_for_file: unused_field + // dart format off name: "pdfium" + From e90f34a5a6adae87866f473c203740edfb14eee1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Apr 2025 08:54:40 +0900 Subject: [PATCH 020/663] Also add comments for every ffi functions from pdfium --- lib/src/pdfium/pdfium_bindings.dart | 4555 +++++++++++++++++++++++++++ pubspec.yaml | 3 + 2 files changed, 4558 insertions(+) diff --git a/lib/src/pdfium/pdfium_bindings.dart b/lib/src/pdfium/pdfium_bindings.dart index 1318c5d4..d8c0d2dd 100644 --- a/lib/src/pdfium/pdfium_bindings.dart +++ b/lib/src/pdfium/pdfium_bindings.dart @@ -21,6 +21,15 @@ class pdfium { lookup) : _lookup = lookup; + /// Function: FPDF_InitLibraryWithConfig + /// Initialize the PDFium library and allocate global resources for it. + /// Parameters: + /// config - configuration information as above. + /// Return value: + /// None. + /// Comments: + /// You have to call this function before you can call any PDF + /// processing functions. void FPDF_InitLibraryWithConfig( ffi.Pointer config, ) { @@ -36,6 +45,17 @@ class pdfium { late final _FPDF_InitLibraryWithConfig = _FPDF_InitLibraryWithConfigPtr .asFunction)>(); + /// Function: FPDF_InitLibrary + /// Initialize the PDFium library (alternative form). + /// Parameters: + /// None + /// Return value: + /// None. + /// Comments: + /// Convenience function to call FPDF_InitLibraryWithConfig() with a + /// default configuration for backwards compatibility purposes. New + /// code should call FPDF_InitLibraryWithConfig() instead. This will + /// be deprecated in the future. void FPDF_InitLibrary() { return _FPDF_InitLibrary(); } @@ -45,6 +65,20 @@ class pdfium { late final _FPDF_InitLibrary = _FPDF_InitLibraryPtr.asFunction(); + /// Function: FPDF_DestroyLibrary + /// Release global resources allocated to the PDFium library by + /// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). + /// Parameters: + /// None. + /// Return value: + /// None. + /// Comments: + /// After this function is called, you must not call any PDF + /// processing functions. + /// + /// Calling this function does not automatically close other + /// objects. It is recommended to close other objects before + /// closing the library with this function. void FPDF_DestroyLibrary() { return _FPDF_DestroyLibrary(); } @@ -54,6 +88,14 @@ class pdfium { late final _FPDF_DestroyLibrary = _FPDF_DestroyLibraryPtr.asFunction(); + /// Function: FPDF_SetSandBoxPolicy + /// Set the policy for the sandbox environment. + /// Parameters: + /// policy - The specified policy for setting, for example: + /// FPDF_POLICY_MACHINETIME_ACCESS. + /// enable - True to enable, false to disable the policy. + /// Return value: + /// None. void FPDF_SetSandBoxPolicy( int policy, int enable, @@ -70,6 +112,31 @@ class pdfium { late final _FPDF_SetSandBoxPolicy = _FPDF_SetSandBoxPolicyPtr.asFunction(); + /// Experimental API. + /// Function: FPDF_SetPrintMode + /// Set printing mode when printing on Windows. + /// Parameters: + /// mode - FPDF_PRINTMODE_EMF to output EMF (default) + /// FPDF_PRINTMODE_TEXTONLY to output text only (for charstream + /// devices) + /// FPDF_PRINTMODE_POSTSCRIPT2 to output level 2 PostScript into + /// EMF as a series of GDI comments. + /// FPDF_PRINTMODE_POSTSCRIPT3 to output level 3 PostScript into + /// EMF as a series of GDI comments. + /// FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH to output level 2 + /// PostScript via ExtEscape() in PASSTHROUGH mode. + /// FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH to output level 3 + /// PostScript via ExtEscape() in PASSTHROUGH mode. + /// FPDF_PRINTMODE_EMF_IMAGE_MASKS to output EMF, with more + /// efficient processing of documents containing image masks. + /// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 to output level 3 + /// PostScript with embedded Type 42 fonts, when applicable, into + /// EMF as a series of GDI comments. + /// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH to output level + /// 3 PostScript with embedded Type 42 fonts, when applicable, + /// via ExtEscape() in PASSTHROUGH mode. + /// Return value: + /// True if successful, false if unsuccessful (typically invalid input). int FPDF_SetPrintMode( int mode, ) { @@ -84,6 +151,27 @@ class pdfium { late final _FPDF_SetPrintMode = _FPDF_SetPrintModePtr.asFunction(); + /// Function: FPDF_LoadDocument + /// Open and load a PDF document. + /// Parameters: + /// file_path - Path to the PDF file (including extension). + /// password - A string used as the password for the PDF file. + /// If no password is needed, empty or NULL can be used. + /// See comments below regarding the encoding. + /// Return value: + /// A handle to the loaded document, or NULL on failure. + /// Comments: + /// Loaded document can be closed by FPDF_CloseDocument(). + /// If this function fails, you can use FPDF_GetLastError() to retrieve + /// the reason why it failed. + /// + /// The encoding for |file_path| is UTF-8. + /// + /// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, + /// depending on the security handler revision, will only accept one or + /// the other encoding. If |password|'s encoding and the PDF's expected + /// encoding do not match, FPDF_LoadDocument() will automatically + /// convert |password| to the other encoding. FPDF_DOCUMENT FPDF_LoadDocument( FPDF_STRING file_path, FPDF_BYTESTRING password, @@ -101,6 +189,27 @@ class pdfium { late final _FPDF_LoadDocument = _FPDF_LoadDocumentPtr.asFunction< FPDF_DOCUMENT Function(FPDF_STRING, FPDF_BYTESTRING)>(); + /// Function: FPDF_LoadMemDocument + /// Open and load a PDF document from memory. + /// Parameters: + /// data_buf - Pointer to a buffer containing the PDF document. + /// size - Number of bytes in the PDF document. + /// password - A string used as the password for the PDF file. + /// If no password is needed, empty or NULL can be used. + /// Return value: + /// A handle to the loaded document, or NULL on failure. + /// Comments: + /// The memory buffer must remain valid when the document is open. + /// The loaded document can be closed by FPDF_CloseDocument. + /// If this function fails, you can use FPDF_GetLastError() to retrieve + /// the reason why it failed. + /// + /// See the comments for FPDF_LoadDocument() regarding the encoding for + /// |password|. + /// Notes: + /// If PDFium is built with the XFA module, the application should call + /// FPDF_LoadXFA() function after the PDF document loaded to support XFA + /// fields defined in the fpdfformfill.h file. FPDF_DOCUMENT FPDF_LoadMemDocument( ffi.Pointer data_buf, int size, @@ -120,6 +229,28 @@ class pdfium { late final _FPDF_LoadMemDocument = _FPDF_LoadMemDocumentPtr.asFunction< FPDF_DOCUMENT Function(ffi.Pointer, int, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Function: FPDF_LoadMemDocument64 + /// Open and load a PDF document from memory. + /// Parameters: + /// data_buf - Pointer to a buffer containing the PDF document. + /// size - Number of bytes in the PDF document. + /// password - A string used as the password for the PDF file. + /// If no password is needed, empty or NULL can be used. + /// Return value: + /// A handle to the loaded document, or NULL on failure. + /// Comments: + /// The memory buffer must remain valid when the document is open. + /// The loaded document can be closed by FPDF_CloseDocument. + /// If this function fails, you can use FPDF_GetLastError() to retrieve + /// the reason why it failed. + /// + /// See the comments for FPDF_LoadDocument() regarding the encoding for + /// |password|. + /// Notes: + /// If PDFium is built with the XFA module, the application should call + /// FPDF_LoadXFA() function after the PDF document loaded to support XFA + /// fields defined in the fpdfformfill.h file. FPDF_DOCUMENT FPDF_LoadMemDocument64( ffi.Pointer data_buf, int size, @@ -139,6 +270,26 @@ class pdfium { late final _FPDF_LoadMemDocument64 = _FPDF_LoadMemDocument64Ptr.asFunction< FPDF_DOCUMENT Function(ffi.Pointer, int, FPDF_BYTESTRING)>(); + /// Function: FPDF_LoadCustomDocument + /// Load PDF document from a custom access descriptor. + /// Parameters: + /// pFileAccess - A structure for accessing the file. + /// password - Optional password for decrypting the PDF file. + /// Return value: + /// A handle to the loaded document, or NULL on failure. + /// Comments: + /// The application must keep the file resources |pFileAccess| points to + /// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| + /// itself does not need to outlive the FPDF_DOCUMENT. + /// + /// The loaded document can be closed with FPDF_CloseDocument(). + /// + /// See the comments for FPDF_LoadDocument() regarding the encoding for + /// |password|. + /// Notes: + /// If PDFium is built with the XFA module, the application should call + /// FPDF_LoadXFA() function after the PDF document loaded to support XFA + /// fields defined in the fpdfformfill.h file. FPDF_DOCUMENT FPDF_LoadCustomDocument( ffi.Pointer pFileAccess, FPDF_BYTESTRING password, @@ -156,6 +307,17 @@ class pdfium { late final _FPDF_LoadCustomDocument = _FPDF_LoadCustomDocumentPtr.asFunction< FPDF_DOCUMENT Function(ffi.Pointer, FPDF_BYTESTRING)>(); + /// Function: FPDF_GetFileVersion + /// Get the file version of the given PDF document. + /// Parameters: + /// doc - Handle to a document. + /// fileVersion - The PDF file version. File version: 14 for 1.4, 15 + /// for 1.5, ... + /// Return value: + /// True if succeeds, false otherwise. + /// Comments: + /// If the document was created by FPDF_CreateNewDocument, + /// then this function will always fail. int FPDF_GetFileVersion( FPDF_DOCUMENT doc, ffi.Pointer fileVersion, @@ -173,6 +335,16 @@ class pdfium { late final _FPDF_GetFileVersion = _FPDF_GetFileVersionPtr.asFunction< int Function(FPDF_DOCUMENT, ffi.Pointer)>(); + /// Function: FPDF_GetLastError + /// Get last error code when a function fails. + /// Parameters: + /// None. + /// Return value: + /// A 32-bit integer indicating error code as defined above. + /// Comments: + /// If the previous SDK call succeeded, the return value of this + /// function is not defined. This function only works in conjunction + /// with APIs that mention FPDF_GetLastError() in their documentation. int FPDF_GetLastError() { return _FPDF_GetLastError(); } @@ -183,6 +355,18 @@ class pdfium { late final _FPDF_GetLastError = _FPDF_GetLastErrorPtr.asFunction(); + /// Experimental API. + /// Function: FPDF_DocumentHasValidCrossReferenceTable + /// Whether the document's cross reference table is valid or not. + /// Parameters: + /// document - Handle to a document. Returned by FPDF_LoadDocument. + /// Return value: + /// True if the PDF parser did not encounter problems parsing the cross + /// reference table. False if the parser could not parse the cross + /// reference table and the table had to be rebuild from other data + /// within the document. + /// Comments: + /// The return value can change over time as the PDF parser evolves. int FPDF_DocumentHasValidCrossReferenceTable( FPDF_DOCUMENT document, ) { @@ -198,6 +382,20 @@ class pdfium { _FPDF_DocumentHasValidCrossReferenceTablePtr.asFunction< int Function(FPDF_DOCUMENT)>(); + /// Experimental API. + /// Function: FPDF_GetTrailerEnds + /// Get the byte offsets of trailer ends. + /// Parameters: + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// buffer - The address of a buffer that receives the + /// byte offsets. + /// length - The size, in ints, of |buffer|. + /// Return value: + /// Returns the number of ints in the buffer on success, 0 on error. + /// + /// |buffer| is an array of integers that describes the exact byte offsets of the + /// trailer ends in the document. If |length| is less than the returned length, + /// or |document| or |buffer| is NULL, |buffer| will not be modified. int FPDF_GetTrailerEnds( FPDF_DOCUMENT document, ffi.Pointer buffer, @@ -217,6 +415,14 @@ class pdfium { late final _FPDF_GetTrailerEnds = _FPDF_GetTrailerEndsPtr.asFunction< int Function(FPDF_DOCUMENT, ffi.Pointer, int)>(); + /// Function: FPDF_GetDocPermissions + /// Get file permission flags of the document. + /// Parameters: + /// document - Handle to a document. Returned by FPDF_LoadDocument. + /// Return value: + /// A 32-bit integer indicating permission flags. Please refer to the + /// PDF Reference for detailed descriptions. If the document is not + /// protected or was unlocked by the owner, 0xffffffff will be returned. int FPDF_GetDocPermissions( FPDF_DOCUMENT document, ) { @@ -231,6 +437,15 @@ class pdfium { late final _FPDF_GetDocPermissions = _FPDF_GetDocPermissionsPtr.asFunction(); + /// Function: FPDF_GetDocUserPermissions + /// Get user file permission flags of the document. + /// Parameters: + /// document - Handle to a document. Returned by FPDF_LoadDocument. + /// Return value: + /// A 32-bit integer indicating permission flags. Please refer to the + /// PDF Reference for detailed descriptions. If the document is not + /// protected, 0xffffffff will be returned. Always returns user + /// permissions, even if the document was unlocked by the owner. int FPDF_GetDocUserPermissions( FPDF_DOCUMENT document, ) { @@ -245,6 +460,14 @@ class pdfium { late final _FPDF_GetDocUserPermissions = _FPDF_GetDocUserPermissionsPtr.asFunction(); + /// Function: FPDF_GetSecurityHandlerRevision + /// Get the revision for the security handler. + /// Parameters: + /// document - Handle to a document. Returned by FPDF_LoadDocument. + /// Return value: + /// The security handler revision number. Please refer to the PDF + /// Reference for a detailed description. If the document is not + /// protected, -1 will be returned. int FPDF_GetSecurityHandlerRevision( FPDF_DOCUMENT document, ) { @@ -260,6 +483,12 @@ class pdfium { _FPDF_GetSecurityHandlerRevisionPtr.asFunction< int Function(FPDF_DOCUMENT)>(); + /// Function: FPDF_GetPageCount + /// Get total number of pages in the document. + /// Parameters: + /// document - Handle to document. Returned by FPDF_LoadDocument. + /// Return value: + /// Total number of pages in the document. int FPDF_GetPageCount( FPDF_DOCUMENT document, ) { @@ -274,6 +503,16 @@ class pdfium { late final _FPDF_GetPageCount = _FPDF_GetPageCountPtr.asFunction(); + /// Function: FPDF_LoadPage + /// Load a page inside the document. + /// Parameters: + /// document - Handle to document. Returned by FPDF_LoadDocument + /// page_index - Index number of the page. 0 for the first page. + /// Return value: + /// A handle to the loaded page, or NULL if page load fails. + /// Comments: + /// The loaded page can be rendered to devices using FPDF_RenderPage. + /// The loaded page can be closed using FPDF_ClosePage. FPDF_PAGE FPDF_LoadPage( FPDF_DOCUMENT document, int page_index, @@ -290,6 +529,14 @@ class pdfium { late final _FPDF_LoadPage = _FPDF_LoadPagePtr.asFunction(); + /// Experimental API + /// Function: FPDF_GetPageWidthF + /// Get page width. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// Return value: + /// Page width (excluding non-displayable area) measured in points. + /// One point is 1/72 inch (around 0.3528 mm). double FPDF_GetPageWidthF( FPDF_PAGE page, ) { @@ -304,6 +551,16 @@ class pdfium { late final _FPDF_GetPageWidthF = _FPDF_GetPageWidthFPtr.asFunction(); + /// Function: FPDF_GetPageWidth + /// Get page width. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// Return value: + /// Page width (excluding non-displayable area) measured in points. + /// One point is 1/72 inch (around 0.3528 mm). + /// Note: + /// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the + /// future. double FPDF_GetPageWidth( FPDF_PAGE page, ) { @@ -318,6 +575,14 @@ class pdfium { late final _FPDF_GetPageWidth = _FPDF_GetPageWidthPtr.asFunction(); + /// Experimental API + /// Function: FPDF_GetPageHeightF + /// Get page height. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// Return value: + /// Page height (excluding non-displayable area) measured in points. + /// One point is 1/72 inch (around 0.3528 mm) double FPDF_GetPageHeightF( FPDF_PAGE page, ) { @@ -332,6 +597,16 @@ class pdfium { late final _FPDF_GetPageHeightF = _FPDF_GetPageHeightFPtr.asFunction(); + /// Function: FPDF_GetPageHeight + /// Get page height. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// Return value: + /// Page height (excluding non-displayable area) measured in points. + /// One point is 1/72 inch (around 0.3528 mm) + /// Note: + /// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the + /// future. double FPDF_GetPageHeight( FPDF_PAGE page, ) { @@ -346,6 +621,16 @@ class pdfium { late final _FPDF_GetPageHeight = _FPDF_GetPageHeightPtr.asFunction(); + /// Experimental API. + /// Function: FPDF_GetPageBoundingBox + /// Get the bounding box of the page. This is the intersection between + /// its media box and its crop box. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// rect - Pointer to a rect to receive the page bounding box. + /// On an error, |rect| won't be filled. + /// Return value: + /// True for success. int FPDF_GetPageBoundingBox( FPDF_PAGE page, ffi.Pointer rect, @@ -363,6 +648,16 @@ class pdfium { late final _FPDF_GetPageBoundingBox = _FPDF_GetPageBoundingBoxPtr.asFunction< int Function(FPDF_PAGE, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDF_GetPageSizeByIndexF + /// Get the size of the page at the given index. + /// Parameters: + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// page_index - Page index, zero for the first page. + /// size - Pointer to a FS_SIZEF to receive the page size. + /// (in points). + /// Return value: + /// Non-zero for success. 0 for error (document or page not found). int FPDF_GetPageSizeByIndexF( FPDF_DOCUMENT document, int page_index, @@ -382,6 +677,20 @@ class pdfium { late final _FPDF_GetPageSizeByIndexF = _FPDF_GetPageSizeByIndexFPtr .asFunction)>(); + /// Function: FPDF_GetPageSizeByIndex + /// Get the size of the page at the given index. + /// Parameters: + /// document - Handle to document. Returned by FPDF_LoadDocument. + /// page_index - Page index, zero for the first page. + /// width - Pointer to a double to receive the page width + /// (in points). + /// height - Pointer to a double to receive the page height + /// (in points). + /// Return value: + /// Non-zero for success. 0 for error (document or page not found). + /// Note: + /// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in + /// the future. int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT document, int page_index, @@ -404,6 +713,27 @@ class pdfium { int Function(FPDF_DOCUMENT, int, ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDF_RenderPage + /// Render contents of a page to a device (screen, bitmap, or printer). + /// This function is only supported on Windows. + /// Parameters: + /// dc - Handle to the device context. + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// start_x - Left pixel position of the display area in + /// device coordinates. + /// start_y - Top pixel position of the display area in device + /// coordinates. + /// size_x - Horizontal size (in pixels) for displaying the page. + /// size_y - Vertical size (in pixels) for displaying the page. + /// rotate - Page orientation: + /// 0 (normal) + /// 1 (rotated 90 degrees clockwise) + /// 2 (rotated 180 degrees) + /// 3 (rotated 90 degrees counter-clockwise) + /// flags - 0 for normal display, or combination of flags + /// defined above. + /// Return value: + /// None. void FPDF_RenderPage( HDC dc, FPDF_PAGE page, @@ -433,6 +763,32 @@ class pdfium { late final _FPDF_RenderPage = _FPDF_RenderPagePtr.asFunction< void Function(HDC, FPDF_PAGE, int, int, int, int, int, int)>(); + /// Function: FPDF_RenderPageBitmap + /// Render contents of a page to a device independent bitmap. + /// Parameters: + /// bitmap - Handle to the device independent bitmap (as the + /// output buffer). The bitmap handle can be created + /// by FPDFBitmap_Create or retrieved from an image + /// object by FPDFImageObj_GetBitmap. + /// page - Handle to the page. Returned by FPDF_LoadPage + /// start_x - Left pixel position of the display area in + /// bitmap coordinates. + /// start_y - Top pixel position of the display area in bitmap + /// coordinates. + /// size_x - Horizontal size (in pixels) for displaying the page. + /// size_y - Vertical size (in pixels) for displaying the page. + /// rotate - Page orientation: + /// 0 (normal) + /// 1 (rotated 90 degrees clockwise) + /// 2 (rotated 180 degrees) + /// 3 (rotated 90 degrees counter-clockwise) + /// flags - 0 for normal display, or combination of the Page + /// Rendering flags defined above. With the FPDF_ANNOT + /// flag, it renders all annotations that do not require + /// user-interaction, which are all annotations except + /// widget and popup annotations. + /// Return value: + /// None. void FPDF_RenderPageBitmap( FPDF_BITMAP bitmap, FPDF_PAGE page, @@ -462,6 +818,24 @@ class pdfium { late final _FPDF_RenderPageBitmap = _FPDF_RenderPageBitmapPtr.asFunction< void Function(FPDF_BITMAP, FPDF_PAGE, int, int, int, int, int, int)>(); + /// Function: FPDF_RenderPageBitmapWithMatrix + /// Render contents of a page to a device independent bitmap. + /// Parameters: + /// bitmap - Handle to the device independent bitmap (as the + /// output buffer). The bitmap handle can be created + /// by FPDFBitmap_Create or retrieved by + /// FPDFImageObj_GetBitmap. + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// matrix - The transform matrix, which must be invertible. + /// See PDF Reference 1.7, 4.2.2 Common Transformations. + /// clipping - The rect to clip to in device coords. + /// flags - 0 for normal display, or combination of the Page + /// Rendering flags defined above. With the FPDF_ANNOT + /// flag, it renders all annotations that do not require + /// user-interaction, which are all annotations except + /// widget and popup annotations. + /// Return value: + /// None. Note that behavior is undefined if det of |matrix| is 0. void FPDF_RenderPageBitmapWithMatrix( FPDF_BITMAP bitmap, FPDF_PAGE page, @@ -491,6 +865,12 @@ class pdfium { void Function(FPDF_BITMAP, FPDF_PAGE, ffi.Pointer, ffi.Pointer, int)>(); + /// Function: FPDF_ClosePage + /// Close a loaded PDF page. + /// Parameters: + /// page - Handle to the loaded page. + /// Return value: + /// None. void FPDF_ClosePage( FPDF_PAGE page, ) { @@ -505,6 +885,12 @@ class pdfium { late final _FPDF_ClosePage = _FPDF_ClosePagePtr.asFunction(); + /// Function: FPDF_CloseDocument + /// Close a loaded PDF document. + /// Parameters: + /// document - Handle to the loaded document. + /// Return value: + /// None. void FPDF_CloseDocument( FPDF_DOCUMENT document, ) { @@ -519,6 +905,47 @@ class pdfium { late final _FPDF_CloseDocument = _FPDF_CloseDocumentPtr.asFunction(); + /// Function: FPDF_DeviceToPage + /// Convert the screen coordinates of a point to page coordinates. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// start_x - Left pixel position of the display area in + /// device coordinates. + /// start_y - Top pixel position of the display area in device + /// coordinates. + /// size_x - Horizontal size (in pixels) for displaying the page. + /// size_y - Vertical size (in pixels) for displaying the page. + /// rotate - Page orientation: + /// 0 (normal) + /// 1 (rotated 90 degrees clockwise) + /// 2 (rotated 180 degrees) + /// 3 (rotated 90 degrees counter-clockwise) + /// device_x - X value in device coordinates to be converted. + /// device_y - Y value in device coordinates to be converted. + /// page_x - A pointer to a double receiving the converted X + /// value in page coordinates. + /// page_y - A pointer to a double receiving the converted Y + /// value in page coordinates. + /// Return value: + /// Returns true if the conversion succeeds, and |page_x| and |page_y| + /// successfully receives the converted coordinates. + /// Comments: + /// The page coordinate system has its origin at the left-bottom corner + /// of the page, with the X-axis on the bottom going to the right, and + /// the Y-axis on the left side going up. + /// + /// NOTE: this coordinate system can be altered when you zoom, scroll, + /// or rotate a page, however, a point on the page should always have + /// the same coordinate values in the page coordinate system. + /// + /// The device coordinate system is device dependent. For screen device, + /// its origin is at the left-top corner of the window. However this + /// origin can be altered by the Windows coordinate transformation + /// utilities. + /// + /// You must make sure the start_x, start_y, size_x, size_y + /// and rotate parameters have exactly same values as you used in + /// the FPDF_RenderPage() function call. int FPDF_DeviceToPage( FPDF_PAGE page, int start_x, @@ -562,6 +989,32 @@ class pdfium { int Function(FPDF_PAGE, int, int, int, int, int, int, int, ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDF_PageToDevice + /// Convert the page coordinates of a point to screen coordinates. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage. + /// start_x - Left pixel position of the display area in + /// device coordinates. + /// start_y - Top pixel position of the display area in device + /// coordinates. + /// size_x - Horizontal size (in pixels) for displaying the page. + /// size_y - Vertical size (in pixels) for displaying the page. + /// rotate - Page orientation: + /// 0 (normal) + /// 1 (rotated 90 degrees clockwise) + /// 2 (rotated 180 degrees) + /// 3 (rotated 90 degrees counter-clockwise) + /// page_x - X value in page coordinates. + /// page_y - Y value in page coordinate. + /// device_x - A pointer to an integer receiving the result X + /// value in device coordinates. + /// device_y - A pointer to an integer receiving the result Y + /// value in device coordinates. + /// Return value: + /// Returns true if the conversion succeeds, and |device_x| and + /// |device_y| successfully receives the converted coordinates. + /// Comments: + /// See comments for FPDF_DeviceToPage(). int FPDF_PageToDevice( FPDF_PAGE page, int start_x, @@ -605,6 +1058,36 @@ class pdfium { int Function(FPDF_PAGE, int, int, int, int, int, double, double, ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDFBitmap_Create + /// Create a device independent bitmap (FXDIB). + /// Parameters: + /// width - The number of pixels in width for the bitmap. + /// Must be greater than 0. + /// height - The number of pixels in height for the bitmap. + /// Must be greater than 0. + /// alpha - A flag indicating whether the alpha channel is used. + /// Non-zero for using alpha, zero for not using. + /// Return value: + /// The created bitmap handle, or NULL if a parameter error or out of + /// memory. + /// Comments: + /// The bitmap always uses 4 bytes per pixel. The first byte is always + /// double word aligned. + /// + /// The byte order is BGRx (the last byte unused if no alpha channel) or + /// BGRA. + /// + /// The pixels in a horizontal line are stored side by side, with the + /// left most pixel stored first (with lower memory address). + /// Each line uses width * 4 bytes. + /// + /// Lines are stored one after another, with the top most line stored + /// first. There is no gap between adjacent lines. + /// + /// This function allocates enough memory for holding all pixels in the + /// bitmap, but it doesn't initialize the buffer. Applications can use + /// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS + /// allows it, this function can allocate up to 4 GB of memory. FPDF_BITMAP FPDFBitmap_Create( int width, int height, @@ -623,6 +1106,39 @@ class pdfium { late final _FPDFBitmap_Create = _FPDFBitmap_CreatePtr.asFunction(); + /// Function: FPDFBitmap_CreateEx + /// Create a device independent bitmap (FXDIB) + /// Parameters: + /// width - The number of pixels in width for the bitmap. + /// Must be greater than 0. + /// height - The number of pixels in height for the bitmap. + /// Must be greater than 0. + /// format - A number indicating for bitmap format, as defined + /// above. + /// first_scan - A pointer to the first byte of the first line if + /// using an external buffer. If this parameter is NULL, + /// then a new buffer will be created. + /// stride - Number of bytes for each scan line. The value must + /// be 0 or greater. When the value is 0, + /// FPDFBitmap_CreateEx() will automatically calculate + /// the appropriate value using |width| and |format|. + /// When using an external buffer, it is recommended for + /// the caller to pass in the value. + /// When not using an external buffer, it is recommended + /// for the caller to pass in 0. + /// Return value: + /// The bitmap handle, or NULL if parameter error or out of memory. + /// Comments: + /// Similar to FPDFBitmap_Create function, but allows for more formats + /// and an external buffer is supported. The bitmap created by this + /// function can be used in any place that a FPDF_BITMAP handle is + /// required. + /// + /// If an external buffer is used, then the caller should destroy the + /// buffer. FPDFBitmap_Destroy() will not destroy the buffer. + /// + /// It is recommended to use FPDFBitmap_GetStride() to get the stride + /// value. FPDF_BITMAP FPDFBitmap_CreateEx( int width, int height, @@ -646,6 +1162,16 @@ class pdfium { late final _FPDFBitmap_CreateEx = _FPDFBitmap_CreateExPtr.asFunction< FPDF_BITMAP Function(int, int, int, ffi.Pointer, int)>(); + /// Function: FPDFBitmap_GetFormat + /// Get the format of the bitmap. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// The format of the bitmap. + /// Comments: + /// Only formats supported by FPDFBitmap_CreateEx are supported by this + /// function; see the list of such formats above. int FPDFBitmap_GetFormat( FPDF_BITMAP bitmap, ) { @@ -660,6 +1186,30 @@ class pdfium { late final _FPDFBitmap_GetFormat = _FPDFBitmap_GetFormatPtr.asFunction(); + /// Function: FPDFBitmap_FillRect + /// Fill a rectangle in a bitmap. + /// Parameters: + /// bitmap - The handle to the bitmap. Returned by + /// FPDFBitmap_Create. + /// left - The left position. Starting from 0 at the + /// left-most pixel. + /// top - The top position. Starting from 0 at the + /// top-most line. + /// width - Width in pixels to be filled. + /// height - Height in pixels to be filled. + /// color - A 32-bit value specifing the color, in 8888 ARGB + /// format. + /// Return value: + /// None. + /// Comments: + /// This function sets the color and (optionally) alpha value in the + /// specified region of the bitmap. + /// + /// NOTE: If the alpha channel is used, this function does NOT + /// composite the background with the source color, instead the + /// background will be replaced by the source color and the alpha. + /// + /// If the alpha channel is not used, the alpha parameter is ignored. void FPDFBitmap_FillRect( FPDF_BITMAP bitmap, int left, @@ -685,6 +1235,21 @@ class pdfium { late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction< void Function(FPDF_BITMAP, int, int, int, int, int)>(); + /// Function: FPDFBitmap_GetBuffer + /// Get data buffer of a bitmap. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// The pointer to the first byte of the bitmap buffer. + /// Comments: + /// The stride may be more than width * number of bytes per pixel + /// + /// Applications can use this function to get the bitmap buffer pointer, + /// then manipulate any color and/or alpha values for any pixels in the + /// bitmap. + /// + /// Use FPDFBitmap_GetFormat() to find out the format of the data. ffi.Pointer FPDFBitmap_GetBuffer( FPDF_BITMAP bitmap, ) { @@ -699,6 +1264,13 @@ class pdfium { late final _FPDFBitmap_GetBuffer = _FPDFBitmap_GetBufferPtr.asFunction< ffi.Pointer Function(FPDF_BITMAP)>(); + /// Function: FPDFBitmap_GetWidth + /// Get width of a bitmap. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// The width of the bitmap in pixels. int FPDFBitmap_GetWidth( FPDF_BITMAP bitmap, ) { @@ -713,6 +1285,13 @@ class pdfium { late final _FPDFBitmap_GetWidth = _FPDFBitmap_GetWidthPtr.asFunction(); + /// Function: FPDFBitmap_GetHeight + /// Get height of a bitmap. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// The height of the bitmap in pixels. int FPDFBitmap_GetHeight( FPDF_BITMAP bitmap, ) { @@ -727,6 +1306,15 @@ class pdfium { late final _FPDFBitmap_GetHeight = _FPDFBitmap_GetHeightPtr.asFunction(); + /// Function: FPDFBitmap_GetStride + /// Get number of bytes for each line in the bitmap buffer. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// The number of bytes for each line in the bitmap buffer. + /// Comments: + /// The stride may be more than width * number of bytes per pixel. int FPDFBitmap_GetStride( FPDF_BITMAP bitmap, ) { @@ -741,6 +1329,16 @@ class pdfium { late final _FPDFBitmap_GetStride = _FPDFBitmap_GetStridePtr.asFunction(); + /// Function: FPDFBitmap_Destroy + /// Destroy a bitmap and release all related buffers. + /// Parameters: + /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create + /// or FPDFImageObj_GetBitmap. + /// Return value: + /// None. + /// Comments: + /// This function will not destroy any external buffers provided when + /// the bitmap was created. void FPDFBitmap_Destroy( FPDF_BITMAP bitmap, ) { @@ -755,6 +1353,12 @@ class pdfium { late final _FPDFBitmap_Destroy = _FPDFBitmap_DestroyPtr.asFunction(); + /// Function: FPDF_VIEWERREF_GetPrintScaling + /// Whether the PDF document prefers to be scaled or not. + /// Parameters: + /// document - Handle to the loaded document. + /// Return value: + /// None. int FPDF_VIEWERREF_GetPrintScaling( FPDF_DOCUMENT document, ) { @@ -770,6 +1374,12 @@ class pdfium { _FPDF_VIEWERREF_GetPrintScalingPtr.asFunction< int Function(FPDF_DOCUMENT)>(); + /// Function: FPDF_VIEWERREF_GetNumCopies + /// Returns the number of copies to be printed. + /// Parameters: + /// document - Handle to the loaded document. + /// Return value: + /// The number of copies to be printed. int FPDF_VIEWERREF_GetNumCopies( FPDF_DOCUMENT document, ) { @@ -784,6 +1394,12 @@ class pdfium { late final _FPDF_VIEWERREF_GetNumCopies = _FPDF_VIEWERREF_GetNumCopiesPtr.asFunction(); + /// Function: FPDF_VIEWERREF_GetPrintPageRange + /// Page numbers to initialize print dialog box when file is printed. + /// Parameters: + /// document - Handle to the loaded document. + /// Return value: + /// The print page range to be used for printing. FPDF_PAGERANGE FPDF_VIEWERREF_GetPrintPageRange( FPDF_DOCUMENT document, ) { @@ -799,6 +1415,13 @@ class pdfium { _FPDF_VIEWERREF_GetPrintPageRangePtr.asFunction< FPDF_PAGERANGE Function(FPDF_DOCUMENT)>(); + /// Experimental API. + /// Function: FPDF_VIEWERREF_GetPrintPageRangeCount + /// Returns the number of elements in a FPDF_PAGERANGE. + /// Parameters: + /// pagerange - Handle to the page range. + /// Return value: + /// The number of elements in the page range. Returns 0 on error. int FPDF_VIEWERREF_GetPrintPageRangeCount( FPDF_PAGERANGE pagerange, ) { @@ -814,6 +1437,15 @@ class pdfium { _FPDF_VIEWERREF_GetPrintPageRangeCountPtr.asFunction< int Function(FPDF_PAGERANGE)>(); + /// Experimental API. + /// Function: FPDF_VIEWERREF_GetPrintPageRangeElement + /// Returns an element from a FPDF_PAGERANGE. + /// Parameters: + /// pagerange - Handle to the page range. + /// index - Index of the element. + /// Return value: + /// The value of the element in the page range at a given index. + /// Returns -1 on error. int FPDF_VIEWERREF_GetPrintPageRangeElement( FPDF_PAGERANGE pagerange, int index, @@ -831,6 +1463,13 @@ class pdfium { _FPDF_VIEWERREF_GetPrintPageRangeElementPtr.asFunction< int Function(FPDF_PAGERANGE, int)>(); + /// Function: FPDF_VIEWERREF_GetDuplex + /// Returns the paper handling option to be used when printing from + /// the print dialog. + /// Parameters: + /// document - Handle to the loaded document. + /// Return value: + /// The paper handling option to be used when printing. _FPDF_DUPLEXTYPE_ FPDF_VIEWERREF_GetDuplex( FPDF_DOCUMENT document, ) { @@ -845,6 +1484,21 @@ class pdfium { late final _FPDF_VIEWERREF_GetDuplex = _FPDF_VIEWERREF_GetDuplexPtr.asFunction(); + /// Function: FPDF_VIEWERREF_GetName + /// Gets the contents for a viewer ref, with a given key. The value must + /// be of type "name". + /// Parameters: + /// document - Handle to the loaded document. + /// key - Name of the key in the viewer pref dictionary, + /// encoded in UTF-8. + /// buffer - Caller-allocate buffer to receive the key, or NULL + /// - to query the required length. + /// length - Length of the buffer. + /// Return value: + /// The number of bytes in the contents, including the NULL terminator. + /// Thus if the return value is 0, then that indicates an error, such + /// as when |document| is invalid. If |length| is less than the required + /// length, or |buffer| is NULL, |buffer| will not be modified. int FPDF_VIEWERREF_GetName( FPDF_DOCUMENT document, FPDF_BYTESTRING key, @@ -870,6 +1524,12 @@ class pdfium { int Function( FPDF_DOCUMENT, FPDF_BYTESTRING, ffi.Pointer, int)>(); + /// Function: FPDF_CountNamedDests + /// Get the count of named destinations in the PDF document. + /// Parameters: + /// document - Handle to a document + /// Return value: + /// The count of named destinations. int FPDF_CountNamedDests( FPDF_DOCUMENT document, ) { @@ -884,6 +1544,13 @@ class pdfium { late final _FPDF_CountNamedDests = _FPDF_CountNamedDestsPtr.asFunction(); + /// Function: FPDF_GetNamedDestByName + /// Get a the destination handle for the given name. + /// Parameters: + /// document - Handle to the loaded document. + /// name - The name of a destination. + /// Return value: + /// The handle to the destination. FPDF_DEST FPDF_GetNamedDestByName( FPDF_DOCUMENT document, FPDF_BYTESTRING name, @@ -901,6 +1568,27 @@ class pdfium { late final _FPDF_GetNamedDestByName = _FPDF_GetNamedDestByNamePtr.asFunction< FPDF_DEST Function(FPDF_DOCUMENT, FPDF_BYTESTRING)>(); + /// Function: FPDF_GetNamedDest + /// Get the named destination by index. + /// Parameters: + /// document - Handle to a document + /// index - The index of a named destination. + /// buffer - The buffer to store the destination name, + /// used as wchar_t*. + /// buflen [in/out] - Size of the buffer in bytes on input, + /// length of the result in bytes on output + /// or -1 if the buffer is too small. + /// Return value: + /// The destination handle for a given index, or NULL if there is no + /// named destination corresponding to |index|. + /// Comments: + /// Call this function twice to get the name of the named destination: + /// 1) First time pass in |buffer| as NULL and get buflen. + /// 2) Second time pass in allocated |buffer| and buflen to retrieve + /// |buffer|, which should be used as wchar_t*. + /// + /// If buflen is not sufficiently large, it will be set to -1 upon + /// return. FPDF_DEST FPDF_GetNamedDest( FPDF_DOCUMENT document, int index, @@ -923,6 +1611,13 @@ class pdfium { FPDF_DEST Function( FPDF_DOCUMENT, int, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDF_GetXFAPacketCount + /// Get the number of valid packets in the XFA entry. + /// Parameters: + /// document - Handle to the document. + /// Return value: + /// The number of valid packets, or -1 on error. int FPDF_GetXFAPacketCount( FPDF_DOCUMENT document, ) { @@ -937,6 +1632,22 @@ class pdfium { late final _FPDF_GetXFAPacketCount = _FPDF_GetXFAPacketCountPtr.asFunction(); + /// Experimental API. + /// Function: FPDF_GetXFAPacketName + /// Get the name of a packet in the XFA array. + /// Parameters: + /// document - Handle to the document. + /// index - Index number of the packet. 0 for the first packet. + /// buffer - Buffer for holding the name of the XFA packet. + /// buflen - Length of |buffer| in bytes. + /// Return value: + /// The length of the packet name in bytes, or 0 on error. + /// + /// |document| must be valid and |index| must be in the range [0, N), where N is + /// the value returned by FPDF_GetXFAPacketCount(). + /// |buffer| is only modified if it is non-NULL and |buflen| is greater than or + /// equal to the length of the packet name. The packet name includes a + /// terminating NUL character. |buffer| is unmodified on error. int FPDF_GetXFAPacketName( FPDF_DOCUMENT document, int index, @@ -961,6 +1672,27 @@ class pdfium { late final _FPDF_GetXFAPacketName = _FPDF_GetXFAPacketNamePtr.asFunction< int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); + /// Experimental API. + /// Function: FPDF_GetXFAPacketContent + /// Get the content of a packet in the XFA array. + /// Parameters: + /// document - Handle to the document. + /// index - Index number of the packet. 0 for the first packet. + /// buffer - Buffer for holding the content of the XFA packet. + /// buflen - Length of |buffer| in bytes. + /// out_buflen - Pointer to the variable that will receive the minimum + /// buffer size needed to contain the content of the XFA + /// packet. + /// Return value: + /// Whether the operation succeeded or not. + /// + /// |document| must be valid and |index| must be in the range [0, N), where N is + /// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be + /// NULL. When the aforementioned arguments are valid, the operation succeeds, + /// and |out_buflen| receives the content size. |buffer| is only modified if + /// |buffer| is non-null and long enough to contain the content. Callers must + /// check both the return value and the input |buflen| is no less than the + /// returned |out_buflen| before using the data in |buffer|. int FPDF_GetXFAPacketContent( FPDF_DOCUMENT document, int index, @@ -990,6 +1722,17 @@ class pdfium { int Function(FPDF_DOCUMENT, int, ffi.Pointer, int, ffi.Pointer)>(); + /// Function: FPDFDOC_InitFormFillEnvironment + /// Initialize form fill environment. + /// Parameters: + /// document - Handle to document from FPDF_LoadDocument(). + /// formInfo - Pointer to a FPDF_FORMFILLINFO structure. + /// Return Value: + /// Handle to the form fill module, or NULL on failure. + /// Comments: + /// This function should be called before any form fill operation. + /// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until + /// the returned FPDF_FORMHANDLE is closed. FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment( FPDF_DOCUMENT document, ffi.Pointer formInfo, @@ -1010,6 +1753,15 @@ class pdfium { FPDF_FORMHANDLE Function( FPDF_DOCUMENT, ffi.Pointer)>(); + /// Function: FPDFDOC_ExitFormFillEnvironment + /// Take ownership of |hHandle| and exit form fill environment. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. + /// Comments: + /// This function is a no-op when |hHandle| is null. void FPDFDOC_ExitFormFillEnvironment( FPDF_FORMHANDLE hHandle, ) { @@ -1025,6 +1777,15 @@ class pdfium { _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction< void Function(FPDF_FORMHANDLE)>(); + /// Function: FORM_OnAfterLoadPage + /// This method is required for implementing all the form related + /// functions. Should be invoked after user successfully loaded a + /// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. void FORM_OnAfterLoadPage( FPDF_PAGE page, FPDF_FORMHANDLE hHandle, @@ -1041,6 +1802,15 @@ class pdfium { late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction< void Function(FPDF_PAGE, FPDF_FORMHANDLE)>(); + /// Function: FORM_OnBeforeClosePage + /// This method is required for implementing all the form related + /// functions. Should be invoked before user closes the PDF page. + /// Parameters: + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. void FORM_OnBeforeClosePage( FPDF_PAGE page, FPDF_FORMHANDLE hHandle, @@ -1057,6 +1827,18 @@ class pdfium { late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction< void Function(FPDF_PAGE, FPDF_FORMHANDLE)>(); + /// Function: FORM_DoDocumentJSAction + /// This method is required for performing document-level JavaScript + /// actions. It should be invoked after the PDF document has been loaded. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. + /// Comments: + /// If there is document-level JavaScript action embedded in the + /// document, this method will execute the JavaScript action. Otherwise, + /// the method will do nothing. void FORM_DoDocumentJSAction( FPDF_FORMHANDLE hHandle, ) { @@ -1071,6 +1853,17 @@ class pdfium { late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); + /// Function: FORM_DoDocumentOpenAction + /// This method is required for performing open-action when the document + /// is opened. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. + /// Comments: + /// This method will do nothing if there are no open-actions embedded + /// in the document. void FORM_DoDocumentOpenAction( FPDF_FORMHANDLE hHandle, ) { @@ -1085,6 +1878,19 @@ class pdfium { late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr .asFunction(); + /// Function: FORM_DoDocumentAAction + /// This method is required for performing the document's + /// additional-action. + /// Parameters: + /// hHandle - Handle to the form fill module. Returned by + /// FPDFDOC_InitFormFillEnvironment. + /// aaType - The type of the additional-actions which defined + /// above. + /// Return Value: + /// None. + /// Comments: + /// This method will do nothing if there is no document + /// additional-action corresponding to the specified |aaType|. void FORM_DoDocumentAAction( FPDF_FORMHANDLE hHandle, int aaType, @@ -1101,6 +1907,20 @@ class pdfium { late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction< void Function(FPDF_FORMHANDLE, int)>(); + /// Function: FORM_DoPageAAction + /// This method is required for performing the page object's + /// additional-action when opened or closed. + /// Parameters: + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// aaType - The type of the page object's additional-actions + /// which defined above. + /// Return Value: + /// None. + /// Comments: + /// This method will do nothing if no additional-action corresponding + /// to the specified |aaType| exists. void FORM_DoPageAAction( FPDF_PAGE page, FPDF_FORMHANDLE hHandle, @@ -1120,6 +1940,19 @@ class pdfium { late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction< void Function(FPDF_PAGE, FPDF_FORMHANDLE, int)>(); + /// Function: FORM_OnMouseMove + /// Call this member function when the mouse cursor moves. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_x - Specifies the x-coordinate of the cursor in PDF user + /// space. + /// page_y - Specifies the y-coordinate of the cursor in PDF user + /// space. + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnMouseMove( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1143,6 +1976,29 @@ class pdfium { late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Experimental API + /// Function: FORM_OnMouseWheel + /// Call this member function when the user scrolls the mouse wheel. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_coord - Specifies the coordinates of the cursor in PDF user + /// space. + /// delta_x - Specifies the amount of wheel movement on the x-axis, + /// in units of platform-agnostic wheel deltas. Negative + /// values mean left. + /// delta_y - Specifies the amount of wheel movement on the y-axis, + /// in units of platform-agnostic wheel deltas. Negative + /// values mean down. + /// Return Value: + /// True indicates success; otherwise false. + /// Comments: + /// For |delta_x| and |delta_y|, the caller must normalize + /// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 + /// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines + /// WHEEL_DELTA as 120. int FORM_OnMouseWheel( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1169,6 +2025,21 @@ class pdfium { int Function( FPDF_FORMHANDLE, FPDF_PAGE, int, ffi.Pointer, int, int)>(); + /// Function: FORM_OnFocus + /// This function focuses the form annotation at a given point. If the + /// annotation at the point already has focus, nothing happens. If there + /// is no annotation at the point, removes form focus. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_x - Specifies the x-coordinate of the cursor in PDF user + /// space. + /// page_y - Specifies the y-coordinate of the cursor in PDF user + /// space. + /// Return Value: + /// True if there is an annotation at the given point and it has focus. int FORM_OnFocus( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1192,6 +2063,20 @@ class pdfium { late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnLButtonDown + /// Call this member function when the user presses the left + /// mouse button. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_x - Specifies the x-coordinate of the cursor in PDF user + /// space. + /// page_y - Specifies the y-coordinate of the cursor in PDF user + /// space. + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnLButtonDown( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1215,6 +2100,11 @@ class pdfium { late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnRButtonDown + /// Same as above, execpt for the right mouse button. + /// Comments: + /// At the present time, has no effect except in XFA builds, but is + /// included for the sake of symmetry. int FORM_OnRButtonDown( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1238,6 +2128,18 @@ class pdfium { late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnLButtonUp + /// Call this member function when the user releases the left + /// mouse button. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_x - Specifies the x-coordinate of the cursor in device. + /// page_y - Specifies the y-coordinate of the cursor in device. + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnLButtonUp( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1261,6 +2163,11 @@ class pdfium { late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnRButtonUp + /// Same as above, execpt for the right mouse button. + /// Comments: + /// At the present time, has no effect except in XFA builds, but is + /// included for the sake of symmetry. int FORM_OnRButtonUp( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1284,6 +2191,20 @@ class pdfium { late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnLButtonDoubleClick + /// Call this member function when the user double clicks the + /// left mouse button. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// modifier - Indicates whether various virtual keys are down. + /// page_x - Specifies the x-coordinate of the cursor in PDF user + /// space. + /// page_y - Specifies the y-coordinate of the cursor in PDF user + /// space. + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnLButtonDoubleClick( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1308,6 +2229,18 @@ class pdfium { _FORM_OnLButtonDoubleClickPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); + /// Function: FORM_OnKeyDown + /// Call this member function when a nonsystem key is pressed. + /// Parameters: + /// hHandle - Handle to the form fill module, aseturned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// nKeyCode - The virtual-key code of the given key (see + /// fpdf_fwlevent.h for virtual key codes). + /// modifier - Mask of key flags (see fpdf_fwlevent.h for key + /// flag values). + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnKeyDown( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1329,6 +2262,21 @@ class pdfium { late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); + /// Function: FORM_OnKeyUp + /// Call this member function when a nonsystem key is released. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// nKeyCode - The virtual-key code of the given key (see + /// fpdf_fwlevent.h for virtual key codes). + /// modifier - Mask of key flags (see fpdf_fwlevent.h for key + /// flag values). + /// Return Value: + /// True indicates success; otherwise false. + /// Comments: + /// Currently unimplemented and always returns false. PDFium reserves this + /// API and may implement it in the future on an as-needed basis. int FORM_OnKeyUp( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1350,6 +2298,18 @@ class pdfium { late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); + /// Function: FORM_OnChar + /// Call this member function when a keystroke translates to a + /// nonsystem character. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// nChar - The character code value itself. + /// modifier - Mask of key flags (see fpdf_fwlevent.h for key + /// flag values). + /// Return Value: + /// True indicates success; otherwise false. int FORM_OnChar( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1371,6 +2331,21 @@ class pdfium { late final _FORM_OnChar = _FORM_OnCharPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); + /// Experimental API + /// Function: FORM_GetFocusedText + /// Call this function to obtain the text within the current focused + /// field, if any. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// buffer - Buffer for holding the form text, encoded in + /// UTF-16LE. If NULL, |buffer| is not modified. + /// buflen - Length of |buffer| in bytes. If |buflen| is less + /// than the length of the form text string, |buffer| is + /// not modified. + /// Return Value: + /// Length in bytes for the text in the focused field. int FORM_GetFocusedText( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1392,6 +2367,21 @@ class pdfium { late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer, int)>(); + /// Function: FORM_GetSelectedText + /// Call this function to obtain selected text within a form text + /// field or form combobox text field. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// buffer - Buffer for holding the selected text, encoded in + /// UTF-16LE. If NULL, |buffer| is not modified. + /// buflen - Length of |buffer| in bytes. If |buflen| is less + /// than the length of the selected text string, + /// |buffer| is not modified. + /// Return Value: + /// Length in bytes of selected text in form text field or form combobox + /// text field. int FORM_GetSelectedText( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1416,6 +2406,21 @@ class pdfium { late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer, int)>(); + /// Experimental API + /// Function: FORM_ReplaceAndKeepSelection + /// Call this function to replace the selected text in a form + /// text field or user-editable form combobox text field with another + /// text string (which can be empty or non-empty). If there is no + /// selected text, this function will append the replacement text after + /// the current caret position. After the insertion, the inserted text + /// will be selected. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as Returned by FPDF_LoadPage(). + /// wsText - The text to be inserted, in UTF-16LE format. + /// Return Value: + /// None. void FORM_ReplaceAndKeepSelection( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1435,6 +2440,20 @@ class pdfium { late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr .asFunction(); + /// Function: FORM_ReplaceSelection + /// Call this function to replace the selected text in a form + /// text field or user-editable form combobox text field with another + /// text string (which can be empty or non-empty). If there is no + /// selected text, this function will append the replacement text after + /// the current caret position. After the insertion, the selection range + /// will be set to empty. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as Returned by FPDF_LoadPage(). + /// wsText - The text to be inserted, in UTF-16LE format. + /// Return Value: + /// None. void FORM_ReplaceSelection( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1454,6 +2473,16 @@ class pdfium { late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction< void Function(FPDF_FORMHANDLE, FPDF_PAGE, FPDF_WIDESTRING)>(); + /// Experimental API + /// Function: FORM_SelectAllText + /// Call this function to select all the text within the currently focused + /// form text field or form combobox text field. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// Return Value: + /// Whether the operation succeeded or not. int FORM_SelectAllText( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1470,6 +2499,15 @@ class pdfium { late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE)>(); + /// Function: FORM_CanUndo + /// Find out if it is possible for the current focused widget in a given + /// form to perform an undo operation. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// Return Value: + /// True if it is possible to undo. int FORM_CanUndo( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1486,6 +2524,15 @@ class pdfium { late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); + /// Function: FORM_CanRedo + /// Find out if it is possible for the current focused widget in a given + /// form to perform a redo operation. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// Return Value: + /// True if it is possible to redo. int FORM_CanRedo( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1502,6 +2549,14 @@ class pdfium { late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); + /// Function: FORM_Undo + /// Make the current focused widget perform an undo operation. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// Return Value: + /// True if the undo operation succeeded. int FORM_Undo( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1518,6 +2573,14 @@ class pdfium { late final _FORM_Undo = _FORM_UndoPtr.asFunction(); + /// Function: FORM_Redo + /// Make the current focused widget perform a redo operation. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// Return Value: + /// True if the redo operation succeeded. int FORM_Redo( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1534,6 +2597,15 @@ class pdfium { late final _FORM_Redo = _FORM_RedoPtr.asFunction(); + /// Function: FORM_ForceToKillFocus. + /// Call this member function to force to kill the focus of the form + /// field which has focus. If it would kill the focus of a form field, + /// save the value of form field if was changed by theuser. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// True indicates success; otherwise false. int FORM_ForceToKillFocus( FPDF_FORMHANDLE hHandle, ) { @@ -1548,6 +2620,26 @@ class pdfium { late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); + /// Experimental API. + /// Function: FORM_GetFocusedAnnot. + /// Call this member function to get the currently focused annotation. + /// Parameters: + /// handle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page_index - Buffer to hold the index number of the page which + /// contains the focused annotation. 0 for the first page. + /// Can't be NULL. + /// annot - Buffer to hold the focused annotation. Can't be NULL. + /// Return Value: + /// On success, return true and write to the out parameters. Otherwise + /// return false and leave the out parameters unmodified. + /// Comments: + /// Not currently supported for XFA forms - will report no focused + /// annotation. + /// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| + /// by this function is no longer needed. + /// This will return true and set |page_index| to -1 and |annot| to NULL, + /// if there is no focused annotation. int FORM_GetFocusedAnnot( FPDF_FORMHANDLE handle, ffi.Pointer page_index, @@ -1568,6 +2660,18 @@ class pdfium { int Function(FPDF_FORMHANDLE, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FORM_SetFocusedAnnot. + /// Call this member function to set the currently focused annotation. + /// Parameters: + /// handle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - Handle to an annotation. + /// Return Value: + /// True indicates success; otherwise false. + /// Comments: + /// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() + /// instead. int FORM_SetFocusedAnnot( FPDF_FORMHANDLE handle, FPDF_ANNOTATION annot, @@ -1585,6 +2689,17 @@ class pdfium { late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION)>(); + /// Function: FPDFPage_HasFormFieldAtPoint + /// Get the form field type by point. + /// Parameters: + /// hHandle - Handle to the form fill module. Returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// page_x - X position in PDF "user space". + /// page_y - Y position in PDF "user space". + /// Return Value: + /// Return the type of the form field; -1 indicates no field. + /// See field types above. int FPDFPage_HasFormFieldAtPoint( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1606,6 +2721,17 @@ class pdfium { late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr .asFunction(); + /// Function: FPDFPage_FormFieldZOrderAtPoint + /// Get the form field z-order by point. + /// Parameters: + /// hHandle - Handle to the form fill module. Returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// page_x - X position in PDF "user space". + /// page_y - Y position in PDF "user space". + /// Return Value: + /// Return the z-order of the form field; -1 indicates no field. + /// Higher numbers are closer to the front. int FPDFPage_FormFieldZOrderAtPoint( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1628,6 +2754,26 @@ class pdfium { _FPDFPage_FormFieldZOrderAtPointPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, double, double)>(); + /// Function: FPDF_SetFormFieldHighlightColor + /// Set the highlight color of the specified (or all) form fields + /// in the document. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// doc - Handle to the document, as returned by + /// FPDF_LoadDocument(). + /// fieldType - A 32-bit integer indicating the type of a form + /// field (defined above). + /// color - The highlight color of the form field. Constructed by + /// 0xxxrrggbb. + /// Return Value: + /// None. + /// Comments: + /// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the + /// highlight color will be applied to all the form fields in the + /// document. + /// Please refresh the client window to show the highlight immediately + /// if necessary. void FPDF_SetFormFieldHighlightColor( FPDF_FORMHANDLE hHandle, int fieldType, @@ -1648,6 +2794,18 @@ class pdfium { _FPDF_SetFormFieldHighlightColorPtr.asFunction< void Function(FPDF_FORMHANDLE, int, int)>(); + /// Function: FPDF_SetFormFieldHighlightAlpha + /// Set the transparency of the form field highlight color in the + /// document. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// doc - Handle to the document, as returaned by + /// FPDF_LoadDocument(). + /// alpha - The transparency of the form field highlight color, + /// between 0-255. + /// Return Value: + /// None. void FPDF_SetFormFieldHighlightAlpha( FPDF_FORMHANDLE hHandle, int alpha, @@ -1666,6 +2824,16 @@ class pdfium { _FPDF_SetFormFieldHighlightAlphaPtr.asFunction< void Function(FPDF_FORMHANDLE, int)>(); + /// Function: FPDF_RemoveFormFieldHighlight + /// Remove the form field highlight color in the document. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// Return Value: + /// None. + /// Comments: + /// Please refresh the client window to remove the highlight immediately + /// if necessary. void FPDF_RemoveFormFieldHighlight( FPDF_FORMHANDLE hHandle, ) { @@ -1680,6 +2848,41 @@ class pdfium { late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr .asFunction(); + /// Function: FPDF_FFLDraw + /// Render FormFields and popup window on a page to a device independent + /// bitmap. + /// Parameters: + /// hHandle - Handle to the form fill module, as returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// bitmap - Handle to the device independent bitmap (as the + /// output buffer). Bitmap handles can be created by + /// FPDFBitmap_Create(). + /// page - Handle to the page, as returned by FPDF_LoadPage(). + /// start_x - Left pixel position of the display area in the + /// device coordinates. + /// start_y - Top pixel position of the display area in the device + /// coordinates. + /// size_x - Horizontal size (in pixels) for displaying the page. + /// size_y - Vertical size (in pixels) for displaying the page. + /// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees + /// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 + /// degrees counter-clockwise). + /// flags - 0 for normal display, or combination of flags + /// defined above. + /// Return Value: + /// None. + /// Comments: + /// This function is designed to render annotations that are + /// user-interactive, which are widget annotations (for FormFields) and + /// popup annotations. + /// With the FPDF_ANNOT flag, this function will render a popup annotation + /// when users mouse-hover on a non-widget annotation. Regardless of + /// FPDF_ANNOT flag, this function will always render widget annotations + /// for FormFields. + /// In order to implement the FormFill functions, implementation should + /// call this function after rendering functions, such as + /// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have + /// finished rendering the page contents. void FPDF_FFLDraw( FPDF_FORMHANDLE hHandle, FPDF_BITMAP bitmap, @@ -1712,6 +2915,15 @@ class pdfium { void Function(FPDF_FORMHANDLE, FPDF_BITMAP, FPDF_PAGE, int, int, int, int, int, int)>(); + /// Experimental API + /// Function: FPDF_GetFormType + /// Returns the type of form contained in the PDF document. + /// Parameters: + /// document - Handle to document. + /// Return Value: + /// Integer value representing one of the FORMTYPE_ values. + /// Comments: + /// If |document| is NULL, then the return value is FORMTYPE_NONE. int FPDF_GetFormType( FPDF_DOCUMENT document, ) { @@ -1726,6 +2938,27 @@ class pdfium { late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); + /// Experimental API + /// Function: FORM_SetIndexSelected + /// Selects/deselects the value at the given |index| of the focused + /// annotation. + /// Parameters: + /// hHandle - Handle to the form fill module. Returned by + /// FPDFDOC_InitFormFillEnvironment. + /// page - Handle to the page. Returned by FPDF_LoadPage + /// index - 0-based index of value to be set as + /// selected/unselected + /// selected - true to select, false to deselect + /// Return Value: + /// TRUE if the operation succeeded. + /// FALSE if the operation failed or widget is not a supported type. + /// Comments: + /// Intended for use with listbox/combobox widget types. Comboboxes + /// have at most a single value selected at a time which cannot be + /// deselected. Deselect on a combobox is a no-op that returns false. + /// Default implementation is a no-op that will return false for + /// other types. + /// Not currently supported for XFA forms - will return false. int FORM_SetIndexSelected( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1747,6 +2980,23 @@ class pdfium { late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); + /// Experimental API + /// Function: FORM_IsIndexSelected + /// Returns whether or not the value at |index| of the focused + /// annotation is currently selected. + /// Parameters: + /// hHandle - Handle to the form fill module. Returned by + /// FPDFDOC_InitFormFillEnvironment. + /// page - Handle to the page. Returned by FPDF_LoadPage + /// index - 0-based Index of value to check + /// Return Value: + /// TRUE if value at |index| is currently selected. + /// FALSE if value at |index| is not selected or widget is not a + /// supported type. + /// Comments: + /// Intended for use with listbox/combobox widget types. Default + /// implementation is a no-op that will return false for other types. + /// Not currently supported for XFA forms - will return false. int FORM_IsIndexSelected( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -1766,6 +3016,14 @@ class pdfium { late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_PAGE, int)>(); + /// Function: FPDF_LoadXFA + /// If the document consists of XFA fields, call this method to + /// attempt to load XFA fields. + /// Parameters: + /// document - Handle to document from FPDF_LoadDocument(). + /// Return Value: + /// TRUE upon success, otherwise FALSE. If XFA support is not built + /// into PDFium, performs no action and always returns FALSE. int FPDF_LoadXFA( FPDF_DOCUMENT document, ) { @@ -1780,6 +3038,26 @@ class pdfium { late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); + /// Experimental API. + /// Check if an annotation subtype is currently supported for creation. + /// Currently supported subtypes: + /// - circle + /// - fileattachment + /// - freetext + /// - highlight + /// - ink + /// - link + /// - popup + /// - square, + /// - squiggly + /// - stamp + /// - strikeout + /// - text + /// - underline + /// + /// subtype - the subtype to be checked. + /// + /// Returns true if this subtype supported. int FPDFAnnot_IsSupportedSubtype( int subtype, ) { @@ -1794,6 +3072,16 @@ class pdfium { late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); + /// Experimental API. + /// Create an annotation in |page| of the subtype |subtype|. If the specified + /// subtype is illegal or unsupported, then a new annotation will not be created. + /// Must call FPDFPage_CloseAnnot() when the annotation returned by this + /// function is no longer needed. + /// + /// page - handle to a page. + /// subtype - the subtype of the new annotation. + /// + /// Returns a handle to the new annotation object, or NULL on failure. FPDF_ANNOTATION FPDFPage_CreateAnnot( FPDF_PAGE page, int subtype, @@ -1811,6 +3099,12 @@ class pdfium { late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction< FPDF_ANNOTATION Function(FPDF_PAGE, int)>(); + /// Experimental API. + /// Get the number of annotations in |page|. + /// + /// page - handle to a page. + /// + /// Returns the number of annotations in |page|. int FPDFPage_GetAnnotCount( FPDF_PAGE page, ) { @@ -1825,6 +3119,14 @@ class pdfium { late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); + /// Experimental API. + /// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the + /// annotation returned by this function is no longer needed. + /// + /// page - handle to a page. + /// index - the index of the annotation. + /// + /// Returns a handle to the annotation object, or NULL on failure. FPDF_ANNOTATION FPDFPage_GetAnnot( FPDF_PAGE page, int index, @@ -1841,6 +3143,14 @@ class pdfium { late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction< FPDF_ANNOTATION Function(FPDF_PAGE, int)>(); + /// Experimental API. + /// Get the index of |annot| in |page|. This is the opposite of + /// FPDFPage_GetAnnot(). + /// + /// page - handle to the page that the annotation is on. + /// annot - handle to an annotation. + /// + /// Returns the index of |annot|, or -1 on failure. int FPDFPage_GetAnnotIndex( FPDF_PAGE page, FPDF_ANNOTATION annot, @@ -1857,6 +3167,12 @@ class pdfium { late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction< int Function(FPDF_PAGE, FPDF_ANNOTATION)>(); + /// Experimental API. + /// Close an annotation. Must be called when the annotation returned by + /// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This + /// function does not remove the annotation from the document. + /// + /// annot - handle to an annotation. void FPDFPage_CloseAnnot( FPDF_ANNOTATION annot, ) { @@ -1871,6 +3187,13 @@ class pdfium { late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); + /// Experimental API. + /// Remove the annotation in |page| at |index|. + /// + /// page - handle to a page. + /// index - the index of the annotation. + /// + /// Returns true if successful. int FPDFPage_RemoveAnnot( FPDF_PAGE page, int index, @@ -1887,6 +3210,12 @@ class pdfium { late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); + /// Experimental API. + /// Get the subtype of an annotation. + /// + /// annot - handle to an annotation. + /// + /// Returns the annotation subtype. int FPDFAnnot_GetSubtype( FPDF_ANNOTATION annot, ) { @@ -1902,6 +3231,14 @@ class pdfium { late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); + /// Experimental API. + /// Check if an annotation subtype is currently supported for object extraction, + /// update, and removal. + /// Currently supported subtypes: ink and stamp. + /// + /// subtype - the subtype to be checked. + /// + /// Returns true if this subtype supported. int FPDFAnnot_IsObjectSupportedSubtype( int subtype, ) { @@ -1916,6 +3253,17 @@ class pdfium { late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); + /// Experimental API. + /// Update |obj| in |annot|. |obj| must be in |annot| already and must have + /// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp + /// annotations are supported by this API. Also note that only path, image, and + /// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and + /// FPDFImageObj_*(). + /// + /// annot - handle to an annotation. + /// obj - handle to the object that |annot| needs to update. + /// + /// Return true if successful. int FPDFAnnot_UpdateObject( FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj, @@ -1933,6 +3281,19 @@ class pdfium { late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction< int Function(FPDF_ANNOTATION, FPDF_PAGEOBJECT)>(); + /// Experimental API. + /// Add a new InkStroke, represented by an array of points, to the InkList of + /// |annot|. The API creates an InkList if one doesn't already exist in |annot|. + /// This API works only for ink annotations. Please refer to ISO 32000-1:2008 + /// spec, section 12.5.6.13. + /// + /// annot - handle to an annotation. + /// points - pointer to a FS_POINTF array representing input points. + /// point_count - number of elements in |points| array. This should not exceed + /// the maximum value that can be represented by an int32_t). + /// + /// Returns the 0-based index at which the new InkStroke is added in the InkList + /// of the |annot|. Returns -1 on failure. int FPDFAnnot_AddInkStroke( FPDF_ANNOTATION annot, ffi.Pointer points, @@ -1952,6 +3313,14 @@ class pdfium { late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Removes an InkList in |annot|. + /// This API works only for ink annotations. + /// + /// annot - handle to an annotation. + /// + /// Return true on successful removal of /InkList entry from context of the + /// non-null ink |annot|. Returns false on failure. int FPDFAnnot_RemoveInkList( FPDF_ANNOTATION annot, ) { @@ -1966,6 +3335,17 @@ class pdfium { late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); + /// Experimental API. + /// Add |obj| to |annot|. |obj| must have been created by + /// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and + /// will be owned by |annot|. Note that an |obj| cannot belong to more than one + /// |annot|. Currently, only ink and stamp annotations are supported by this API. + /// Also note that only path, image, and text objects have APIs for creation. + /// + /// annot - handle to an annotation. + /// obj - handle to the object that is to be added to |annot|. + /// + /// Return true if successful. int FPDFAnnot_AppendObject( FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj, @@ -1983,6 +3363,13 @@ class pdfium { late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction< int Function(FPDF_ANNOTATION, FPDF_PAGEOBJECT)>(); + /// Experimental API. + /// Get the total number of objects in |annot|, including path objects, text + /// objects, external objects, image objects, and shading objects. + /// + /// annot - handle to an annotation. + /// + /// Returns the number of objects in |annot|. int FPDFAnnot_GetObjectCount( FPDF_ANNOTATION annot, ) { @@ -1997,6 +3384,13 @@ class pdfium { late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); + /// Experimental API. + /// Get the object in |annot| at |index|. + /// + /// annot - handle to an annotation. + /// index - the index of the object. + /// + /// Return a handle to the object, or NULL on failure. FPDF_PAGEOBJECT FPDFAnnot_GetObject( FPDF_ANNOTATION annot, int index, @@ -2014,6 +3408,13 @@ class pdfium { late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_ANNOTATION, int)>(); + /// Experimental API. + /// Remove the object in |annot| at |index|. + /// + /// annot - handle to an annotation. + /// index - the index of the object to be removed. + /// + /// Return true if successful. int FPDFAnnot_RemoveObject( FPDF_ANNOTATION annot, int index, @@ -2030,6 +3431,17 @@ class pdfium { late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction< int Function(FPDF_ANNOTATION, int)>(); + /// Experimental API. + /// Set the color of an annotation. Fails when called on annotations with + /// appearance streams already defined; instead use + /// FPDFPath_Set{Stroke|Fill}Color(). + /// + /// annot - handle to an annotation. + /// type - type of the color to be set. + /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. + /// A - buffer to hold the opacity. Ranges from 0 to 255. + /// + /// Returns true if successful. DartFPDF_BOOL FPDFAnnot_SetColor( FPDF_ANNOTATION annot, FPDFANNOT_COLORTYPE type, @@ -2060,6 +3472,18 @@ class pdfium { late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction< int Function(FPDF_ANNOTATION, int, int, int, int, int)>(); + /// Experimental API. + /// Get the color of an annotation. If no color is specified, default to yellow + /// for highlight annotation, black for all else. Fails when called on + /// annotations with appearance streams already defined; instead use + /// FPDFPath_Get{Stroke|Fill}Color(). + /// + /// annot - handle to an annotation. + /// type - type of the color requested. + /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. + /// A - buffer to hold the opacity. Ranges from 0 to 255. + /// + /// Returns true if successful. DartFPDF_BOOL FPDFAnnot_GetColor( FPDF_ANNOTATION annot, FPDFANNOT_COLORTYPE type, @@ -2096,6 +3520,18 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Check if the annotation is of a type that has attachment points + /// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that + /// encompasses the texts affected by the annotation. They provide the + /// coordinates in the page where the annotation is attached. Only text markup + /// annotations (i.e. highlight, strikeout, squiggly, and underline) and link + /// annotations have quadpoints. + /// + /// annot - handle to an annotation. + /// + /// Returns true if the annotation is of a type that has quadpoints, false + /// otherwise. int FPDFAnnot_HasAttachmentPoints( FPDF_ANNOTATION annot, ) { @@ -2110,6 +3546,19 @@ class pdfium { late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr .asFunction(); + /// Experimental API. + /// Replace the attachment points (i.e. quadpoints) set of an annotation at + /// |quad_index|. This index needs to be within the result of + /// FPDFAnnot_CountAttachmentPoints(). + /// If the annotation's appearance stream is defined and this annotation is of a + /// type with quadpoints, then update the bounding box too if the new quadpoints + /// define a bigger one. + /// + /// annot - handle to an annotation. + /// quad_index - index of the set of quadpoints. + /// quad_points - the quadpoints to be set. + /// + /// Returns true if successful. int FPDFAnnot_SetAttachmentPoints( FPDF_ANNOTATION annot, int quad_index, @@ -2130,6 +3579,16 @@ class pdfium { _FPDFAnnot_SetAttachmentPointsPtr.asFunction< int Function(FPDF_ANNOTATION, int, ffi.Pointer)>(); + /// Experimental API. + /// Append to the list of attachment points (i.e. quadpoints) of an annotation. + /// If the annotation's appearance stream is defined and this annotation is of a + /// type with quadpoints, then update the bounding box too if the new quadpoints + /// define a bigger one. + /// + /// annot - handle to an annotation. + /// quad_points - the quadpoints to be set. + /// + /// Returns true if successful. int FPDFAnnot_AppendAttachmentPoints( FPDF_ANNOTATION annot, ffi.Pointer quad_points, @@ -2149,6 +3608,12 @@ class pdfium { _FPDFAnnot_AppendAttachmentPointsPtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Get the number of sets of quadpoints of an annotation. + /// + /// annot - handle to an annotation. + /// + /// Returns the number of sets of quadpoints, or 0 on failure. int FPDFAnnot_CountAttachmentPoints( FPDF_ANNOTATION annot, ) { @@ -2164,6 +3629,14 @@ class pdfium { _FPDFAnnot_CountAttachmentPointsPtr.asFunction< int Function(FPDF_ANNOTATION)>(); + /// Experimental API. + /// Get the attachment points (i.e. quadpoints) of an annotation. + /// + /// annot - handle to an annotation. + /// quad_index - index of the set of quadpoints. + /// quad_points - receives the quadpoints; must not be NULL. + /// + /// Returns true if successful. int FPDFAnnot_GetAttachmentPoints( FPDF_ANNOTATION annot, int quad_index, @@ -2184,6 +3657,16 @@ class pdfium { _FPDFAnnot_GetAttachmentPointsPtr.asFunction< int Function(FPDF_ANNOTATION, int, ffi.Pointer)>(); + /// Experimental API. + /// Set the annotation rectangle defining the location of the annotation. If the + /// annotation's appearance stream is defined and this annotation is of a type + /// without quadpoints, then update the bounding box too if the new rectangle + /// defines a bigger one. + /// + /// annot - handle to an annotation. + /// rect - the annotation rectangle to be set. + /// + /// Returns true if successful. int FPDFAnnot_SetRect( FPDF_ANNOTATION annot, ffi.Pointer rect, @@ -2201,6 +3684,13 @@ class pdfium { late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Get the annotation rectangle defining the location of the annotation. + /// + /// annot - handle to an annotation. + /// rect - receives the rectangle; must not be NULL. + /// + /// Returns true if successful. int FPDFAnnot_GetRect( FPDF_ANNOTATION annot, ffi.Pointer rect, @@ -2218,6 +3708,17 @@ class pdfium { late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Get the vertices of a polygon or polyline annotation. |buffer| is an array of + /// points of the annotation. If |length| is less than the returned length, or + /// |annot| or |buffer| is NULL, |buffer| will not be modified. + /// + /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() + /// buffer - buffer for holding the points. + /// length - length of the buffer in points. + /// + /// Returns the number of points if the annotation is of type polygon or + /// polyline, 0 otherwise. int FPDFAnnot_GetVertices( FPDF_ANNOTATION annot, ffi.Pointer buffer, @@ -2237,6 +3738,13 @@ class pdfium { late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the number of paths in the ink list of an ink annotation. + /// + /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() + /// + /// Returns the number of paths in the ink list if the annotation is of type ink, + /// 0 otherwise. int FPDFAnnot_GetInkListCount( FPDF_ANNOTATION annot, ) { @@ -2251,6 +3759,18 @@ class pdfium { late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); + /// Experimental API. + /// Get a path in the ink list of an ink annotation. |buffer| is an array of + /// points of the path. If |length| is less than the returned length, or |annot| + /// or |buffer| is NULL, |buffer| will not be modified. + /// + /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() + /// path_index - index of the path + /// buffer - buffer for holding the points. + /// length - length of the buffer in points. + /// + /// Returns the number of points of the path if the annotation is of type ink, 0 + /// otherwise. int FPDFAnnot_GetInkListPath( FPDF_ANNOTATION annot, int path_index, @@ -2276,6 +3796,15 @@ class pdfium { _FPDFAnnot_GetInkListPathPtr.asFunction< int Function(FPDF_ANNOTATION, int, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the starting and ending coordinates of a line annotation. + /// + /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() + /// start - starting point + /// end - ending point + /// + /// Returns true if the annotation is of type line, |start| and |end| are not + /// NULL, false otherwise. int FPDFAnnot_GetLine( FPDF_ANNOTATION annot, ffi.Pointer start, @@ -2296,6 +3825,18 @@ class pdfium { int Function( FPDF_ANNOTATION, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Set the characteristics of the annotation's border (rounded rectangle). + /// + /// annot - handle to an annotation + /// horizontal_radius - horizontal corner radius, in default user space units + /// vertical_radius - vertical corner radius, in default user space units + /// border_width - border width, in default user space units + /// + /// Returns true if setting the border for |annot| succeeds, false otherwise. + /// + /// If |annot| contains an appearance stream that overrides the border values, + /// then the appearance stream will be removed on success. int FPDFAnnot_SetBorder( FPDF_ANNOTATION annot, double horizontal_radius, @@ -2317,6 +3858,16 @@ class pdfium { late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction< int Function(FPDF_ANNOTATION, double, double, double)>(); + /// Experimental API. + /// Get the characteristics of the annotation's border (rounded rectangle). + /// + /// annot - handle to an annotation + /// horizontal_radius - horizontal corner radius, in default user space units + /// vertical_radius - vertical corner radius, in default user space units + /// border_width - border width, in default user space units + /// + /// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are + /// not NULL, false otherwise. int FPDFAnnot_GetBorder( FPDF_ANNOTATION annot, ffi.Pointer horizontal_radius, @@ -2342,6 +3893,24 @@ class pdfium { int Function(FPDF_ANNOTATION, ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Get the JavaScript of an event of the annotation's additional actions. + /// |buffer| is only modified if |buflen| is large enough to hold the whole + /// JavaScript string. If |buflen| is smaller, the total size of the JavaScript + /// is still returned, but nothing is copied. If there is no JavaScript for + /// |event| in |annot|, an empty string is written to |buf| and 2 is returned, + /// denoting the size of the null terminator in the buffer. On other errors, + /// nothing is written to |buffer| and 0 is returned. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// event - event type, one of the FPDF_ANNOT_AACTION_* values. + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes, including the 2-byte + /// null terminator. int FPDFAnnot_GetFormAdditionalActionJavaScript( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2368,6 +3937,13 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, ffi.Pointer, int)>(); + /// Experimental API. + /// Check if |annot|'s dictionary has |key| as a key. + /// + /// annot - handle to an annotation. + /// key - the key to look for, encoded in UTF-8. + /// + /// Returns true if |key| exists. int FPDFAnnot_HasKey( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2385,6 +3961,13 @@ class pdfium { late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction< int Function(FPDF_ANNOTATION, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Get the type of the value corresponding to |key| in |annot|'s dictionary. + /// + /// annot - handle to an annotation. + /// key - the key to look for, encoded in UTF-8. + /// + /// Returns the type of the dictionary value. int FPDFAnnot_GetValueType( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2402,6 +3985,16 @@ class pdfium { late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction< int Function(FPDF_ANNOTATION, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Set the string value corresponding to |key| in |annot|'s dictionary, + /// overwriting the existing value if any. The value type would be + /// FPDF_OBJECT_STRING after this function call succeeds. + /// + /// annot - handle to an annotation. + /// key - the key to the dictionary entry to be set, encoded in UTF-8. + /// value - the string value to be set, encoded in UTF-16LE. + /// + /// Returns true if successful. int FPDFAnnot_SetStringValue( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2422,6 +4015,21 @@ class pdfium { _FPDFAnnot_SetStringValuePtr.asFunction< int Function(FPDF_ANNOTATION, FPDF_BYTESTRING, FPDF_WIDESTRING)>(); + /// Experimental API. + /// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| + /// is only modified if |buflen| is longer than the length of contents. Note that + /// if |key| does not exist in the dictionary or if |key|'s corresponding value + /// in the dictionary is not a string (i.e. the value is not of type + /// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied + /// to |buffer| and the return value would be 2. On other errors, nothing would + /// be added to |buffer| and the return value would be 0. + /// + /// annot - handle to an annotation. + /// key - the key to the requested dictionary entry, encoded in UTF-8. + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetStringValue( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2448,6 +4056,17 @@ class pdfium { int Function(FPDF_ANNOTATION, FPDF_BYTESTRING, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the float value corresponding to |key| in |annot|'s dictionary. Writes + /// value to |value| and returns True if |key| exists in the dictionary and + /// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False + /// otherwise. + /// + /// annot - handle to an annotation. + /// key - the key to the requested dictionary entry, encoded in UTF-8. + /// value - receives the value, must not be NULL. + /// + /// Returns True if value found, False otherwise. int FPDFAnnot_GetNumberValue( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2469,6 +4088,18 @@ class pdfium { int Function( FPDF_ANNOTATION, FPDF_BYTESTRING, ffi.Pointer)>(); + /// Experimental API. + /// Set the AP (appearance string) in |annot|'s dictionary for a given + /// |appearanceMode|. + /// + /// annot - handle to an annotation. + /// appearanceMode - the appearance mode (normal, rollover or down) for which + /// to get the AP. + /// value - the string value to be set, encoded in UTF-16LE. If + /// nullptr is passed, the AP is cleared for that mode. If the + /// mode is Normal, APs for all modes are cleared. + /// + /// Returns true if successful. int FPDFAnnot_SetAP( FPDF_ANNOTATION annot, int appearanceMode, @@ -2488,6 +4119,23 @@ class pdfium { late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction< int Function(FPDF_ANNOTATION, int, FPDF_WIDESTRING)>(); + /// Experimental API. + /// Get the AP (appearance string) from |annot|'s dictionary for a given + /// |appearanceMode|. + /// |buffer| is only modified if |buflen| is large enough to hold the whole AP + /// string. If |buflen| is smaller, the total size of the AP is still returned, + /// but nothing is copied. + /// If there is no appearance stream for |annot| in |appearanceMode|, an empty + /// string is written to |buf| and 2 is returned. + /// On other errors, nothing is written to |buffer| and 0 is returned. + /// + /// annot - handle to an annotation. + /// appearanceMode - the appearance mode (normal, rollover or down) for which + /// to get the AP. + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetAP( FPDF_ANNOTATION annot, int appearanceMode, @@ -2509,6 +4157,16 @@ class pdfium { late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction< int Function(FPDF_ANNOTATION, int, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the annotation corresponding to |key| in |annot|'s dictionary. Common + /// keys for linking annotations include "IRT" and "Popup". Must call + /// FPDFPage_CloseAnnot() when the annotation returned by this function is no + /// longer needed. + /// + /// annot - handle to an annotation. + /// key - the key to the requested dictionary entry, encoded in UTF-8. + /// + /// Returns a handle to the linked annotation object, or NULL on failure. FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot( FPDF_ANNOTATION annot, FPDF_BYTESTRING key, @@ -2526,6 +4184,12 @@ class pdfium { late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr .asFunction(); + /// Experimental API. + /// Get the annotation flags of |annot|. + /// + /// annot - handle to an annotation. + /// + /// Returns the annotation flags. int FPDFAnnot_GetFlags( FPDF_ANNOTATION annot, ) { @@ -2540,6 +4204,13 @@ class pdfium { late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); + /// Experimental API. + /// Set the |annot|'s flags to be of the value |flags|. + /// + /// annot - handle to an annotation. + /// flags - the flag values to be set. + /// + /// Returns true if successful. int FPDFAnnot_SetFlags( FPDF_ANNOTATION annot, int flags, @@ -2556,6 +4227,14 @@ class pdfium { late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); + /// Experimental API. + /// Get the annotation flags of |annot|. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// + /// Returns the annotation flags specific to interactive forms. int FPDFAnnot_GetFormFieldFlags( FPDF_FORMHANDLE handle, FPDF_ANNOTATION annot, @@ -2573,6 +4252,19 @@ class pdfium { late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr .asFunction(); + /// Experimental API. + /// Retrieves an interactive form annotation whose rectangle contains a given + /// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned + /// is no longer needed. + /// + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// page - handle to the page, returned by FPDF_LoadPage function. + /// point - position in PDF "user space". + /// + /// Returns the interactive form annotation whose rectangle contains the given + /// coordinates on the page. If there is no such annotation, return NULL. FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint( FPDF_FORMHANDLE hHandle, FPDF_PAGE page, @@ -2594,6 +4286,19 @@ class pdfium { FPDF_ANNOTATION Function( FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer)>(); + /// Experimental API. + /// Gets the name of |annot|, which is an interactive form annotation. + /// |buffer| is only modified if |buflen| is longer than the length of contents. + /// In case of error, nothing will be added to |buffer| and the return value will + /// be 0. Note that return value of empty string is 2 for "\0\0". + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// buffer - buffer for holding the name string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetFormFieldName( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2620,6 +4325,20 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Gets the alternate name of |annot|, which is an interactive form annotation. + /// |buffer| is only modified if |buflen| is longer than the length of contents. + /// In case of error, nothing will be added to |buffer| and the return value will + /// be 0. Note that return value of empty string is 2 for "\0\0". + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// buffer - buffer for holding the alternate name string, encoded in + /// UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetFormFieldAlternateName( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2646,6 +4365,16 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Gets the form field type of |annot|, which is an interactive form annotation. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// + /// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on + /// success. Returns -1 on error. + /// See field types in fpdf_formfill.h. int FPDFAnnot_GetFormFieldType( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2663,6 +4392,19 @@ class pdfium { late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr .asFunction(); + /// Experimental API. + /// Gets the value of |annot|, which is an interactive form annotation. + /// |buffer| is only modified if |buflen| is longer than the length of contents. + /// In case of error, nothing will be added to |buffer| and the return value will + /// be 0. Note that return value of empty string is 2 for "\0\0". + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetFormFieldValue( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2689,6 +4431,16 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the number of options in the |annot|'s "Opt" dictionary. Intended for + /// use with listbox and combobox widget annotations. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// + /// Returns the number of options in "Opt" dictionary on success. Return value + /// will be -1 if annotation does not have an "Opt" dictionary or other error. int FPDFAnnot_GetOptionCount( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2706,6 +4458,24 @@ class pdfium { late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr .asFunction(); + /// Experimental API. + /// Get the string value for the label of the option at |index| in |annot|'s + /// "Opt" dictionary. Intended for use with listbox and combobox widget + /// annotations. |buffer| is only modified if |buflen| is longer than the length + /// of contents. If index is out of range or in case of other error, nothing + /// will be added to |buffer| and the return value will be 0. Note that + /// return value of empty string is 2 for "\0\0". + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// index - numeric index of the option in the "Opt" array + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. + /// If |annot| does not have an "Opt" array, |index| is out of range or if any + /// other error occurs, returns 0. int FPDFAnnot_GetOptionLabel( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2735,6 +4505,17 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, ffi.Pointer, int)>(); + /// Experimental API. + /// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary + /// is selected. Intended for use with listbox and combobox widget annotations. + /// + /// handle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// index - numeric index of the option in the "Opt" array. + /// + /// Returns true if the option at |index| in |annot|'s "Opt" dictionary is + /// selected, false otherwise. int FPDFAnnot_IsOptionSelected( FPDF_FORMHANDLE handle, FPDF_ANNOTATION annot, @@ -2754,6 +4535,18 @@ class pdfium { late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr .asFunction(); + /// Experimental API. + /// Get the float value of the font size for an |annot| with variable text. + /// If 0, the font is to be auto-sized: its size is computed as a function of + /// the height of the annotation rectangle. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// value - Required. Float which will be set to font size on success. + /// + /// Returns true if the font size was set in |value|, false on error or if + /// |value| not provided. int FPDFAnnot_GetFontSize( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2773,6 +4566,16 @@ class pdfium { late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Get the RGB value of the font color for an |annot| with variable text. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. + /// + /// Returns true if the font color was set, false on error or if the font + /// color was not provided. int FPDFAnnot_GetFontColor( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2805,6 +4608,15 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Determine if |annot| is a form widget that is checked. Intended for use with + /// checkbox and radio button widgets. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// + /// Returns true if |annot| is a form widget and is checked, false otherwise. int FPDFAnnot_IsChecked( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2822,6 +4634,17 @@ class pdfium { late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION)>(); + /// Experimental API. + /// Set the list of focusable annotation subtypes. Annotations of subtype + /// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API + /// will override the existing subtypes. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// subtypes - list of annotation subtype which can be tabbed over. + /// count - total number of annotation subtype in list. + /// Returns true if list of annotation subtype is set successfully, false + /// otherwise. int FPDFAnnot_SetFocusableSubtypes( FPDF_FORMHANDLE hHandle, ffi.Pointer subtypes, @@ -2845,6 +4668,14 @@ class pdfium { int Function( FPDF_FORMHANDLE, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the count of focusable annotation subtypes as set by host + /// for a |hHandle|. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// Returns the count of focusable annotation subtypes or -1 on error. + /// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. int FPDFAnnot_GetFocusableSubtypesCount( FPDF_FORMHANDLE hHandle, ) { @@ -2860,6 +4691,19 @@ class pdfium { _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction< int Function(FPDF_FORMHANDLE)>(); + /// Experimental API. + /// Get the list of focusable annotation subtype as set by host. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// subtypes - receives the list of annotation subtype which can be tabbed + /// over. Caller must have allocated |subtypes| more than or + /// equal to the count obtained from + /// FPDFAnnot_GetFocusableSubtypesCount() API. + /// count - size of |subtypes|. + /// Returns true on success and set list of annotation subtype to |subtypes|, + /// false otherwise. + /// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. int FPDFAnnot_GetFocusableSubtypes( FPDF_FORMHANDLE hHandle, ffi.Pointer subtypes, @@ -2883,6 +4727,13 @@ class pdfium { int Function( FPDF_FORMHANDLE, ffi.Pointer, int)>(); + /// Experimental API. + /// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. + /// + /// annot - handle to an annotation. + /// + /// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, + /// if the input annot is NULL or input annot's subtype is not link. FPDF_LINK FPDFAnnot_GetLink( FPDF_ANNOTATION annot, ) { @@ -2897,6 +4748,17 @@ class pdfium { late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); + /// Experimental API. + /// Gets the count of annotations in the |annot|'s control group. + /// A group of interactive form annotations is collectively called a form + /// control group. Here, |annot|, an interactive form annotation, should be + /// either a radio button or a checkbox. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// + /// Returns number of controls in its control group or -1 on error. int FPDFAnnot_GetFormControlCount( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2914,6 +4776,17 @@ class pdfium { late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr .asFunction(); + /// Experimental API. + /// Gets the index of |annot| in |annot|'s control group. + /// A group of interactive form annotations is collectively called a form + /// control group. Here, |annot|, an interactive form annotation, should be + /// either a radio button or a checkbox. + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// + /// Returns index of a given |annot| in its control group or -1 on error. int FPDFAnnot_GetFormControlIndex( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2931,6 +4804,20 @@ class pdfium { late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr .asFunction(); + /// Experimental API. + /// Gets the export value of |annot| which is an interactive form annotation. + /// Intended for use with radio button and checkbox widget annotations. + /// |buffer| is only modified if |buflen| is longer than the length of contents. + /// In case of error, nothing will be added to |buffer| and the return value + /// will be 0. Note that return value of empty string is 2 for "\0\0". + /// + /// hHandle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// buffer - buffer for holding the value string, encoded in UTF-16LE. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the string value in bytes. int FPDFAnnot_GetFormFieldExportValue( FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot, @@ -2957,6 +4844,13 @@ class pdfium { int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer, int)>(); + /// Experimental API. + /// Add a URI action to |annot|, overwriting the existing action, if any. + /// + /// annot - handle to a link annotation. + /// uri - the URI to be set, encoded in 7-bit ASCII. + /// + /// Returns true if successful. int FPDFAnnot_SetURI( FPDF_ANNOTATION annot, ffi.Pointer uri, @@ -2974,6 +4868,12 @@ class pdfium { late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction< int Function(FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Get the attachment from |annot|. + /// + /// annot - handle to a file annotation. + /// + /// Returns the handle to the attachment object, or NULL on failure. FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment( FPDF_ANNOTATION annot, ) { @@ -2988,6 +4888,13 @@ class pdfium { late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr .asFunction(); + /// Experimental API. + /// Add an embedded file with |name| to |annot|. + /// + /// annot - handle to a file annotation. + /// name - name of the new attachment. + /// + /// Returns a handle to the new attachment object, or NULL on failure. FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment( FPDF_ANNOTATION annot, FPDF_WIDESTRING name, @@ -3005,6 +4912,17 @@ class pdfium { late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr .asFunction(); + /// Function: FPDFText_LoadPage + /// Prepare information about all characters in a page. + /// Parameters: + /// page - Handle to the page. Returned by FPDF_LoadPage function + /// (in FPDFVIEW module). + /// Return value: + /// A handle to the text page information structure. + /// NULL if something goes wrong. + /// Comments: + /// Application must call FPDFText_ClosePage to release the text page + /// information. FPDF_TEXTPAGE FPDFText_LoadPage( FPDF_PAGE page, ) { @@ -3019,6 +4937,14 @@ class pdfium { late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); + /// Function: FPDFText_ClosePage + /// Release all resources allocated for a text page information + /// structure. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// Return Value: + /// None. void FPDFText_ClosePage( FPDF_TEXTPAGE text_page, ) { @@ -3033,6 +4959,21 @@ class pdfium { late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); + /// Function: FPDFText_CountChars + /// Get number of characters in a page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// Return value: + /// Number of characters in the page. Return -1 for error. + /// Generated characters, like additional space characters, new line + /// characters, are also counted. + /// Comments: + /// Characters in a page form a "stream", inside the stream, each + /// character has an index. + /// We will use the index parameters in many of FPDFTEXT functions. The + /// first character in the page + /// has an index value of zero. int FPDFText_CountChars( FPDF_TEXTPAGE text_page, ) { @@ -3047,6 +4988,17 @@ class pdfium { late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); + /// Function: FPDFText_GetUnicode + /// Get Unicode of a character in a page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// The Unicode of the particular character. + /// If a character is not encoded in Unicode and Foxit engine can't + /// convert to Unicode, + /// the return value will be zero. int FPDFText_GetUnicode( FPDF_TEXTPAGE text_page, int index, @@ -3063,6 +5015,17 @@ class pdfium { late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); + /// Experimental API. + /// Function: FPDFText_IsGenerated + /// Get if a character in a page is generated by PDFium. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// 1 if the character is generated by PDFium. + /// 0 if the character is not generated by PDFium. + /// -1 if there was an error. int FPDFText_IsGenerated( FPDF_TEXTPAGE text_page, int index, @@ -3079,6 +5042,17 @@ class pdfium { late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); + /// Experimental API. + /// Function: FPDFText_IsHyphen + /// Get if a character in a page is a hyphen. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// 1 if the character is a hyphen. + /// 0 if the character is not a hyphen. + /// -1 if there was an error. int FPDFText_IsHyphen( FPDF_TEXTPAGE text_page, int index, @@ -3095,6 +5069,17 @@ class pdfium { late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); + /// Experimental API. + /// Function: FPDFText_HasUnicodeMapError + /// Get if a character in a page has an invalid unicode mapping. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// 1 if the character has an invalid unicode mapping. + /// 0 if the character has no known unicode mapping issues. + /// -1 if there was an error. int FPDFText_HasUnicodeMapError( FPDF_TEXTPAGE text_page, int index, @@ -3111,6 +5096,16 @@ class pdfium { late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr .asFunction(); + /// Function: FPDFText_GetFontSize + /// Get the font size of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// The font size of the particular character, measured in points (about + /// 1/72 inch). This is the typographic size of the font (so called + /// "em size"). double FPDFText_GetFontSize( FPDF_TEXTPAGE text_page, int index, @@ -3127,6 +5122,24 @@ class pdfium { late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction< double Function(FPDF_TEXTPAGE, int)>(); + /// Experimental API. + /// Function: FPDFText_GetFontInfo + /// Get the font name and flags of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// buffer - A buffer receiving the font name. + /// buflen - The length of |buffer| in bytes. + /// flags - Optional pointer to an int receiving the font flags. + /// These flags should be interpreted per PDF spec 1.7 + /// Section 5.7.1 Font Descriptor Flags. + /// Return value: + /// On success, return the length of the font name, including the + /// trailing NUL character, in bytes. If this length is less than or + /// equal to |length|, |buffer| is set to the font name, |flags| is + /// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on + /// failure. int FPDFText_GetFontInfo( FPDF_TEXTPAGE text_page, int index, @@ -3155,6 +5168,17 @@ class pdfium { int Function(FPDF_TEXTPAGE, int, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDFText_GetFontWeight + /// Get the font weight of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// On success, return the font weight of the particular character. If + /// |text_page| is invalid, if |index| is out of bounds, or if the + /// character's text object is undefined, return -1. int FPDFText_GetFontWeight( FPDF_TEXTPAGE text_page, int index, @@ -3171,6 +5195,18 @@ class pdfium { late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); + /// Experimental API. + /// Function: FPDFText_GetTextRenderMode + /// Get text rendering mode of character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return Value: + /// On success, return the render mode value. A valid value is of type + /// FPDF_TEXT_RENDERMODE. If |text_page| is invalid, if |index| is out + /// of bounds, or if the text object is undefined, then return + /// FPDF_TEXTRENDERMODE_UNKNOWN. FPDF_TEXT_RENDERMODE FPDFText_GetTextRenderMode( FPDF_TEXTPAGE text_page, int index, @@ -3187,6 +5223,24 @@ class pdfium { late final _FPDFText_GetTextRenderMode = _FPDFText_GetTextRenderModePtr .asFunction(); + /// Experimental API. + /// Function: FPDFText_GetFillColor + /// Get the fill color of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// R - Pointer to an unsigned int number receiving the + /// red value of the fill color. + /// G - Pointer to an unsigned int number receiving the + /// green value of the fill color. + /// B - Pointer to an unsigned int number receiving the + /// blue value of the fill color. + /// A - Pointer to an unsigned int number receiving the + /// alpha value of the fill color. + /// Return value: + /// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are + /// unchanged. int FPDFText_GetFillColor( FPDF_TEXTPAGE text_page, int index, @@ -3223,6 +5277,24 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDFText_GetStrokeColor + /// Get the stroke color of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// R - Pointer to an unsigned int number receiving the + /// red value of the stroke color. + /// G - Pointer to an unsigned int number receiving the + /// green value of the stroke color. + /// B - Pointer to an unsigned int number receiving the + /// blue value of the stroke color. + /// A - Pointer to an unsigned int number receiving the + /// alpha value of the stroke color. + /// Return value: + /// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are + /// unchanged. int FPDFText_GetStrokeColor( FPDF_TEXTPAGE text_page, int index, @@ -3259,6 +5331,17 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDFText_GetCharAngle + /// Get character rotation angle. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return Value: + /// On success, return the angle value in radian. Value will always be + /// greater or equal to 0. If |text_page| is invalid, or if |index| is + /// out of bounds, then return -1. double FPDFText_GetCharAngle( FPDF_TEXTPAGE text_page, int index, @@ -3275,6 +5358,26 @@ class pdfium { late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction< double Function(FPDF_TEXTPAGE, int)>(); + /// Function: FPDFText_GetCharBox + /// Get bounding box of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// left - Pointer to a double number receiving left position + /// of the character box. + /// right - Pointer to a double number receiving right position + /// of the character box. + /// bottom - Pointer to a double number receiving bottom position + /// of the character box. + /// top - Pointer to a double number receiving top position of + /// the character box. + /// Return Value: + /// On success, return TRUE and fill in |left|, |right|, |bottom|, and + /// |top|. If |text_page| is invalid, or if |index| is out of bounds, + /// then return FALSE, and the out parameters remain unmodified. + /// Comments: + /// All positions are measured in PDF "user space". int FPDFText_GetCharBox( FPDF_TEXTPAGE text_page, int index, @@ -3311,6 +5414,22 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDFText_GetLooseCharBox + /// Get a "loose" bounding box of a particular character, i.e., covering + /// the entire glyph bounds, without taking the actual glyph shape into + /// account. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// rect - Pointer to a FS_RECTF receiving the character box. + /// Return Value: + /// On success, return TRUE and fill in |rect|. If |text_page| is + /// invalid, or if |index| is out of bounds, then return FALSE, and the + /// |rect| out parameter remains unmodified. + /// Comments: + /// All positions are measured in PDF "user space". int FPDFText_GetLooseCharBox( FPDF_TEXTPAGE text_page, int index, @@ -3330,6 +5449,19 @@ class pdfium { late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr .asFunction)>(); + /// Experimental API. + /// Function: FPDFText_GetMatrix + /// Get the effective transformation matrix for a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage(). + /// index - Zero-based index of the character. + /// matrix - Pointer to a FS_MATRIX receiving the transformation + /// matrix. + /// Return Value: + /// On success, return TRUE and fill in |matrix|. If |text_page| is + /// invalid, or if |index| is out of bounds, or if |matrix| is NULL, + /// then return FALSE, and |matrix| remains unmodified. int FPDFText_GetMatrix( FPDF_TEXTPAGE text_page, int index, @@ -3349,6 +5481,20 @@ class pdfium { late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction< int Function(FPDF_TEXTPAGE, int, ffi.Pointer)>(); + /// Function: FPDFText_GetCharOrigin + /// Get origin of a particular character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// x - Pointer to a double number receiving x coordinate of + /// the character origin. + /// y - Pointer to a double number receiving y coordinate of + /// the character origin. + /// Return Value: + /// Whether the call succeeded. If false, x and y are unchanged. + /// Comments: + /// All positions are measured in PDF "user space". int FPDFText_GetCharOrigin( FPDF_TEXTPAGE text_page, int index, @@ -3371,6 +5517,22 @@ class pdfium { int Function(FPDF_TEXTPAGE, int, ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDFText_GetCharIndexAtPos + /// Get the index of a character at or nearby a certain position on the + /// page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// x - X position in PDF "user space". + /// y - Y position in PDF "user space". + /// xTolerance - An x-axis tolerance value for character hit + /// detection, in point units. + /// yTolerance - A y-axis tolerance value for character hit + /// detection, in point units. + /// Return Value: + /// The zero-based index of the character at, or nearby the point (x,y). + /// If there is no character at or nearby the point, return value will + /// be -1. If an error occurs, -3 will be returned. int FPDFText_GetCharIndexAtPos( FPDF_TEXTPAGE text_page, double x, @@ -3395,6 +5557,25 @@ class pdfium { _FPDFText_GetCharIndexAtPosPtr.asFunction< int Function(FPDF_TEXTPAGE, double, double, double, double)>(); + /// Function: FPDFText_GetText + /// Extract unicode text string from the page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// start_index - Index for the start characters. + /// count - Number of UCS-2 values to be extracted. + /// result - A buffer (allocated by application) receiving the + /// extracted UCS-2 values. The buffer must be able to + /// hold `count` UCS-2 values plus a terminator. + /// Return Value: + /// Number of characters written into the result buffer, including the + /// trailing terminator. + /// Comments: + /// This function ignores characters without UCS-2 representations. + /// It considers all characters on the page, even those that are not + /// visible when the page has a cropbox. To filter out the characters + /// outside of the cropbox, use FPDF_GetPageBoundingBox() and + /// FPDFText_GetCharBox(). int FPDFText_GetText( FPDF_TEXTPAGE text_page, int start_index, @@ -3416,6 +5597,23 @@ class pdfium { late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction< int Function(FPDF_TEXTPAGE, int, int, ffi.Pointer)>(); + /// Function: FPDFText_CountRects + /// Counts number of rectangular areas occupied by a segment of text, + /// and caches the result for subsequent FPDFText_GetRect() calls. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// start_index - Index for the start character. + /// count - Number of characters, or -1 for all remaining. + /// Return value: + /// Number of rectangles, 0 if text_page is null, or -1 on bad + /// start_index. + /// Comments: + /// This function, along with FPDFText_GetRect can be used by + /// applications to detect the position on the page for a text segment, + /// so proper areas can be highlighted. The FPDFText_* functions will + /// automatically merge small character boxes into bigger one if those + /// characters are on the same line and use same font settings. int FPDFText_CountRects( FPDF_TEXTPAGE text_page, int start_index, @@ -3435,6 +5633,27 @@ class pdfium { late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction< int Function(FPDF_TEXTPAGE, int, int)>(); + /// Function: FPDFText_GetRect + /// Get a rectangular area from the result generated by + /// FPDFText_CountRects. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// rect_index - Zero-based index for the rectangle. + /// left - Pointer to a double value receiving the rectangle + /// left boundary. + /// top - Pointer to a double value receiving the rectangle + /// top boundary. + /// right - Pointer to a double value receiving the rectangle + /// right boundary. + /// bottom - Pointer to a double value receiving the rectangle + /// bottom boundary. + /// Return Value: + /// On success, return TRUE and fill in |left|, |top|, |right|, and + /// |bottom|. If |text_page| is invalid then return FALSE, and the out + /// parameters remain unmodified. If |text_page| is valid but + /// |rect_index| is out of bounds, then return FALSE and set the out + /// parameters to 0. int FPDFText_GetRect( FPDF_TEXTPAGE text_page, int rect_index, @@ -3471,6 +5690,29 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDFText_GetBoundedText + /// Extract unicode text within a rectangular boundary on the page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// left - Left boundary. + /// top - Top boundary. + /// right - Right boundary. + /// bottom - Bottom boundary. + /// buffer - Caller-allocated buffer to receive UTF-16 values. + /// buflen - Number of UTF-16 values (not bytes) that `buffer` + /// is capable of holding. + /// Return Value: + /// If buffer is NULL or buflen is zero, return number of UTF-16 + /// values (not bytes) of text present within the rectangle, excluding + /// a terminating NUL. Generally you should pass a buffer at least one + /// larger than this if you want a terminating NUL, which will be + /// provided if space is available. Otherwise, return number of UTF-16 + /// values copied into the buffer, including the terminating NUL when + /// space for it is available. + /// Comment: + /// If the buffer is too small, as much text as will fit is copied into + /// it. May return a split surrogate in that case. int FPDFText_GetBoundedText( FPDF_TEXTPAGE text_page, double left, @@ -3505,6 +5747,17 @@ class pdfium { int Function(FPDF_TEXTPAGE, double, double, double, double, ffi.Pointer, int)>(); + /// Function: FPDFText_FindStart + /// Start a search. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// findwhat - A unicode match pattern. + /// flags - Option flags. + /// start_index - Start from this character. -1 for end of the page. + /// Return Value: + /// A handle for the search context. FPDFText_FindClose must be called + /// to release this handle. FPDF_SCHHANDLE FPDFText_FindStart( FPDF_TEXTPAGE text_page, FPDF_WIDESTRING findwhat, @@ -3526,6 +5779,13 @@ class pdfium { late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction< FPDF_SCHHANDLE Function(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int)>(); + /// Function: FPDFText_FindNext + /// Search in the direction from page start to end. + /// Parameters: + /// handle - A search context handle returned by + /// FPDFText_FindStart. + /// Return Value: + /// Whether a match is found. int FPDFText_FindNext( FPDF_SCHHANDLE handle, ) { @@ -3540,6 +5800,13 @@ class pdfium { late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); + /// Function: FPDFText_FindPrev + /// Search in the direction from page end to start. + /// Parameters: + /// handle - A search context handle returned by + /// FPDFText_FindStart. + /// Return Value: + /// Whether a match is found. int FPDFText_FindPrev( FPDF_SCHHANDLE handle, ) { @@ -3554,6 +5821,13 @@ class pdfium { late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); + /// Function: FPDFText_GetSchResultIndex + /// Get the starting character index of the search result. + /// Parameters: + /// handle - A search context handle returned by + /// FPDFText_FindStart. + /// Return Value: + /// Index for the starting character. int FPDFText_GetSchResultIndex( FPDF_SCHHANDLE handle, ) { @@ -3568,6 +5842,13 @@ class pdfium { late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); + /// Function: FPDFText_GetSchCount + /// Get the number of matched characters in the search result. + /// Parameters: + /// handle - A search context handle returned by + /// FPDFText_FindStart. + /// Return Value: + /// Number of matched characters. int FPDFText_GetSchCount( FPDF_SCHHANDLE handle, ) { @@ -3582,6 +5863,13 @@ class pdfium { late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); + /// Function: FPDFText_FindClose + /// Release a search context. + /// Parameters: + /// handle - A search context handle returned by + /// FPDFText_FindStart. + /// Return Value: + /// None. void FPDFText_FindClose( FPDF_SCHHANDLE handle, ) { @@ -3596,6 +5884,24 @@ class pdfium { late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); + /// Function: FPDFLink_LoadWebLinks + /// Prepare information about weblinks in a page. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// Return Value: + /// A handle to the page's links information structure, or + /// NULL if something goes wrong. + /// Comments: + /// Weblinks are those links implicitly embedded in PDF pages. PDF also + /// has a type of annotation called "link" (FPDFTEXT doesn't deal with + /// that kind of link). FPDFTEXT weblink feature is useful for + /// automatically detecting links in the page contents. For example, + /// things like "https://www.example.com" will be detected, so + /// applications can allow user to click on those characters to activate + /// the link, even the PDF doesn't come with link annotations. + /// + /// FPDFLink_CloseWebLinks must be called to release resources. FPDF_PAGELINK FPDFLink_LoadWebLinks( FPDF_TEXTPAGE text_page, ) { @@ -3610,6 +5916,12 @@ class pdfium { late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction< FPDF_PAGELINK Function(FPDF_TEXTPAGE)>(); + /// Function: FPDFLink_CountWebLinks + /// Count number of detected web links. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// Return Value: + /// Number of detected web links. int FPDFLink_CountWebLinks( FPDF_PAGELINK link_page, ) { @@ -3624,6 +5936,24 @@ class pdfium { late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); + /// Function: FPDFLink_GetURL + /// Fetch the URL information for a detected web link. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// link_index - Zero-based index for the link. + /// buffer - A unicode buffer for the result. + /// buflen - Number of 16-bit code units (not bytes) for the + /// buffer, including an additional terminator. + /// Return Value: + /// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit + /// code units (not bytes) needed to buffer the result (an additional + /// terminator is included in this count). + /// Otherwise, copy the result into |buffer|, truncating at |buflen| if + /// the result is too large to fit, and return the number of 16-bit code + /// units actually copied into the buffer (the additional terminator is + /// also included in this count). + /// If |link_index| does not correspond to a valid link, then the result + /// is an empty string. int FPDFLink_GetURL( FPDF_PAGELINK link_page, int link_index, @@ -3645,6 +5975,14 @@ class pdfium { late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction< int Function(FPDF_PAGELINK, int, ffi.Pointer, int)>(); + /// Function: FPDFLink_CountRects + /// Count number of rectangular areas for the link. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// link_index - Zero-based index for the link. + /// Return Value: + /// Number of rectangular areas for the link. If |link_index| does + /// not correspond to a valid link, then 0 is returned. int FPDFLink_CountRects( FPDF_PAGELINK link_page, int link_index, @@ -3661,6 +5999,25 @@ class pdfium { late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); + /// Function: FPDFLink_GetRect + /// Fetch the boundaries of a rectangle for a link. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// link_index - Zero-based index for the link. + /// rect_index - Zero-based index for a rectangle. + /// left - Pointer to a double value receiving the rectangle + /// left boundary. + /// top - Pointer to a double value receiving the rectangle + /// top boundary. + /// right - Pointer to a double value receiving the rectangle + /// right boundary. + /// bottom - Pointer to a double value receiving the rectangle + /// bottom boundary. + /// Return Value: + /// On success, return TRUE and fill in |left|, |top|, |right|, and + /// |bottom|. If |link_page| is invalid or if |link_index| does not + /// correspond to a valid link, then return FALSE, and the out + /// parameters remain unmodified. int FPDFLink_GetRect( FPDF_PAGELINK link_page, int link_index, @@ -3701,6 +6058,19 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Function: FPDFLink_GetTextRange + /// Fetch the start char index and char count for a link. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// link_index - Zero-based index for the link. + /// start_char_index - pointer to int receiving the start char index + /// char_count - pointer to int receiving the char count + /// Return Value: + /// On success, return TRUE and fill in |start_char_index| and + /// |char_count|. if |link_page| is invalid or if |link_index| does + /// not correspond to a valid link, then return FALSE and the out + /// parameters remain unmodified. int FPDFLink_GetTextRange( FPDF_PAGELINK link_page, int link_index, @@ -3723,6 +6093,12 @@ class pdfium { int Function( FPDF_PAGELINK, int, ffi.Pointer, ffi.Pointer)>(); + /// Function: FPDFLink_CloseWebLinks + /// Release resources used by weblink feature. + /// Parameters: + /// link_page - Handle returned by FPDFLink_LoadWebLinks. + /// Return Value: + /// None. void FPDFLink_CloseWebLinks( FPDF_PAGELINK link_page, ) { @@ -3737,6 +6113,16 @@ class pdfium { late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); + /// Get the first child of |bookmark|, or the first top-level bookmark item. + /// + /// document - handle to the document. + /// bookmark - handle to the current bookmark. Pass NULL for the first top + /// level item. + /// + /// Returns a handle to the first child of |bookmark| or the first top-level + /// bookmark item. NULL if no child or top-level bookmark found. + /// Note that another name for the bookmarks is the document outline, as + /// described in ISO 32000-1:2008, section 12.3.3. FPDF_BOOKMARK FPDFBookmark_GetFirstChild( FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark, @@ -3754,6 +6140,16 @@ class pdfium { late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr .asFunction(); + /// Get the next sibling of |bookmark|. + /// + /// document - handle to the document. + /// bookmark - handle to the current bookmark. + /// + /// Returns a handle to the next sibling of |bookmark|, or NULL if this is the + /// last bookmark at this level. + /// + /// Note that the caller is responsible for handling circular bookmark + /// references, as may arise from malformed documents. FPDF_BOOKMARK FPDFBookmark_GetNextSibling( FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark, @@ -3771,6 +6167,19 @@ class pdfium { late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr .asFunction(); + /// Get the title of |bookmark|. + /// + /// bookmark - handle to the bookmark. + /// buffer - buffer for the title. May be NULL. + /// buflen - the length of the buffer in bytes. May be 0. + /// + /// Returns the number of bytes in the title, including the terminating NUL + /// character. The number of bytes is returned regardless of the |buffer| and + /// |buflen| parameters. + /// + /// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The + /// string is terminated by a UTF16 NUL character. If |buflen| is less than the + /// required length, or |buffer| is NULL, |buffer| will not be modified. int FPDFBookmark_GetTitle( FPDF_BOOKMARK bookmark, ffi.Pointer buffer, @@ -3790,6 +6199,16 @@ class pdfium { late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction< int Function(FPDF_BOOKMARK, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the number of chlidren of |bookmark|. + /// + /// bookmark - handle to the bookmark. + /// + /// Returns a signed integer that represents the number of sub-items the given + /// bookmark has. If the value is positive, child items shall be shown by default + /// (open state). If the value is negative, child items shall be hidden by + /// default (closed state). Please refer to PDF 32000-1:2008, Table 153. + /// Returns 0 if the bookmark has no children or is invalid. int FPDFBookmark_GetCount( FPDF_BOOKMARK bookmark, ) { @@ -3804,6 +6223,15 @@ class pdfium { late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); + /// Find the bookmark with |title| in |document|. + /// + /// document - handle to the document. + /// title - the UTF-16LE encoded Unicode title for which to search. + /// + /// Returns the handle to the bookmark, or NULL if |title| can't be found. + /// + /// FPDFBookmark_Find() will always return the first bookmark found even if + /// multiple bookmarks have the same |title|. FPDF_BOOKMARK FPDFBookmark_Find( FPDF_DOCUMENT document, FPDF_WIDESTRING title, @@ -3821,6 +6249,13 @@ class pdfium { late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction< FPDF_BOOKMARK Function(FPDF_DOCUMENT, FPDF_WIDESTRING)>(); + /// Get the destination associated with |bookmark|. + /// + /// document - handle to the document. + /// bookmark - handle to the bookmark. + /// + /// Returns the handle to the destination data, or NULL if no destination is + /// associated with |bookmark|. FPDF_DEST FPDFBookmark_GetDest( FPDF_DOCUMENT document, FPDF_BOOKMARK bookmark, @@ -3837,6 +6272,16 @@ class pdfium { late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction< FPDF_DEST Function(FPDF_DOCUMENT, FPDF_BOOKMARK)>(); + /// Get the action associated with |bookmark|. + /// + /// bookmark - handle to the bookmark. + /// + /// Returns the handle to the action data, or NULL if no action is associated + /// with |bookmark|. + /// If this function returns a valid handle, it is valid as long as |bookmark| is + /// valid. + /// If this function returns NULL, FPDFBookmark_GetDest() should be called to get + /// the |bookmark| destination data. FPDF_ACTION FPDFBookmark_GetAction( FPDF_BOOKMARK bookmark, ) { @@ -3851,6 +6296,16 @@ class pdfium { late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction< FPDF_ACTION Function(FPDF_BOOKMARK)>(); + /// Get the type of |action|. + /// + /// action - handle to the action. + /// + /// Returns one of: + /// PDFACTION_UNSUPPORTED + /// PDFACTION_GOTO + /// PDFACTION_REMOTEGOTO + /// PDFACTION_URI + /// PDFACTION_LAUNCH int FPDFAction_GetType( FPDF_ACTION action, ) { @@ -3865,6 +6320,18 @@ class pdfium { late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); + /// Get the destination of |action|. + /// + /// document - handle to the document. + /// action - handle to the action. |action| must be a |PDFACTION_GOTO| or + /// |PDFACTION_REMOTEGOTO|. + /// + /// Returns a handle to the destination data, or NULL on error, typically + /// because the arguments were bad or the action was of the wrong type. + /// + /// In the case of |PDFACTION_REMOTEGOTO|, you must first call + /// FPDFAction_GetFilePath(), then load the document at that path, then pass + /// the document handle from that document as |document| to FPDFAction_GetDest(). FPDF_DEST FPDFAction_GetDest( FPDF_DOCUMENT document, FPDF_ACTION action, @@ -3881,6 +6348,20 @@ class pdfium { late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction< FPDF_DEST Function(FPDF_DOCUMENT, FPDF_ACTION)>(); + /// Get the file path of |action|. + /// + /// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or + /// |PDFACTION_REMOTEGOTO|. + /// buffer - a buffer for output the path string. May be NULL. + /// buflen - the length of the buffer, in bytes. May be 0. + /// + /// Returns the number of bytes in the file path, including the trailing NUL + /// character, or 0 on error, typically because the arguments were bad or the + /// action was of the wrong type. + /// + /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. + /// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| + /// will not be modified. int FPDFAction_GetFilePath( FPDF_ACTION action, ffi.Pointer buffer, @@ -3900,6 +6381,29 @@ class pdfium { late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction< int Function(FPDF_ACTION, ffi.Pointer, int)>(); + /// Get the URI path of |action|. + /// + /// document - handle to the document. + /// action - handle to the action. Must be a |PDFACTION_URI|. + /// buffer - a buffer for the path string. May be NULL. + /// buflen - the length of the buffer, in bytes. May be 0. + /// + /// Returns the number of bytes in the URI path, including the trailing NUL + /// character, or 0 on error, typically because the arguments were bad or the + /// action was of the wrong type. + /// + /// The |buffer| may contain badly encoded data. The caller should validate the + /// output. e.g. Check to see if it is UTF-8. + /// + /// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| + /// will not be modified. + /// + /// Historically, the documentation for this API claimed |buffer| is always + /// encoded in 7-bit ASCII, but did not actually enforce it. + /// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 + /// added that enforcement, but that did not work well for real world PDFs that + /// used UTF-8. As of this writing, this API reverted back to its original + /// behavior prior to commit d609e84cee. int FPDFAction_GetURIPath( FPDF_DOCUMENT document, FPDF_ACTION action, @@ -3924,6 +6428,12 @@ class pdfium { late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction< int Function(FPDF_DOCUMENT, FPDF_ACTION, ffi.Pointer, int)>(); + /// Get the page index of |dest|. + /// + /// document - handle to the document. + /// dest - handle to the destination. + /// + /// Returns the 0-based page index containing |dest|. Returns -1 on error. int FPDFDest_GetDestPageIndex( FPDF_DOCUMENT document, FPDF_DEST dest, @@ -3940,6 +6450,15 @@ class pdfium { late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr .asFunction(); + /// Experimental API. + /// Get the view (fit type) specified by |dest|. + /// + /// dest - handle to the destination. + /// pNumParams - receives the number of view parameters, which is at most 4. + /// pParams - buffer to write the view parameters. Must be at least 4 + /// FS_FLOATs long. + /// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if + /// |dest| does not specify a view. int FPDFDest_GetView( FPDF_DEST dest, ffi.Pointer pNumParams, @@ -3960,6 +6479,20 @@ class pdfium { int Function( FPDF_DEST, ffi.Pointer, ffi.Pointer)>(); + /// Get the (x, y, zoom) location of |dest| in the destination page, if the + /// destination is in [page /XYZ x y zoom] syntax. + /// + /// dest - handle to the destination. + /// hasXVal - out parameter; true if the x value is not null + /// hasYVal - out parameter; true if the y value is not null + /// hasZoomVal - out parameter; true if the zoom value is not null + /// x - out parameter; the x coordinate, in page coordinates. + /// y - out parameter; the y coordinate, in page coordinates. + /// zoom - out parameter; the zoom value. + /// Returns TRUE on successfully reading the /XYZ value. + /// + /// Note the [x, y, zoom] values are only set if the corresponding hasXVal, + /// hasYVal or hasZoomVal flags are true. int FPDFDest_GetLocationInPage( FPDF_DEST dest, ffi.Pointer hasXVal, @@ -4001,6 +6534,16 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Find a link at point (|x|,|y|) on |page|. + /// + /// page - handle to the document page. + /// x - the x coordinate, in the page coordinate system. + /// y - the y coordinate, in the page coordinate system. + /// + /// Returns a handle to the link, or NULL if no link found at the given point. + /// + /// You can convert coordinates from screen coordinates to page coordinates using + /// FPDF_DeviceToPage(). FPDF_LINK FPDFLink_GetLinkAtPoint( FPDF_PAGE page, double x, @@ -4020,6 +6563,17 @@ class pdfium { late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction< FPDF_LINK Function(FPDF_PAGE, double, double)>(); + /// Find the Z-order of link at point (|x|,|y|) on |page|. + /// + /// page - handle to the document page. + /// x - the x coordinate, in the page coordinate system. + /// y - the y coordinate, in the page coordinate system. + /// + /// Returns the Z-order of the link, or -1 if no link found at the given point. + /// Larger Z-order numbers are closer to the front. + /// + /// You can convert coordinates from screen coordinates to page coordinates using + /// FPDF_DeviceToPage(). int FPDFLink_GetLinkZOrderAtPoint( FPDF_PAGE page, double x, @@ -4039,6 +6593,14 @@ class pdfium { late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr .asFunction(); + /// Get destination info for |link|. + /// + /// document - handle to the document. + /// link - handle to the link. + /// + /// Returns a handle to the destination, or NULL if there is no destination + /// associated with the link. In this case, you should call FPDFLink_GetAction() + /// to retrieve the action associated with |link|. FPDF_DEST FPDFLink_GetDest( FPDF_DOCUMENT document, FPDF_LINK link, @@ -4055,6 +6617,13 @@ class pdfium { late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction< FPDF_DEST Function(FPDF_DOCUMENT, FPDF_LINK)>(); + /// Get action info for |link|. + /// + /// link - handle to the link. + /// + /// Returns a handle to the action associated to |link|, or NULL if no action. + /// If this function returns a valid handle, it is valid as long as |link| is + /// valid. FPDF_ACTION FPDFLink_GetAction( FPDF_LINK link, ) { @@ -4069,6 +6638,14 @@ class pdfium { late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); + /// Enumerates all the link annotations in |page|. + /// + /// page - handle to the page. + /// start_pos - the start position, should initially be 0 and is updated with + /// the next start position on return. + /// link_annot - the link handle for |startPos|. + /// + /// Returns TRUE on success. int FPDFLink_Enumerate( FPDF_PAGE page, ffi.Pointer start_pos, @@ -4088,6 +6665,14 @@ class pdfium { late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction< int Function(FPDF_PAGE, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Gets FPDF_ANNOTATION object for |link_annot|. + /// + /// page - handle to the page in which FPDF_LINK object is present. + /// link_annot - handle to link annotation. + /// + /// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, + /// if the input link annot or page is NULL. FPDF_ANNOTATION FPDFLink_GetAnnot( FPDF_PAGE page, FPDF_LINK link_annot, @@ -4104,6 +6689,12 @@ class pdfium { late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction< FPDF_ANNOTATION Function(FPDF_PAGE, FPDF_LINK)>(); + /// Get the rectangle for |link_annot|. + /// + /// link_annot - handle to the link annotation. + /// rect - the annotation rectangle. + /// + /// Returns true on success. int FPDFLink_GetAnnotRect( FPDF_LINK link_annot, ffi.Pointer rect, @@ -4121,6 +6712,11 @@ class pdfium { late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction< int Function(FPDF_LINK, ffi.Pointer)>(); + /// Get the count of quadrilateral points to the |link_annot|. + /// + /// link_annot - handle to the link annotation. + /// + /// Returns the count of quadrilateral points. int FPDFLink_CountQuadPoints( FPDF_LINK link_annot, ) { @@ -4135,6 +6731,13 @@ class pdfium { late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); + /// Get the quadrilateral points for the specified |quad_index| in |link_annot|. + /// + /// link_annot - handle to the link annotation. + /// quad_index - the specified quad point index. + /// quad_points - receives the quadrilateral points. + /// + /// Returns true on success. int FPDFLink_GetQuadPoints( FPDF_LINK link_annot, int quad_index, @@ -4154,6 +6757,17 @@ class pdfium { late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction< int Function(FPDF_LINK, int, ffi.Pointer)>(); + /// Experimental API + /// Gets an additional-action from |page|. + /// + /// page - handle to the page, as returned by FPDF_LoadPage(). + /// aa_type - the type of the page object's addtional-action, defined + /// in public/fpdf_formfill.h + /// + /// Returns the handle to the action data, or NULL if there is no + /// additional-action of type |aa_type|. + /// If this function returns a valid handle, it is valid as long as |page| is + /// valid. FPDF_ACTION FPDF_GetPageAAction( FPDF_PAGE page, int aa_type, @@ -4170,6 +6784,20 @@ class pdfium { late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction< FPDF_ACTION Function(FPDF_PAGE, int)>(); + /// Experimental API. + /// Get the file identifer defined in the trailer of |document|. + /// + /// document - handle to the document. + /// id_type - the file identifier type to retrieve. + /// buffer - a buffer for the file identifier. May be NULL. + /// buflen - the length of the buffer, in bytes. May be 0. + /// + /// Returns the number of bytes in the file identifier, including the NUL + /// terminator. + /// + /// The |buffer| is always a byte string. The |buffer| is followed by a NUL + /// terminator. If |buflen| is less than the returned length, or |buffer| is + /// NULL, |buffer| will not be modified. int FPDF_GetFileIdentifier( FPDF_DOCUMENT document, FPDF_FILEIDTYPE id_type, @@ -4194,6 +6822,27 @@ class pdfium { late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction< int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); + /// Get meta-data |tag| content from |document|. + /// + /// document - handle to the document. + /// tag - the tag to retrieve. The tag can be one of: + /// Title, Author, Subject, Keywords, Creator, Producer, + /// CreationDate, or ModDate. + /// For detailed explanations of these tags and their respective + /// values, please refer to PDF Reference 1.6, section 10.2.1, + /// 'Document Information Dictionary'. + /// buffer - a buffer for the tag. May be NULL. + /// buflen - the length of the buffer, in bytes. May be 0. + /// + /// Returns the number of bytes in the tag, including trailing zeros. + /// + /// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two + /// bytes of zeros indicating the end of the string. If |buflen| is less than + /// the returned length, or |buffer| is NULL, |buffer| will not be modified. + /// + /// For linearized files, FPDFAvail_IsFormAvail must be called before this, and + /// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there + /// is no guarantee the metadata has been loaded. int FPDF_GetMetaText( FPDF_DOCUMENT document, FPDF_BYTESTRING tag, @@ -4216,6 +6865,18 @@ class pdfium { int Function( FPDF_DOCUMENT, FPDF_BYTESTRING, ffi.Pointer, int)>(); + /// Get the page label for |page_index| from |document|. + /// + /// document - handle to the document. + /// page_index - the 0-based index of the page. + /// buffer - a buffer for the page label. May be NULL. + /// buflen - the length of the buffer, in bytes. May be 0. + /// + /// Returns the number of bytes in the page label, including trailing zeros. + /// + /// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two + /// bytes of zeros indicating the end of the string. If |buflen| is less than + /// the returned length, or |buffer| is NULL, |buffer| will not be modified. int FPDF_GetPageLabel( FPDF_DOCUMENT document, int page_index, @@ -4237,6 +6898,9 @@ class pdfium { late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction< int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); + /// Create a new PDF document. + /// + /// Returns a handle to a new document, or NULL on failure. FPDF_DOCUMENT FPDF_CreateNewDocument() { return _FPDF_CreateNewDocument(); } @@ -4247,6 +6911,19 @@ class pdfium { late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); + /// Create a new PDF page. + /// + /// document - handle to document. + /// page_index - suggested 0-based index of the page to create. If it is larger + /// than document's current last index(L), the created page index + /// is the next available index -- L+1. + /// width - the page width in points. + /// height - the page height in points. + /// + /// Returns the handle to the new page or NULL on failure. + /// + /// The page should be closed with FPDF_ClosePage() when finished as + /// with any other page in the document. FPDF_PAGE FPDFPage_New( FPDF_DOCUMENT document, int page_index, @@ -4268,6 +6945,10 @@ class pdfium { late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction< FPDF_PAGE Function(FPDF_DOCUMENT, int, double, double)>(); + /// Delete the page at |page_index|. + /// + /// document - handle to document. + /// page_index - the index of the page to delete. void FPDFPage_Delete( FPDF_DOCUMENT document, int page_index, @@ -4284,6 +6965,31 @@ class pdfium { late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); + /// Experimental API. + /// Move the given pages to a new index position. + /// + /// page_indices - the ordered list of pages to move. No duplicates allowed. + /// page_indices_len - the number of elements in |page_indices| + /// dest_page_index - the new index position to which the pages in + /// |page_indices| are moved. + /// + /// Returns TRUE on success. If it returns FALSE, the document may be left in an + /// indeterminate state. + /// + /// Example: The PDF document starts out with pages [A, B, C, D], with indices + /// [0, 1, 2, 3]. + /// + /// > Move(doc, [3, 2], 2, 1); // returns true + /// > // The document has pages [A, D, C, B]. + /// > + /// > Move(doc, [0, 4, 3], 3, 1); // returns false + /// > // Returned false because index 4 is out of range. + /// > + /// > Move(doc, [0, 3, 1], 3, 2); // returns false + /// > // Returned false because index 2 is out of range for 3 page indices. + /// > + /// > Move(doc, [2, 2], 2, 0); // returns false + /// > // Returned false because [2, 2] contains duplicates. int FPDF_MovePages( FPDF_DOCUMENT document, ffi.Pointer page_indices, @@ -4305,6 +7011,15 @@ class pdfium { late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction< int Function(FPDF_DOCUMENT, ffi.Pointer, int, int)>(); + /// Get the rotation of |page|. + /// + /// page - handle to a page + /// + /// Returns one of the following indicating the page rotation: + /// 0 - No rotation. + /// 1 - Rotated 90 degrees clockwise. + /// 2 - Rotated 180 degrees clockwise. + /// 3 - Rotated 270 degrees clockwise. int FPDFPage_GetRotation( FPDF_PAGE page, ) { @@ -4319,6 +7034,14 @@ class pdfium { late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); + /// Set rotation for |page|. + /// + /// page - handle to a page. + /// rotate - the rotation value, one of: + /// 0 - No rotation. + /// 1 - Rotated 90 degrees clockwise. + /// 2 - Rotated 180 degrees clockwise. + /// 3 - Rotated 270 degrees clockwise. void FPDFPage_SetRotation( FPDF_PAGE page, int rotate, @@ -4335,6 +7058,11 @@ class pdfium { late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); + /// Insert |page_object| into |page|. + /// + /// page - handle to a page + /// page_object - handle to a page object. The |page_object| will be + /// automatically freed. void FPDFPage_InsertObject( FPDF_PAGE page, FPDF_PAGEOBJECT page_object, @@ -4351,6 +7079,18 @@ class pdfium { late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction< void Function(FPDF_PAGE, FPDF_PAGEOBJECT)>(); + /// Experimental API. + /// Remove |page_object| from |page|. + /// + /// page - handle to a page + /// page_object - handle to a page object to be removed. + /// + /// Returns TRUE on success. + /// + /// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free + /// it. + /// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all + /// FPDF_TEXTPAGE handles for |page| are no longer valid. int FPDFPage_RemoveObject( FPDF_PAGE page, FPDF_PAGEOBJECT page_object, @@ -4367,6 +7107,11 @@ class pdfium { late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction< int Function(FPDF_PAGE, FPDF_PAGEOBJECT)>(); + /// Get number of page objects inside |page|. + /// + /// page - handle to a page. + /// + /// Returns the number of objects in |page|. int FPDFPage_CountObjects( FPDF_PAGE page, ) { @@ -4381,6 +7126,12 @@ class pdfium { late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); + /// Get object in |page| at |index|. + /// + /// page - handle to a page. + /// index - the index of a page object. + /// + /// Returns the handle to the page object, or NULL on failed. FPDF_PAGEOBJECT FPDFPage_GetObject( FPDF_PAGE page, int index, @@ -4397,6 +7148,11 @@ class pdfium { late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_PAGE, int)>(); + /// Checks if |page| contains transparency. + /// + /// page - handle to a page. + /// + /// Returns TRUE if |page| contains transparency. int FPDFPage_HasTransparency( FPDF_PAGE page, ) { @@ -4411,6 +7167,14 @@ class pdfium { late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); + /// Generate the content of |page|. + /// + /// page - handle to a page. + /// + /// Returns TRUE on success. + /// + /// Before you save the page to a file, or reload the page, you must call + /// |FPDFPage_GenerateContent| or any changes to |page| will be lost. int FPDFPage_GenerateContent( FPDF_PAGE page, ) { @@ -4425,6 +7189,13 @@ class pdfium { late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); + /// Destroy |page_object| by releasing its resources. |page_object| must have + /// been created by FPDFPageObj_CreateNew{Path|Rect}() or + /// FPDFPageObj_New{Text|Image}Obj(). This function must be called on + /// newly-created objects if they are not added to a page through + /// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). + /// + /// page_object - handle to a page object. void FPDFPageObj_Destroy( FPDF_PAGEOBJECT page_object, ) { @@ -4439,6 +7210,11 @@ class pdfium { late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); + /// Checks if |page_object| contains transparency. + /// + /// page_object - handle to a page object. + /// + /// Returns TRUE if |page_object| contains transparency. int FPDFPageObj_HasTransparency( FPDF_PAGEOBJECT page_object, ) { @@ -4453,6 +7229,12 @@ class pdfium { late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr .asFunction(); + /// Get type of |page_object|. + /// + /// page_object - handle to a page object. + /// + /// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on + /// error. int FPDFPageObj_GetType( FPDF_PAGEOBJECT page_object, ) { @@ -4467,6 +7249,20 @@ class pdfium { late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); + /// Transform |page_object| by the given matrix. + /// + /// page_object - handle to a page object. + /// a - matrix value. + /// b - matrix value. + /// c - matrix value. + /// d - matrix value. + /// e - matrix value. + /// f - matrix value. + /// + /// The matrix is composed as: + /// |a c e| + /// |b d f| + /// and can be used to scale, rotate, shear and translate the |page_object|. void FPDFPageObj_Transform( FPDF_PAGEOBJECT page_object, double a, @@ -4495,6 +7291,23 @@ class pdfium { void Function( FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); + /// Experimental API. + /// Get the transform matrix of a page object. + /// + /// page_object - handle to a page object. + /// matrix - pointer to struct to receive the matrix value. + /// + /// The matrix is composed as: + /// |a c e| + /// |b d f| + /// and used to scale, rotate, shear and translate the page object. + /// + /// For page objects outside form objects, the matrix values are relative to the + /// page that contains it. + /// For page objects inside form objects, the matrix values are relative to the + /// form that contains it. + /// + /// Returns TRUE on success. int FPDFPageObj_GetMatrix( FPDF_PAGEOBJECT page_object, ffi.Pointer matrix, @@ -4512,6 +7325,18 @@ class pdfium { late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// Experimental API. + /// Set the transform matrix of a page object. + /// + /// page_object - handle to a page object. + /// matrix - pointer to struct with the matrix value. + /// + /// The matrix is composed as: + /// |a c e| + /// |b d f| + /// and can be used to scale, rotate, shear and translate the page object. + /// + /// Returns TRUE on success. int FPDFPageObj_SetMatrix( FPDF_PAGEOBJECT page_object, ffi.Pointer matrix, @@ -4529,6 +7354,20 @@ class pdfium { late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// Transform all annotations in |page|. + /// + /// page - handle to a page. + /// a - matrix value. + /// b - matrix value. + /// c - matrix value. + /// d - matrix value. + /// e - matrix value. + /// f - matrix value. + /// + /// The matrix is composed as: + /// |a c e| + /// |b d f| + /// and can be used to scale, rotate, shear and translate the |page| annotations. void FPDFPage_TransformAnnots( FPDF_PAGE page, double a, @@ -4558,6 +7397,11 @@ class pdfium { void Function( FPDF_PAGE, double, double, double, double, double, double)>(); + /// Create a new image object. + /// + /// document - handle to a document. + /// + /// Returns a handle to a new image object. FPDF_PAGEOBJECT FPDFPageObj_NewImageObj( FPDF_DOCUMENT document, ) { @@ -4572,6 +7416,13 @@ class pdfium { late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_DOCUMENT)>(); + /// Experimental API. + /// Get number of content marks in |page_object|. + /// + /// page_object - handle to a page object. + /// + /// Returns the number of content marks in |page_object|, or -1 in case of + /// failure. int FPDFPageObj_CountMarks( FPDF_PAGEOBJECT page_object, ) { @@ -4586,6 +7437,16 @@ class pdfium { late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); + /// Experimental API. + /// Get content mark in |page_object| at |index|. + /// + /// page_object - handle to a page object. + /// index - the index of a page object. + /// + /// Returns the handle to the content mark, or NULL on failure. The handle is + /// still owned by the library, and it should not be freed directly. It becomes + /// invalid if the page object is destroyed, either directly or indirectly by + /// unloading the page. FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark( FPDF_PAGEOBJECT page_object, int index, @@ -4603,6 +7464,16 @@ class pdfium { late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction< FPDF_PAGEOBJECTMARK Function(FPDF_PAGEOBJECT, int)>(); + /// Experimental API. + /// Add a new content mark to a |page_object|. + /// + /// page_object - handle to a page object. + /// name - the name (tag) of the mark. + /// + /// Returns the handle to the content mark, or NULL on failure. The handle is + /// still owned by the library, and it should not be freed directly. It becomes + /// invalid if the page object is destroyed, either directly or indirectly by + /// unloading the page. FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark( FPDF_PAGEOBJECT page_object, FPDF_BYTESTRING name, @@ -4620,6 +7491,14 @@ class pdfium { late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction< FPDF_PAGEOBJECTMARK Function(FPDF_PAGEOBJECT, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Removes a content |mark| from a |page_object|. + /// The mark handle will be invalid after the removal. + /// + /// page_object - handle to a page object. + /// mark - handle to a content mark in that object to remove. + /// + /// Returns TRUE if the operation succeeded, FALSE if it failed. int FPDFPageObj_RemoveMark( FPDF_PAGEOBJECT page_object, FPDF_PAGEOBJECTMARK mark, @@ -4637,6 +7516,19 @@ class pdfium { late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction< int Function(FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK)>(); + /// Experimental API. + /// Get the name of a content mark. + /// + /// mark - handle to a content mark. + /// buffer - buffer for holding the returned name in UTF-16LE. This is only + /// modified if |buflen| is longer than the length of the name. + /// Optional, pass null to just retrieve the size of the buffer + /// needed. + /// buflen - length of the buffer. + /// out_buflen - pointer to variable that will receive the minimum buffer size + /// to contain the name. Not filled if FALSE is returned. + /// + /// Returns TRUE if the operation succeeded, FALSE if it failed. int FPDFPageObjMark_GetName( FPDF_PAGEOBJECTMARK mark, ffi.Pointer buffer, @@ -4662,6 +7554,13 @@ class pdfium { int Function(FPDF_PAGEOBJECTMARK, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Get the number of key/value pair parameters in |mark|. + /// + /// mark - handle to a content mark. + /// + /// Returns the number of key/value pair parameters |mark|, or -1 in case of + /// failure. int FPDFPageObjMark_CountParams( FPDF_PAGEOBJECTMARK mark, ) { @@ -4676,6 +7575,20 @@ class pdfium { late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr .asFunction(); + /// Experimental API. + /// Get the key of a property in a content mark. + /// + /// mark - handle to a content mark. + /// index - index of the property. + /// buffer - buffer for holding the returned key in UTF-16LE. This is only + /// modified if |buflen| is longer than the length of the key. + /// Optional, pass null to just retrieve the size of the buffer + /// needed. + /// buflen - length of the buffer. + /// out_buflen - pointer to variable that will receive the minimum buffer size + /// to contain the key. Not filled if FALSE is returned. + /// + /// Returns TRUE if the operation was successful, FALSE otherwise. int FPDFPageObjMark_GetParamKey( FPDF_PAGEOBJECTMARK mark, int index, @@ -4705,6 +7618,13 @@ class pdfium { int Function(FPDF_PAGEOBJECTMARK, int, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Get the type of the value of a property in a content mark by key. + /// + /// mark - handle to a content mark. + /// key - string key of the property. + /// + /// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. int FPDFPageObjMark_GetParamValueType( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, @@ -4723,6 +7643,17 @@ class pdfium { _FPDFPageObjMark_GetParamValueTypePtr.asFunction< int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Get the value of a number property in a content mark by key as int. + /// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER + /// for this property. + /// + /// mark - handle to a content mark. + /// key - string key of the property. + /// out_value - pointer to variable that will receive the value. Not filled if + /// false is returned. + /// + /// Returns TRUE if the key maps to a number value, FALSE otherwise. int FPDFPageObjMark_GetParamIntValue( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, @@ -4744,6 +7675,21 @@ class pdfium { int Function( FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, ffi.Pointer)>(); + /// Experimental API. + /// Get the value of a string property in a content mark by key. + /// + /// mark - handle to a content mark. + /// key - string key of the property. + /// buffer - buffer for holding the returned value in UTF-16LE. This is + /// only modified if |buflen| is longer than the length of the + /// value. + /// Optional, pass null to just retrieve the size of the buffer + /// needed. + /// buflen - length of the buffer. + /// out_buflen - pointer to variable that will receive the minimum buffer size + /// to contain the value. Not filled if FALSE is returned. + /// + /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. int FPDFPageObjMark_GetParamStringValue( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, @@ -4774,6 +7720,20 @@ class pdfium { int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Get the value of a blob property in a content mark by key. + /// + /// mark - handle to a content mark. + /// key - string key of the property. + /// buffer - buffer for holding the returned value. This is only modified + /// if |buflen| is at least as long as the length of the value. + /// Optional, pass null to just retrieve the size of the buffer + /// needed. + /// buflen - length of the buffer. + /// out_buflen - pointer to variable that will receive the minimum buffer size + /// to contain the value. Not filled if FALSE is returned. + /// + /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. int FPDFPageObjMark_GetParamBlobValue( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, @@ -4804,6 +7764,18 @@ class pdfium { int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Set the value of an int property in a content mark by key. If a parameter + /// with key |key| exists, its value is set to |value|. Otherwise, it is added as + /// a new parameter. + /// + /// document - handle to the document. + /// page_object - handle to the page object with the mark. + /// mark - handle to a content mark. + /// key - string key of the property. + /// value - int value to set. + /// + /// Returns TRUE if the operation succeeded, FALSE otherwise. int FPDFPageObjMark_SetIntParam( FPDF_DOCUMENT document, FPDF_PAGEOBJECT page_object, @@ -4833,6 +7805,18 @@ class pdfium { int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, int)>(); + /// Experimental API. + /// Set the value of a string property in a content mark by key. If a parameter + /// with key |key| exists, its value is set to |value|. Otherwise, it is added as + /// a new parameter. + /// + /// document - handle to the document. + /// page_object - handle to the page object with the mark. + /// mark - handle to a content mark. + /// key - string key of the property. + /// value - string value to set. + /// + /// Returns TRUE if the operation succeeded, FALSE otherwise. int FPDFPageObjMark_SetStringParam( FPDF_DOCUMENT document, FPDF_PAGEOBJECT page_object, @@ -4862,6 +7846,19 @@ class pdfium { int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, FPDF_BYTESTRING)>(); + /// Experimental API. + /// Set the value of a blob property in a content mark by key. If a parameter + /// with key |key| exists, its value is set to |value|. Otherwise, it is added as + /// a new parameter. + /// + /// document - handle to the document. + /// page_object - handle to the page object with the mark. + /// mark - handle to a content mark. + /// key - string key of the property. + /// value - pointer to blob value to set. + /// value_len - size in bytes of |value|. + /// + /// Returns TRUE if the operation succeeded, FALSE otherwise. int FPDFPageObjMark_SetBlobParam( FPDF_DOCUMENT document, FPDF_PAGEOBJECT page_object, @@ -4894,6 +7891,14 @@ class pdfium { int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, ffi.Pointer, int)>(); + /// Experimental API. + /// Removes a property from a content mark by key. + /// + /// page_object - handle to the page object with the mark. + /// mark - handle to a content mark. + /// key - string key of the property. + /// + /// Returns TRUE if the operation succeeded, FALSE otherwise. int FPDFPageObjMark_RemoveParam( FPDF_PAGEOBJECT page_object, FPDF_PAGEOBJECTMARK mark, @@ -4915,6 +7920,20 @@ class pdfium { int Function( FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING)>(); + /// Load an image from a JPEG image file and then set it into |image_object|. + /// + /// pages - pointer to the start of all loaded pages, may be NULL. + /// count - number of |pages|, may be 0. + /// image_object - handle to an image object. + /// file_access - file access handler which specifies the JPEG image file. + /// + /// Returns TRUE on success. + /// + /// The image object might already have an associated image, which is shared and + /// cached by the loaded pages. In that case, we need to clear the cached image + /// for all the loaded pages. Pass |pages| and page count (|count|) to this API + /// to clear the image cache. If the image is not previously shared, or NULL is a + /// valid |pages| value. int FPDFImageObj_LoadJpegFile( ffi.Pointer pages, int count, @@ -4938,6 +7957,22 @@ class pdfium { int Function(ffi.Pointer, int, FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// Load an image from a JPEG image file and then set it into |image_object|. + /// + /// pages - pointer to the start of all loaded pages, may be NULL. + /// count - number of |pages|, may be 0. + /// image_object - handle to an image object. + /// file_access - file access handler which specifies the JPEG image file. + /// + /// Returns TRUE on success. + /// + /// The image object might already have an associated image, which is shared and + /// cached by the loaded pages. In that case, we need to clear the cached image + /// for all the loaded pages. Pass |pages| and page count (|count|) to this API + /// to clear the image cache. If the image is not previously shared, or NULL is a + /// valid |pages| value. This function loads the JPEG image inline, so the image + /// content is copied to the file. This allows |file_access| and its associated + /// data to be deleted after this function returns. int FPDFImageObj_LoadJpegFileInline( ffi.Pointer pages, int count, @@ -4962,6 +7997,24 @@ class pdfium { int Function(ffi.Pointer, int, FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. + /// + /// Set the transform matrix of |image_object|. + /// + /// image_object - handle to an image object. + /// a - matrix value. + /// b - matrix value. + /// c - matrix value. + /// d - matrix value. + /// e - matrix value. + /// f - matrix value. + /// + /// The matrix is composed as: + /// |a c e| + /// |b d f| + /// and can be used to scale, rotate, shear and translate the |image_object|. + /// + /// Returns TRUE on success. int FPDFImageObj_SetMatrix( FPDF_PAGEOBJECT image_object, double a, @@ -4996,6 +8049,14 @@ class pdfium { int Function( FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); + /// Set |bitmap| to |image_object|. + /// + /// pages - pointer to the start of all loaded pages, may be NULL. + /// count - number of |pages|, may be 0. + /// image_object - handle to an image object. + /// bitmap - handle of the bitmap. + /// + /// Returns TRUE on success. int FPDFImageObj_SetBitmap( ffi.Pointer pages, int count, @@ -5018,6 +8079,15 @@ class pdfium { int Function( ffi.Pointer, int, FPDF_PAGEOBJECT, FPDF_BITMAP)>(); + /// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only + /// operates on |image_object| and does not take the associated image mask into + /// account. It also ignores the matrix for |image_object|. + /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() + /// must be called on the returned bitmap when it is no longer needed. + /// + /// image_object - handle to an image object. + /// + /// Returns the bitmap. FPDF_BITMAP FPDFImageObj_GetBitmap( FPDF_PAGEOBJECT image_object, ) { @@ -5032,6 +8102,19 @@ class pdfium { late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction< FPDF_BITMAP Function(FPDF_PAGEOBJECT)>(); + /// Experimental API. + /// Get a bitmap rasterization of |image_object| that takes the image mask and + /// image matrix into account. To render correctly, the caller must provide the + /// |document| associated with |image_object|. If there is a |page| associated + /// with |image_object|, the caller should provide that as well. + /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() + /// must be called on the returned bitmap when it is no longer needed. + /// + /// document - handle to a document associated with |image_object|. + /// page - handle to an optional page associated with |image_object|. + /// image_object - handle to an image object. + /// + /// Returns the bitmap or NULL on failure. FPDF_BITMAP FPDFImageObj_GetRenderedBitmap( FPDF_DOCUMENT document, FPDF_PAGE page, @@ -5052,6 +8135,16 @@ class pdfium { _FPDFImageObj_GetRenderedBitmapPtr.asFunction< FPDF_BITMAP Function(FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT)>(); + /// Get the decoded image data of |image_object|. The decoded data is the + /// uncompressed image data, i.e. the raw image data after having all filters + /// applied. |buffer| is only modified if |buflen| is longer than the length of + /// the decoded image data. + /// + /// image_object - handle to an image object. + /// buffer - buffer for holding the decoded image data. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the decoded image data. int FPDFImageObj_GetImageDataDecoded( FPDF_PAGEOBJECT image_object, ffi.Pointer buffer, @@ -5072,6 +8165,15 @@ class pdfium { _FPDFImageObj_GetImageDataDecodedPtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer, int)>(); + /// Get the raw image data of |image_object|. The raw data is the image data as + /// stored in the PDF without applying any filters. |buffer| is only modified if + /// |buflen| is longer than the length of the raw image data. + /// + /// image_object - handle to an image object. + /// buffer - buffer for holding the raw image data. + /// buflen - length of the buffer in bytes. + /// + /// Returns the length of the raw image data. int FPDFImageObj_GetImageDataRaw( FPDF_PAGEOBJECT image_object, ffi.Pointer buffer, @@ -5091,6 +8193,11 @@ class pdfium { late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr .asFunction, int)>(); + /// Get the number of filters (i.e. decoders) of the image in |image_object|. + /// + /// image_object - handle to an image object. + /// + /// Returns the number of |image_object|'s filters. int FPDFImageObj_GetImageFilterCount( FPDF_PAGEOBJECT image_object, ) { @@ -5106,6 +8213,17 @@ class pdfium { _FPDFImageObj_GetImageFilterCountPtr.asFunction< int Function(FPDF_PAGEOBJECT)>(); + /// Get the filter at |index| of |image_object|'s list of filters. Note that the + /// filters need to be applied in order, i.e. the first filter should be applied + /// first, then the second, etc. |buffer| is only modified if |buflen| is longer + /// than the length of the filter string. + /// + /// image_object - handle to an image object. + /// index - the index of the filter requested. + /// buffer - buffer for holding filter string, encoded in UTF-8. + /// buflen - length of the buffer. + /// + /// Returns the length of the filter string. int FPDFImageObj_GetImageFilter( FPDF_PAGEOBJECT image_object, int index, @@ -5131,6 +8249,17 @@ class pdfium { _FPDFImageObj_GetImageFilterPtr.asFunction< int Function(FPDF_PAGEOBJECT, int, ffi.Pointer, int)>(); + /// Get the image metadata of |image_object|, including dimension, DPI, bits per + /// pixel, and colorspace. If the |image_object| is not an image object or if it + /// does not have an image, then the return value will be false. Otherwise, + /// failure to retrieve any specific parameter would result in its value being 0. + /// + /// image_object - handle to an image object. + /// page - handle to the page that |image_object| is on. Required for + /// retrieving the image's bits per pixel and colorspace. + /// metadata - receives the image metadata; must not be NULL. + /// + /// Returns true if successful. int FPDFImageObj_GetImageMetadata( FPDF_PAGEOBJECT image_object, FPDF_PAGE page, @@ -5153,6 +8282,14 @@ class pdfium { int Function(FPDF_PAGEOBJECT, FPDF_PAGE, ffi.Pointer)>(); + /// Experimental API. + /// Get the image size in pixels. Faster method to get only image size. + /// + /// image_object - handle to an image object. + /// width - receives the image width in pixels; must not be NULL. + /// height - receives the image height in pixels; must not be NULL. + /// + /// Returns true if successful. int FPDFImageObj_GetImagePixelSize( FPDF_PAGEOBJECT image_object, ffi.Pointer width, @@ -5174,6 +8311,12 @@ class pdfium { int Function(FPDF_PAGEOBJECT, ffi.Pointer, ffi.Pointer)>(); + /// Create a new path object at an initial position. + /// + /// x - initial horizontal position. + /// y - initial vertical position. + /// + /// Returns a handle to a new path object. FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath( double x, double y, @@ -5190,6 +8333,14 @@ class pdfium { late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr .asFunction(); + /// Create a closed path consisting of a rectangle. + /// + /// x - horizontal position for the left boundary of the rectangle. + /// y - vertical position for the bottom boundary of the rectangle. + /// w - width of the rectangle. + /// h - height of the rectangle. + /// + /// Returns a handle to the new path object. FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect( double x, double y, @@ -5211,6 +8362,15 @@ class pdfium { late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr .asFunction(); + /// Get the bounding box of |page_object|. + /// + /// page_object - handle to a page object. + /// left - pointer where the left coordinate will be stored + /// bottom - pointer where the bottom coordinate will be stored + /// right - pointer where the right coordinate will be stored + /// top - pointer where the top coordinate will be stored + /// + /// On success, returns TRUE and fills in the 4 coordinates. int FPDFPageObj_GetBounds( FPDF_PAGEOBJECT page_object, ffi.Pointer left, @@ -5243,6 +8403,21 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Get the quad points that bounds |page_object|. + /// + /// page_object - handle to a page object. + /// quad_points - pointer where the quadrilateral points will be stored. + /// + /// On success, returns TRUE and fills in |quad_points|. + /// + /// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page + /// object. When the object is rotated by a non-multiple of 90 degrees, this API + /// returns a tighter bound that cannot be represented with just the 4 sides of + /// a rectangle. + /// + /// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and + /// FPDF_PAGEOBJ_IMAGE. int FPDFPageObj_GetRotatedBounds( FPDF_PAGEOBJECT page_object, ffi.Pointer quad_points, @@ -5260,6 +8435,14 @@ class pdfium { late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr .asFunction)>(); + /// Set the blend mode of |page_object|. + /// + /// page_object - handle to a page object. + /// blend_mode - string containing the blend mode. + /// + /// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, + /// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, + /// Overlay, Saturation, Screen, SoftLight void FPDFPageObj_SetBlendMode( FPDF_PAGEOBJECT page_object, FPDF_BYTESTRING blend_mode, @@ -5277,6 +8460,15 @@ class pdfium { late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr .asFunction(); + /// Set the stroke RGBA of a page object. Range of values: 0 - 255. + /// + /// page_object - the handle to the page object. + /// R - the red component for the object's stroke color. + /// G - the green component for the object's stroke color. + /// B - the blue component for the object's stroke color. + /// A - the stroke alpha for the object. + /// + /// Returns TRUE on success. int FPDFPageObj_SetStrokeColor( FPDF_PAGEOBJECT page_object, int R, @@ -5300,6 +8492,15 @@ class pdfium { late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr .asFunction(); + /// Get the stroke RGBA of a page object. Range of values: 0 - 255. + /// + /// page_object - the handle to the page object. + /// R - the red component of the path stroke color. + /// G - the green component of the object's stroke color. + /// B - the blue component of the object's stroke color. + /// A - the stroke alpha of the object. + /// + /// Returns TRUE on success. int FPDFPageObj_GetStrokeColor( FPDF_PAGEOBJECT page_object, ffi.Pointer R, @@ -5333,6 +8534,12 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Set the stroke width of a page object. + /// + /// path - the handle to the page object. + /// width - the width of the stroke. + /// + /// Returns TRUE on success int FPDFPageObj_SetStrokeWidth( FPDF_PAGEOBJECT page_object, double width, @@ -5349,6 +8556,12 @@ class pdfium { late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr .asFunction(); + /// Get the stroke width of a page object. + /// + /// path - the handle to the page object. + /// width - the width of the stroke. + /// + /// Returns TRUE on success int FPDFPageObj_GetStrokeWidth( FPDF_PAGEOBJECT page_object, ffi.Pointer width, @@ -5366,6 +8579,13 @@ class pdfium { late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr .asFunction)>(); + /// Get the line join of |page_object|. + /// + /// page_object - handle to a page object. + /// + /// Returns the line join, or -1 on failure. + /// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, + /// FPDF_LINEJOIN_BEVEL int FPDFPageObj_GetLineJoin( FPDF_PAGEOBJECT page_object, ) { @@ -5380,6 +8600,13 @@ class pdfium { late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); + /// Set the line join of |page_object|. + /// + /// page_object - handle to a page object. + /// line_join - line join + /// + /// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, + /// FPDF_LINEJOIN_BEVEL int FPDFPageObj_SetLineJoin( FPDF_PAGEOBJECT page_object, int line_join, @@ -5396,6 +8623,13 @@ class pdfium { late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction< int Function(FPDF_PAGEOBJECT, int)>(); + /// Get the line cap of |page_object|. + /// + /// page_object - handle to a page object. + /// + /// Returns the line cap, or -1 on failure. + /// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, + /// FPDF_LINECAP_PROJECTING_SQUARE int FPDFPageObj_GetLineCap( FPDF_PAGEOBJECT page_object, ) { @@ -5410,6 +8644,13 @@ class pdfium { late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); + /// Set the line cap of |page_object|. + /// + /// page_object - handle to a page object. + /// line_cap - line cap + /// + /// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, + /// FPDF_LINECAP_PROJECTING_SQUARE int FPDFPageObj_SetLineCap( FPDF_PAGEOBJECT page_object, int line_cap, @@ -5426,6 +8667,15 @@ class pdfium { late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction< int Function(FPDF_PAGEOBJECT, int)>(); + /// Set the fill RGBA of a page object. Range of values: 0 - 255. + /// + /// page_object - the handle to the page object. + /// R - the red component for the object's fill color. + /// G - the green component for the object's fill color. + /// B - the blue component for the object's fill color. + /// A - the fill alpha for the object. + /// + /// Returns TRUE on success. int FPDFPageObj_SetFillColor( FPDF_PAGEOBJECT page_object, int R, @@ -5449,6 +8699,15 @@ class pdfium { late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr .asFunction(); + /// Get the fill RGBA of a page object. Range of values: 0 - 255. + /// + /// page_object - the handle to the page object. + /// R - the red component of the object's fill color. + /// G - the green component of the object's fill color. + /// B - the blue component of the object's fill color. + /// A - the fill alpha of the object. + /// + /// Returns TRUE on success. int FPDFPageObj_GetFillColor( FPDF_PAGEOBJECT page_object, ffi.Pointer R, @@ -5482,6 +8741,13 @@ class pdfium { ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Get the line dash |phase| of |page_object|. + /// + /// page_object - handle to a page object. + /// phase - pointer where the dashing phase will be stored. + /// + /// Returns TRUE on success. int FPDFPageObj_GetDashPhase( FPDF_PAGEOBJECT page_object, ffi.Pointer phase, @@ -5499,6 +8765,13 @@ class pdfium { late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr .asFunction)>(); + /// Experimental API. + /// Set the line dash phase of |page_object|. + /// + /// page_object - handle to a page object. + /// phase - line dash phase. + /// + /// Returns TRUE on success. int FPDFPageObj_SetDashPhase( FPDF_PAGEOBJECT page_object, double phase, @@ -5515,6 +8788,12 @@ class pdfium { late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr .asFunction(); + /// Experimental API. + /// Get the line dash array of |page_object|. + /// + /// page_object - handle to a page object. + /// + /// Returns the line dash array size or -1 on failure. int FPDFPageObj_GetDashCount( FPDF_PAGEOBJECT page_object, ) { @@ -5529,6 +8808,14 @@ class pdfium { late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); + /// Experimental API. + /// Get the line dash array of |page_object|. + /// + /// page_object - handle to a page object. + /// dash_array - pointer where the dashing array will be stored. + /// dash_count - number of elements in |dash_array|. + /// + /// Returns TRUE on success. int FPDFPageObj_GetDashArray( FPDF_PAGEOBJECT page_object, ffi.Pointer dash_array, @@ -5548,6 +8835,15 @@ class pdfium { late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr .asFunction, int)>(); + /// Experimental API. + /// Set the line dash array of |page_object|. + /// + /// page_object - handle to a page object. + /// dash_array - the dash array. + /// dash_count - number of elements in |dash_array|. + /// phase - the line dash phase. + /// + /// Returns TRUE on success. int FPDFPageObj_SetDashArray( FPDF_PAGEOBJECT page_object, ffi.Pointer dash_array, @@ -5570,6 +8866,14 @@ class pdfium { _FPDFPageObj_SetDashArrayPtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer, int, double)>(); + /// Get number of segments inside |path|. + /// + /// path - handle to a path. + /// + /// A segment is a command, created by e.g. FPDFPath_MoveTo(), + /// FPDFPath_LineTo() or FPDFPath_BezierTo(). + /// + /// Returns the number of objects in |path| or -1 on failure. int FPDFPath_CountSegments( FPDF_PAGEOBJECT path, ) { @@ -5584,6 +8888,12 @@ class pdfium { late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); + /// Get segment in |path| at |index|. + /// + /// path - handle to a path. + /// index - the index of a segment. + /// + /// Returns the handle to the segment, or NULL on faiure. FPDF_PATHSEGMENT FPDFPath_GetPathSegment( FPDF_PAGEOBJECT path, int index, @@ -5601,6 +8911,13 @@ class pdfium { late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction< FPDF_PATHSEGMENT Function(FPDF_PAGEOBJECT, int)>(); + /// Get coordinates of |segment|. + /// + /// segment - handle to a segment. + /// x - the horizontal position of the segment. + /// y - the vertical position of the segment. + /// + /// Returns TRUE on success, otherwise |x| and |y| is not set. int FPDFPathSegment_GetPoint( FPDF_PATHSEGMENT segment, ffi.Pointer x, @@ -5622,6 +8939,12 @@ class pdfium { int Function(FPDF_PATHSEGMENT, ffi.Pointer, ffi.Pointer)>(); + /// Get type of |segment|. + /// + /// segment - handle to a segment. + /// + /// Returns one of the FPDF_SEGMENT_* values on success, + /// FPDF_SEGMENT_UNKNOWN on error. int FPDFPathSegment_GetType( FPDF_PATHSEGMENT segment, ) { @@ -5636,6 +8959,11 @@ class pdfium { late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); + /// Gets if the |segment| closes the current subpath of a given path. + /// + /// segment - handle to a segment. + /// + /// Returns close flag for non-NULL segment, FALSE otherwise. int FPDFPathSegment_GetClose( FPDF_PATHSEGMENT segment, ) { @@ -5650,6 +8978,16 @@ class pdfium { late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); + /// Move a path's current point. + /// + /// path - the handle to the path object. + /// x - the horizontal position of the new current point. + /// y - the vertical position of the new current point. + /// + /// Note that no line will be created between the previous current point and the + /// new one. + /// + /// Returns TRUE on success int FPDFPath_MoveTo( FPDF_PAGEOBJECT path, double x, @@ -5669,6 +9007,15 @@ class pdfium { late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction< int Function(FPDF_PAGEOBJECT, double, double)>(); + /// Add a line between the current point and a new point in the path. + /// + /// path - the handle to the path object. + /// x - the horizontal position of the new point. + /// y - the vertical position of the new point. + /// + /// The path's current point is changed to (x, y). + /// + /// Returns TRUE on success int FPDFPath_LineTo( FPDF_PAGEOBJECT path, double x, @@ -5688,6 +9035,17 @@ class pdfium { late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction< int Function(FPDF_PAGEOBJECT, double, double)>(); + /// Add a cubic Bezier curve to the given path, starting at the current point. + /// + /// path - the handle to the path object. + /// x1 - the horizontal position of the first Bezier control point. + /// y1 - the vertical position of the first Bezier control point. + /// x2 - the horizontal position of the second Bezier control point. + /// y2 - the vertical position of the second Bezier control point. + /// x3 - the horizontal position of the ending point of the Bezier curve. + /// y3 - the vertical position of the ending point of the Bezier curve. + /// + /// Returns TRUE on success int FPDFPath_BezierTo( FPDF_PAGEOBJECT path, double x1, @@ -5716,6 +9074,14 @@ class pdfium { int Function( FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); + /// Close the current subpath of a given path. + /// + /// path - the handle to the path object. + /// + /// This will add a line between the current point and the initial point of the + /// subpath, thus terminating the current subpath. + /// + /// Returns TRUE on success int FPDFPath_Close( FPDF_PAGEOBJECT path, ) { @@ -5730,6 +9096,13 @@ class pdfium { late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); + /// Set the drawing mode of a path. + /// + /// path - the handle to the path object. + /// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. + /// stroke - a boolean specifying if the path should be stroked or not. + /// + /// Returns TRUE on success int FPDFPath_SetDrawMode( FPDF_PAGEOBJECT path, int fillmode, @@ -5749,6 +9122,13 @@ class pdfium { late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction< int Function(FPDF_PAGEOBJECT, int, int)>(); + /// Get the drawing mode of a path. + /// + /// path - the handle to the path object. + /// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. + /// stroke - a boolean specifying if the path is stroked or not. + /// + /// Returns TRUE on success int FPDFPath_GetDrawMode( FPDF_PAGEOBJECT path, ffi.Pointer fillmode, @@ -5769,6 +9149,13 @@ class pdfium { int Function( FPDF_PAGEOBJECT, ffi.Pointer, ffi.Pointer)>(); + /// Create a new text object using one of the standard PDF fonts. + /// + /// document - handle to the document. + /// font - string containing the font name, without spaces. + /// font_size - the font size for the new text object. + /// + /// Returns a handle to a new text object, or NULL on failure FPDF_PAGEOBJECT FPDFPageObj_NewTextObj( FPDF_DOCUMENT document, FPDF_BYTESTRING font, @@ -5788,6 +9175,12 @@ class pdfium { late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_DOCUMENT, FPDF_BYTESTRING, double)>(); + /// Set the text for a text object. If it had text, it will be replaced. + /// + /// text_object - handle to the text object. + /// text - the UTF-16LE encoded string containing the text to be added. + /// + /// Returns TRUE on success int FPDFText_SetText( FPDF_PAGEOBJECT text_object, FPDF_WIDESTRING text, @@ -5805,6 +9198,15 @@ class pdfium { late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction< int Function(FPDF_PAGEOBJECT, FPDF_WIDESTRING)>(); + /// Experimental API. + /// Set the text using charcodes for a text object. If it had text, it will be + /// replaced. + /// + /// text_object - handle to the text object. + /// charcodes - pointer to an array of charcodes to be added. + /// count - number of elements in |charcodes|. + /// + /// Returns TRUE on success int FPDFText_SetCharcodes( FPDF_PAGEOBJECT text_object, ffi.Pointer charcodes, @@ -5824,6 +9226,19 @@ class pdfium { late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer, int)>(); + /// Returns a font object loaded from a stream of data. The font is loaded + /// into the document. Various font data structures, such as the ToUnicode data, + /// are auto-generated based on the inputs. + /// + /// document - handle to the document. + /// data - the stream of font data, which will be copied by the font object. + /// size - the size of the font data, in bytes. + /// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. + /// cid - a boolean specifying if the font is a CID font or not. + /// + /// The loaded font can be closed using FPDFFont_Close(). + /// + /// Returns NULL on failure FPDF_FONT FPDFText_LoadFont( FPDF_DOCUMENT document, ffi.Pointer data, @@ -5848,6 +9263,17 @@ class pdfium { FPDF_FONT Function( FPDF_DOCUMENT, ffi.Pointer, int, int, int)>(); + /// Experimental API. + /// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred + /// way of using font style is using a dash to separate the name from the style, + /// for example 'Helvetica-BoldItalic'. + /// + /// document - handle to the document. + /// font - string containing the font name, without spaces. + /// + /// The loaded font can be closed using FPDFFont_Close(). + /// + /// Returns NULL on failure. FPDF_FONT FPDFText_LoadStandardFont( FPDF_DOCUMENT document, FPDF_BYTESTRING font, @@ -5865,6 +9291,22 @@ class pdfium { late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr .asFunction(); + /// Experimental API. + /// Returns a font object loaded from a stream of data for a type 2 CID font. The + /// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode + /// data and the CIDToGIDMap data are caller provided, instead of auto-generated. + /// + /// document - handle to the document. + /// font_data - the stream of font data, which will be copied by + /// the font object. + /// font_data_size - the size of the font data, in bytes. + /// to_unicode_cmap - the ToUnicode data. + /// cid_to_gid_map_data - the stream of CIDToGIDMap data. + /// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. + /// + /// The loaded font can be closed using FPDFFont_Close(). + /// + /// Returns NULL on failure. FPDF_FONT FPDFText_LoadCidType2Font( FPDF_DOCUMENT document, ffi.Pointer font_data, @@ -5897,6 +9339,13 @@ class pdfium { FPDF_FONT Function(FPDF_DOCUMENT, ffi.Pointer, int, FPDF_BYTESTRING, ffi.Pointer, int)>(); + /// Get the font size of a text object. + /// + /// text - handle to a text. + /// size - pointer to the font size of the text object, measured in points + /// (about 1/72 inch) + /// + /// Returns TRUE on success. int FPDFTextObj_GetFontSize( FPDF_PAGEOBJECT text, ffi.Pointer size, @@ -5914,6 +9363,9 @@ class pdfium { late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction< int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// Close a loaded PDF font. + /// + /// font - Handle to the loaded font. void FPDFFont_Close( FPDF_FONT font, ) { @@ -5928,6 +9380,13 @@ class pdfium { late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); + /// Create a new text object using a loaded font. + /// + /// document - handle to the document. + /// font - handle to the font object. + /// font_size - the font size for the new text object. + /// + /// Returns a handle to a new text object, or NULL on failure FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj( FPDF_DOCUMENT document, FPDF_FONT font, @@ -5947,6 +9406,12 @@ class pdfium { late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr .asFunction(); + /// Get the text rendering mode of a text object. + /// + /// text - the handle to the text object. + /// + /// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, + /// FPDF_TEXTRENDERMODE_UNKNOWN on error. FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode( FPDF_PAGEOBJECT text, ) { @@ -5961,6 +9426,14 @@ class pdfium { late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr .asFunction(); + /// Experimental API. + /// Set the text rendering mode of a text object. + /// + /// text - the handle to the text object. + /// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to + /// FPDF_TEXTRENDERMODE_UNKNOWN). + /// + /// Returns TRUE on success. DartFPDF_BOOL FPDFTextObj_SetTextRenderMode( FPDF_PAGEOBJECT text, FPDF_TEXT_RENDERMODE render_mode, @@ -5977,6 +9450,19 @@ class pdfium { late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr .asFunction(); + /// Get the text of a text object. + /// + /// text_object - the handle to the text object. + /// text_page - the handle to the text page. + /// buffer - the address of a buffer that receives the text. + /// length - the size, in bytes, of |buffer|. + /// + /// Returns the number of bytes in the text (including the trailing NUL + /// character) on success, 0 on error. + /// + /// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. + /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| + /// will not be modified. int FPDFTextObj_GetText( FPDF_PAGEOBJECT text_object, FPDF_TEXTPAGE text_page, @@ -6002,6 +9488,19 @@ class pdfium { int Function( FPDF_PAGEOBJECT, FPDF_TEXTPAGE, ffi.Pointer, int)>(); + /// Experimental API. + /// Get a bitmap rasterization of |text_object|. To render correctly, the caller + /// must provide the |document| associated with |text_object|. If there is a + /// |page| associated with |text_object|, the caller should provide that as well. + /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() + /// must be called on the returned bitmap when it is no longer needed. + /// + /// document - handle to a document associated with |text_object|. + /// page - handle to an optional page associated with |text_object|. + /// text_object - handle to a text object. + /// scale - the scaling factor, which must be greater than 0. + /// + /// Returns the bitmap or NULL on failure. FPDF_BITMAP FPDFTextObj_GetRenderedBitmap( FPDF_DOCUMENT document, FPDF_PAGE page, @@ -6025,6 +9524,12 @@ class pdfium { FPDF_BITMAP Function( FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT, double)>(); + /// Experimental API. + /// Get the font of a text object. + /// + /// text - the handle to the text object. + /// + /// Returns a handle to the font object held by |text| which retains ownership. FPDF_FONT FPDFTextObj_GetFont( FPDF_PAGEOBJECT text, ) { @@ -6039,6 +9544,19 @@ class pdfium { late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); + /// Experimental API. + /// Get the font name of a font. + /// + /// font - the handle to the font object. + /// buffer - the address of a buffer that receives the font name. + /// length - the size, in bytes, of |buffer|. + /// + /// Returns the number of bytes in the font name (including the trailing NUL + /// character) on success, 0 on error. + /// + /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. + /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| + /// will not be modified. int FPDFFont_GetFontName( FPDF_FONT font, ffi.Pointer buffer, @@ -6058,6 +9576,25 @@ class pdfium { late final _FPDFFont_GetFontName = _FPDFFont_GetFontNamePtr.asFunction< int Function(FPDF_FONT, ffi.Pointer, int)>(); + /// Experimental API. + /// Get the decoded data from the |font| object. + /// + /// font - The handle to the font object. (Required) + /// buffer - The address of a buffer that receives the font data. + /// buflen - Length of the buffer. + /// out_buflen - Pointer to variable that will receive the minimum buffer size + /// to contain the font data. Not filled if the return value is + /// FALSE. (Required) + /// + /// Returns TRUE on success. In which case, |out_buflen| will be filled, and + /// |buffer| will be filled if it is large enough. Returns FALSE if any of the + /// required parameters are null. + /// + /// The decoded data is the uncompressed font data. i.e. the raw font data after + /// having all stream filters applied, when the data is embedded. + /// + /// If the font is not embedded, then this API will instead return the data for + /// the substitution font it is using. int FPDFFont_GetFontData( FPDF_FONT font, ffi.Pointer buffer, @@ -6080,6 +9617,12 @@ class pdfium { int Function( FPDF_FONT, ffi.Pointer, int, ffi.Pointer)>(); + /// Experimental API. + /// Get whether |font| is embedded or not. + /// + /// font - the handle to the font object. + /// + /// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. int FPDFFont_GetIsEmbedded( FPDF_FONT font, ) { @@ -6094,6 +9637,13 @@ class pdfium { late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); + /// Experimental API. + /// Get the descriptor flags of a font. + /// + /// font - the handle to the font object. + /// + /// Returns the bit flags specifying various characteristics of the font as + /// defined in ISO 32000-1:2008, table 123, -1 on failure. int FPDFFont_GetFlags( FPDF_FONT font, ) { @@ -6108,6 +9658,13 @@ class pdfium { late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); + /// Experimental API. + /// Get the font weight of a font. + /// + /// font - the handle to the font object. + /// + /// Returns the font weight, -1 on failure. + /// Typical values are 400 (normal) and 700 (bold). int FPDFFont_GetWeight( FPDF_FONT font, ) { @@ -6122,6 +9679,16 @@ class pdfium { late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); + /// Experimental API. + /// Get the italic angle of a font. + /// + /// font - the handle to the font object. + /// angle - pointer where the italic angle will be stored + /// + /// The italic angle of a |font| is defined as degrees counterclockwise + /// from vertical. For a font that slopes to the right, this will be negative. + /// + /// Returns TRUE on success; |angle| unmodified on failure. int FPDFFont_GetItalicAngle( FPDF_FONT font, ffi.Pointer angle, @@ -6139,6 +9706,17 @@ class pdfium { late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction< int Function(FPDF_FONT, ffi.Pointer)>(); + /// Experimental API. + /// Get ascent distance of a font. + /// + /// font - the handle to the font object. + /// font_size - the size of the |font|. + /// ascent - pointer where the font ascent will be stored + /// + /// Ascent is the maximum distance in points above the baseline reached by the + /// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). + /// + /// Returns TRUE on success; |ascent| unmodified on failure. int FPDFFont_GetAscent( FPDF_FONT font, double font_size, @@ -6158,6 +9736,17 @@ class pdfium { late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction< int Function(FPDF_FONT, double, ffi.Pointer)>(); + /// Experimental API. + /// Get descent distance of a font. + /// + /// font - the handle to the font object. + /// font_size - the size of the |font|. + /// descent - pointer where the font descent will be stored + /// + /// Descent is the maximum distance in points below the baseline reached by the + /// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). + /// + /// Returns TRUE on success; |descent| unmodified on failure. int FPDFFont_GetDescent( FPDF_FONT font, double font_size, @@ -6177,6 +9766,18 @@ class pdfium { late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction< int Function(FPDF_FONT, double, ffi.Pointer)>(); + /// Experimental API. + /// Get the width of a glyph in a font. + /// + /// font - the handle to the font object. + /// glyph - the glyph. + /// font_size - the size of the font. + /// width - pointer where the glyph width will be stored + /// + /// Glyph width is the distance from the end of the prior glyph to the next + /// glyph. This will be the vertical distance for vertical writing. + /// + /// Returns TRUE on success; |width| unmodified on failure. int FPDFFont_GetGlyphWidth( FPDF_FONT font, int glyph, @@ -6198,6 +9799,14 @@ class pdfium { late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction< int Function(FPDF_FONT, int, double, ffi.Pointer)>(); + /// Experimental API. + /// Get the glyphpath describing how to draw a font glyph. + /// + /// font - the handle to the font object. + /// glyph - the glyph being drawn. + /// font_size - the size of the font. + /// + /// Returns the handle to the segment, or NULL on faiure. FPDF_GLYPHPATH FPDFFont_GetGlyphPath( FPDF_FONT font, int glyph, @@ -6217,6 +9826,12 @@ class pdfium { late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction< FPDF_GLYPHPATH Function(FPDF_FONT, int, double)>(); + /// Experimental API. + /// Get number of segments inside glyphpath. + /// + /// glyphpath - handle to a glyph path. + /// + /// Returns the number of objects in |glyphpath| or -1 on failure. int FPDFGlyphPath_CountGlyphSegments( FPDF_GLYPHPATH glyphpath, ) { @@ -6232,6 +9847,13 @@ class pdfium { _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction< int Function(FPDF_GLYPHPATH)>(); + /// Experimental API. + /// Get segment in glyphpath at index. + /// + /// glyphpath - handle to a glyph path. + /// index - the index of a segment. + /// + /// Returns the handle to the segment, or NULL on faiure. FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment( FPDF_GLYPHPATH glyphpath, int index, @@ -6250,6 +9872,11 @@ class pdfium { _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction< FPDF_PATHSEGMENT Function(FPDF_GLYPHPATH, int)>(); + /// Get number of page objects inside |form_object|. + /// + /// form_object - handle to a form object. + /// + /// Returns the number of objects in |form_object| on success, -1 on error. int FPDFFormObj_CountObjects( FPDF_PAGEOBJECT form_object, ) { @@ -6264,6 +9891,12 @@ class pdfium { late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); + /// Get page object in |form_object| at |index|. + /// + /// form_object - handle to a form object. + /// index - the 0-based index of a page object. + /// + /// Returns the handle to the page object, or NULL on error. FPDF_PAGEOBJECT FPDFFormObj_GetObject( FPDF_PAGEOBJECT form_object, int index, @@ -6282,6 +9915,7 @@ class pdfium { FPDF_PAGEOBJECT Function(FPDF_PAGEOBJECT, int)>(); } +/// PDF text rendering modes enum FPDF_TEXT_RENDERMODE { FPDF_TEXTRENDERMODE_UNKNOWN(-1), FPDF_TEXTRENDERMODE_FILL(0), @@ -6322,6 +9956,7 @@ enum FPDF_TEXT_RENDERMODE { final class fpdf_action_t__ extends ffi.Opaque {} +/// PDF types - use incomplete types (never completed) to force API type safety. typedef FPDF_ACTION = ffi.Pointer; final class fpdf_annotation_t__ extends ffi.Opaque {} @@ -6437,6 +10072,8 @@ typedef FPDF_WIDGET = ffi.Pointer; final class fpdf_xobject_t__ extends ffi.Opaque {} typedef FPDF_XOBJECT = ffi.Pointer; + +/// Basic data types typedef FPDF_BOOL = ffi.Int; typedef DartFPDF_BOOL = int; typedef FPDF_RESULT = ffi.Int; @@ -6446,6 +10083,7 @@ typedef DartFPDF_DWORD = int; typedef FS_FLOAT = ffi.Float; typedef DartFS_FLOAT = double; +/// Duplex types enum _FPDF_DUPLEXTYPE_ { DuplexUndefined(0), Simplex(1), @@ -6464,21 +10102,51 @@ enum _FPDF_DUPLEXTYPE_ { }; } +/// String types typedef FPDF_WCHAR = ffi.UnsignedShort; typedef DartFPDF_WCHAR = int; + +/// Public PDFium API type for byte strings. typedef FPDF_BYTESTRING = ffi.Pointer; + +/// The public PDFium API always uses UTF-16LE encoded wide strings, each +/// character uses 2 bytes (except surrogation), with the low byte first. typedef FPDF_WIDESTRING = ffi.Pointer; +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. final class FPDF_BSTR_ extends ffi.Struct { + /// String buffer, manipulate only with FPDF_BStr_* methods. external ffi.Pointer str; + /// Length of the string, in bytes. @ffi.Int() external int len; } +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. typedef FPDF_BSTR = FPDF_BSTR_; + +/// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a +/// Windows unicode string, however, special care needs to be taken if you +/// expect to process Unicode larger than 0xffff. +/// +/// For Linux/Unix programmers: most compiler/library environments use 4 bytes +/// for a Unicode character, and you have to convert between FPDF_WIDESTRING and +/// system wide string by yourself. typedef FPDF_STRING = ffi.Pointer; +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. final class _FS_MATRIX_ extends ffi.Struct { @ffi.Float() external double a; @@ -6499,26 +10167,43 @@ final class _FS_MATRIX_ extends ffi.Struct { external double f; } +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. typedef FS_MATRIX = _FS_MATRIX_; +/// Rectangle area(float) in device or page coordinate system. final class _FS_RECTF_ extends ffi.Struct { + /// The x-coordinate of the left-top corner. @ffi.Float() external double left; + /// The y-coordinate of the left-top corner. @ffi.Float() external double top; + /// The x-coordinate of the right-bottom corner. @ffi.Float() external double right; + /// The y-coordinate of the right-bottom corner. @ffi.Float() external double bottom; } +/// Rectangle area(float) in device or page coordinate system. typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; typedef FS_RECTF = _FS_RECTF_; + +/// Const Pointer to FS_RECTF structure. typedef FS_LPCRECTF = ffi.Pointer; +/// Rectangle size. Coordinate system agnostic. final class FS_SIZEF_ extends ffi.Struct { @ffi.Float() external double width; @@ -6527,10 +10212,14 @@ final class FS_SIZEF_ extends ffi.Struct { external double height; } +/// Rectangle size. Coordinate system agnostic. typedef FS_LPSIZEF = ffi.Pointer; typedef FS_SIZEF = FS_SIZEF_; + +/// Const Pointer to FS_SIZEF structure. typedef FS_LPCSIZEF = ffi.Pointer; +/// 2D Point. Coordinate system agnostic. final class FS_POINTF_ extends ffi.Struct { @ffi.Float() external double x; @@ -6539,8 +10228,11 @@ final class FS_POINTF_ extends ffi.Struct { external double y; } +/// 2D Point. Coordinate system agnostic. typedef FS_LPPOINTF = ffi.Pointer; typedef FS_POINTF = FS_POINTF_; + +/// Const Pointer to FS_POINTF structure. typedef FS_LPCPOINTF = ffi.Pointer; final class _FS_QUADPOINTSF extends ffi.Struct { @@ -6570,15 +10262,24 @@ final class _FS_QUADPOINTSF extends ffi.Struct { } typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; + +/// Annotation enums. typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; typedef DartFPDF_ANNOTATION_SUBTYPE = int; typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; typedef DartFPDF_ANNOT_APPEARANCEMODE = int; + +/// Dictionary value types. typedef FPDF_OBJECT_TYPE = ffi.Int; typedef DartFPDF_OBJECT_TYPE = int; +/// PDF renderer types - Experimental. +/// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. enum FPDF_RENDERER_TYPE { + /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ FPDF_RENDERERTYPE_AGG(0), + + /// Skia - https://skia.org/ FPDF_RENDERERTYPE_SKIA(1); final int value; @@ -6592,19 +10293,38 @@ enum FPDF_RENDERER_TYPE { }; } +/// Process-wide options for initializing the library. final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct { + /// Version number of the interface. Currently must be 2. + /// Support for version 1 will be deprecated in the future. @ffi.Int() external int version; + /// Array of paths to scan in place of the defaults when using built-in + /// FXGE font loading code. The array is terminated by a NULL pointer. + /// The Array may be NULL itself to use the default paths. May be ignored + /// entirely depending upon the platform. external ffi.Pointer> m_pUserFontPaths; + /// Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. external ffi.Pointer m_pIsolate; + /// The embedder data slot to use in the v8::Isolate to store PDFium's + /// per-isolate data. The value needs to be in the range + /// [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most + /// embedders. @ffi.UnsignedInt() external int m_v8EmbedderSlot; + /// Pointer to the V8::Platform to use. external ffi.Pointer m_pPlatform; + /// Explicit specification of core renderer to use. |m_RendererType| must be + /// a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, + /// or else the initialization will fail with an immediate crash. + /// Note that use of a specified |FPDF_RENDERER_TYPE| value for which the + /// corresponding render library is not included in the build will similarly + /// fail with an immediate crash. @ffi.UnsignedInt() external int m_RendererTypeAsInt; @@ -6612,12 +10332,22 @@ final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct { FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); } +/// Process-wide options for initializing the library. typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; +/// Structure for custom file access. final class FPDF_FILEACCESS extends ffi.Struct { + /// File length, in bytes. @ffi.UnsignedLong() external int m_FileLen; + /// A function pointer for getting a block of data from a specific position. + /// Position is specified by byte offset from the beginning of the file. + /// The pointer to the buffer is never NULL and the size is never 0. + /// The position and size will never go out of range of the file length. + /// It may be possible for PDFium to call this function multiple times for + /// the same position. + /// Return value: should be non-zero if successful, zero for error. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function( @@ -6626,21 +10356,54 @@ final class FPDF_FILEACCESS extends ffi.Struct { ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; + /// A custom pointer for all implementation specific data. This pointer will + /// be used as the first parameter to the m_GetBlock callback. external ffi.Pointer m_Param; } +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. final class FPDF_FILEHANDLER_ extends ffi.Struct { + /// User-defined data. + /// Note: Callers can use this field to track controls. external ffi.Pointer clientData; + /// Callback function to release the current file stream object. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// None. external ffi.Pointer< ffi .NativeFunction clientData)>> Release; + /// Callback function to retrieve the current file stream size. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// Size of file stream. external ffi.Pointer< ffi.NativeFunction< FPDF_DWORD Function(ffi.Pointer clientData)>> GetSize; + /// Callback function to read data from the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates reading position. + /// buffer - Memory buffer to store data which are read from + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be read from file stream, + /// in bytes. The buffer indicated by |buffer| must be + /// large enough to store specified data. + /// Returns: + /// 0 for success, other value for failure. external ffi.Pointer< ffi.NativeFunction< FPDF_RESULT Function( @@ -6649,6 +10412,18 @@ final class FPDF_FILEHANDLER_ extends ffi.Struct { ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; + /// Callback function to write data into the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates writing position. + /// buffer - Memory buffer contains data which is written into + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be written into file + /// stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. external ffi.Pointer< ffi.NativeFunction< FPDF_RESULT Function( @@ -6657,18 +10432,40 @@ final class FPDF_FILEHANDLER_ extends ffi.Struct { ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; + /// Callback function to flush all internal accessing buffers. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// 0 for success, other value for failure. external ffi.Pointer< ffi.NativeFunction< FPDF_RESULT Function(ffi.Pointer clientData)>> Flush; + /// Callback function to change file size. + /// + /// Description: + /// This function is called under writing mode usually. Implementer + /// can determine whether to realize it based on application requests. + /// Parameters: + /// clientData - Pointer to user-defined data. + /// size - New size of file stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. external ffi.Pointer< ffi.NativeFunction< FPDF_RESULT Function( ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; } +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. final class FPDF_COLORSCHEME_ extends ffi.Struct { @FPDF_DWORD() external int path_fill_color; @@ -6683,6 +10480,8 @@ final class FPDF_COLORSCHEME_ extends ffi.Struct { external int text_stroke_color; } +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; final class HDC__ extends ffi.Struct { @@ -6693,9 +10492,27 @@ final class HDC__ extends ffi.Struct { typedef HDC = ffi.Pointer; final class _IPDF_JsPlatform extends ffi.Struct { + /// Version number of the interface. Currently must be 2. @ffi.Int() external int version; + /// Method: app_alert + /// Pop up a dialog to show warning or hint. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Msg - A string containing the message to be displayed. + /// Title - The title of the dialog. + /// Type - The type of button group, one of the + /// JSPLATFORM_ALERT_BUTTON_* values above. + /// nIcon - The type of the icon, one of the + /// JSPLATFORM_ALERT_ICON_* above. + /// Return Value: + /// Option selected by user in dialogue, one of the + /// JSPLATFORM_ALERT_RETURN_* values above. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function( @@ -6705,11 +10522,56 @@ final class _IPDF_JsPlatform extends ffi.Struct { ffi.Int Type, ffi.Int Icon)>> app_alert; + /// Method: app_beep + /// Causes the system to play a sound. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nType - The sound type, see JSPLATFORM_BEEP_TYPE_* + /// above. + /// Return Value: + /// None external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Int nType)>> app_beep; + /// Method: app_response + /// Displays a dialog box containing a question and an entry field for + /// the user to reply to the question. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Question - The question to be posed to the user. + /// Title - The title of the dialog box. + /// Default - A default value for the answer to the question. If + /// not specified, no default value is presented. + /// cLabel - A short string to appear in front of and on the + /// same line as the edit text field. + /// bPassword - If true, indicates that the user's response should + /// be shown as asterisks (*) or bullets (?) to mask + /// the response, which might be sensitive information. + /// response - A string buffer allocated by PDFium, to receive the + /// user's response. + /// length - The length of the buffer in bytes. Currently, it is + /// always 2048. + /// Return Value: + /// Number of bytes the complete user input would actually require, not + /// including trailing zeros, regardless of the value of the length + /// parameter or the presence of the response buffer. + /// Comments: + /// No matter on what platform, the response buffer should be always + /// written using UTF-16LE encoding. If a response buffer is + /// present and the size of the user input exceeds the capacity of the + /// buffer as specified by the length parameter, only the + /// first "length" bytes of the user input are to be written to the + /// buffer. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function( @@ -6722,11 +10584,62 @@ final class _IPDF_JsPlatform extends ffi.Struct { ffi.Pointer response, ffi.Int length)>> app_response; + /// Method: Doc_getFilePath + /// Get the file path of the current document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// filePath - The string buffer to receive the file path. Can + /// be NULL. + /// length - The length of the buffer, number of bytes. Can + /// be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in the local encoding. + /// The return value always indicated number of bytes required for + /// the buffer, even when there is no buffer specified, or the buffer + /// size is less than required. In this case, the buffer will not + /// be modified. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; + /// Method: Doc_mail + /// Mails the data buffer as an attachment to all recipients, with or + /// without user interaction. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// mailData - Pointer to the data buffer to be sent. Can be NULL. + /// length - The size,in bytes, of the buffer pointed by + /// mailData parameter. Can be 0. + /// bUI - If true, the rest of the parameters are used in a + /// compose-new-message window that is displayed to the + /// user. If false, the cTo parameter is required and + /// all others are optional. + /// To - A semicolon-delimited list of recipients for the + /// message. + /// Subject - The subject of the message. The length limit is + /// 64 KB. + /// CC - A semicolon-delimited list of CC recipients for + /// the message. + /// BCC - A semicolon-delimited list of BCC recipients for + /// the message. + /// Msg - The content of the message. The length limit is + /// 64 KB. + /// Return Value: + /// None. + /// Comments: + /// If the parameter mailData is NULL or length is 0, the current + /// document will be mailed as an attachment to all recipients. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6740,6 +10653,31 @@ final class _IPDF_JsPlatform extends ffi.Struct { FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; + /// Method: Doc_print + /// Prints all or a specific number of pages of the document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// bUI - If true, will cause a UI to be presented to the + /// user to obtain printing information and confirm + /// the action. + /// nStart - A 0-based index that defines the start of an + /// inclusive range of pages. + /// nEnd - A 0-based index that defines the end of an + /// inclusive page range. + /// bSilent - If true, suppresses the cancel dialog box while + /// the document is printing. The default is false. + /// bShrinkToFit - If true, the page is shrunk (if necessary) to + /// fit within the imageable area of the printed page. + /// bPrintAsImage - If true, print pages as an image. + /// bReverse - If true, print from nEnd to nStart. + /// bAnnotations - If true (the default), annotations are + /// printed. + /// Return Value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6753,6 +10691,20 @@ final class _IPDF_JsPlatform extends ffi.Struct { FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; + /// Method: Doc_submitForm + /// Send the form data to a specified URL. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// formData - Pointer to the data buffer to be sent. + /// length - The size,in bytes, of the buffer pointed by + /// formData parameter. + /// URL - The URL to send to. + /// Return Value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6761,21 +10713,52 @@ final class _IPDF_JsPlatform extends ffi.Struct { ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; + /// Method: Doc_gotoPage + /// Jump to a specified page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nPageNum - The specified page number, zero for the first page. + /// Return Value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Int nPageNum)>> Doc_gotoPage; + /// Method: Field_browse + /// Show a file selection dialog, and return the selected file path. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// filePath - Pointer to the data buffer to receive the file + /// path. Can be NULL. + /// length - The length of the buffer, in bytes. Can be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in local encoding. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; + /// Pointer for embedder-specific data. Unused by PDFium, and despite + /// its name, can be any data the embedder desires, though traditionally + /// a FPDF_FORMFILLINFO interface. external ffi.Pointer m_pFormfillinfo; + /// Unused in v3, retain for compatibility. external ffi.Pointer m_isolate; + /// Unused in v3, retain for compatibility. @ffi.UnsignedInt() external int m_v8EmbedderSlot; } @@ -6783,44 +10766,108 @@ final class _IPDF_JsPlatform extends ffi.Struct { typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); typedef DartTimerCallbackFunction = void Function(int idEvent); + +/// Function signature for the callback function passed to the FFI_SetTimer +/// method. +/// Parameters: +/// idEvent - Identifier of the timer. +/// Return value: +/// None. typedef TimerCallback = ffi.Pointer>; +/// Declares of a struct type to the local system time. final class _FPDF_SYSTEMTIME extends ffi.Struct { + /// years since 1900 @ffi.UnsignedShort() external int wYear; + /// months since January - [0,11] @ffi.UnsignedShort() external int wMonth; + /// days since Sunday - [0,6] @ffi.UnsignedShort() external int wDayOfWeek; + /// day of the month - [1,31] @ffi.UnsignedShort() external int wDay; + /// hours since midnight - [0,23] @ffi.UnsignedShort() external int wHour; + /// minutes after the hour - [0,59] @ffi.UnsignedShort() external int wMinute; + /// seconds after the minute - [0,59] @ffi.UnsignedShort() external int wSecond; + /// milliseconds after the second - [0,999] @ffi.UnsignedShort() external int wMilliseconds; } +/// Declares of a struct type to the local system time. typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; final class _FPDF_FORMFILLINFO extends ffi.Struct { + /// Version number of the interface. + /// Version 1 contains stable interfaces. Version 2 has additional + /// experimental interfaces. + /// When PDFium is built without the XFA module, version can be 1 or 2. + /// With version 1, only stable interfaces are called. With version 2, + /// additional experimental interfaces are also called. + /// When PDFium is built with the XFA module, version must be 2. + /// All the XFA related interfaces are experimental. If PDFium is built with + /// the XFA module and version 1 then none of the XFA related interfaces + /// would be called. When PDFium is built with XFA module then the version + /// must be 2. @ffi.Int() external int version; + /// Method: Release + /// Give the implementation a chance to release any resources after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Comments: + /// Called by PDFium during the final cleanup process. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> Release; + /// Method: FFI_Invalidate + /// Invalidate the client area within the specified rectangle. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// All positions are measured in PDF "user space". + /// Implementation should call FPDF_RenderPageBitmap() for repainting + /// the specified page area. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6831,6 +10878,32 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; + /// Method: FFI_OutputSelectedRect + /// When the user selects text in form fields with the mouse, this + /// callback function will be invoked with the selected areas. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage()/ + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// This callback function is useful for implementing special text + /// selection effects. An implementation should first record the + /// returned rectangles, then draw them one by one during the next + /// painting period. Lastly, it should remove all the recorded + /// rectangles when finished painting. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6841,54 +10914,201 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; + /// Method: FFI_SetCursor + /// Set the Cursor shape. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nCursorType - Cursor type, see Flags for Cursor type for details. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int nCursorType)>> FFI_SetCursor; + /// Method: FFI_SetTimer + /// This method installs a system timer. An interval value is specified, + /// and every time that interval elapses, the system must call into the + /// callback function with the timer ID as returned by this function. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// uElapse - Specifies the time-out value, in milliseconds. + /// lpTimerFunc - A pointer to the callback function-TimerCallback. + /// Return value: + /// The timer identifier of the new timer if the function is successful. + /// An application passes this value to the FFI_KillTimer method to kill + /// the timer. Nonzero if it is successful; otherwise, it is zero. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; + /// Method: FFI_KillTimer + /// This method uninstalls a system timer, as set by an earlier call to + /// FFI_SetTimer. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nTimerID - The timer ID returned by FFI_SetTimer function. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int nTimerID)>> FFI_KillTimer; + /// Method: FFI_GetLocalTime + /// This method receives the current local time on the system. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// The local time. See FPDF_SYSTEMTIME above for details. + /// Note: Unused. external ffi.Pointer< ffi.NativeFunction< FPDF_SYSTEMTIME Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> FFI_GetLocalTime; + /// Method: FFI_OnChange + /// This method will be invoked to notify the implementation when the + /// value of any FormField on the document had been changed. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> FFI_OnChange; + /// Method: FFI_GetPage + /// This method receives the page handle associated with a specified + /// page index. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// nPageIndex - Index number of the page. 0 for the first page. + /// Return value: + /// Handle to the page, as previously returned to the implementation by + /// FPDF_LoadPage(). + /// Comments: + /// The implementation is expected to keep track of the page handles it + /// receives from PDFium, and their mappings to page numbers. In some + /// cases, the document-level JavaScript action may refer to a page + /// which hadn't been loaded yet. To successfully run the Javascript + /// action, the implementation needs to load the page. external ffi.Pointer< ffi.NativeFunction< FPDF_PAGE Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; + /// Method: FFI_GetCurrentPage + /// This method receives the handle to the current page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes when V8 support is present, otherwise unused. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// Return value: + /// Handle to the page. Returned by FPDF_LoadPage(). + /// Comments: + /// PDFium doesn't keep keep track of the "current page" (e.g. the one + /// that is most visible on screen), so it must ask the embedder for + /// this information. external ffi.Pointer< ffi.NativeFunction< FPDF_PAGE Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; + /// Method: FFI_GetRotation + /// This method receives currently rotation of the page view. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page, as returned by FPDF_LoadPage(). + /// Return value: + /// A number to indicate the page rotation in 90 degree increments + /// in a clockwise direction: + /// 0 - 0 degrees + /// 1 - 90 degrees + /// 2 - 180 degrees + /// 3 - 270 degrees + /// Note: Unused. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function( ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_PAGE page)>> FFI_GetRotation; + /// Method: FFI_ExecuteNamedAction + /// This method will execute a named action. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// namedAction - A byte string which indicates the named action, + /// terminated by 0. + /// Return value: + /// None. + /// Comments: + /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the + /// standard named actions, but note that a document may supply any + /// name of its choosing. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; + /// Method: FFI_SetTextFieldFocus + /// Called when a text field is getting or losing focus. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// value - The string value of the form field, in UTF-16LE + /// format. + /// valueLen - The length of the string value. This is the + /// number of characters, not bytes. + /// is_focus - True if the form field is getting focus, false + /// if the form field is losing focus. + /// Return value: + /// None. + /// Comments: + /// Only supports text fields and combobox fields. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6897,11 +11117,56 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; + /// Method: FFI_DoURIAction + /// Ask the implementation to navigate to a uniform resource identifier. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// bsURI - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// Return value: + /// None. + /// Comments: + /// If the embedder is version 2 or higher and have implementation for + /// FFI_DoURIActionWithKeyboardModifier, then + /// FFI_DoURIActionWithKeyboardModifier takes precedence over + /// FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; + /// Method: FFI_DoGoToAction + /// This action changes the view to a specified destination. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nPageIndex - The index of the PDF page. + /// zoomMode - The zoom mode for viewing page. See below. + /// fPosArray - The float array which carries the position info. + /// sizeofArray - The size of float array. + /// PDFZoom values: + /// - XYZ = 1 + /// - FITPAGE = 2 + /// - FITHORZ = 3 + /// - FITVERT = 4 + /// - FITRECT = 5 + /// - FITBBOX = 6 + /// - FITBHORZ = 7 + /// - FITBVERT = 8 + /// Return value: + /// None. + /// Comments: + /// See the Destinations description of <> + /// in 8.2.1 for more details. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6911,11 +11176,36 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; + /// Pointer to IPDF_JSPLATFORM interface. + /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then + /// JavaScript will be prevented from executing while rendering the document. external ffi.Pointer m_pJsPlatform; + /// Whether the XFA module is disabled when built with the XFA module. + /// Interface Version: + /// Ignored if |version| < 2. @FPDF_BOOL() external int xfa_disabled; + /// Method: FFI_DisplayCaret + /// This method will show the caret at specified position. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6927,21 +11217,75 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; + /// Method: FFI_GetCurrentPageIndex + /// This method will get the current page index. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// Return value: + /// The index of current page. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; + /// Method: FFI_SetCurrentPage + /// This method will set the current page. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// iCurPage - The index of the PDF page. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; + /// Method: FFI_GotoURL + /// This method will navigate to the specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// wsURL - The string value of the URL, in UTF-16LE format. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; + /// Method: FFI_GetPageViewRect + /// This method will get the current page view rectangle. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - The pointer to receive left position of the page + /// view area in PDF page coordinates. + /// top - The pointer to receive top position of the page + /// view area in PDF page coordinates. + /// right - The pointer to receive right position of the + /// page view area in PDF page coordinates. + /// bottom - The pointer to receive bottom position of the + /// page view area in PDF page coordinates. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6952,11 +11296,50 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; + /// Method: FFI_PageEvent + /// This method fires when pages have been added to or deleted from + /// the XFA document. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page_count - The number of pages to be added or deleted. + /// event_type - See FXFA_PAGEVIEWEVENT_* above. + /// Return value: + /// None. + /// Comments: + /// The pages to be added or deleted always start from the last page + /// of document. This means that if parameter page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTADDED, 2 new pages have been + /// appended to the tail of document; If page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages + /// have been deleted. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; + /// Method: FFI_PopupMenu + /// This method will track the right context menu for XFA fields. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// hWidget - Always null, exists for compatibility. + /// menuFlag - The menu flags. Please refer to macro definition + /// of FXFA_MENU_XXX and this can be one or a + /// combination of these macros. + /// x - X position of the client area in PDF page + /// coordinates. + /// y - Y position of the client area in PDF page + /// coordinates. + /// Return value: + /// TRUE indicates success; otherwise false. external ffi.Pointer< ffi.NativeFunction< FPDF_BOOL Function( @@ -6967,6 +11350,21 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Float x, ffi.Float y)>> FFI_PopupMenu; + /// Method: FFI_OpenFile + /// This method will open the specified file with the specified mode. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// wsURL - The string value of the file URL, in UTF-16LE + /// format. + /// mode - The mode for open file, e.g. "rb" or "wb". + /// Return value: + /// The handle to FPDF_FILEHANDLER. external ffi.Pointer< ffi.NativeFunction< ffi.Pointer Function( @@ -6975,6 +11373,27 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; + /// Method: FFI_EmailTo + /// This method will email the specified file stream to the specified + /// contact. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// pTo - A semicolon-delimited list of recipients for the + /// message,in UTF-16LE format. + /// pSubject - The subject of the message,in UTF-16LE format. + /// pCC - A semicolon-delimited list of CC recipients for + /// the message,in UTF-16LE format. + /// pBcc - A semicolon-delimited list of BCC recipients for + /// the message,in UTF-16LE format. + /// pMsg - Pointer to the data buffer to be sent.Can be + /// NULL,in UTF-16LE format. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6986,6 +11405,21 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; + /// Method: FFI_UploadTo + /// This method will upload the specified file stream to the + /// specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// uploadTo - Pointer to the URL path, in UTF-16LE format. + /// Return value: + /// None. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -6994,22 +11428,81 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; + /// Method: FFI_GetPlatform + /// This method will get the current platform. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// platform - Pointer to the data buffer to receive the + /// platform,in UTF-16LE format. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; + /// Method: FFI_GetLanguage + /// This method will get the current language. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// language - Pointer to the data buffer to receive the + /// current language. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. external ffi.Pointer< ffi.NativeFunction< ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; + /// Method: FFI_DownloadFromURL + /// This method will download the specified file from the URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// URL - The string value of the file URL, in UTF-16LE + /// format. + /// Return value: + /// The handle to FPDF_FILEHANDLER. external ffi.Pointer< ffi.NativeFunction< ffi.Pointer Function( ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; + /// Method: FFI_PostRequestURL + /// This method will post the request to the server URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The post data,in UTF-16LE format. + /// wsContentType - The content type of the request data, in + /// UTF-16LE format. + /// wsEncode - The encode type, in UTF-16LE format. + /// wsHeader - The request header,in UTF-16LE format. + /// response - Pointer to the FPDF_BSTR to receive the response + /// data from the server, in UTF-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. external ffi.Pointer< ffi.NativeFunction< FPDF_BOOL Function( @@ -7021,6 +11514,20 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; + /// Method: FFI_PutRequestURL + /// This method will put the request to the server URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The put data, in UTF-16LE format. + /// wsEncode - The encode type, in UTR-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. external ffi.Pointer< ffi.NativeFunction< FPDF_BOOL Function( @@ -7029,11 +11536,49 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; + /// Method: FFI_OnFocusChange + /// Called when the focused annotation is updated. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// No + /// Parameters: + /// param - Pointer to the interface structure itself. + /// annot - The focused annotation. + /// page_index - Index number of the page which contains the + /// focused annotation. 0 for the first page. + /// Return value: + /// None. + /// Comments: + /// This callback function is useful for implementing any view based + /// action such as scrolling the annotation rect into view. The + /// embedder should not copy and store the annot as its scope is + /// limited to this call only. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; + /// Method: FFI_DoURIActionWithKeyboardModifier + /// Ask the implementation to navigate to a uniform resource identifier + /// with the specified modifiers. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// No + /// Parameters: + /// param - Pointer to the interface structure itself. + /// uri - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// modifiers - Keyboard modifier that indicates which of + /// the virtual keys are down, if any. + /// Return value: + /// None. + /// Comments: + /// If the embedder who is version 2 and does not implement this API, + /// then a call will be redirected to FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. external ffi.Pointer< ffi.NativeFunction< ffi.Void Function( @@ -7059,6 +11604,8 @@ enum FPDFANNOT_COLORTYPE { }; } +/// The file identifier entry type. See section 14.4 "File Identifiers" of the +/// ISO 32000-1:2008 spec. enum FPDF_FILEIDTYPE { FILEIDTYPE_PERMANENT(0), FILEIDTYPE_CHANGING(1); @@ -7074,24 +11621,32 @@ enum FPDF_FILEIDTYPE { } final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct { + /// The image width in pixels. @ffi.UnsignedInt() external int width; + /// The image height in pixels. @ffi.UnsignedInt() external int height; + /// The image's horizontal pixel-per-inch. @ffi.Float() external double horizontal_dpi; + /// The image's vertical pixel-per-inch. @ffi.Float() external double vertical_dpi; + /// The number of bits used to represent each pixel. @ffi.UnsignedInt() external int bits_per_pixel; + /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. @ffi.Int() external int colorspace; + /// The image's marked content ID. Useful for pairing with associated alt-text. + /// A value of -1 indicates no ID. @ffi.Int() external int marked_content_id; } diff --git a/pubspec.yaml b/pubspec.yaml index 71f3983d..40f00385 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,4 +64,7 @@ ffigen: // ignore_for_file: unused_field // dart format off name: "pdfium" + comments: + style: any + length: full From d9c1c7b9d0be4a9e7a9acb109539b3d38c4fd107 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Apr 2025 08:57:46 +0900 Subject: [PATCH 021/663] 1.1.19 --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 240fc6b4..8396d6fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.19 + +- `lib/src/pdfium/pdfium_bindings.dart` now keep Pdfium's original comments + # 1.1.18 - Merge PR #338 from mtallenca/cache_expired_not_modified_fix diff --git a/README.md b/README.md index 278ae085..19b05a4d 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.18 + pdfrx: ^1.1.19 ``` ### Note for Windows @@ -69,7 +69,7 @@ This is still not production-ready, but you can try it by adding additional [pdf ```yaml dependencies: - pdfrx: ^1.1.18 + pdfrx: ^1.1.19 pdfrx_wasm: ^1.1.6 ``` diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 203ee421..e1b084ef 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.18" + version: "1.1.19" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 40f00385..a5cf39c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.18 +version: 1.1.19 homepage: https://github.com/espresso3389/pdfrx environment: From bf8d6335939026841a7c0e9439030d02c81fc8e0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Apr 2025 15:44:58 +0900 Subject: [PATCH 022/663] WIP --- lib/src/pdfium/pdf_file_cache.dart | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index 35085c0a..0ae463d3 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -13,12 +13,24 @@ import 'package:synchronized/extension.dart'; import '../../pdfrx.dart'; import 'http_cache_control.dart'; +final _rafFinalizer = Finalizer((raf) { + // Attempt to close the file if it hasn't been closed explicitly. + // Use try-catch as close might fail or already be closed. + try { + raf.close(); + // Consider adding logging here if needed for debugging finalization. + // print('PdfFileCache: Finalizer closed RandomAccessFile.'); + } catch (_) { + // Ignore errors during finalization. + } +}); + /// PDF file cache backed by a file. /// class PdfFileCache { PdfFileCache(this.file); - /// Default cache block size is 32KB. + /// Default cache block size is 1MB. static const defaultBlockSize = 1024 * 1024; /// Cache file. @@ -59,12 +71,20 @@ class PdfFileCache { bool get isInitialized => _initialized; Future close() async { - await _raf?.close(); - _raf = null; + final raf = _raf; + if (raf != null) { + _rafFinalizer.detach(this); // Detach from finalizer since we are closing explicitly + _raf = null; + await raf.close(); + } } Future _ensureFileOpen() async { - _raf ??= await file.open(mode: FileMode.append); + if (_raf == null) { + _raf = await file.open(mode: FileMode.append); + // Attach the file handle to the finalizer, associated with 'this' cache instance. + _rafFinalizer.attach(this, _raf!, detach: this); + } } Future _read(List buffer, int bufferPosition, int position, int size) async { @@ -148,7 +168,7 @@ class PdfFileCache { _fileSize = await _getSize() - _headerSize!; } _initialized = true; - } catch (e) { + } catch (_) { _initialized = false; } } From 74ba11a237c17ee494103e23333ce4ce34c77f83 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 15:06:20 +0900 Subject: [PATCH 023/663] 1.1.20; adding Add HTML meta tags to enable Pdfium WASM support --- .github/workflows/github-pages.yml | 1 + CHANGELOG.md | 4 ++++ README.md | 35 +++--------------------------- example/viewer/lib/main.dart | 7 +++--- example/viewer/pubspec.lock | 2 +- example/viewer/web/index.html | 6 +++++ lib/src/web/pdfrx_web.dart | 22 +++++++++++++++++++ pubspec.yaml | 2 +- 8 files changed, 41 insertions(+), 38 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index bd073e4f..e335b824 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -39,6 +39,7 @@ jobs: sed -i \ -e 's|||' \ -e "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" \ + -e "s/__PDFIUM_WASM_DISABLED__/enabled/g" \ build/web/index.html - name: Configure Git for deployment run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 8396d6fe..d3cc367d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.20 + +- Add HTML meta tags (`pdfrx-pdfium-wasm`/`pdfium-wasm-module-url`) to enable Pdfium WASM support + # 1.1.19 - `lib/src/pdfium/pdfium_bindings.dart` now keep Pdfium's original comments diff --git a/README.md b/README.md index 19b05a4d..1067e764 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.19 + pdfrx: ^1.1.20 ``` ### Note for Windows @@ -62,38 +62,9 @@ dependencies: The build process internally uses *symbolic links* and it requires Developer Mode to be enabled. Without this, you may encounter errors [like this](https://github.com/espresso3389/pdfrx/issues/34). -### "Bleeding Edge" Pdfium WASM support on Web +### Pdfium WASM support on Web -pdfrx now supports "bleeding edge" Pdfium WASM support on Web. -This is still not production-ready, but you can try it by adding additional [pdfrx_wasm](https://pub.dartlang.org/packages/pdfrx_wasm) to your dependencies: - -```yaml -dependencies: - pdfrx: ^1.1.19 - pdfrx_wasm: ^1.1.6 -``` - -And then, enable Pdfium WASM by adding the following line to somewhere that runs before calling any pdfrx functions (typically `main` function): - -```dart -Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; -``` - -The plugin, `pdfrx_wasm`, is a satellite plugin for `pdfrx` that contains files required to run Pdfium WASM. Because the WASM binary/support files are relatively large (about 4MB), it is not included in the main `pdfrx` package and you need to add `pdfrx_wasm` to your dependencies. - -## Open PDF File - -[PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) supports following functions to open PDF file on specific medium: - -- [PdfViewer.asset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html) - - Open PDF of Flutter's asset -- [PdfViewer.file](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html) - - Open PDF from file - - macOS: may be blocked by [App Sandbox](https://github.com/espresso3389/pdfrx/wiki/macOS:-Deal-with-App-Sandbox) -- [PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - - Open PDF from URI (`https://...` or relative path) - - Flutter Web: may be blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - - macOS: may be blocked by [App Sandbox](https://github.com/espresso3389/pdfrx/wiki/macOS:-Deal-with-App-Sandbox) +pdfrx now supports Pdfium WASM on Web, for more informatin, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). ### Deal with Password Protected PDF Files diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index 6f3451da..e912f2e1 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -11,10 +11,9 @@ import 'search_view.dart'; import 'thumbnails_view.dart'; void main() { - // NOTE: To enable bleeding-edge Pdfium WASM support on Flutter Web; - // 1. add pdfrx_wasm to your pubspec.yaml's dependencies. - // 2. uncomment the following line. - Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; + // NOTE: + // For Pdfium WASM support, see https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support + // Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; runApp(const MyApp()); } diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index e1b084ef..74a5fb87 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.19" + version: "1.1.20" pdfrx_wasm: dependency: "direct main" description: diff --git a/example/viewer/web/index.html b/example/viewer/web/index.html index 64196cf0..ff0eccf3 100644 --- a/example/viewer/web/index.html +++ b/example/viewer/web/index.html @@ -17,6 +17,12 @@ + + diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index 201c9c2f..79834a2c 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -1,3 +1,5 @@ +import 'package:web/web.dart' as web; + import '../../pdfrx.dart'; import 'pdfrx_js.dart'; import 'pdfrx_wasm.dart'; @@ -5,6 +7,7 @@ import 'pdfrx_wasm.dart'; /// Ugly, but working solution to provide a factory that switches between JS and WASM implementations. abstract class PdfDocumentFactoryImpl extends PdfDocumentFactory { factory PdfDocumentFactoryImpl() { + _init(); if (Pdfrx.webRuntimeType == PdfrxWebRuntimeType.pdfiumWasm) { return PdfDocumentFactoryWasmImpl(); } else { @@ -15,3 +18,22 @@ abstract class PdfDocumentFactoryImpl extends PdfDocumentFactory { /// Call this method to extend the factory like `super.callMeIfYouWantToExtendMe()` on its constructor implementation. PdfDocumentFactoryImpl.callMeIfYouWantToExtendMe(); } + +bool _initialized = false; + +void _init() { + if (_initialized) return; + Pdfrx.webRuntimeType = _isWasmEnabled() ? PdfrxWebRuntimeType.pdfiumWasm : PdfrxWebRuntimeType.pdfjs; + Pdfrx.pdfiumWasmModulesUrl = _pdfiumWasmModulesUrlFromMetaTag(); + _initialized = true; +} + +bool _isWasmEnabled() { + final meta = web.document.querySelector('meta[name="pdfrx-pdfium-wasm"]') as web.HTMLMetaElement?; + return meta?.content == 'enabled'; +} + +String? _pdfiumWasmModulesUrlFromMetaTag() { + final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; + return meta?.content; +} diff --git a/pubspec.yaml b/pubspec.yaml index a5cf39c4..c1a651cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.19 +version: 1.1.20 homepage: https://github.com/espresso3389/pdfrx environment: From c79d026b8a2b40dd062dfc7a7c316333123b0d74 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 17:12:48 +0900 Subject: [PATCH 024/663] FIXED: loadOutline is not implemented on Pdfium WASM --- lib/src/web/pdfrx_wasm.dart | 30 ++++++++++++++++++------- wasm/pdfrx_wasm/assets/pdfium_worker.js | 17 ++++++++++---- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 635d871d..0ee28f85 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -228,7 +228,17 @@ class PdfDocumentWasm extends PdfDocument { @override Future> loadOutline() async { - return []; + final result = await factory.sendCommand('loadOutline', parameters: {'docHandle': document['docHandle']}); + final outlineList = result['outline'] as List; + return outlineList.map((node) => _nodeFromMap(node)).toList(); + } + + static PdfOutlineNode _nodeFromMap(dynamic node) { + return PdfOutlineNode( + title: node['title'], + dest: _pdfDestFromMap(node['dest']), + children: (node['children'] as List).map((child) => _nodeFromMap(child)).toList(), + ); } @override @@ -310,13 +320,7 @@ class PdfPageWasm extends PdfPage { if (dest is! Map) { throw FormatException('Unexpected link destination structure: $dest'); } - final params = dest['params'] as List; - final pdfDest = PdfDest( - (dest['pageIndex'] as num).toInt() + 1, - PdfDestCommand.parse(dest['command'] as String), - params.map((p) => p as double).toList(), - ); - return PdfLink(rects, dest: pdfDest); + return PdfLink(rects, dest: _pdfDestFromMap(dest)); }).toList(); } @@ -415,3 +419,13 @@ class PdfPageTextFragmentPdfium implements PdfPageTextFragment { @override String get text => pageText.fullText.substring(index, index + length); } + +PdfDest? _pdfDestFromMap(dynamic dest) { + if (dest == null) return null; + final params = dest['params'] as List; + return PdfDest( + (dest['pageIndex'] as num).toInt() + 1, + PdfDestCommand.parse(dest['command'] as String), + params.map((p) => p as double).toList(), + ); +} diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index bbb5348b..d461ae2f 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -606,18 +606,28 @@ function closeDocument(params) { return { message: "Document closed" }; } +/** + * @typedef {{pageIndex: number, command: string, params: number[]}} PdfDest + * @typedef {{title: string, dest: PdfDest, children: OutlineNode[]}} OutlineNode + */ + /** * @param {{docHandle: number}} params + * @return {OutlineNode[]} */ function loadOutline(params) { - return _getOutlineNodeSiblings(Pdfium.wasmExports.FPDFBookmark_GetFirstChild(params.docHandle, null), params.docHandle); + return { + outline: _getOutlineNodeSiblings(Pdfium.wasmExports.FPDFBookmark_GetFirstChild(params.docHandle, null), params.docHandle), + }; } /** * @param {number} bookmark * @param {number} docHandle + * @return {OutlineNode[]} */ function _getOutlineNodeSiblings(bookmark, docHandle) { + /** @type {OutlineNode[]} */ const siblings = []; while (bookmark) { const titleBufSize = Pdfium.wasmExports.FPDFBookmark_GetTitle(bookmark, null, 0); @@ -627,7 +637,7 @@ function _getOutlineNodeSiblings(bookmark, docHandle) { Pdfium.wasmExports.free(titleBuf); siblings.push({ title: title, - dest: Pdfium.wasmExports.FPDFBookmark_GetDest(docHandle, bookmark), + dest: _pdfDestFromDest(Pdfium.wasmExports.FPDFBookmark_GetDest(docHandle, bookmark), docHandle), children: _getOutlineNodeSiblings(Pdfium.wasmExports.FPDFBookmark_GetFirstChild(docHandle, bookmark), docHandle), }); bookmark = Pdfium.wasmExports.FPDFBookmark_GetNextSibling(docHandle, bookmark); @@ -917,7 +927,6 @@ function _getText(textPage, from, length) { /** - * @typedef {{pageIndex: number, command: string, params: number[]}} PdfDest * @typedef {{rects: number[][], dest: url: string}} PdfUrlLink * @typedef {{rects: number[][], dest: PdfDest}} PdfDestLink */ @@ -1073,7 +1082,7 @@ const pdfDestCommands = ['unknown', 'xyz', 'fit', 'fitH', 'fitV', 'fitR', 'fitB' * @returns {PdfDest|null} */ function _pdfDestFromDest(dest, docHandle) { - if (dest == null) return null; + if (dest === 0) return null; const buf = Pdfium.wasmExports.malloc(40); const pageIndex = Pdfium.wasmExports.FPDFDest_GetDestPageIndex(docHandle, dest); const type = Pdfium.wasmExports.FPDFDest_GetView(dest, buf, buf + 4); From 44fd62be4295260adb246a8c74c0c1dc33d89387 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 17:14:12 +0900 Subject: [PATCH 025/663] 1.1.21 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cc367d..d9f30be3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.21 + +- FIXED: loadOutline is not implemented on Pdfium WASM + # 1.1.20 - Add HTML meta tags (`pdfrx-pdfium-wasm`/`pdfium-wasm-module-url`) to enable Pdfium WASM support diff --git a/README.md b/README.md index 1067e764..10e2528c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.20 + pdfrx: ^1.1.21 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 74a5fb87..d222e98a 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.20" + version: "1.1.21" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c1a651cc..eafc6a21 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.20 +version: 1.1.21 homepage: https://github.com/espresso3389/pdfrx environment: From e68d169db99f3207c427aa1ab4b46953513ea97b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 17:26:33 +0900 Subject: [PATCH 026/663] pdfrx_wasm 1.1.8 --- .pubignore | 2 +- wasm/pdfrx_wasm/CHANGELOG.md | 4 ++++ wasm/pdfrx_wasm/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.pubignore b/.pubignore index 5a90ca91..c270f665 100644 --- a/.pubignore +++ b/.pubignore @@ -1,2 +1,2 @@ -wasm/ +# wasm/ example/viewer/ diff --git a/wasm/pdfrx_wasm/CHANGELOG.md b/wasm/pdfrx_wasm/CHANGELOG.md index 8767693d..2849487d 100644 --- a/wasm/pdfrx_wasm/CHANGELOG.md +++ b/wasm/pdfrx_wasm/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.8 + +* FIXED: loadOutline is not implemented on Pdfium WASM + ## 1.1.7 * FIXED: WASM: could not open PDF files smaller than 1MB (#326) diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml index 8edcde3f..1da81ab3 100644 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ b/wasm/pdfrx_wasm/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_wasm description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.7 +version: 1.1.8 homepage: https://github.com/espresso3389/pdfrx environment: From 1fe3d34f59b895f62aa0803cfe9d1aff3c7f59c2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 18:19:39 +0900 Subject: [PATCH 027/663] WIP --- lib/src/pdf_api.dart | 12 +++++++++++- lib/src/pdfium/pdfrx_pdfium.dart | 6 ++++++ lib/src/web/pdfrx_js.dart | 5 ++--- lib/src/web/pdfrx_wasm.dart | 5 ++--- lib/src/web/pdfrx_web.dart | 28 ++++++++++++++++------------ 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index d8e4e250..e4536e62 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -108,7 +108,17 @@ abstract class PdfDocumentFactory { /// /// It is used to switch PDFium/web implementation based on the running platform and of course, you can /// override it to use your own implementation. - static PdfDocumentFactory instance = PdfDocumentFactoryImpl(); + static PdfDocumentFactory instance = getDocumentFactory(); + + /// Get [PdfDocumentFactory] that uses PDFium implementation. + /// + /// For Flutter Web, it uses PDFium (WASM) implementation. + static PdfDocumentFactory get pdfium => getPdfiumDocumentFactory(); + + /// Get [PdfDocumentFactory] that uses PDF.js implementation. + /// + /// It is only supported on Flutter Web. + static PdfDocumentFactory get pdfjs => getPdfjsDocumentFactory(); } /// Callback function to notify download progress. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index f532ab17..2cb05f2f 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -15,6 +15,12 @@ import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; import 'worker.dart'; +PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryImpl(); + +PdfDocumentFactory getPdfjsDocumentFactory() => throw UnsupportedError('Pdf.js is only supported on Web'); + +PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); + /// Get the module file name for pdfium. String _getModuleFileName() { if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart index f1b0e6c6..00a96815 100644 --- a/lib/src/web/pdfrx_js.dart +++ b/lib/src/web/pdfrx_js.dart @@ -8,10 +8,9 @@ import 'package:web/web.dart' as web; import '../../pdfrx.dart'; import 'pdfjs.dart'; -import 'pdfrx_web.dart'; -class PdfDocumentFactoryJsImpl extends PdfDocumentFactoryImpl { - PdfDocumentFactoryJsImpl() : super.callMeIfYouWantToExtendMe(); +class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { + PdfDocumentFactoryJsImpl(); @override Future openAsset( diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 0ee28f85..bf52f546 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -9,7 +9,6 @@ import 'package:web/web.dart' as web; import '../pdf_api.dart'; import 'pdfrx_js.dart'; -import 'pdfrx_web.dart'; /// Calls PDFium WASM worker with the given command and parameters. @JS() @@ -22,8 +21,8 @@ external JSPromise pdfiumWasmSendCommand([String command, JSAny? paramet external String pdfiumWasmWorkerUrl; /// [PdfDocumentFactory] for PDFium WASM implementation. -class PdfDocumentFactoryWasmImpl extends PdfDocumentFactoryImpl { - PdfDocumentFactoryWasmImpl() : super.callMeIfYouWantToExtendMe(); +class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { + PdfDocumentFactoryWasmImpl(); /// Default path to the WASM modules /// diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index 79834a2c..db8f72a4 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -4,19 +4,23 @@ import '../../pdfrx.dart'; import 'pdfrx_js.dart'; import 'pdfrx_wasm.dart'; -/// Ugly, but working solution to provide a factory that switches between JS and WASM implementations. -abstract class PdfDocumentFactoryImpl extends PdfDocumentFactory { - factory PdfDocumentFactoryImpl() { - _init(); - if (Pdfrx.webRuntimeType == PdfrxWebRuntimeType.pdfiumWasm) { - return PdfDocumentFactoryWasmImpl(); - } else { - return PdfDocumentFactoryJsImpl(); - } - } +PdfDocumentFactory getPdfiumDocumentFactory() { + _init(); + return PdfDocumentFactoryWasmImpl(); +} - /// Call this method to extend the factory like `super.callMeIfYouWantToExtendMe()` on its constructor implementation. - PdfDocumentFactoryImpl.callMeIfYouWantToExtendMe(); +PdfDocumentFactory getPdfjsDocumentFactory() { + _init(); + return PdfDocumentFactoryJsImpl(); +} + +PdfDocumentFactory getDocumentFactory() { + _init(); + if (Pdfrx.webRuntimeType == PdfrxWebRuntimeType.pdfiumWasm) { + return getPdfiumDocumentFactory(); + } else { + return getPdfjsDocumentFactory(); + } } bool _initialized = false; From 00a66fa16591e86e86220164b12edd8269fdba3d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 7 Apr 2025 21:44:46 +0900 Subject: [PATCH 028/663] WIP: export getPdfiumDocumentFactory/getPdfjsDocumentFactory --- lib/src/pdfium/pdfrx_pdfium.dart | 8 ++++++++ lib/src/web/pdfrx_js.dart | 6 +++--- lib/src/web/pdfrx_web.dart | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 2cb05f2f..3f3e9276 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -15,10 +15,18 @@ import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; import 'worker.dart'; +/// Get [PdfDocumentFactory] backed by Pdfium. +/// +/// For Flutter Web, you must set up Pdfium WASM module. +/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryImpl(); +/// Get [PdfDocumentFactory] backed by PDF.js. +/// +/// Only supported on Flutter Web. PdfDocumentFactory getPdfjsDocumentFactory() => throw UnsupportedError('Pdf.js is only supported on Web'); +/// Get the default [PdfDocumentFactory]. PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); /// Get the module file name for pdfium. diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart index 00a96815..0b60e63f 100644 --- a/lib/src/web/pdfrx_js.dart +++ b/lib/src/web/pdfrx_js.dart @@ -24,7 +24,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { final bytes = await rootBundle.load(name); return await pdfjsGetDocumentFromData(bytes.buffer, password: password, allowDataOwnershipTransfer: true); }, - sourceName: 'asset:$name', + sourceName: 'asset_js:$name', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, ); @@ -65,7 +65,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { password: password, allowDataOwnershipTransfer: allowDataOwnershipTransfer, ), - sourceName: sourceName ?? 'memory-${data.hashCode}', + sourceName: sourceName ?? 'memory_js:${data.hashCode}', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: onDispose, @@ -97,7 +97,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => pdfjsGetDocument(uri.toString(), password: password, headers: headers, withCredentials: withCredentials), - sourceName: uri.toString(), + sourceName: 'uri_js:$uri', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, ); diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index db8f72a4..01a40aef 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -4,16 +4,24 @@ import '../../pdfrx.dart'; import 'pdfrx_js.dart'; import 'pdfrx_wasm.dart'; +/// Get [PdfDocumentFactory] backed by Pdfium. +/// +/// For Flutter Web, you must set up Pdfium WASM module. +/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() { _init(); return PdfDocumentFactoryWasmImpl(); } +/// Get [PdfDocumentFactory] backed by PDF.js. +/// +/// Only supported on Flutter Web. PdfDocumentFactory getPdfjsDocumentFactory() { _init(); return PdfDocumentFactoryJsImpl(); } +/// Get the default [PdfDocumentFactory]. PdfDocumentFactory getDocumentFactory() { _init(); if (Pdfrx.webRuntimeType == PdfrxWebRuntimeType.pdfiumWasm) { From da309673647c258116905870adb2e13a0c4e0617 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 8 Apr 2025 13:52:51 +0900 Subject: [PATCH 029/663] WIP --- lib/src/pdfium/pdfrx_pdfium.dart | 4 +++- lib/src/web/pdfrx_web.dart | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 3f3e9276..058b842c 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -15,11 +15,13 @@ import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; import 'worker.dart'; +PdfDocumentFactory? _pdfiumDocumentFactory; + /// Get [PdfDocumentFactory] backed by Pdfium. /// /// For Flutter Web, you must set up Pdfium WASM module. /// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). -PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryImpl(); +PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= PdfDocumentFactoryImpl(); /// Get [PdfDocumentFactory] backed by PDF.js. /// diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index 01a40aef..cfd0a2b5 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -4,13 +4,16 @@ import '../../pdfrx.dart'; import 'pdfrx_js.dart'; import 'pdfrx_wasm.dart'; +PdfDocumentFactory? _pdfiumDocumentFactory; +PdfDocumentFactory? _pdfjsDocumentFactory; + /// Get [PdfDocumentFactory] backed by Pdfium. /// /// For Flutter Web, you must set up Pdfium WASM module. /// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() { _init(); - return PdfDocumentFactoryWasmImpl(); + return _pdfiumDocumentFactory ??= PdfDocumentFactoryWasmImpl(); } /// Get [PdfDocumentFactory] backed by PDF.js. @@ -18,7 +21,7 @@ PdfDocumentFactory getPdfiumDocumentFactory() { /// Only supported on Flutter Web. PdfDocumentFactory getPdfjsDocumentFactory() { _init(); - return PdfDocumentFactoryJsImpl(); + return _pdfjsDocumentFactory ??= PdfDocumentFactoryJsImpl(); } /// Get the default [PdfDocumentFactory]. From 33613e23765553525c9261c998e3c505eefcd990 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 8 Apr 2025 15:33:41 +0900 Subject: [PATCH 030/663] WIP --- example/viewer/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index d222e98a..b7f021eb 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -322,7 +322,7 @@ packages: path: "../../wasm/pdfrx_wasm" relative: true source: path - version: "1.1.7" + version: "1.1.8" platform: dependency: transitive description: From f02fb0d8ca2281216ceb3fe30cf470233c100bab Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 10 Apr 2025 16:15:34 +0900 Subject: [PATCH 031/663] WIP: add PdfLink.toString --- lib/src/pdf_api.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index e4536e62..84f275dd 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -965,6 +965,11 @@ class PdfLink { PdfLink compact() { return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact()); } + + @override + String toString() { + return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects}'; + } } /// Outline (a.k.a. Bookmark) node in PDF document. From 5f1e13f7c05f0a8c94015ef182f415b0b58b3bea Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 10 Apr 2025 16:18:24 +0900 Subject: [PATCH 032/663] Related #341 --- lib/src/widgets/pdf_viewer.dart | 142 ++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 15e73e74..e5e7bcca 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -413,8 +413,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return Container( color: widget.params.backgroundColor, - child: Focus( - onKeyEvent: _onKeyEvent, + child: _FocusWithKeyRepeat( + onKeyRepeat: _onKeyEvent, child: StreamBuilder( stream: _updateStream, builder: (context, snapshot) { @@ -496,61 +496,33 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Last page number that is explicitly requested to go to. int? _gotoTargetPageNumber; - KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) { - final isDown = event is KeyDownEvent; - switch (event.logicalKey) { + void _onKeyEvent(FocusNode node, LogicalKeyboardKey key) { + switch (key) { case LogicalKeyboardKey.pageUp: - if (isDown) { - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1); - } - return KeyEventResult.handled; + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1); case LogicalKeyboardKey.pageDown: - if (isDown) { - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1); - } - return KeyEventResult.handled; + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1); case LogicalKeyboardKey.home: - if (isDown) { - _goToPage(pageNumber: 1); - } - return KeyEventResult.handled; + _goToPage(pageNumber: 1); case LogicalKeyboardKey.end: - if (isDown) { - _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd); - } - return KeyEventResult.handled; + _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd); case LogicalKeyboardKey.equal: - if (isDown && isCommandKeyPressed) { + if (isCommandKeyPressed) { _zoomUp(); } - return KeyEventResult.handled; case LogicalKeyboardKey.minus: - if (isDown && isCommandKeyPressed) { + if (isCommandKeyPressed) { _zoomDown(); } - return KeyEventResult.handled; case LogicalKeyboardKey.arrowDown: - if (isDown) { - _goToManipulated((m) => m.translate(0.0, -widget.params.scrollByArrowKey)); - } - return KeyEventResult.handled; + _goToManipulated((m) => m.translate(0.0, -widget.params.scrollByArrowKey)); case LogicalKeyboardKey.arrowUp: - if (isDown) { - _goToManipulated((m) => m.translate(0.0, widget.params.scrollByArrowKey)); - } - return KeyEventResult.handled; + _goToManipulated((m) => m.translate(0.0, widget.params.scrollByArrowKey)); case LogicalKeyboardKey.arrowLeft: - if (isDown) { - _goToManipulated((m) => m.translate(widget.params.scrollByArrowKey, 0.0)); - } - return KeyEventResult.handled; + _goToManipulated((m) => m.translate(widget.params.scrollByArrowKey, 0.0)); case LogicalKeyboardKey.arrowRight: - if (isDown) { - _goToManipulated((m) => m.translate(-widget.params.scrollByArrowKey, 0.0)); - } - return KeyEventResult.handled; + _goToManipulated((m) => m.translate(-widget.params.scrollByArrowKey, 0.0)); } - return KeyEventResult.ignored; } Future _goToManipulated(void Function(Matrix4 m) manipulate) async { @@ -2121,3 +2093,89 @@ class _CanvasLinkPainter { } } } + +class _FocusWithKeyRepeat extends StatefulWidget { + const _FocusWithKeyRepeat({ + required this.child, + required this.onKeyRepeat, + super.key, + this.focusNode, + this.parentNode, + this.initialDelay = const Duration(milliseconds: 500), + this.repeatInterval = const Duration(milliseconds: 100), + }); + final Widget child; + final Function(FocusNode, LogicalKeyboardKey) onKeyRepeat; + final Duration initialDelay; + final Duration repeatInterval; + final FocusNode? focusNode; + final FocusNode? parentNode; + + @override + _FocusWithKeyRepeatState createState() => _FocusWithKeyRepeatState(); +} + +class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { + Timer? _timer; + LogicalKeyboardKey? _currentKey; + + void _startRepeating(FocusNode node, LogicalKeyboardKey key) { + _currentKey = key; + + // Initial delay before starting to repeat + _timer = Timer(widget.initialDelay, () { + // Start repeating at the specified interval + _timer = Timer.periodic(widget.repeatInterval, (_) { + widget.onKeyRepeat(node, _currentKey!); + }); + }); + } + + void _stopRepeating() { + _timer?.cancel(); + _timer = null; + _currentKey = null; + } + + @override + void dispose() { + _stopRepeating(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: widget.focusNode, + parentNode: widget.parentNode, + onKeyEvent: (node, event) { + if (event is KeyDownEvent) { + // Key pressed down + if (_currentKey == null) { + _startRepeating(node, event.logicalKey); + widget.onKeyRepeat(node, event.logicalKey); // Immediate first response + } + return KeyEventResult.handled; + } else if (event is KeyUpEvent) { + // Key released + if (_currentKey == event.logicalKey) { + _stopRepeating(); + } + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (context) { + final focusNode = Focus.of(context); + return ListenableBuilder( + listenable: focusNode, + builder: (context, _) { + return widget.child; + }, + ); + }, + ), + ); + } +} From fc82cab115c776794ea1f4fa804632c9dee78f54 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 10 Apr 2025 18:17:16 +0900 Subject: [PATCH 033/663] Related #341: PdfViewerParams.onKey to handle key events on PdfViewer. WIP --- lib/src/widgets/pdf_viewer.dart | 13 ++++++++----- lib/src/widgets/pdf_viewer_params.dart | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index e5e7bcca..dbedcc40 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -414,7 +414,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return Container( color: widget.params.backgroundColor, child: _FocusWithKeyRepeat( - onKeyRepeat: _onKeyEvent, + onKeyRepeat: _onKey, child: StreamBuilder( stream: _updateStream, builder: (context, snapshot) { @@ -496,11 +496,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Last page number that is explicitly requested to go to. int? _gotoTargetPageNumber; - void _onKeyEvent(FocusNode node, LogicalKeyboardKey key) { + void _onKey(FocusNode node, LogicalKeyboardKey key, bool isRealKeyPress) { + if (widget.params.onKey?.call(node, key, isRealKeyPress) == true) return; + switch (key) { case LogicalKeyboardKey.pageUp: _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1); case LogicalKeyboardKey.pageDown: + case LogicalKeyboardKey.space: _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1); case LogicalKeyboardKey.home: _goToPage(pageNumber: 1); @@ -2105,7 +2108,7 @@ class _FocusWithKeyRepeat extends StatefulWidget { this.repeatInterval = const Duration(milliseconds: 100), }); final Widget child; - final Function(FocusNode, LogicalKeyboardKey) onKeyRepeat; + final Function(FocusNode, LogicalKeyboardKey, bool) onKeyRepeat; final Duration initialDelay; final Duration repeatInterval; final FocusNode? focusNode; @@ -2126,7 +2129,7 @@ class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { _timer = Timer(widget.initialDelay, () { // Start repeating at the specified interval _timer = Timer.periodic(widget.repeatInterval, (_) { - widget.onKeyRepeat(node, _currentKey!); + widget.onKeyRepeat(node, _currentKey!, false); }); }); } @@ -2153,7 +2156,7 @@ class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { // Key pressed down if (_currentKey == null) { _startRepeating(node, event.logicalKey); - widget.onKeyRepeat(node, event.logicalKey); // Immediate first response + widget.onKeyRepeat(node, event.logicalKey, true); // Immediate first response } return KeyEventResult.handled; } else if (event is KeyUpEvent) { diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index bc635c8e..51146c42 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -1,6 +1,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../../pdfrx.dart'; @@ -58,6 +59,7 @@ class PdfViewerParams { this.onTextSelectionChange, this.selectableRegionInjector, this.perPageSelectableRegionInjector, + this.onKey, this.forceReload = false, }); @@ -490,6 +492,14 @@ class PdfViewerParams { /// You can even enable both of [selectableRegionInjector] and [perPageSelectableRegionInjector] at the same time. final PdfPerPageSelectableRegionInjector? perPageSelectableRegionInjector; + /// Function to handle key events. + /// + /// The function should return true if it processes the key event; otherwise, it returns false. + /// + /// By default, [PdfViewer] handles key events such as arrow keys and page up/down keys. You can even override the + /// default behavior by returning true from this function. + final PdfViewerOnKeyCallback? onKey; + /// Force reload the viewer. /// /// Normally whether to reload the viewer is determined by the changes of the parameters but @@ -578,6 +588,7 @@ class PdfViewerParams { other.onTextSelectionChange == onTextSelectionChange && other.selectableRegionInjector == selectableRegionInjector && other.perPageSelectableRegionInjector == perPageSelectableRegionInjector && + other.onKey == onKey && other.forceReload == forceReload; } @@ -627,6 +638,7 @@ class PdfViewerParams { onTextSelectionChange.hashCode ^ selectableRegionInjector.hashCode ^ perPageSelectableRegionInjector.hashCode ^ + onKey.hashCode ^ forceReload.hashCode; } } @@ -749,6 +761,16 @@ typedef PdfViewerPagePaintCallback = void Function(ui.Canvas canvas, Rect pageRe /// [selections] contains the selected text ranges on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(List selections); +/// Function to handle key events. +/// +/// The function should return true if it processes the key event; otherwise, it returns false. +/// +/// [node] is the focus node of the viewer. +/// [key] is the key event. +/// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated +/// by key repeat feature. +typedef PdfViewerOnKeyCallback = bool Function(FocusNode node, LogicalKeyboardKey key, bool isRealKeyPress); + /// When [PdfViewerController.goToPage] is called, the page is aligned to the specified anchor. /// /// If the viewer area is smaller than the page, only some part of the page is shown in the viewer. From a1aa88de073dd77b0277587c498f63e041f8f4d2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 10 Apr 2025 20:44:22 +0900 Subject: [PATCH 034/663] WIP: PdfViewerKeyHandlerParams --- lib/src/widgets/pdf_viewer.dart | 45 ++++++++-------- lib/src/widgets/pdf_viewer_params.dart | 73 ++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index dbedcc40..3255ed2a 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -413,8 +413,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return Container( color: widget.params.backgroundColor, - child: _FocusWithKeyRepeat( + child: _PdfViewerKeyHandler( onKeyRepeat: _onKey, + params: widget.params.keyHandlerParams, child: StreamBuilder( stream: _updateStream, builder: (context, snapshot) { @@ -496,8 +497,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Last page number that is explicitly requested to go to. int? _gotoTargetPageNumber; - void _onKey(FocusNode node, LogicalKeyboardKey key, bool isRealKeyPress) { - if (widget.params.onKey?.call(node, key, isRealKeyPress) == true) return; + void _onKey(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress) { + if (widget.params.onKey?.call(params, key, isRealKeyPress) == true) return; switch (key) { case LogicalKeyboardKey.pageUp: @@ -2097,28 +2098,25 @@ class _CanvasLinkPainter { } } -class _FocusWithKeyRepeat extends StatefulWidget { - const _FocusWithKeyRepeat({ +class _PdfViewerKeyHandler extends StatefulWidget { + const _PdfViewerKeyHandler({ required this.child, required this.onKeyRepeat, + required this.params, + this.onFocusChange, super.key, - this.focusNode, - this.parentNode, - this.initialDelay = const Duration(milliseconds: 500), - this.repeatInterval = const Duration(milliseconds: 100), }); + + final void Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; + final void Function(PdfViewerKeyHandlerParams, bool)? onFocusChange; + final PdfViewerKeyHandlerParams params; final Widget child; - final Function(FocusNode, LogicalKeyboardKey, bool) onKeyRepeat; - final Duration initialDelay; - final Duration repeatInterval; - final FocusNode? focusNode; - final FocusNode? parentNode; @override - _FocusWithKeyRepeatState createState() => _FocusWithKeyRepeatState(); + _PdfViewerKeyHandlerState createState() => _PdfViewerKeyHandlerState(); } -class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { +class _PdfViewerKeyHandlerState extends State<_PdfViewerKeyHandler> { Timer? _timer; LogicalKeyboardKey? _currentKey; @@ -2126,10 +2124,10 @@ class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { _currentKey = key; // Initial delay before starting to repeat - _timer = Timer(widget.initialDelay, () { + _timer = Timer(widget.params.initialDelay, () { // Start repeating at the specified interval - _timer = Timer.periodic(widget.repeatInterval, (_) { - widget.onKeyRepeat(node, _currentKey!, false); + _timer = Timer.periodic(widget.params.repeatInterval, (_) { + widget.onKeyRepeat(widget.params, _currentKey!, false); }); }); } @@ -2149,14 +2147,17 @@ class _FocusWithKeyRepeatState extends State<_FocusWithKeyRepeat> { @override Widget build(BuildContext context) { return Focus( - focusNode: widget.focusNode, - parentNode: widget.parentNode, + focusNode: widget.params.focusNode, + parentNode: widget.params.parentNode, + autofocus: widget.params.autofocus, + canRequestFocus: widget.params.canRequestFocus, + onFocusChange: widget.onFocusChange != null ? (value) => widget.onFocusChange!(widget.params, value) : null, onKeyEvent: (node, event) { if (event is KeyDownEvent) { // Key pressed down if (_currentKey == null) { _startRepeating(node, event.logicalKey); - widget.onKeyRepeat(node, event.logicalKey, true); // Immediate first response + widget.onKeyRepeat(widget.params, event.logicalKey, true); // Immediate first response } return KeyEventResult.handled; } else if (event is KeyUpEvent) { diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index 51146c42..d8025e26 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -60,6 +60,7 @@ class PdfViewerParams { this.selectableRegionInjector, this.perPageSelectableRegionInjector, this.onKey, + this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.forceReload = false, }); @@ -500,6 +501,9 @@ class PdfViewerParams { /// default behavior by returning true from this function. final PdfViewerOnKeyCallback? onKey; + /// Parameters to customize key handling. + final PdfViewerKeyHandlerParams keyHandlerParams; + /// Force reload the viewer. /// /// Normally whether to reload the viewer is determined by the changes of the parameters but @@ -589,6 +593,7 @@ class PdfViewerParams { other.selectableRegionInjector == selectableRegionInjector && other.perPageSelectableRegionInjector == perPageSelectableRegionInjector && other.onKey == onKey && + other.keyHandlerParams == keyHandlerParams && other.forceReload == forceReload; } @@ -639,6 +644,7 @@ class PdfViewerParams { selectableRegionInjector.hashCode ^ perPageSelectableRegionInjector.hashCode ^ onKey.hashCode ^ + keyHandlerParams.hashCode ^ forceReload.hashCode; } } @@ -761,16 +767,6 @@ typedef PdfViewerPagePaintCallback = void Function(ui.Canvas canvas, Rect pageRe /// [selections] contains the selected text ranges on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(List selections); -/// Function to handle key events. -/// -/// The function should return true if it processes the key event; otherwise, it returns false. -/// -/// [node] is the focus node of the viewer. -/// [key] is the key event. -/// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated -/// by key repeat feature. -typedef PdfViewerOnKeyCallback = bool Function(FocusNode node, LogicalKeyboardKey key, bool isRealKeyPress); - /// When [PdfViewerController.goToPage] is called, the page is aligned to the specified anchor. /// /// If the viewer area is smaller than the page, only some part of the page is shown in the viewer. @@ -852,3 +848,60 @@ class PdfLinkHandlerParams { /// Custom painter for the page links. typedef PdfLinkCustomPagePainter = void Function(ui.Canvas canvas, Rect pageRect, PdfPage page, List links); + +/// Function to handle key events. +/// +/// The function should return true if it processes the key event; otherwise, it returns false. +/// +/// [params] is the key handler parameters. +/// [key] is the key event. +/// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated +/// by key repeat feature. +typedef PdfViewerOnKeyCallback = + bool Function(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress); + +/// Parameters for the built-in key handler. +/// +/// [initialDelay] is the initial delay before the key repeat starts. +/// [repeatInterval] is the interval between key repeats. +/// +/// For [autofocus], [canRequestFocus], [focusNode], and [parentNode], +/// please refer to the documentation of [Focus] widget. +class PdfViewerKeyHandlerParams { + const PdfViewerKeyHandlerParams({ + this.initialDelay = const Duration(milliseconds: 500), + this.repeatInterval = const Duration(milliseconds: 100), + this.autofocus = false, + this.canRequestFocus = true, + this.focusNode, + this.parentNode, + }); + + final Duration initialDelay; + final Duration repeatInterval; + final bool autofocus; + final bool canRequestFocus; + final FocusNode? focusNode; + final FocusNode? parentNode; + + @override + operator ==(covariant PdfViewerKeyHandlerParams other) { + if (identical(this, other)) return true; + + return other.initialDelay == initialDelay && + other.repeatInterval == repeatInterval && + other.autofocus == autofocus && + other.canRequestFocus == canRequestFocus && + other.focusNode == focusNode && + other.parentNode == parentNode; + } + + @override + int get hashCode => + initialDelay.hashCode ^ + repeatInterval.hashCode ^ + autofocus.hashCode ^ + canRequestFocus.hashCode ^ + focusNode.hashCode ^ + parentNode.hashCode; +} From e1f58d06033f2daa57b04ce4bfcf271df2ddcf03 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Apr 2025 00:29:22 +0900 Subject: [PATCH 035/663] 1.1.22 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 7 ++++++- screenshot.jpg | Bin 0 -> 398847 bytes 5 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 screenshot.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f30be3..bef7fbf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.1.22 + +- PdfDocumentFactory refactoring to improve the code integrity + - Introduces getDocumentFactory/getPdfjsDocumentFactory/getPdffiumDocumentFactory to get the direct/internal document factory +- Introduces PdfViewerParams.onKey/PdfViewerKeyHandlerParams to handle key events on PdfViewer + # 1.1.21 - FIXED: loadOutline is not implemented on Pdfium WASM diff --git a/README.md b/README.md index 10e2528c..32525639 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.21 + pdfrx: ^1.1.22 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index b7f021eb..963ac471 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.21" + version: "1.1.22" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index eafc6a21..6f098392 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,12 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.21 +version: 1.1.22 homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx +issue_tracker: https://github.com/espresso3389/pdfrx/issues +screenshots: + - description: 'Viewer example on Web' + path: screenshot.jpg environment: sdk: '>=3.7.0 <4.0.0' diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..408faeab13a07418b0b3749728c906d9f5c8fc6c GIT binary patch literal 398847 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 z zPDxYT$S2M}eN$4_T~SoY8{hz1@?OBb)`UV}EWP>*DO9y!A0?1(z=h^em-fz)o9oon zZ#X?t6dfkUq~EfJCH%l>kgWm#{vPgD!wkdq0o|5?EC8_T87A}VYQm9rkM_bsvB zb~b=0-`MxsvEJ*Xoc9L+Zm@-;%M7%0XYnuYibuF5$2@9#>;SG-MB}gCn=w)bE>J79+3(0{{9iLQF z+MB2F!u*Pv=})TqqnRhV5uuKsCR)OOQ@NeiCU8|nU1MaWXPdaPsqf+GUf~yo5?pz8 zWPGe3sn4v-(Hm4Gw&RLojeWR$Ck(P{qiesoab@!yjcSIhy~OFZ3F$hr(Y_d=7J&3X z%Yt&_J-{!op;m29kOL+QR*j}qCGOl&`zYY2`Vu3+G%>LRqY1&Te;9A}AlY|RnG6&X zoXpfey;M+5KiL5{82mHw>nEBCyrV#hayhDLzCM<5j1oL<*0ja$SW~asS@_bvb>wdQ zdRYRZQoa@vK07}vHrmz5_M-6n#kxJKVDfYqANOgQVhGcYVEY=7rs_)NrP)c%06Ony`5?3+=c@y*vnnLgriDts{vy} z7gb~xn)d8W?a^O8?I{!kIC+(XjIqPVGK8=lR~fR7Xz#GF^C$AQ%}-Np&4(45BVYNM zNHYsv%!@7zFCxH#YoT8y+pSFp)NYC{$ErD=}C zwRgjmf^XG-NFVX9GCU=hD1AD#-%a#jyr`?~&(*#J?AnwsUG@qD?u-0mvfK_Yk7s0R z+{Jd>rQHbeCu_}X#ZHYcULU=#)#j~D(5Y}Nyg8N6oO#Sd0``ycQje#-2_VA8L5682 z)`X-e0Q<9}o;k26j1tOe+`fsx?!b?Ff66U|Z}rz1_H))w;mr3UWJln{!q&6Hpbx9v zx93PMtj&F+z@pN}-C-#Jfxz5;MIv?4&Xl%ALJFZ)xRdZ$%(w$8j8eAzs;soo{2{$F zRR62U^P{#gW)T7(&aZoI!J_^`NR~t3JD)zxDz16>mF5GzCS2N-eH2o5#^`mM<(=f)$+Cu=j}Eza6AloU!4iIiX8nd{XMemz z-i=LAKV%yCr;GkhzEAae`@ns2pVST$84O|K?5w-CC<^jj3bQ9THtvvFdnyWbRUrab z%?j$^Q{NBopJ=lxCEpoZqz`SJ0((%2;e4p*F`p_9Din z;`u=8b!O(&hmeCV(5(T!osNrtAqUG~mR`s^ZcO8A>uB_igS%{TBWd~LtnNLYsSf4^Fy&>G44WpN>+iU5q~14;zbW4yz0Q!3Mms7VAy;| zNoww}qrF38WnDuMJXE*(82hS)wK#YjrGFu5^7Fp**ky2>csXrQJ!o8G`udn&cB8M( zTH2d9H?Ma#?QIXO6I2`68!onoEkx+s(6g~VSU&Vo%CUJQ#|pMOJ~7BlN5^8Y#LiG9 zWp{eXBTo7}_cderBx_-+2jlCUIxyXm@@NFwZBnYk8s6G;U!@@9>b!eN-p7WSIL@7y z1R>UX82jFM;}4Mw8Jz=<(3kb9q+IIB6HTRR`4pFnIb4qCxa9KF6?W=I$}{#2LvZI3 zV?n;66y*H=`g9n30{T{1u+Zj#f~r5;X#3+!DWT`xRv-Y%>5BQHT~S_6Vt5l`g zv{3RRtwPl3XI*lF4D)wB(Kg;+HUyF_PEs?KB;4gB7&$bhv2okB;KnCo&U9n?&VU65 z_**YWVT945^%Q$D+&Z;=?hNT=di!`|Rm~lD+s_Ff{fvy(T~>?&45S*s12ANO2|%10 z`PJAh(fTQRR*0om-V2lc-mNYTB9?_AiE}|mPrlh_OSoB42m#*>ssI6P$>6-SzvOnh@u*n)KKJfpL zr){kS`Vi`)7#5lz9k&rQeG0EHxMCNS8=V|mfIa=dhe=XP?MZh?KGc^2AVI7gZMCG@ zxOh_H-aWD|L$C^Qy64piod?tp^!9cZ8hUl}2Lh%qsla zOGhltT&@0?7I&XN@+r|-s`lX4JIpMsdOu2+SE|%^aZqw+g~;Q3`|Y7oxP;7QwhJ54 z3A80(*Cc+OUff13#t4#yAFG!p3(dOm|2lBo7EBj+clMC(*PD(%sD(T%q6xl0e$&0M zh=|6qt#3LhC@cz$MxfHIpIi|2b^H(j8uIuNuwkJ)K(>WxxcStp>Emy+3+;@(`J{XO z&bzFd4#}J|`U7&|PK^)_=bp=Ig7R_4Apld_+MqDnq<$vjEV z1g}d%b|(kyje?i@45f2xJg_NQ;p(W5)xyw~#_Lj3BP?f6kg$(aME*iroPL4#1wni!C1M2WGbS6qDX_P+D z%^VHMJ4WiR*B7CPzo{FLX{f)kH<%w0`G~q1s)P8*rajC)#}F0fnjchjj5-rr=H%|v zw8zb8DAxumIiHm&3KNgyfu0R4n!tJAp?NBHdFtn5gjKp>oMI~V4>FvxcC!|U^T6y{ zQhDttAqG#nwIMPQu`tRC{&U-uPG8OmFbrgM8P-sSA1qXl^> zYF{1>UM?#KW%@M|)7dIbW{(Rp*_Axo-6^U6ar@}UYK2en3Qvw`765I!W8kG|qlM}a zhbcu1b0t32USd7#nVL8H8{Fq_WZNRB*ZdGv!6A^N+-sHf%*rY1!pCWQIyiKp2TL4m zr+&12+S*<)V84+|k$Pi2+4jCDvTfdpF+kwisZ0HoW3H;~D$+qvReSjtLI~QnrU_=X z=j_MohLGA8gHv6O@8aT?Q^YqCOk&=2+GMu~U!M{gHc=CBuB7)}4+ZNz7BdACX2>!oEGeo=`k*N8oXU)SIC^|1v+A?0xbLTK zvm`;b*8O2D@ePbA2oayjIN3<u9X)-q0e*fsx~yc^`hI9CA565>8&smaZ|Dj%H63`MT@vj9G?yCDA_t% zxrU44lD-{gdv`7_(Dit8?UXQg=&lA8CWWpYa^Rlmz*hS9L6h5RjeCcVqPAB(nsrD{ zLV@o`mchc#>>a!LK+S(YJ6?35buXW-p;L87@Q**JUOzseL<7fRYWWOpALH83J!?em z!qHi*S1*XNJe*jPUk@s(@4!<|rS>Yb&Owg_6<7G&@A8wgdgj9qBeD7R53|=`I~;V8 zq4TMeGTUFi7$s6&Zq`+1KV4L%jqp4W#K*QiNYq zWc%ih)sx~Ab(MHW@6~;~WVqBPdQ8+*(xCI%x$^={Ko*JhImNZm3&y+y6MHUe?q%{# zwnmuAz%A<5{a1SJuGisW{q!cZeb_vKa>A|4OWagSV_Y%N{`|+6_f$RQ6ev&a$U~G< zgpHznboj7hGnDjeX}tODo4Mngdms&s4&pU1%zfbPaac zeM*sS*>RMnQ|OtgqCEF(q0YQt@1qbiL4b(}uU6gwHFgw?MOR^761wL{_9=05^AxRB zFnP;6*MuHa>1IFnxc>(^VFAp+;Zr)jxZ3)zL9L6($;Do6w(=rm`Ok{ty@1caE<+n~ zjGbM!jg)Wz-Q%FQ=`wkd0*p!Q-QK})4peoQ>OTn6H^Bn4M^ENC?xV{JexCLp$^UnV z738FpFT%NK`t%pR&SXRa-lubxV0iD0^c0Hsr2mk-A{`>>&isKgK?Ow4y*7HRE>B_l zfpyCL_4~Ub#IbL$mHnQqqkgiunjH$2jZPqKabRX~u)h%3a;Q0}tp^vi|1c$^pglA3 z-J}t@^0C0_3>mh>&B!5^ef%4&cUuBX9K(+JVkexaD<0q$g!fqDT`2JJ=eBcGMxp`fs(B|56|GlO zub~go3(@{T6)1a?>aUpNpM!%xR@_8f<;K^&N)Uk2l|%bXDjaFPwRzqU|V zlL1SreWSMh?shG!ieB!gaIZL0*_<#1B(aVW*Di35sy{9!luvb6}A(*#S zao6A6<&EMAjqid_;^&7ta7WjsKw7G+1xTR>1Hml~k19R1R3Dtz<>@;oxZHXg1f@6{ z2N+$uk=6Be-;eb@ajhTS(9#$iN_y1kBE6jA#~O;H@w@ckgj>%{Qx+HCsk<#!zFeLY ziU6;J`OzmPeD7Woj0kxLeFf|e!R1^i_quH9?=X5!j?;ZQ)6JFg&-00Ti~PWylvBZ; zaoFuIL|&PC5j!yeYLW)`{3D}#2O}?#BzeFCEuY!r341qXpV(wIt$2JPGUV0zrkB>tQ(YjhItz1==ieA*ECp z97c;a33EFByx%wW?o>7%vW{EBJ@->ZdeFS+FZE%V;Y=Z`xpTueZiWfDcm+w|SxC!U zr~6|hiOSEAyfLlqFn1jQ9 z&(ATN4GCes!0OcJpxHjM4HL05e0C42Cb_cGuRmw-L$K`=p;VKV1HqwQJU;<_ot_i4 z#kcu^`O*YhRQ;#MIl)84O{yy5+h?1^+mHEOJTx~5Lii3vwj5yh+!o5Md!%3po*7iz z%`i$cTn5{h`<5d`{>JDQ{G-vujiK`p<|RA_O^ckB0@%3a=kWmg0zKjZ=O$da-1zlO z&5M9N>F4+A??9gPS2Xkfu7o6IAYZ|%H=~7PTiGeX#?kh*-Hgui&8zog0AJ7fLz=mp6# z-Y#wr0oCHm$pWYU)$o!K0UlYR#}4j@2aI$xmCuwcHjT*pWIOEqqU*sL(^Z+z{jPbjo*_LH9;#P(QN*;MpL*hHvdJPyB4?dK$F}utlM>~ zv9*XYMAZiIE?*t=;UrKGm2ArfH0w=eyZqT6#-?$2gST$V&MX^grGJFRLXvMc7YaYy z&VWT5#mz_0n70(>1&j9namaL5^E(@v5pmX_42>is_xxMv&zpr8j}K$rMQNg>kH3dL zWONR)Ja9Uzjoa}G<=UK~aEM-f`C$6J={}YsuNA3GJ4`)Axvin#;t}-jlYZ&X`nm0& z*jW&%A8HsK@H*xdP+{of6Z$8S=nLOy@<*x{5(h@+m&fGOZ7#igS8`Nno5Im@a|iEw z*=Fo?#Jd6rQv@rsi5-CacK&~W@BW`4zyE*1d!0f-Up|}?O8z*afR#WYu94Z(IpVyY z8@~{yR-A|BK9mGfjT2E=XIMll)40qx^`ZF2z^wQ0QEysi{eQk+xp`h%?BK|`5n!Kw z>#}9-EhC6j#0?-FDFg6~cT=Me+EQi6xkFuaL7KT_=WOx?@6Lwj5|59ZQ{-UR|2B%M zCT2x6hb#;Nb*f{e%x7h&_$!lpl2W33#u%Y-D<1(aj z7gM>vqvGaduAW$xcH#>w2)ePOG5gg(N{iI=Pf3pSK+;fGnV73RxGQ2ex9V}@OS;MP zqhB-c=f2@n-Ev&+8j5A`QOsZKrxFQJ4~E!C-VGf)kKsoJYDbywANj%b#~PS{h7R=% zO)kz)^w|SrvA$S!diX1mxUfg@x&QT80+5_-F3QnZ)?L?cXa%CT_RuZFZpi4V9QVJF zS+@aLY?z|}6YFkrU5Tq+?hoFXsV{NoZ$G;vTOuM21ecs{gMsOu6$L@U_wu0%Ix%b` zfs#RA7paafbwqEqlAPLMp1YnWnbRm)q^yp=5Rn?TuV}aVH83=LvuWdd2U^+IT=Wj- z?*Qnd-zSJ$9gcUr?K?4t-&cL7KOyc?t%l2c>4+c->;V#g7u$-~)31WFNC}z%`aBDG$y9{b^VfFNt zlg!HqK2Rj-wL(y(PT@`aK^a*qt#kv{@#Y(m{%0)Uq_rv z6`=8vgp+Bv3n`^Yu*6F#8D?OKUk6J(EWEto-x7Z;JJ$qM{3`O(4;PreO1skh;H0bb z!zifbbxQ-7Hl>ttc#Fi|EA~03aI33DTf9UZjdPhjq!T3f?#U6aafTR8hUC_Cv5 z^=hRq;*sM00^XjZav8P8SJM%T_P}gF{2zQSlA&U6GfkQ9oN`N$u6**zTdK(C_|XOt z#fB9V!SShSLsxIXX5KQ3%aO9lY=??PAQ(X<0j33{0QSySG)$z{?Hd|FG}m0a^<>Fx zg?}Iiv7fDq>#fJ|GCqSeh&3ytL;uKVpnU_|M01ML==&WtiJV=||1dk5xBD2Jk_u=S zdJ}`2ruHbid_!zGw1$;%o{`<|mHqN{n`&KQ!Olf0B8Ef)%MqTp z6xl9KDOerGRkISZjkCs-Vcym-j?vUe^*yOa%=0O27Xer$e%=x-@O>blA+S~vwzH2BL^T8;acs`_yC$|`~ow@_^STdc=uG> zqmh0e%%geMo5K}`-SeTztumO|aX1v~`5424HY5$^Wz6gBSh7Fzbv<#|1yxn}ElxA* zBmZzn3C(eS==&P6Oc;4oeiVHuE4Y2;(jI%%f^|mm_G>G+svbGTYo_%aV-R)(>)vu& z941M5z=P}`N((%kupqHJDKj6x?8F3SpdH8IZgd4Jvw@mi`|By1Gnw^vPvewdLALdh zAt;di3U}J!dts>r{x)|dtB&=Wjf)=T>g{V0S5riz!emY>OMTct76R`qmjxcR z^KI`NE=i0}a7djO*kzI8xdpIM>R1+cp@%0T-%fjondfk9tZ&F3mhjYpDA+~l`(`l? zPg3;;D{^nOivP;<2w$4<5&H8NQV}|x>bXxif^st@VN!kH0FUm9jJNj1Cgq9?yuH!i zIYA@tDR_NYSz) z`J@$7tj)gPuSraF4GVjaA3GHv8DT>6YJDA{@arJFF5|YhtJ@)aJA0$|UEF4|$pW{5 z)F_12xO8PtUwZUR;KsEt;#97us)OEsk&9^viwneWAHe|a50;pVtDc$q_04|1HG3&* z&bxQs7k99n`(Y0s4)pB8K9S*ZfkUE{FpJ^c(sQ*u3Ko&&o8I>v${uyGh(WONK~&(2 zqa9rR*5UX#4(pSp`P=9LUcA^yXqwx0HG z^|rv~ab4d0^RTF$El$dVQ^c=8n+7U%t#K*xOjh`CW{(6bOVIfs_hZu=5hJQJ}Br zQMNkO7K=V!=q{F57J{6$u~&THklR!BBk0d#_zlBgaE~z3BExh!7Qh$;!Qyyr)p&Xt zBaR)=A=T5P%CS3FCJgPm=8YW@$p-*TWo7W+38ebpT#M-*;hzVar9HiCx5P`-$3+Pw z{3UY#tNZnT&(E#S6fksPWu*j=LKfNTL5|q{-cu+m=UQISVSLo7*sPCyr^4?iLt>9#EOkSewjum_s0?ahRjZH^dk5g`edvKt5Ecyz8rg@K@ zR5fY!V58IiQ(3->syuT5gfn{8NZ}VW6)UBC7I3W98puVD?Y137oz5|PJbC;>NuI-? zzkh!af?g!MF*tEVL_CmRTqa3H&L<;#50VQfzHe@-FW*c!f63dqf#t)E4qfhOq^JE= zAEkw-uaX+$PCl$KzCtsiN5DOYVhM+Iv1DN+`Da{Vco^P6dbFr^bo}SntLbMqKk>Xt z&pkWRb{m|5_eiOosqs~)(H^^+hx$=zimz=M)J)Nvw^l!gvqFM*te3wLXhK@;y?eGb zg!sJEV`x48XW_RkEd`GJ^fNf{!B^-X!hFqRqrahnR{mjVl!j&4Ar-S1wppJ()MA?M zUYqNC5)Q~v07OIqNVB^XoxK1`G)QkbQF&9O!G%lD@yED+OjnR%-^h{J0(JzK;H+3; zkrNwbjXVO4my#ll*ATqYZ`OXBzMW!_VACXj%R$)z5TO99d;wR3+Xy4A?b(e;H;_}_ zY!uHP@>YK#$me*Co$TE|)xeNFm>6t?(uDZi&w7qmQZm}}yvpCV(aOBAG{8sjYsB?4+IIalJJF;QDx0rr^W58-nV2jZI^}0Hv+s=MP*52{r_mw4RE!UQMsJ_+g^nT!UN&u1{ zMP+c#lcq0_HO5oHtKk7L6)MKeZx#kCd(=*>M5&FrmTSF(XKHxZH2vMGavTDul*=9xw#p)|w zL;OXlbKNuq@eCGhoBBMT`+---1cQs0w5#zn6@@}^gG~9^26S)0tRlv zG6qI77}h_9x?~PcPmPncj{Fvn%zOh56H|yO-BKKS92E%>GL219y`Zz0T~J^D*la+t zOpw?3x+xL*5HE!H2zqm-$U`?I7y-pe46C@NsXn=x!Xev&0r%M92>0-XDn}CLozlwZ zrYNlh%lZ#(wUM^G8#yQL3$v&FKGqYqwSJpwL16}M^iCy(4t>!vx5f$J4En;q_|owP z!=y4Z@!P4$7YUFJtSf#R+J&vX+j=S$oSuy5Xt=r`VsE6aS*htik2Dfhf zN!$g>03JoSshKudV&CWGcIZ!*)-~?#N29un4d)KEmQlbcpVfz=v8c=kGP?5C>Ul)% zITPg|1wZHW=U6AN-3$X%*6F7=t?Di2rVaglPa{~0rOvxJe_S=Xz!Jn_{TW`93Z_i+ zsp~%%nCDC5n#qVnHs)VqYVq!-D^+kq7y9tw0}J-CK}5pL2GUMWzbYbxhcWkghKd>P07iY)?WDEI@2h6!)LNz^@8 z(z+c!MFU%<(IGS5IjK8cny$TmHHE#TJs)Z<56J=PCQ)l6V;-Lh9K*bb<}i~n#ULox z+49f(hUkXik9TzZhb(ip;egQ8VUOXV6d~(JcEf<4F-N;l! zr&h`1jZJa-lJmLQdRfoq>$-qxz{y`9lIE>-?=nR5q5iL1S-|8^h=2aaRBdfrf-e687BDXAzT-D# z_+GIWxN!QfB~46B)%Ol@ey2&bQnFn!jf7b5!2Pkat-<8K5SpObyjz^E#{A%=t^=Rig^e2ykrmEg%a8HSTsnHs;nRLy`pqZj7xPXs zZ*VI7Kt{BYZXjaeVnK=JpbxT<$2Q@;knZADaoh5lKy{@2egq3Ka-{h;5`^YXn*QkS zr#B1+|pSs=F;#wz%S z{s!A|N0;s3S>*(pX(5;S;s@u-)oq#n7^!$4Hsj$OYJ59Je^~zn1%V(d$7z^oB=a%g zYFtghAK!06{fsX{Wg@gW6_&z=C?`WYW|eP|8MxwAySQw4)XT17@g{>(^{9(c=Ty4; zdm`K07jww7Fa{SpY0vxNoMb?UzC39!I^aZqQ0b#+qxKzdpp8fb|KVzhS-A(v^Zl=M zr|Gu^>fVZ0$)_JX3YW5cF%gy6sQ^|%1M|vI^yv?jRLyr;`bXpXJ=ra4rS8f z#Bq7(9~*J?sA;)ArPJgg8l0Itrk1YbXLuj@@}jfA9H--oz9{-I*`ZaSF4}^OPk7~> z@oVaIoQ-|s+26mzAYT0cNRohO)14|fP4Plje#5}lw9Zs^H;zE%8dl1sdq=LRrUA?u zRX^cJt31t$)Yd^@L89t=a>}%w;@w~6&3`e=*uMp#ehyei%2wH9NwT_$<^KtRzb6pGO z%C}54!p=P_NPeY~X<;Lw^5-cayjv=rnQyC&TbjC=H+PrMSke1gkgRm{4DSu=FSyF( z4Pz2@F*Qn29d>v%z;RVyVnlJ&(v+yG==SMwhn|sT+v(rP1On+6A})`O96J%GtNeNm z$*bXI?Y<jGePH-GdjI`CD$uo>EINlSn z<2G+d8*2rx`kkHcZ~)v+Dpd(H+6djSCMpaGqOj)8qlz1Nr=Hg>vZ1cG?ilHyJ6kea zLF|oE0}%0=HNu_>Xs5k?jlWGVgcI}s2{Ydt^g!!i_Azo~F9^0WFc2yix+3S3fVe#X zAR}*!@v$^vaAJ8-!ip&gqgTlFF=%7by5oBrpIw^}!vX5xb_v%e6TUvM+-k^S)H?{M zc`Hnd7mvH2k@g#qQR#P;EtZK;`ifM*fUzmb5)luto=CvX^V@^OM-_Zp&;%R;XNMpX6%n6M+Xs()=^l1;b1>b82LDDK1hmPyF$@V#BTz!RgOBpZW#370Y^7e}Tx=sIweo+}y+q3BOJB{=iz}3SIC5HVw(82w zP=$8IBMMt>yfiN0O*pIO4Ywv)#fG_^eum2HRN;VMFc8FW(y%1?4fgqh3Y550v`y_r zAyoW;MiXp44SE`j07Nx4&|@BJyMvo%Ubq)cnqzpGD=|e%TPyNus$LN|cq;F%2YO#q zTk)I&q~D#ZtG9Zed2;6E|50!hbfKukkuWjFmu@rth4g#fY?YyzkS02j9CP5zXe_hS zjCZU5()di2WE)k?8Bxt=0y%l2E}_cgSH_Zeb>Zt*l`ZkttLmHTv1)UuaT@}QkCiAp zds@eq!+0#&x-;(td9IB(t89}1{IFR3%}OQ>K`vf!nC*sg{cU;btH+Y#bBx{K6@>c(eNLyS_ELs-kzrpIxco zHU0DGi;5bm`~dQQ7uuD&w-z0RJ5Ck0)}reaN(fzrQ$opv!G@=464;aHzCh)f#}XjZ z|C5acdHr6OYjKck0U4JMU|d^mHGz+R=7?eTC-amN;7vImUce_;xwOt3nfxzNGGoe5i z3EY#(6FGUw0W4hfU)*l)v}+2#QyGUnh|nm4pswr!oE1gwkb-^F#lG=`o=&#=X18Sj z0CHOEL9p6;**Zf=2vBk8+hC?DGKSljYw6k(J&jlAl&b5!%7vE1PhG$5S8zJAxw6Z} zgJm&iPnmLJzuN<&#^}?&ijhA2Zq_~ByimunIO}0IQ&R5a0LMo-Ct?a1>#Vl`wGEXq zw9A5W%$xvjfJsSxtS0$H%hNpBtBkvgkxr3R2~>2alq!Wu_z{hlMAjEu(SDO*F7z~^ zbm@s^cB=&wPd(&-6GB;I9Avv)H;-h$(Fzf4q-d4VPrg< z03aG8MN=m`RRtpAb1qKLdRFmF3lH9Ld}LuNqrrJ@(Z2!+5B>yl=Ba(q1A0BsUS6(% zGdQ{CbA{-GB@r%&qq*Zgr^rt%(d$(@k2&?joIRIiAHOcla_xwsqXquJeG9*5%9EFQ=Oyfkjt=cIpg0P zk0rQWoIdm(7Hr7k-;TB>G+M9wQ_rvEnAepQ(Nsv`OSu6@bHt197r4)I^gDMYSr6g3 zb;GIJ4w&yMz;Pmobi<@^Jn-?=(gphIlQEiAMh(FtqMf)bKi!NTNGOaZ_!n|f>Lle4 z32A)iRuIS3SlSr-2SWyc`4YEc7)MD`FA59`P$A~;!)1o;?S^=&6w}@gbIh=X_G$bf zfMejwUr0t7Jr9ttmXzs=DQ$?Tt!UqOi$I&X2Vi#rJ;If&u(wC<>xQOiZ?v>V7C4v- zCAwny2f1nG`_j6O z@zUN%U66W4-dgV>ZOsowxA`C+W4T*ZDQ+fX6PArHs8Ws(j(&ZtJ1Q4u=BL{~oq8RD zAI6Gc2HL8T5zS|dDbY?8S0Y`@badWh^Nd@i;Vp}^gtmO3|(4w(LNp%)7wUH0MT zwT|p2O;k*p4u3S4j;tX26LC*XJyX0aJsxv-1t$#m^?f`sCsUJpeH4AZ*lCq_@|u?6 z@s7s3$=L{YADg88o^Vjof)s=Dj8wGnZ6YZM8wY21riA!9TYdDl5=c^7k$Z1&^GF06 z*h45c8ox3Fp|BSNrWC*bLVmdnvwz3dWp(0tP=q~aN~Bx(8{5jN5&4$dH0N9H#~*VQ zoMKL~-o`^g3gb+>-U55iC=4&TGI4m`cB>$!Vc=+=$aCdkADPyCdQsbtS^eXGA>Uj6 zbBwoYgFm_9&fbHqM5Y8<_ytKCF@|CL8wJQU0|_@8U(x(*R&SdG`U>USq7G_zHhfps z*W>u=W0o_-0ojv!BunG`!SGV*4a`R=L3)wCs1wTbV0^-CY`epvhZPR{*t! zqWTWiz!(TBi+cYn$9pW+aHc0>IwHiGJKgxI1$!^N{#G8ID$VF@6`+{8E`+t*GrvwR zyF2D8<$$~EI8t*-Quw&_ubv=66=Fk>dXg~&XVZn}_bf_EYE0Coc-w87iQ3s#*j~s8 z2n}aCZtyG8$&DJe>ZD9BXuI7O4-=}z^Yp!*o%ZAj3MiWP2}&8i|Jo!ZAi5R`pA+1{ zfdwZI^p^HX)?HGkM=_!lqrC;MbLn)}boD1Q?;>SBtR5jE^e%wrsSBX&_)yNmd!S&` z3t>1Y=>yGPMV?&rLZKJFa);a}xT{o7=jVw+i#_QPq`G?8W37dYI>{ zxNYtxu<8)BWQ-fOLmd0Vl`{Dkf&`q+1Bo4`rJVXs9_2+}nqX76zSM+_u(n(X&ZoK7 zW&2B-eL>9a86mDTWC`vlMgVoAhrm;d<080ghy4=PRI{hQw9mgO&cW(;PBscuB167l zac@8W3lYao)b=qljFE2-`e;$U*tNvJkQE^R1QsU98BezQvZKvNNu#=TVw$BAb?^FQ z;40ehrMi2e3tl@>ZL0r%3tUKu0>JdhJ;H&&?t#AnEDuStg&kgbk7gO!{k~IrdHgM? zzB4RrZeCrH$>2nmNF32C{gpbI)eaP8w$^{m^B~N?NlG``?-F{uv^vFVdg3$ZwIycx zyih!@jac;&vEEal!V*j77S+c!X0~R~Uux)>Blhe`vQrK{eg$1Z`OjZ!azq?DqSpb7 zg<#yV9ST@CEDy~V=rQ00DFujV>sr!SzU|~pF)Fz`U0=>sSMt@7volm{0AVCuM@F~u zk)i8Zi;`CyIXflIE=4Ne_aWAs{D|#N;9z0uxPC#9h$R7*KZ61aliOUr%NpSTv&9PH zNyd2!0O>jp(Tkrl{lrzldRxpcfi3NQY0>PD-72-K(eU8n_RGv|u1Asj-z_QD{RGmr zwnQv5nk#f+=n+9g_r@K<(Ot8BA>i*3ynT(-jd6Q&v`kA!Rdg*DUx0N%{7VbK1sy%} zM61Q|8ak3z9KzHQ;qU_TIXq~vN7ZzSHY%wGj8E_y<@OEiwOX=IhF0SJjj1)mhPaP6pF5oVnD49T_kHTZ zxuupq#^GqP-Cqb8z2@fEmH6%3#XZ(-yfkEqU&M+*LdH3hd?W=^7 zkhtjxFj@%vdQK4NZ%$mPrN0NOsz^$`g?!hn6y@8&rDp3f0aJ%Jq}?&NNlyOx@*sz9 zAuF9g!_%{UZv%}MViA&GS9!t1+B~u6#4=TTfw*pZwW^E^LBLdI+AI?wbO=DsokKwv zLn1U_Tx)Yu?^O_7io!>yUz>TI(6^O!R^vE_|1w08^NsPrNHIo`A~dasA&%~Q$*!_k zR9`?nJVjxbzb{XFm?1_%TqnaG?TVcjYCR2JoMUfXGhrjRm(2 zM{-{e@392Qd#99TFMYGztUn2l6kxL6b0+;P+5n-Sm5Y+Kh!DNl*qb$Pdsi$xxu3xI z^l1-d3e_2VuW!ErKZ=k5kpH%B1P6iQ<@8k%zmwQ6#$KN;*nVC0+2T*xO2I#msmD?8 zM)$y!2_S6|BzWRm^An%CC-{wBLmmVh*%tmTA8={sE;9=E+EwmpJu{*UozK10EjCLN z3(jz1M(B-g3JMwwA%Ex$vo8T40Kfeh<|t!?AfnIlDdhN<$3>s~{oT_wFY2D<_{22* zKy8`+)fQF-jlT~L2*mW_LTWcu1Xzk?#l6L@uj#yMJ=y=Xhy5d}^R;SU$)FRssk@Vo zv^(@t%vrRv(6(WLqei!iNf%RIXnpKZ@nzA21U!{_4Xk^xkT$-N!{+NByE`GQ_Z-!a(>-@z9Zs=U>vpv{7aX4_`#oMKxqW2*1rbN ztyaF3eR!=~mxy|kO-)m(K!;%AkIa7lF!5By^L^pvshw>W&1Xr-jyxbG;Y<7kJ@SjZ za&$8M$<&J=nak5r_9>=6(!;%eFEThme@H%_st3dcx?u-4&koSoF>E^)V8~st9kO^g zpw5L)!SK|j#XWXG9gjMM0xLCDb~PW`Zk;*{B6a}Tn8B&EFvL&0KOtR1@{M!LnZ8=x zlKeByD3bP}U@D)SI|94mR14C%a#4B_++B6;0pZ}5QYgL?$ApHHglPh93$VymRaD58 zv7PdVtMfZ0K_4z%tTL0%5n}xy{`A=pUVv%O7#v&o36tV|z+R3tUcXSzxo_MQn;&*> zv=DMiqwyVu4lFSp&RbqXcnlY86>&eP_LAbQ%ih%!3V12f{Z_4A%IK%+%ZCXE z``z#===%J;`H_9wZwwKN4q{||!DG2G@1usvFn78sUY;JskVi`a%P54XBl-+Q%bRGk z6GK(ZldezEufQE-Ie` zu2gTNKG=~Ko=x?npP(1(3!vQkkky2g!0<~(Rqp)84LOJ2pHvx;xWCeGK#a2-X_cnO zz<}x>{txtlws!JP36BJ&kiw(*DHKDE19MWnYQ@1+Y7oR9@k;Hwk z#f-sG2_zV_dI@_l_`z|7SbEOZM$HgK0nroFl&5nq+cy)g5w&$b`Tbb}1lJh>w^&zH zs%bi@xK2!&8fW3{;l3isYtRlk5@DUq7$fldVE7qB`ub$YjQg#lH6?jqGc_sj6l2+( zy~e&p7rP#NniQoiMh536wLJ^V*LDy4C{J>9@hsBum)dh#%k*vOxp#U44i@YmaU_$i zC_7oS^jPajFHMWp)>Gso1=?I|-F&7JC%;Pzec{qyKwuPRvGZ1hM~}ZDgp~BS$NElwG)Te^93?{e9+;~b=ST>>5}!a z=9&;sJhju;@^E5fh!PjI8QiAo0g@(uI)sw$%W<(C26m^425W<0M39WAp%V8X-JYxL3+D ztdZ_ppEuGEO!N*WzP0gSi7);Wg(GV0Ph@mtVq`v~2!<-}7HLVjxEv>4`?}V5C_|~t#~H|%GC{_Y;1_P7gjF#FT~CCtv9*<;yx?q58~a4I&6m%fb)fwf2*vliuvF}A2)7ojJJ(!cI>h=kcece=!8`o!vHm@b^#TMIm#QbtE zWqNL6wlUq&5#F$e&xaFhIkEG`sNW!4_S5p_}?FX@4 zes(jSIky_+h%Fz)f2Y{^$hozEc-fAN#T{xR;vPzsyWmk=bRGvN(?@7TUo+m$$$0||E}Qn7LC7c zPl@6|-k%qyDQ-Rb>VTT=ZcKTlZBjqs^R<(=GR^{X4<44+ZNUbj(8S&=3Ol)eUQWz7 zCdexs+iMjryGg1r6}7rf(Jp&d5?6uGU_lu6us2ncf8W` z<5>#lpK1nY+I(9yfm#31Bt@ZPPJCD$#_xF$qZa<+TBJ~5eUgydo%4{~g1p)tXy=QV z2JqsY?R4*p8b|sZO zCpC60v{sUt7u}M)7r_`H;9YHP+5S7ZZQJ;4g@~}+Ri<^;e*&+euebC#m+Wz`w2n!hb zPQz;v?NDx72(a04WWP1Ck+*UGXHF;SGE3bA$D0BL7umEZohYwGC`F!qL+~BNcTbB_ zmh*Hgm;g=pSif4HUQE`J!y2p*a>F~Q2-=;m7jTu^@8f0v z{8iIC+JT}PB(49!+j~Vd)xPb%K@e1W?*x@5Rgt2U1XP-cs7M#0g0u($X%Rv~k=_IZ z6ok+_L^{$!uOdhby#y5LiBh73ynDX??;C6FvDV4jW9+dGa^M(}%*@RB%=@|T>-t^5 zkG2A-USprZuHC~i_d*l60)w<}^k;s*wQ3b8C=z$_fXNhim?TneI}msI`@ljd(=%`} znW;l-_nelNhnMf(vpa-TRY)G*}6LaGMbLzfBjQLilHhxme;x2j(m#(S+hpYC`_%I)1>< zukIuqt@Yu|+4Cp03Bh9UP0i)`+M`x-R{3T&n8MZ!$yXm1U7qm}$UQt`asCehD1;d9 zL&?rmv?j(wnig$yDwf0kRcV$Pz`qjDQ+0`^QOjcMb_PAoe;ZV~#go>Yef^UyL_CIq zc{~|iaNp)+=u=3{f3g`!Yhsl}^I+QH;C+qpwVUK}?Ccfv}&Mpo*Eac$ZIdh!Sr z1h>7FFb@9)qxMa~i#(W)>+14r`cbbr?I~bg-Yl9jXaTgy3Vg zt^thM8_^!w;;8y24inL(QRMS*5iEtXv%|g_h*47O2CkZhUa#`6laFNm#v$R0fcX2iXZl&xGg5i)UubV8E(*i0 z$|~y1YH1p2GVNm*^uiWiAO5~m*@VL>L61uVn;LfJ27^;6%7S7-rYDZ>?5`o1wuQci zE5)dOpLcs2V;jhbiy2+@32{3=a_5=M0HQVR7KS|Jy|V7eI6cZI4$Kr2y|DIqI%Qc5D5*_Rs0mFvyM3bbgE z^M&_5eQ2fJc`2mFEP`!GO1QtASs4J^r6+ZpQ*fjucJ@tvKELDYt!8&}^5LSxEW(%P zt8!Vtq=UFu+?s@MG4(OUxeU3{k72GaKfimip43pUm>-bNHuOt>x~7Za>6NpMhQ%z8 zpNa)OU{YAY@gWJol2j)^@g^p68LWw3e@r@P-c86m;0d^{G`#3gX!=YYnI#zpAm%K64x3KmfWu z*)Hgoj;-?xUpr{36;e%%s5)>JcgEBS7B)qgzp!iZI~yM&88Rp$RzHXgJo265J_?|O zkhU!*D^^>`3QP6!mT7_C6L?xPj4sIwK2FXhc#1f3oZr^rp;{BcA}I0GOe)m3(5=y4 zW=axk8oFWAGCuA+A@AhKwIB?4)RhTmrx)Ct?X1Lvid`eLniAn@HI$^){TTZTYH!+3 zYq}ZppCJXFODE8M>Kjh^efEWT(9Vy#exNE}L@A8Da`BY+E;WXs3G6>jhhH7R9cQ8&DXflejI6nJZkGYETcYuy6;RW^f;=(+2qkze8W2 zEN32PwrP0Tr>x*2go+~}np9(QEq0?7^?SY|DD zGSr-$fGxGPA1RP3T`j4L8fOgMGt85^*(bq;%MkWIXLv*B>J_vEQnSY&)6gF*(MaAa zbr9`~E_-6zEgGMqP;f=EBze*Jma#;Z^~ZF9Ki2Ruh$M=Kp!EVEb74CCENxIW?b$vs zwb$!j9?Oo|=OyPlnO|LU|NJmn-)sIh5z!6i-dg6P0&}|yi<_7B?=-YdAmr;4RUg>~ zhH*N_=QBi_@q^+-4teMd^eExDMJfgW%nu@tUFO8X3YRvImhL$WW|)6gR$FPwgxGlY zgd~6lal+%kK{%Vvs+&;0`LAVZ5PuCDnOL|ppYU;>rON7)Rq|PCZm@WB=sgLkTk|@0 zmtNJ!IgpEqrm^cyH5!O$A9v{ex@bG<&-D;zHb-GkoXu-}4@Ur0;M{&G@&^|Zo= z%dXFU^ZzigxZ^9%ecb*?!y5=>Qe}nr;_FR&D*Yc#9>lx5`>?7TXK!~8{pgzWlK&Ny zD*60gRaxhUCh-Brsg(zp=V{lGDGvaG-iWc;gl`*ncRJ@wCJQp-8r&^z<@%1jBT7Wp z(c?K|AefzkApf?4C5rKqj48#B$PIw?S?-U?QuV5Jowd7j!{fICcDd2g(W&pB-RA8| zUcNe7NgIP+K+qH^Qp2X>s)j0A@ViqLhiwq}z`f}a?Yg`>MsGQ6)cV-i446JXZyJDb zq3)3l`j#Vs%yheQ^BC6yY~nCo zmHvhepX$jQiTf*Kt7$H=Hr8M7l?5B zxWO`e#3T82mU$S1Q~cV8qzJk{(V}NTPm!bD+1G(64fPZ4P9luIca2%SeOgHFgAb5} z`aJza*{a{16uX(*%zWdr@#P3>LSvj+k}+bdyul3aW9>#wLPfdZ8V?tgP1T*usTxbIUS8?|}lUBmVFUq#I)tG+|>Z;BD$iy0LFY#)(b6;pO(X{&J& zGWqZBZqX!FyxB*sp?ZG6AftgG@h8-9s)i-5^N6#~sd%c@VNEIeACM5$dfpK2ANqT^l7$~XSNh5_p>~I(ifEk`f|5gvO{3oIFG`#c5^~SiN!5jX-q*yLD`UR z4K^+Xocrc6)DI0ZvIF4{mX0>HpF?`n)=5H zTY{A~fyQCkiYu?Ga-$GD))^h=Z3!4NM_6QXiyhA21a8w&q9}X zYPDQ1&kx|+EKRSffBhnG<&VCXIa-D)GpWUNqD~tVyG-UQMpPiXdo#?Z_KLln&&SM3 z#!U8}ZI$h57#MSqe`Vy*=Vh_i&OtSx43d_+(ClP=+-d=d*`RpQO`VV9YjZ>)gQldh zUv#aAZj96qw%N|EIO5xczIkFq<;v(BeIHtwY+oWt>g@{wNBanh2~6yK(|y06yHv)! zi%`^UN@j@s$fmVM_4E+Xnew7vLtk5GZ($&d3Wy+7PpEFCo)-p_=d=9wf z62Ry7X%{y!?DBPj@-Lr}2(dm{udMbyd_xbrUUW{-E}IQ%y1>R%%EPkm%g%w)C#Ua% z*8mS3`dVbSz37CBiQ1nRLXPT|SBI}Pv3^Gg{AoCIFSBFjOv!>_Zw=s#&|AR-1GC8b zW$suY+`b(&y{96uCmy*&-g<;yv1!pS*pc^eurS|a(;$>m;H%lC`6x8GF+nQ(TU{2y zaN?yxZv4#GWZSy$!=bsiu&Q1W(qV5kNkYXP!br3bDsTdYD4Ux_V)}%7mzvV2Uw)ld z19~*U5($D#UlSQm%bPrJZUnQK#t5bNA>KgPN$vGPWWG_$&y%gX1)G1fa>iD|-KPUf z#_u#d!=|5o`zIumwMqG{+?EX`kVuGb^;#3iqIfV@5TAk@VnV9y3o+id%oLQNPaoAd}Z8RsjEg??jq|GTvQ!mpn0qHRW zTc}m|QsE!}Re+vvG2iu10(`u`f5f48`Gz?Ru}?Cfzwxy5{XbIJ$RtPpP>6z9`ztHPa8qs&9xF@@@IE*JvQraNIex@pN1?>K%bzF-ajnpkD_iYK?ccO? zOSeslMh+RilU%Btyox$@G0^sazZdq=+O*MZmeT?)R;6$N4;xf7;r{{QB{-{H@qTs9 zqyNt_&L|IxREJ%@SGgBtPqBm3%F_>DUy$TCi zVyq#hhaC(Yw24iMT>ACv=GI<+n!`fIuhH4xM~bB5o!?~3KAOgO>+LeP#w9ubz*yVK zC_T2#d&SSpnM(Vm3xhcIK;Fw7R6zupzav|m)VA2ukT%yFH?2GhI=g1gQTk$3P;eg* z!^)zE|Elj`Nk{u#fFll5IrjvnO>UYr=hOL8R;b^>2zmI2PzU_pm(b$?hYHF>^(I%7 zzlK+CiGj$q9OGLyMqLY6SFYm(5T3f)+tDN_m8;gMiTom=jciNg`%J!8VWlDzw3ga< zV;wOlk@!?cdwX9hfctbK7rKhr!lacdJrs~hwgG)@KhseTSgh z`R5_S%%+!nybV;gOA6dB$vg>e;lZE|4{*7FRyGTWx8kD&c0=odD4(yuz9O$ypwj)&aPrQB&vbq=v_IRMuv`Ctfh!Dv_3elAK>@8FCAMQO^{_9~q0;s= z+qPV%$f|GURZEEAT2s}J7|^f%*~S&I4=1W9nTgi(1#bQsZSn7FhSDvELM@bUK0o(> zNf5_)ngjIBKcLT3TGn1u5vxJok0}1c*@a>4uW!A0eu-6ChE@coUe?~;DU+dJ0kdE_ zrF|A{dm3y2!9;WbU<$qQaz-Tpwf9JxzLv|Ve?kz~>SMy!-JPMAonAPn_pI2u=acQc zPVOU#hru~)!53P9MeFze?!yyar0`K$Z4K52p2gqOjOum;F7MSoCFbapca7KwU&qz5(czAejihdkh0?G+?~)Sf zYdJ=Sd_e$T^TTj@>6_Nbs>58)iqCqs+|MoG^kU|Uf&a|3YkRhGy17wEJz9R^MZ@Q! zx3y)57dl8=?Dr0KVH$eCf|}M&;9E8(JNJ!p!|eq_A{JGR1Rs98|Ksuj?wf`TeH2BL z1n^{P{n({1&_)C zhg_EVZlQxdV!u7`u{l6bpVm5uy@*|F>mzb+R?r)}IG==NCRR$k4|T+aUj0q{IKSNZ$dJkkABG z7_dm2?}v+uElm$zx^~avq0I7Uqpha3`XKhk_4#>$VqgK&M2`Te$NrIcr_m_1I@u$M zC~9WeLVg@!92>YEWpG}(4l6aqRGu_iXnbNu>&XV4mb+Ifsvb6tYZ*AUD5x%~gg3=| zXH{m4dJp`2O>qI&)RrklVj5Rzz?MQpBtRqqX#zJpC0+5=V?WLJiR0}AU}LA*1z=!9 z+)>giBiDR(mBCpvS>vM@wPl z(k_@9gRnykyNk__{3_cm0QLR4o;Ti3_^Dm~Q+lu>d=Tmirbt1Z)-ktL z_uW^9I|l1AIEqcyJz(m=2;x`LX+mU74>MkvszvrICk^-TCyXyty~li8ocaDRryw+kW42;0+%gNB5v;>I;+BD(-`SDd{U9wf;Ew60&F0hLALtXK+ z4xQL~gV5)|S{9R+u>?0zo^kVYls`()KmUZJEBct^Dffd}i6Xfl<8dige;N&?)a-XX ze8zu2WawI)&y%#v#iDWtz!E^g_u@&$;0S&^DSQD5IeAD@D_{M9I+(1uHKM&95?21@ zE87niW%hQtMZ61kwxt_eN9bxGS;k5?S#|eoX~b9Yxi~-2Nzz^WJg_+uFvUani?Gvq z!sLRjZH#Pn$XP;SACjx0iMBp97RuT$;NE`_hVl758*v+0!F&ePDdNFmxo-N zI27JzKz?~U?iOGKy5AlA6~6@N;-6;xB?1@z$v6B;idwe#^iCBZq%bFg6JE;C@JwV4 z>96?78;J)2j2OGidfy;|NM%s~D3o|_%qP&K+GehZ??#e$I6aP@T>1B9QpLAAkKCjX z3$rocEsv%!PD&x*#x#5x%7F54O~IfGeWl@}RH1Kv>6;Hx1Fn~WwlDaD>5U(){YQDH zZ>Yj2n&_|Ds^rmFszW)jlYXQtnwIKSG4e}SK3V2;&RMb{LhGOtXGI1)q6WPX7D{Y6 zvU>&GD~yj+cJmm2;`&mcH~E#hpy}eE*3}!kkAZwOsm2~yteEupAvh5$u#a-j-~4s{ z^0&&y@R#6b>jaQC>H5Bo7%+HYm?RYv!Z8l8GrF&Z^%T~^Orx4cGb+E~TDF|A5+1(YX`8V`l>pIndCHMK^iq*9!`T0kG2NtMq?`8CD;N{@PZ zzb}C(ofRlUiph$p&`%mWJdG?o-?kXK^$>UD+xq5D$_G))k@F$H$|7Ep?ei_19!=Mo zX!c~7Pp6m9*lcCEj|)`E??1E7{lISS^Yk|RbJ+#2f@>9CWd$y0%1SywJ|KXvb|!{g zdHX2lgfWtgdk7fEF&L_xgUqk?`NUn(G-F@F@aeSw%b%y$ckOdmwf$*ai5Gs(jV2u- zc7 z!C3yfem6@(+_@Kk?zyvccb8b#7<2BD;nV0_20U)O5C2sff#~av^gvGY6bJn}f=7Lm zN~VBsd4W}xs=#iNhm;;jwl&z}DM)~U95bHx85^<8S!Wt6##%!*h%8DAxI}-wv|$H%@~Vs%Q9gE1 z^IG}o^l9?qEjz#DgxvJY)t&#cApmIDe_nFIlxngqjW=jN1keNkQvN!icPc}c#78y; zul)Oft2zpubLJ^p|Ax?=vr;sOEx{=byCWtl>;Jj$V9)=cAUM!Q+% z%+oLeu4nXH>aHr6F^H=P=bm)6J;%$U87TRNBp8)fY3+V})t1`d`zg-uK?=R!Cjk)I zZM{K2t8QL`%TO_;$n5X@!6DzckWf1HF-{mo&Dbc%1SXF#HvOB2H*mQgRJ~$rN`SlP zqQu-|i(Nkt_tE#29Cp3w9{wyW8n6HC#7=x7_K4I@%PRPDMInG&x z+cQn`+fFDHtBpS?xE~_{;sRnjR}#OrY=LB|;6$b#EHhP>wEp)vwtiTf-Y_zivDI{yd~s4CgV8)rB2=wgjQvZ8nd4~*8f-uwL~}Nj%aFSj$uHK`7G9$f z%bUlqaS?!cy_63|kvZNy^Rx3?Uc2njt8*tlPr_j4CVA*l=cCpHn(>Je#U5fc5C1W| z;r{E|u~+S>Wg93+QqPf6+#3K5RS|B3!zL>MKiD^xlqI_XE)}u$e?W?dzZUxe)Be%x zMuM#gu(Ia;GPjsNn%NPV$9gUTUI-?_(~mxj0EM_;<70+B$)*15HT0=6Y_FQM2xB%v zvwC?}tT*3n4ts+t`?eQ1{h3MzfbS(G@T=$kK$Pt@3~@p(#TzK%z0 zn&|DT7rhUpTA(5L`r|I_oaEASC;Ce0zM04?29-)7CJn#`N^s^;Z#zg<##B%_S zb&$*+?>h`s*Lg3(cGGoZUdLyZ{|bHO8EBbGwfh&ChvKan-ypPFq$_Xvk(X@dHme1H zxQH+sT_D$S=n8gj05l?cz1Pwm*tzmm?5-$WlOo*X<)&`2=5xe-CFY0Zp-f<49HD4C zU%64_@#_Id5M$?8;Iv!ejgbh%^{S>dSsd&7iR z(Tr9#IG^kKA4?pS<+Pl}Py6PoOab?dxv7sS{;Au1-0Lj_q=e`Dimj>8ZP!`yk^|TH z!_j9c$_QR${{s!DXr5B$g>%EJ3#azrMR>60P&3E5=4FUnRS$VDb9hiD>H#8c<;mb}B^lhr`kwv|t8xtAQ33}&r5Uq?} zNq-3C2f)hs8%js}_78|1B1Ji|BRNN)Zu3sdyp&5lOMnicCOP( z@}T2CO#{b(OtK-b2_Fs<)Dk2&b{pZ4(mi|`w2b?NK<7Vsh`r|Q$ESLh+xqK+ev=4m z{Gl`nq(|UBGbOnn)-ds0_qD>i%pPec&m{_Du;$Pzpz3!c6?Z_-4ia&KFbFnZV$fs4 zfb;FXcenuf?k@#nK*UXwyGPO{#ANoyt7VGtzaN$8yv=*(h6G4gx>%Jzk*0ti z#n%<@EM!ZQvnz#B%GsxvT@TNuEx#B7r&`HK-_)*=pZar%M8FEe+Yo*%6T1%BsU1=C~PN8f~^XJmV&}ju!xKec%^! zUS{0(c1TeRoq zX7ly0+I@PUfN&m5VKR)kAC5kcylcvh95xfuj}|FqUQ~IxYMsEtk&|r#@_TH-A3}A{ zqZ*L1KLCdBWvRlvtq0M&9{B^-9LF0Ot5p~`(+D-J0QV}Y{Ys;$aBHaK_z-0GyL$Ly z{O9>m8_=QKI?zOaL^=jStlS`qWRnSNmjI-hmxlh6svj3_h`xSiv!pzui$4+0Hy4U6Kz^Aj1{_i zlWbCzPSpD-eV%9FXG-Zg@D2Uelp#Iog%A2$J?ueuyJTh1qO z^c?`trDB)Js3IkH}RZV=ag`u+C;RSyE*&nIj5!N;ivL=(9#A<-YTm0Ns& z?bDqF(};UpI!S%`pQZk-8WImN6j{Q~?}Bair&@X?u(R%Xc-_R}3S>2!2^=28NlICB z4n=Dqy-&P5XyWyR3f>08r!Tb>P6@Y%umDz=81sWB4gf1gyYQ?}C}F+Li$!7i^Z$S< znp^)zJ<#q96my|v=u!2Ity4m(b!0?$Ih8j-UjohyFY z#5YtH+T|oOw;0j)U&>yInf%W(7zeJybngc@3f-~SAgqnuulSPYE`NXc;v;pI5^)F5 zZTlpImj43-6-9{{J+_46iy?vx4;bEAW>8_L`M`|r9M&MLZ|P#Be$&)qeoc5%)(`Pu zPad%yrZt;oE}Ams`pSMdQxx?DZq7x>*uk;UJ>qTB+p_#04m$uj?wuuYt&olov)d=P zckuH84eIVE21oGMuqPWgsB)XQ2q+&AZJFN9iHz*b*e${r4EX&y!ezxTTARuSMdh$r z_Ab}6ckk0vp8~slx4#vika#Q3OijIz53tu4UoA4p!l&ha?H`o_CYeuE!0cA#2mY&U zbMkwQeV22EJvht|a+5#=TC(*8E)~>)#n$O3j;uiY7>V|w+vIudc`yBHzr1_%O&(?J zgV(%2j+cQmHKMt87T>j<$k7Wd1|eS|x=U+GV1r%&{rv%OX#}a3r1U;0OE;07 zal0XdmU^)t>-J}6dGUo->CvT9lpSRm7;)N*&=$!3Q3^hCjGC8z&nw?sUc4hx4d|(T z^-v*sz_Me3an5qEH0CC1-|D#Teqz3ty?O3f{ycDIx0cx`iN<8VXy*!-#-qPQ>8}OS zmiDG~$OFZ>3=bGnc`Tr@TCh9{clU9|MwG9nJJ_(!S+E$^EW|<+|C;FJ!Q%5panIHg-Y*N&sP(wQwYG1;%9yXIx;fvxzGpITsyhC6){v z=dhdPwG!44yX-4K1jyZqdLW&(->7a@(Pdy@iN`MsSFv^;*k z!DRx~&DC#cRls*|+Jg`f-o4!y5K&s*&r530xKj6ir72C|X~Rs%9p2|=!I0-@_o)N_ zE^i=_iuE7RCsiyh>h8J{-f8iSouwVtl%Qpl$OdzwEA)MekNp-YqZAGoF%}!zo<9b@HRLI|TGV&WuIEvf<-8!1-7gU`f zTNQG09T{jq-jAfh{c%Wa59G=>2J*a{#@FJF>KLmp?%d)JLUbhJ%ZjA|X_MGxBKsds zygQd@agLh(15d2y^+_u7s{Y@lem8l z>95-c|7qLZZtSX2pEVfz_p9Ms{ThifMgbhlwfYe8sv9}-uhFTi!{AVSJySU9%IewV z;jVVMG2iottaMJV72Jy&Tu?1h-=aCwxoWOHVeA+f7NVeSpn7$xm;+uzGQ6`u|g978v@4Bkx_7=(;J^kil?h~&G z1vA||(;orUjzRtX03-aoEiB3Ty0<%QLQP z@!mxK$W~pVm0~^f{0TIAiDT-`v4%pC{~bQ|YmRSp_|HB!;@JVTxx5De(Lc0Zi1Lhx z6Gn#JiE+e}egAH0L}m<`J6_PA>3DD}%~d4SXz{Yh?~oxkSWR*GzJBcs*D6ZB9%-pw zWnv2=Jbq}#hV>l(IPz;(N#ND}@9|@zBmF986CaY|4;zYCP56F|nrTcG_P$f@E>*kk z7cZ3heueILsBo|b;R}Yjof&CN?C(J&!bqtzCgt7W*e?&}jr?VApFiQPR=#u@BykfA zT@toru$&UQpX!?HY7yXp^+-$O^DJy@YrW0Pu>;SNcYY;;y#q-6Hm;Ce;Xj~q$uxDq=X7n|QLI{5OdK7;x|SU8RGCxnY~yA*Y^wq?@so6_3kr8MH}%j&X9 z5~<(kI?s@t+&`NIGkSluE~V0w+N(+PT}v#@>bvmOD0c@yV(5X&JTX#Ofk8Zo?;NH zhdN)rh+-x|^+}e|8)TKg*|k8Z@i6>|vjQ2h^Td?3eYa00v`oUy>Wp8Byw2c_icDZ` ztCMa{aGp3E<#G{nYEwzqsERCbgXpC$jDsXngpK<~6ElZ=7L24N#`qxa)qsay?=%IN z8vBw)Y45ZHnBr-y6tMm2XDwC5{8e|wU$)B^mbTKBRGX{Q_TLe%Ty)YmxVZHr0imWYk6ze!W_)3Z~LjP5D%Nt`v3YtX;( zs94;4E(8ljK>+1OVhl_a0>Sdyj8<58;;j4mu#%IdFXMbpUcLDBEm+wz=jPx8$P>qn zgs?uyrN+~Qx)C8!%)>@FRv60!sXq$!*gaS)*KS8m3_RoD+~18QlJ()yaO5q>i^Gac3D6XF4@wMK(TqJ%Qj!x4W0PDZE6)x$f0dvwjVe z-Knqnipd_&6ZTxCKVQeENbgNq3&t7@+|(1U9iemh(k@HoBc*p?qn7EGyBfG%IY-ub zVgg`TYrVB#ZI^>AqzeNL z1^l9;%B?RluVxpnCB}hS73;QPQO7p^;l6^Za=Xq%?m9V!=S=Ym?!=2Ig0rubN%8l@ z(`P@?{W+X~ClXNWi2=sv(3i*)>j{-uV^K?sZ}pS-)==@9Cl%`crRQF0?7-dyT-ZTh zq5?GtFI9!|Yz@!eo{Lx$sIO!cJS;{0NDdsRl1jLlo9XTDGVA!1o6a&gSEn}+f<3uL zf#0PBt(h{|Y|S0m^OLpwIn_RTd|R7H^Ghl`k^1>{WJFGV1pm>}c+K>Uka|KZZyv>- z_T#TGtL$Fauk#88da2ptcap_>av8V<7K4?qhhB;4ABFL$YQu>9yQ+YWWf=vX0)*}G z?hMIyV=fjp_4TQrGQJlmc`KW0-Ew$y4v&Ikc< zQ)YJty0Wyh+ECg@ONF>Eg9|xo8o{@gvZK|zlnXJF!Q6;pubz;#Pm>C;w z1+xCoBmyM-oC#(G6LjNRnS#7F|l^Sp2|;Wq{C#@BbR3hH}7)T zaXLEBe&o^>UwPbo9G-WYsl`mTeL}ildD(j2g=;HKj5GDiH`hka+ZTU7XX#B-zi5;w z{OW_iAkL7ILJGS>$x;J6bdcPF?EtMrsjSMM1gW`SW6irs<|JJUkcU1?F%XaRkixw3 zHDu*_<$`=8F#fLZh%R%R+wGsL!g2DH>G#v!aTEL$QF}ZkfD}L&>cKe5@AqO6uE^D2=+4#4IgM9GBs_oy_HIW{ z)5YLUEwKT4WF&y^ZU;9|TFAAlcbZ?pP+AEDy-9J>>F>Iu>3xlRFCJS;pR+kPkI*sT z{Q;(*BdlPl+=Lz)loXNTT8yl*wRV-K#&d@zYp zs#NJcV14A9Jv8g|TFhnDB&SUC?s|m6Zp@kf5e-Z?Uf8v}vl$ zw3F?zUZ=%wc6wtiXUK6w&RJO>^ zcJyhcF@q)X;dP^2w0^MYfwtcad>Vary44h(qgFEFEtxzofpwPMGxmrx9ICjlgS+2O zxksW6wu_QBx~re924>w=Q2P~V=#h}!{NWk*f{v7FhWZEI^jC4e4{_$CV?ckyO2KqP zZjk<*SQHi_`yWlJYCmG$64Tc9jo7nFh4Xu(6%YJQ(o@QmZCf)GLI;)L3mrT zZyr=WyX>Rj&D}2%^&;-mI?pSSy+;}Wqq|JRAi2(eK#bIfrQ4Q&8#0RvKZO^KIa=+{ z7Wl?r4tf8~(cf1G*|)Rq%R$jx(N9`M@OKh3cM&kMf0Vm>L2WIb%=$B{^5WaDOFdCh zu`O<)^Tr%G+;*0UtxS2S`y_PfDu&ZK_Eh*$>`+tK>svY(Q&U6ET3gATxye9(k3Ct6 zwA4b%C+>GK?`n4V!Ps^V<1r_Szl@>WBN*?JUdf6Iht@Ye)wT2ji0fbcSX#M15=>V{ z-zA~$4ZH@uMgcJODFnP2ZM@^#&)|!dLINi;zxr)3R$gd3@P@Bt|zh-ANZ2+ z^dZo1Yv~EVMIs#9CKP?+pNt*_r!%g3Wa`W4$NnV{}l!!vf6V0zgxue1f2M_&T+vo7m3jzby;b z7p+})%VIt6R2X|$eax$tNYFm(0~3ZYQlP5_(P#$KRrvXX227MtQ0q@6sU$^j#4FUS z`ApPK|4!D&HC{_w)#G`b%mXM1|NngPQc8VKAh`cz z(QA;8-{3r?8pjxmwSQEu-?T`c(=O;wI{h&N6*j3T=qf7 z^Z$TC2$umn)+}xDA5bH{+x_}Iub>soIoAhp%Rsctm$=gFS9X&}E`Wi2&3cRjICPUF zeB?#KgwI_R%RhSfx5hRt161!qtd118E1`)A;NAhL8i7Z$) z(3$@GO)%0~kf{=YJ!=j?crcyT5DAyR^Fe8*9F-e0_8tuFa0zm7;P=OgCB5i;`z76C z>rr1{Wk8V`VI-LpS|%eXkupZ715U<}j7HCIWz(O2Ldzn(ZT!;B$AR!4&1jKiyYW3< zY3VAjjBOnLyNbm>pu-)2;^~T6!HTI*&?GROw`dxal)KNQGE1x%jS-g)kTk+M3#!iO zni23LT*+$$-1g4^gZFP8KIlqbw)hkc0yLpnq0Z$Bx8MqA{6jY$8QO|D4D=)~wOo3n zA6Orh`3%4;aD5`(MOvdpf7WbEW%r_Gx5Y7c2%g-DYR~)9%+QWl*2)@MeWBDNTYcQ0R}+5$q|R zWPyn-yU%QZ2@a%^ADmsL$Qwke?4Lu4QuOj%)rOr5H~+@Y+u9Zzt{Iw}8;sG}e>(w` zxRdSYX+1~EKhs%VE4CYsGVbtmepI$rTGF! z>F(i+E?Hc5dk~$W`p&~@OwIF564oq(%uc!Dzq6W81;us)8zeqkrLe;hO|t~y$qA-Z z;j}>`zXZCWT}W~*jqAiTgKoSt)pO&dV^A#$c7keDX+QSXpzLG#&*%q_jL$soA;2NwTA6C;%GC!X!mi!~3(Bqt#fWmb zsq|0qH`^G$RtE2j>Qh!WM~$L-DndN$=Ps>PrrsN z*yr=vMzO^zQ1`Z=8*z$hzDFIA*Icyd+%`A`Su6^Xr771Una3uzN&+o#X_28kHGuxcsFNCR@Nzr(d9SFEq zt{Eb*T85WCa+Wd9AUmL^kb;O?Uw>}XgbFvOU)5~V3rpP0@UGh{Pv7&uuU|?Hoo>;j zzxy(B@C>j)2BmM&JW5z!U7)OWM>yN!P4sUuI0-WK`;CY;v>1RQmn6}Lpy{~13@{a# zP?-$|y$~Upp~Ey!1Augf$mUD=D2~J;e8RmITRUGV72^hZYsQh+0QN59B9M&%_>ZWJ zePPz0qCf3NiRx6$tJIac=#YEJPc4$b!~2pO2HT!Vod%b`Frof5UmMT!Sujeu#d^<+ z&Zi~vS_D8bPnEWd2&p>|=+-?}cQwU_74hSH{K%vZEl68X=F)S?<2}VttPW%DgPHT2&oZbQW=up6Q z`!onbKUK^$t7!M$nxdm(&Y1XH0A0B1KshD9aI>^)sWO5>vB>-POU{9I zC|686M8Z|UVMNWVG%+$~lp?1v9J$Nigyw{Hmib1s&Wh_&H}r2YfAX=br5YlP$tFFV z6iI1HM@Q)mx0mIa?vlkdfw<)_p`OoKZ+`zkeO5o1g#C)!3I$kY<`98)DUf~M^R`(lj%*F6w=Wqy9p=egN4f2Og- z=Qx#zv-epCa=wBSuud$L>mS$nfCwYek}?ffcH6{XIcKcinboOJaJ|>@x+{paBWY6V z4vCX^o3&dcvgK;c5JE=X(O{9T#FDmm;!dS>oqgk}4v;FBE}D8AIRL?cU-<%5v1{2P zu=8^MQ(my0YFCa5weA<>`0~YXfL_f+FZWtDFk~9$$(YbQPRb4?1P$^~z@2-o=)$>M z5*-rr4>*ON3;LM>JZxLMfht5xQoW`Ea?tJ;DL3B0JMh!D@z!{}uK+=u!IIbf^q!Vx zwL+xn$#3F-SQU`+gz3}nGv?{<{W#A+`)c;uFUF4#ln3fV594xj6*$|m8nlOH?o~9~ zCiI`ya(Z$i`ba*EQxK_$A9rLjgQW-DbK^xcjiQ%QAtXt068atO zIvEK5J+RS`N_0o6xa|!5W!JOuhsASj$ulh#)*Up8Mydqm9j>$z*Hn&u?`RH=0$}9;!Yvg{3H$@H@r3N> z8UCJOu$lh>iJ7$HsBD7(M0-3$17KD6qH1mAaA2LRFXh^3B$g8R#VRcE9_r7lotCGA zxUba%F#YkJt%m?Jm<9z3^s#D>dLn)4K3xr6Zvp^%wBpvPo37_n(i*EG?t5>)aPNjJ zd_i(Hj{(|1B2gFV)yA?H<>u<^rEgS7gu6Hp>#oq;iQuUyyzYB4bU`XiU8m4SPyNPv zvfdS4*6;7n%0>sDd;CSQVTr=?J6n5{3?suUJN`bkFI{s zeB9~j&n5G}V+##35=#aj$dOoMtM#{8qcE04@Gsw*UN(VtOJ)K^JDcXr>&0MGA#Aci z!{>QOcVB#Im`9fuPFr8?i_SN*ojh~~z1*|wDnU3?P*ib7KZLDGi44eMTf?;VYDY4r zWRW8R-zucuC7x>S-eyD%hulO>MRfu}S)As1m-!1r1k#591`(ohriX%=o8G}%eAb1@gNpYC}6l`kxE^<6v zw)pe{f0oQI{`{1hpaVNA%l+df)3u%B5DcJW$7)Qm#$&m)KO&S#C0)2_`Ly|~LJr)9 z0oR#>xW$E91IZj$!Rui4m|sE=3%K8aqEUXK0R+jr?i5+!^2jOnqo!NZ=kt)qyhErq4Wt69eVv|$xLj-k$@;wRjdFxKE;oI9{xRpX z2P7eww|MgQ=SiC>Tiq&a8{Huz0rp6<3!;n@Sjm>-2n*;W4<1eABCT4!>nO6GHvZyr z^=fO{eaMT_?R7C{`ghP~lg_Dsa2gkSBEoEw5P*J|tp{6OOT;=*)z%o&jU?w+We0_`Y4WB<;Y)$T zx5SSp;@;}r+h;o9jG^&RZ%|CyeOxlel0tmvgl{$#VcrwR!T^$}MkTTdjr-7QL>)|h zf&ieC;TRL5$T4H~!}A;hX$iwJ5f&eu=~2Lh<(dgQ5DH5H&`#L#ooGHwvQ8&ki&4VE z2TpfDy|SgDvAYr(Ne4i*ozE9>DRQF2-8on|lNWnbacs7m$E)Tz$PZw5p%q3g z?9hF_0@SoA<59HpC?I+Q;2a5qjG-?8$7d14w1X7DsGmfw^}3rg*o<8jRXN*(&HEgj zVV`XzQkdE+EGC(MoyNTdA(LGoyUc)x+nk5cS;X64edBY1t0kC&rj2!+T5&kJwsK zry!01=I{JW2bb}JknYqIE*l%%-?4C?1N-)JRVwrn@HArtPo56-IYMca=EE`!=d;FO zFwH5qF3RerDtu>7e8p-;7;h=80XD%sM*l<_+2lmT=RWq&LK4PqFTNBwp7r_G1wu@a z;>>}G?R3EDo&5^%-(b0|-Ei!*7tw&KEZOr?O`35VpS1pLr95kJSuA8B|Do$Nir7z@ zKEyXSFAuis6ECTWo4I7NuRI7H-JYuRo3V>o1ZqiGpjF!rokj!0B|`v52A2U@{`)pt z=54P)jjpB}oLP4h8bKinw*^lh$>4F_s8B#N5_!#v#zfwJqm*+N3i>&{q0CekuIM!3 zQ9$ZsXAi)K+zO=eQe}{%IUGP;ZZQxc0M7AvS{cNf$X04_dXfJe5mC~^i$iv!s_vCi zWoXyxNPa-NIn$-QLN15BYj?1I((0+hlC5y#mt+$g9!KLASWK&O_bUNV*>1uJp2Ix@aMK>fX#T!_}`$Mo6PB;G)ii*t%wzy!0 zJFvIe=N0a%JyDHh{z!xx!6A%Oe5CAgfk2wm7a$*QNc%Zr9Dp1WhxkOS^8ZrWZO(~k zI!@YSoUUKMvePsxYeuSt#8w3K5~mKY)=|~oD@-ldDs$F-VmR$%&4!prp3@~7wKOijd_pkb| zP#!LnMaM%bWb2*985U9%<)y(rwB0xcLxdsOVtuo>qCUS+U0(UoIkV4K8-HU1g?nlu zq*G0!l)o%t7bQoV`55#ept7x8xQ0F4ZkRh1+4~NrQc#o(m08r_73>bM8S(C`s4-ar zBZ^mZ6{sV<{q-5Tw~m5}rvNXIX}S-k9_rB~rqcmr9IjzA;NO4df&&by4*WM3h<;-7 z>81maHSdSpDB8PVw{+SObmH6p`B!lH1A0`IbR_tQ3Mc?bidYr?0cocH+Pww-mo-rB z#w9PobZU9+zYUP^@2>;-@4i9QcnNB-kd8<+VsOKZhEO|mBBWF|yEq{H+${UwzVyF8 zE9sZf%|D>rE)yUE+kN^UZD48*PkNAsmw@G3hCrk|CLXpB7=ge#p*R6H5nXE1?`1wZ z4MH=vx%p5fel*+bLE-Qql=eYbIu+Uum?9%K>fg`$@7|lPwP#RV{C{ZGkEj0Ymofik z1mJxA?~LF&R9)EOt0Vk*3CR4l_+bC&Bk^KqAy#P>X)6k|K))&F&c8<4{yEb2fAJ?) zvk3*beRL`w$j77YG)HNkY0#NUjrrH~9OfRMPjg;WPv`2XE-P3~?O5Jt4U@tL9GpWaYRu_Y)W>&S$R$l_VISe z>%52uzfXh4fB`?|Wk4(V;Nl3%)xBS)OH~*n6^;~JqfpP+9!n9t`u$7D6H*|HU{WF6 zX=2Ng`Ve6e24P1SP!>uS2DsOK@%V3BiQ$b{W^SQjjAH)7NG$^ERn-3g7yQRGXl-ws&J z`+z{Gl9d=C9H|Zz3D^dP)USwB!84&gH^VQ?jQC&gRzWHqK>(ftm95uQF}TBlF$p%1 zSuiMBR=y#7dGuH2>lk?(Uy%Rp)X<&Oe#9R2E)asChZe_Z0F@3;N(a$wiZU;r!n z&|E1@c$nel0=Nsim3549r@wH+I`qLzz+(?zRuH-07t03JH#0RQ$dE20k!Wl+Yf=vW zOsV7M<(l*hUZM&gieJn<5)}Fj0%&iJt}wSjV4PFs)Pn+wEP|tOQ@KSrllap%uJql- zM{L47>2Aq^{V(}iccM~hmnqtd;1hFpJ;k#w8NSL2ws#`9O=Fp!6r?ci|4qp9Tjk(8 zJx&QV9p3W?bcUu~1kdif9oN45{&LvH*s|-Mgja-ghu|f_qlu?fd&V~_FwvcKG^p2v z4HmE+Ub?ZK@x`^V_r7VE}=WMs?0q=0Shdy;@Qg&1=M?`s7*bPetcE zChmw{yVbo#n6E;>fb;SC3PK7^QR%5dC+%+nQ5MHTn~AYis&DSwv9OZg{*?^TiM6?N zObaMX?bhZO#t5#83g_@^l;bhWaCeVEXS1dV4SlG4%p%D1!N(T2MJII=76#jrPEsVd zkL2#HYJW`}FsgI7lJb@zT9Cp@unX4#JsNYsWEwRY{fz9-^}d$6z123CfBZc7u zecBYKQp>L6(|s?*(|E$FK)C^D^_~RwqH_UtQ*?+bcx#N5s!zHXk8`13Q<$5tTr4ja zNKdAlfc!!CSxbPya;7C*HFj%i5jq{W*va4_tEyX|H2dy{3;PwcK2crW3{&NTUUY#} zL;;3R2igIzpsiSeGDp&vCq3>5DH{jm?C%$@vjmA0B2xew5MiJ~L^8<)hiWP`Qlbc6 zTt1mH?U34aO|Cq7#Za46Tl1gSq;0pmgip;{*z-=~&ZK zk<2tBiZ*GaQ&^A^N9ZCKjuD7oKkRr_>MVOqrKu+}wH~7L0pg^$K;R0U#670J_CUCL zeTJtEcIrVJV^@@`Yv73F&^A^d!PS6u+azKR^%j&diaqX3(#$Hec2QB7Z}U6oE4F&X zbV>qddgh+{+}cOZn-nEdf~FwtGzr(y{LDT0g@&4;t-{7_E{pO<^5ee^ZnE&Sq2n-U zisWi1I$T(Fl%`EpqnV&iPr88{Y|m4^KIR{o>$vpB|Jz_QG9Ivc)=PknJ{)h}og?Im z6r<<0NY=)FQC#IXAM?c8fqki*5b`-_!Fj%Al#&Y&x#XNj@<3^-mTYR_iOLIuY zXc6XHBI%zQr{+8ILjwKFeCpJ>4o+XV zZu64au8Jy&YJ;E_4QaQV02(IdE>p@OUgIYMs(&(ewyxvatbwz5d^TuJW(&CcS2$;+ z4lV%wB%(E}XyfaKDID5!(Ue#*zF@QLlbpg7rllTsM$Z$oM*~((h4{L3jkqWP1VR3# zi@iKsyLM4NYC7>x7EW}lJ;Uk&OPqsp1W)4H0hPZ9`7+<$rEztg65e1VtGfFV?-HZ) zDfhYIJEIdn9|1HM2xF=@po?mKP)TNl_aHMD}=lbipIJrLC)&KQMW;G+uQ)4M14cTQn@16&vPXOw1x zN56vDspAvvfaZkU#MV01WD=nTpyQ3Kp{#5VTm$mlu1eINi4S$`eBW(!+&lZ%p&UTO z1MS)Y;uy+X>QjmoK?57J^#w?NIm;@03ys`}vh&tzf;T?g?6>S2x|l`>And6C=?B1w=Wng;$|!Em*JX>H9!vYIV!wo+f>U2mgHZ?bpd zv57fA`Xe4W3asK_1jG#&)zgtJz9%P7oEZ*w$ws@3-Q?Dbp?y~pThSabpQYkX#j z_?oU3t3-h0XFDJqyozhZacPQBMiz788NKB$H_GOVyz$3_B1fGqRrobcmA8+-Nh(A} z0nQ!F0NiG!Fq&!$S8G4g%)+aI&zKb0HP2SgS*!O+t{o7WEF4lZWHwG;VSYuqJ=hKf zkQI~MeL!91vLEIf4L~-e7%U`FnE+J5x~loWMRfNLCryhIhlf)B(gdSBP$$<_3#KJs z=etgSc^N!jD9g@`-wwo%pQO!w3pGT4Gmug$goJ5E`2|b&jWL`jsvnAG%M^=e@N>I3 z-d~sS;J4KOCYty$@z)Zx8c5H68LPFX3G zI#$13Z61PYHGqs=fpz7BX_{`IwnA1}eQ>buNyQW63dESgmCC@x9{hqByb zk9{?k4@T^X>m7=*pC9$~CKvyHbIhRim;dwE>7onlujfOAbP$Jyj2R^R7>)HQ0j{n> z5TV?ax<6qxHFRhV+^NbVs|xks9v)^~5<`n7-)7E_Iw&L+WR%AVW;jQ~6oovu)r4+z z@u@oK70&fv5)-ZDsgrrVt0rpp?NYSpPnqlv9iXX~)S|RD&qC(NiF$Z%jAo1vYV>bP z()lT;)73sxGX3nukCKa?zi<2VtFz<3z{LpwMVcQVJ77^$pZS;Z8G}n>L?svFvVjb8 z`Jv@d%QoN2ptPRoYpnr%ECra1jHQ=|lfkWOM7?%1V8CE0;BeYy81dJ*!qjbexos`gFe)$RB?8-Stt7MhR#4ygeT$J2zb z0U>oAUf87 zk7M*{5{olyEa>|vCT`M??Tu0TBg+dQ&f6Jf^m?Fb0x?DUwIQ@AjYSlP?g^{o>qk@z2GefaMT$%!{<*(Jnm#n#&yV$ax5s}CtDhZ_TOX@(@MN5xA`x@ zD%*#?nR~>xhMf|zF(=PMxU%SO`4Lth%3&UDl5%c4EZ*Aoog$w`rXkU~k z^bcroG$#vAPbcy$ZkG9ISLPT_?Dq-z`(cKEWzuhUI0f7|W~%fe;8?3r;hPf#S0$C` z7LBzd&P0p}t4^N#(L}VYoM^Lh!{e0xRXuqcFKj`6?nC;t!gnSKd>y?exWAX^W;a1u z#%#^gg_CMciFYO{sYzBqR`LgwQvy+i)PnzjRyRxrng|`Ji|TTC2-5~qp6Zxkms)6` zu9`fGzh>+p3)vKZ%PHNFc*5s({F0ew67|lSRbo;8G;VGF5^1>wBi2#4p*sEnMnp?I zeWL^G%)dr06}=PWbH9Rr&&O^Y-I#w^*f3W1t8(IA#+KUs5uFks%bfxGH+#RNR`m{+ z|A1(8gWnT_N2@*1v+Ft4&(as(Aug4fm*v%6R%xu1$Di=ODwpWp@2*S+Pp03!*o%MFZJXXqSgGZ`9Pt!=NDuX zKTQqF^2Rv`I>!#*7!-84WiN@osyn;8a!-1j+zfQ;ZX;KSSIW>8MBG1L;NSMhgbxzV zq%MkZX)mrR60JM4#ZqR73572Hd4YN1{zDQG?;o7|5LzF4^Cl~=86-)Vzm5XKL-^al z1y!Vtdwv?X%4nsVu9s{F**ce>Q`-1N&)#NbRq7MG7kEY2t<2i6H)wb>Z*y$xAee3W zW2PC@djs{)m#{5Q7dI}lR;zG!X_Giq+Pj=ilv-?ToI7b$2oLMfpO8wNFq!c3CGf`Y z88FJ2Q|mm{Wq41|!t75fZM29|77O4v%tmNnuLAe%t7LK4&0>Q>3i5}^<>m@ln5E@= zE~Y1~$7V8JfhP7iO{3%;9a(*SB-19tN0~8f;{VH9v$SNHK+h3-Zsaq1rQCWE-Pz2B z?}_BICDBF<+@#Wm;hrB|q-yFCYI@l#z8)8{coKdTCi8K0N8sLw=l1Y;&}tapTNv!l z`)ASxue3TjdM@ybI>x_-Hf3LW7W-QSkRs#7b%{i0YmlR26u}oeO0C_k$_n59`gK(P zzR0R`^Rz`-Q-EyC%W|XZF_8sfW!#e9H}kfXepe`sL9hnvC&|-@$_&`n}~MfS!^)G{CBnvRf1w| zOETYDUa;lsOzv|pwVG1iv<`nSm*;A`7=fKmluY1kn8>7*8j-!af#fohfntT{^>rwt zIAqh>yuj7rB|7rCFEdw9XZva?XkD_My192DK31}&@`GmLj9M+nyve;&<*0?7P9eSpfCAOmZB1~c^l8Yed|*ko6^WWZZq$dn;69Ooa4zj(&`EF~FXMeD(&NX#`4(M(W@+MH6kTL?G;fGToWXTvM}T`h9s4w2I^gD=CwS75ob_F)$u)pT)4#2eUK zzDt3Fm>h;mmv+LFHLDg-NkETdp)pvdN9E;ej8xdP%arg<&-W3S?Q8B8(1LyE+b91JMI_??)Xb6ZL|Thw5I2b4e<%wwcSEFvK<7V`D{wMc$T6|U-I z)+Oji^e;_vDm`UJx|Cas;LA-$h0`?{RJl!K(pH^%QS432d?(nUPQ23#NWh9RXCKt! zfxelhB_#@9ta%;TKCcqh(IA-NU+R!=TxbV>Hm~0IY0f&Na{84Ztr2PCXItQhMp0Y| zasoXqr-m^JCw?mlsaiZf?`!s;UW@%&t6N}|zV87cNWVZgPqafcwR)wg=G#C7Y0S=;GgH}urAKcH*{6iyPG;vHCK zc=LVxg}@+;*x{k|E3|LD?~9F@mzyr*&uLBRI#~rCKqWtXU4lJ>&@i~XR`CC;eVA5tM6{;rq*k#cv@&Nq!axiiM~J*xTxxkDU8u6Sy>1ig(nP>Pmif&`X+ray8LG`9vJPnA!17eqKK9qEG;_}k79lX4!{C2F z<`_~E@xel{gSmgz@Q%7#aG|#UP(?y{_R3s}SnFB-kejUKG8yEe_%Z?fYP>HtnjuC8 zr0_DKfbf(`k&Efv^ru{|SBi-#R-qERzcbVO=2{;_Y`AZ5R`vt13rC^@PC z*bRG9m@ut2&va1xy!?!1j_0XfiR@LSX__n9E|mafqFosCweD*9n%Z-8TJ~E3N8eTU zYg)HhxG3f$|9-4yp9+nf%J{7`B0KzLlNt~eaFCL{LoCRk_)S?JtxrFe$nWbP0@wXs zfQR)h3=+A>%$DTv-l8?6Ox1|fh*F8Ye{K9s_mGj>z{gjx9}*yf|6crS81D@`o4g zQa;VFws{%PUwa~`;8=rg zu{eva0_QOHJ_(O8z`G595cDY6KH4)>g$?6kR#vzSuNKk8JCPg+m%0%KmzyS_T61M& zpVD+&GV3YO|8z2PJ8EtmNS>a5w6UL!5tx$IP7<=0Wz zuiG#IqCsIqZKuSA-^n-4X1WdK;omgmkVo*S!JJAjS`6wOP+mlmMHp|D;MoxXrsgVD z6E!*b5w+gkvXd8TLDG<7n3Q(r9GHB8>@WK05rJ(aNh|^UtOL-19?8N4EuRY^$!Rl8 zdVof5r4LX6DUx{!4eJoWND(ivf@arPffCzPe$G$xmj5V_^FK)(=Re`w{hM?5|E>R+ zLn%stR&aPL?%)_u{T=o#Pc<5^Cc@-J?qO@NMuGFw&oJ*DxxPnn3c#e;i^7Ut&)1{F zA-j4sHh@`HMuaY9ND3!!J|cN-;mAonFIA#wYDF6@0%XlH_v08$Y7flUzR99#eaw-4 zx9*ldyFwtL&=$PdGbBVwBA5V0%&~Lk;qFjAq&S7STa!goeh6_hn~&^|7LHC{PaQa% zhILjLNN75I-Cberlb(5e%|xIyon+h7*+Z{J*g6EE*KW9;bur`PvFvepQF>sWy`(F4 zV&SvNZo)3FbSsF<+v>xiW^P^!ypi%7zrzo(?^VFrDaw5Yq&Zpx^gTXqof~GheTljh zt*aHO*_g$7^CsFaAaJSv+h5T0C%`yCa^SG#>z;>e&DE%a4tk@(P>4(v@!b z^*$yRqQ~bC^7^fOnyGnBN!@k|l$eDiRu?n^As}3&ID&GA$1>Kx_y#MBT67}nB^18> zXyjuG`S$Fx?(ruy9(v(N@=6sUfzND|2Czy+z(hwKCurh<2U(gM^E$~Nj{R_W&Hfj^ zipp_)^`AH9i_uw%(0D9g(}M*zlNc+Na9_2Z({{SG~)6ex+S%svR;l9Gx#=)8@h$lar zAbD^kN01>vl;-oaq|b3acy{-4Z=za}`9lfAZ_35eTF+K9a($?J8!vHf(8z2t(pLDek98g6!r9khY z&U7$uG}OkH3*L2pkN8H+ja4?Y7w@={*1r89QfZ8Kf&zd=_-Nv&z(8lOBKHi~+&w4tofMJMFwtB&za9ZhRwfAe5GNkT*+XxCPI2*jVjGKPe*P|Lmyg`zm z&>Kal@P4VyC}?oBD$mqtO~h+xOzVA+U0>m-pX7ULwv@JRmq`$bwUe!x)Sj-GwiZ2J#pC zpWz4k_hHH#PNQ;54ejlSRr(0WR%pnB+5q;+e%}Ki=4XfkI`BQ=2r!{pts~Vn8|+>z z>q*nxi>j=TWL8PPmQJ^?EDhPZqn6(vsaxsQm$uv+w#STk6-?|!=shmJz;qrKMphG%a{`x8xrD=vSa2fL%35_ z-CCpoNgXAE3C~6a+`)FlVdNK;3eyZ$FrBzjIJWu$%=VX=%Umkcqf4hEBmf@A8adD0 zmYT?wX_sxemD>_8I@?~4#z&go6b(<-(Re48X3wkDEA zuabk`c)1>5!a0s8Qp9Twq~4O#xIX$mP6)LBu5e2U(vcH?G=L$@1ugExoeA|Rw_YPy zRszRZO`~wR1SPHI;QuUEf3{erKJ-kHMloAT=Q0-^$ z``{#GV8+jy>*s9AMJYDh@Phgii&dx5$pSW)W4hhbQG)ppHkMKp3 zF*-udnN-jVqXX@(n6$&k{t-@d+}(O_xn_ACFYRhSmzhQgd~p*NC6&J|ahq107arY) z4I5=i<8@vW9mQAT8&b-`v-KYIU(fs>ElgO}QlmCaN6r3#p3~i|x~<>!J{!vdVZHT-+kj>1F8#7#j7&f$wc4=Hh-$jf6G4*5RfbI$79 zN*jGVJKzcxQ7QJ;#8=E+!5kz9hW=qFYK!^lA5g)%ZNfjUW#LG1%E&L&djb&XvWEEW z2-LQR=S|XUshr)@{^(-l(M?*WMShlJ>gvN|{CMN+wtYFK$Li=z>~V(4hjLoM@f{uZ zzvK}&0({e!=N)eg{g8yty%(SCSnc5!x~Vs0TALU8WDMZSb|*n-=qjc_gpL#}3a zN|~m*U(XLMif0i~6Z|Y%&qS5eL{PlLw7*Esab+zkh+E|G@cg1!x$0(3s{UwM*Q|eK zw>lc#V+qMllB_Ra3}d;Q%>5jeT`qRNF0h)M`#qGSs;@b; zQrg}Lftf>GfelpqS74F zT}9b#l}{~k7F+?oLL&h^MXt_Cu#+a|(4^5EG@GF&)3o7!_s@FI!25m0`CL3aHs#$B zLyun9oo!tZoTvmS-eO3o@Of4eG?I2)X>7I@oKLY=v^Xm$&Ck!pyF6LUAJPLlro}ry zq%|l?DO!*V=tKzvLT*X-DyX^%i+yEw#-^(S3CMve3-t#=U)pkfk~Qypk1qH;LO3i8 zUVuvx{CYZ*Sl24xm4oI(_+h^U_IW+m7iBDoBopS|=$RNH{tIY(P8RbT0A;4J4n5uE zfL7@myaL}XkxTA7-Adzj#Y%%4*L%L_GfSmDbOVO_Odwkd zIh+swn!}S9T&hu9JJmV>Wrlsf!}Z^pP1u%`bB$gIeDe_iR>dViIGXhExUd$spT1dY z#FsT$r7q?wW(bq$7FjlTzQKsP#$FvNi( zNY=&R>|3%8F`;t& z0Y#|asnEb|DYS7Le%>yR1E!uUuM3!ntFJU*!*QHE`s04VW+LI5JQQoeG!ds-n6jF7 z$-s}P*N#DslNE@H#efP4N5Zk@8sG#io3J536}9F{epaua~>sB3bYI$?~XnVF@y88;p4Y+kxd$1JsIDzXhvOAMzPDa2>DelURLm z{;j#2UYhj11%uwUgQGIZ_s;UU9Moy8!=K$`9p9mFS(nc9k3AfZJvLx2A6nNs?~)gL z;(z3R`mehw>&w28{lFjt2yKK2c!W&^;^1(s$ZT~ zPP%O^xCDjO0wq2&3TL16ua7k)3Wp`+|GdxgU%&h>Bm94RCu9kAK2+v@txF zwjoI9#DAjP6dlO>JD2>BacYpf*PX+SFhRRQ_#suk%|;Khx5o#G0Y*!Aw=EwseZ6*4 z(0o0DS0X0@f~jo*dP);x2gQMOtrGu2qUYIU`Wq|5A^3ewA^TGZJ;c4IJ(vSE%>jG#0y_ z0E5<+9Z*ghlF<_8rSc$wLLLv0ffxsLsYWGfilW*b+>56Qlz;x-GgxYjTWymt&lB_^-83yp=E2B z*|7NTd2tzO<{@j=#LkPLBfD4%j&Yia`eKl(FzK3oj^tNhRoiLn9bl~^_B+OvU(M`J z_|6TL)6FFJR$mifnrMl95XTpbX2|v~5>1_`IxR_ZE8R)z5tq!}L>xZcXWdZZ{Y|Ed zB4_L%BMb#2k_=%vV~Wyc{e9$@X)Z@cXmV1b!1HArzjf%xeb57)bD#&%bI3Vptt5s7 z?aY9*|IKab@p7kg&PDQP?@HM_pXlyEy#EH%!Wm=u*kA~1;iKl+5tF!{IHFPs-lV#B zaYys&J=HBPB2yAo^yhKNbX5;h0^oouMsZYI%LP5C9$HN`-1E59;1_zRKT{d9u-c5w zPkc);DWW0AP%gNcnMH66hA2Q~gVtb2_UG;l-99Z9YtJ8|UheaA{}y@_GShK@5vg=; z*T=v#rv-M-xs7CM$j_tdzVOM(oLSVpxvl?^v#=Q52Z@5PpgeY_^*WTZ6~|h@kT4I- zT|P52_N}Ns&qu$#lajf+%`2IAQ)_%dhnU|xynf& zDZyog>XgMpljklAQloh@-(V}9o zm`Q!Z(^J^1mKM#GT%fC5vRdQ@qTkBH%4?oQj{N}%0@EenX$L*ZZ8GjYlzSa$kZF5; z{-*sc%xvaduPF12?S&FSpLJ+)>(aB3^ZyvWqX-;O=~e213tlluVTv3nat^hqGM;ur zj;uAvm(i&w_=I#}p~B&(eG}vGz?fd6SazyO$BSH#R=-p11iR!VAVIjy{B|-}^*-x* zKcboHj+`-rRyv`hwl4i#no!rlpKz+ugQbzBD>YuaI)8lk$v(S&|njHU=p z7h?g4IgiPD4uipiI>CvRR~A%jZY0+KEmpQr@Dkm<34m7_LLxk06laVqJhyuy6H=}y z++LMuf2aPzV{=DKr0ycD&(3wcmL243Z!YS~65`g6=%)&8LaHT;U}Z@O8UCqHorRi2 zrdW;dn$9ZIXAFpySWTJ=^(k^FTZ2+hW?u9P$57{-J72bxDkZ*_{Sn(>!_|N3^;pF& zEmxQDf&C}z7Qot-wB<}XJcy>)79g}!36NOHu@srdFiDk0>q5?o_eIxOXK z5%DsaldxSxmIMn@!i>EYi)zk?-F>`U%OBt?bddBt1fo@3?uEZs;*$#~*(&EdU5eZt z`KvygA2cvwF}@%>ovnB zu{`)0@vnEw_TX~$%;qz@|`!u`)e`OTl&HZ}WFpD`7^x?U# zJKifcqo-#b>j`0>r=;E+&ZBfzPSL~0&zV%+V4`NV3JLX6|;sh1~ z7MoYBVpttjG!31#QqG7K*B|B>Qa+&@aDrAkfL%y~h#-8(Q~;i>1QBw7;htWjS~)Y* zCno(yo4e-bp(4}4GVcnsdLs6h$uY(t142F#$kn7yScTl({9--yqp+9RRCku)2XdEK0krM_!jxhN2I2PAj6uK z-=4!pX-_~491*&NE>Q504$>1j2C0?m%n?RB_X@>T_1C3ZN1D~NjOX7UxD0LKN;(-s zyB8F0vyGvuGiGzx_hZy;i)fF%cs9&>58@K|J)1^E1>c_T!!W}6KXQ&*$t;5>ZyXGZ zVDuvV%S*Eo)U8}bd6)(L&yDWanyy!2#4 zfq6$Vng+CD9AOjimT{I0xf05l9Jbg%B*RWxyJ~V&L_s9QL2`7c{2mraGxWzjYGYcxV>0q3f%%w;|2c9<$nnwsXLJ)&r~R0QL@P|Q5H@Y*>XpAM9R`AeIG)0xf; zla}zyjJ{u<7@`TB&uAVxzC4F&5K#6I%FCOaR=V-r!o)Y0>EOu=7+e7x^%nvfj0x;b zI>vqHpL76s;r#DLS;Vy*E%{WX{+=OiSk&ka6fkCU)2M*c)+pDXMKZv*cMeqMnd>Qw zni6ux8CJxKP@;{(+w<&J-DBbg1Q{~P7Hq(d(aQk!fD%N8 z(<=eMFi=&{6)g${qlLCdb6gUe)@NBo?rICWLft)W0cHn7h#}ctp=TC1GSNKY)$3pF z740m({IR@iHlwZ3nuR@$HwP6F^mDxoxr6>y0|P|-u1?F=z4YOFLSS^~xa5>B_r@;9 zTSQ-wU(p_ElVm5#6N;#o&Nux@jZ#1*ggtAGnIP($QI_W84f9FX6gg^m#`@3je@>l0 za1hiuR2=;n78mHziWxr+-mSCWW{hOnXuz#=Zl52&V_K5Zy(}U=0`_UmcVW&z^@SZdc$of{e?TF zs^ky}69V$*H^Eud-qGKGEMIaHGO5>G5474AICRZeiGe?8^9X!69+B3&TE1X|jbFq@;~Zfz&-xcBe+}*w09M|hDA2} z3d;CLjUfu7U65E%>8rNFqb@?*;~^{4XNdIiDd+K=vdLDt0E@taK*0+vfzgmPXY5bV z22^1|5*XK?|G3ox8*2&& zoj38xS9`U^*d$-^+B;HxP?M=6&1gP==;vS@cPU-R@%wyA)0a=Jde|c)28){b!UiZ9 zOymZXE#r5FHc2iCyghx2+vhRFcwBD0PO4SfL$1L>?r)Y{@7UOM*B1Z3s@svDHt0@* z6*-RH(8xzgv+u{1>{1bQnAwoB0MBRt+!ODae=N&{_AR}D`LbQJxjc~k;wS|-7r!w^ zif_YL?S(hWI5$cGO3JaPx#d9(+ElZ_zz&$dPC|30UjmS zm8d`S>q-zyO*z8R>7i6Njc(}748#X0b&R8Esy^V<`%oNbb>U6pUstWF$4jef5;E^; z87-qIyZ}mw>FP}a&IKL8RpCEtGlXyeJMj^mwBec36zn)2v{xp zhEa`?D0&z}!#}76EurZ-ugZcb=2O0#_S1Rr*==b(rhWFqZ^)VlX>OhpQ}wr7=8Ur~ zR@R;$=Y8XR=o(m_=-86W>A5@DG1J-m8E1xey5sB?YHYP_wVw+ke->JKNycoar>?=A zKv5wjY`XQ&)f09O{-?=(+f30C%f^#5V8L5rcaHBi@FJ zQM%0sZ0`>d+#iScwDD8B7Avg>&o9LNJjN#1K=GGh96dab;GdTz85D;M=8Zohu1y;7 zatxh_cY(~u@`D#CNWu~3sTYuN%KRLf*&cazq$AELCkkD`vu2K1a_qx$?A2HnZjG{$ zr$-vK*ADhts!fM!MQ3?ndcbrBd#nATGNL0Kn2og^!ibJgJ_qOM4jXg%CAx zalOGZ!YA=1n=EocuW6(SbF^Lp5bwuqASA#SP-dEe9dqT}%9}9kkoDm-yrm(+kr4Z~ zM}c|QHJbLhALoJ@7bBmbtj=z+A`!pQvzo-@O6fws9^}Y&49i&qDaazbh(5f);%7Ba z_7^{yFOGVj`j}V#8M|Y{&@-8^D!*71uYX=EaAXvkkZsf^@nr=Uz0tl@cENZ+Q^(^G zxBMr;C1vVK)C6=CPE0_zY&py+XLyi}Cd7)>t5H)KQ=rDo699}^qPgmXQ3p;4`EU*v z1{G9(K`IU@?h2G19f-s~PWZtlHuUEC#n@qP!YtK{W<)We-b1PoQ^Jr2Dk+~_*PT7w z_%t7?P|M;H4Fl{!t#U2eRs8eY5yAZ_stsxo&8H{9m#nK# z0(xw-csX%t$&K}g$&;iQ?b0w4*^KOwc0AE$Fg`?&6}Bq@J2zv|;u_E5 z90+eQjYL5KSI1ZAz-FziENjYCz_n2Sjj8zS7Y{&rVXJ`cu2UVylV8!J7_!dOQ2M9Y zn09-v-|)wQi#41p>ft^KmnN;G-aQ=5L8Yyv{z{3g2vWXnR%2SnF?wrm+P}xrc=S*q zca8K5m3{-up-M3~=#6iU^&i{_01lm-)Fa4iX#I8t@`*#kYB9VH(B)9{8ipbQO!%2# zG--Rz0qeqVW71mQU5AX`gvO1YH&_;YOOos+|Ds3lRFWF@UK_Km3XV1_H$pV)DI@()TY=`Hz99Yun8=<49f z(j-Fh?yHFY`&k?%B_#kG~i{!sI0=p!sZ#4@Fmb5L9Oo9+Gh6eGk@qt_d*>daXYnbBR6A zHafn~w5FZ`PT5xhs|9gioPTo&gA z&zwOM0W?(D44euX{f~y~a39@_*_UCkf}%n&p7A!vSMUxx0(v#@_ zHvJno{h$3}dV=4C7Mb3(rMK?pBShRClv|<|@?~i~puhs$+wU)dg;^o`p}Y7O z;{6wLng(kAkemKCotAX0p8UG-nZjxtyN2Ge58=qF#AU>pz40S7GgRGubZ0$o6SD|^ z2P2wr?_a>h;&(gD>8CZA4l2JGmW|%edhw&bL~8Fi_6L?C^Rvi<)H$!2?&MaT9Jl!T zgYYr76*E-9G`chUFJx0|8~ek9A_H!3@L=Hgd`GXE_OZn?1~7t}67r0rPKEwS_3E%V zN8)s*sEu=iqS_mM$k>Zd>8$$)0O3m9D{8B&HXb%T@rMbod4QNz9cP~HjibrC&6&w}1S*YRC$Yck4H9=` zJ*%(Cb0=Q^5;PuDq1u6tL{}Jf#)JNcM_G?&eVYGwg~%tPRK-)1KlvW4S#L<3ZCyd^ zu}a&>%IDuqrEQadxG1dsFN6<=a1)-zG;_FVwdskU&1oR#=y0yJoqO`(zIU8(?4Hat zPQT-s`bYyr`NYhv{!7qAT)BRHGz^#zNUR-n9$fu)9k>iArEjS5viKjIn#0)?Y%6vO z%acK`5t4?B-KAt#6V0P@l0B*kCxsMWQd=f0$;ttF`XW#jD)?TNZ zVEf_Y@SBv&v%SnJb)@hKW6ag^M^cQzSJyi9X=p4GwC3r#O2Os!V5xODn=#}8MJn0OpCLwt+f8^a?oU73|3IjF zGKtb)TC+)^2`;zT_Ix02&7z~~@4C6OoEvBsPLb?8!`jZ-8tVy{Lg8^0uy$yCs&J?~ z4MA)%2R^prn-00X=h0^ue-sn_96n9-`C&)kVZuO34=GGszM9rj-6%3pp|FwV>{Ok4 zq~q6%#k32(#R1rEC`FNt0Gq|UmQ*M9hSS85-+JO_vlQ*teD}}j{!BA$caLzs(U^Sz z2GGl|K4#O?y~7=nJviD-fLtP&T>zqJGnn4b@6Bz|eaabLR?~kWoLCtW_eSSz?Z*4- zDfU94-3nfbZkIZj;b~B!F*c@=hvG1Yi5fSL-d)!{w9PQ}D9hQ&fT!PFu)N#*%+4S9 z#o}PD9%9I&6f3tJO8MMkZjvs&j(Y>IH)`&`5mysnIyCcV6|T7kBU;s&`8rh-xm&me z{NHU(a5clHaGB%L##jyz9FYcxmuUj(O(m4wGzQNp;TuPtWG|C%%WpN5*70*}s|?-=YAV50O=Z_^%85w zavz<6!#dme)@rwssUas3CF)L%*Km@m~3Wrqbqtr=#HNgo6>Jm8yiKe=uGLG zC-*CFS$Qs|F{^+Jj7L6|Qo!H7B{Hi9^_4GQ1iv9POxfSsiYJ7UDh<29v7Tk3dFVdEXZjWbg_L)`XxrZpVozP#V zCz^Heb-fYy)sfUK_~XG~fB6?8-#{v0)Z)>ru6=B4@L1Twtzq2y4-YzIWXXueYsAop zfkT5Y{|o8M#SWR-e0L4JyO?=in5Q1}zzW69s(biLF#@4;|OSI<)-G$smF7`J;*bRqY1nv*Aid9BS&7HrGlP%eylbxe7e zBkdK7w`2JXL+}7QA8cvH(VR-o5k5OM+WQQ5H|{<9G_8{I5I7Vilc03uG6HSFr4{#qTE<+OU1=jID3DQKD(bV-6>d~%Rol?v zS_!Lo5j|czo8#Y2Jd|aEtQ^)WJ6b8CR8umbzU%R#`U35i@`;O23wISd2v=G+qTKvM z9MWr;mDm|7UUp6~0L+}$&N!C3+!lxr^Fbf$V)6@`cz-gs!_Y)Tc4ub^rvgBk+7f4} zjHBvvLA_z6X3*9GpT-N<_pc1a8tR3c0 z@%7Aes$4z3?FBA>#?%7?5|Y4JDg{Tp9v>qFFNd~!M8Xa?%1j!~7EogrgG@Zqx});q z6}oGPTE)Y+`@rn(?rxFvAiRIeyPLwd_G4Q(#Q(8~l_AR+nMqL^xcWoj{@0*Ps0s9O zoeNLB+;VK~#@ZQk_NTEchlBr6FQGV8#Z=HKTLIpYWDA( ze)}^0aR%fU$`9QvgER^P=0u%yH|oCRo6=3MXKee(9QUg=l=-8_r3>@71|H!64KX}bDz9(1uDFvmp|!zfb$Mg(@R zU@AYl+2kohetP`+G!Kb$iLe}ZL-f?`?%JDcKiaDFk`BT$kpV#a$(%!fP8ZhkO?+IZ zG@xYxZ}>??{kyyrl{XbaLsK$IUUO`n=HsoiBW>&OSKsztc~sODoOid;J~eus54rV3 z0TvcbKO8&@Ns^pG{aPd?hwH^W#Zjun!TMx!d1HHR968xy>b(Gq6IQzZV32W? zciub}S0(q6xIRP3z!XwWoHWMzXvg?I?Z{K_DZVHrt`hi07Xk88$ScMOln>{q?ANT- zC^4v{f;^Gm*u7pC$@#iWCZsIV0qloH0rPy@afP^D zX19~|VRsy~hmLp2)ULyCGk)T#q0R7dzjogCJnPHgxuMtU!g34Tj@PC*uZqtg7mhXU zU>%9@PV;d1aU_>h(LMjSSBAV}MlFrPx93#f$|NO-6OFDM!T#VLm+Wpj0C%BwQx%?R zu9|AICS-;jm!|~<7n*$#IY)Z&e$q(7nRx*0a*I=*Q_{$Bz{XfH;$g0i!Xo>=so8Vh z_=OPM-HKp6E_su((#p7pRaIdpqBV{m{rU+4V}hsRiPl?T2Nf1HJxWoIZGBhfwWnK; zd)51ev)a#Slzk;nf+ z^NIlkw1T`qw4QlxIx`pU-`x`E5m~(RtEC6AQvKeK$JS$n8FcPAigHfr7ScTS6-KZ6Owm+wVCPRL66{@Z8IA->!)zE`;ry&RWjH3WlBWH#E z3mKV;XT;fnN<8GTKhzwsuYzW33+D`KhK1}5*DNk18YaBxBHz79vE-%+Ixwzdfzk@L z4oVlTT*4DgJ;tfcduY}jTo^;{chL?v-%8qWzYjahseq@OcGuJ&Ja(6XUV|K<$y3Cn z2$1U?VNt#pUzx~yI{%sKxF6gl4OcrJDE2wykKWD{}HYtZ9bsZPo*S ziUQJjH~B6lZypm0=Xa76Mbyo>ZK~e*p7rf|rltG~3EwwU=Orr-Oh~rm4-`*gXtUPz z$zhsRPMWQMO#9)=$>-hAM)z-N-ZGIT0m(-+(G*89)#x3^b*7|%xgOBx#q*BcpHi;; zon6|>!Tx*x($7zP%x468$1*$HK{Ez>4M;z9X*;=K148l*-{X-XI>7=YxG|_#^l+}d z(YQ+c^0_I8wr_@*hcgHvHS5XCOeV5P7{%ikTHS8s4Mp$A|KFNp+GWmU20Q6a*)K9Z z`*Zsu5yV)TZ zJaJHhngapsZ<{1exq$r%@X17wL23W4k;2SqtiaJ(arY4a`?p@`sxmkps1vQ4vKU!K zpwzPmli2urU8h;}Sm^A>uvk+8nJbm=!;W{?Kb(rMeInOr2;zMNRDwc8ReFRiCX@wDORrh7l+_()bc}A4x9`^>Q#AU^1d+9OX(2Tc6mn2{Q zYu=h+#Csn5nsj+a#}U}hoyr~6jmzLjrVz^+O@N_tz~#?P6S~rK4;-2p_N*)Y&De>G zSKQw?pT97lGYEv!_?PFQp-2a?Y0xDOznv3P#PO_-pEdH!$;RMbb@-M#Zk#pJn|q-r zJ4jP@nCqn|B@wDJkM??ae65^`>$vP+#GEJj6ZoRlYEkN2`f&y?f0EgHGeb)J7AS&zT~8^pz;y57mZ zkm3>i!By8znotnqnsD>h9&ihKdWUHbPVk`W#*cf<2w`X5ZwZ?Z$yskcb-(W@~A6_vU>mA0K&q^)4fa$ z^7V(ILs!$X;NCk&QBAcZwKwOIR$q@|zhfwhF-VEpZ^S}-j(C%_IgJz}LR{w43)K^0 zeEUct)mXoEcrK>AGPyhtd4zZv8(H5=sPQ(vTE~}O^X6(T*Vg(a$*6yU(1amc^o}lf z;(1Y0{!a74EdiDu$Nf>~fA4HJKSrPG?v%YZQz9FZhZS4|ny`ZE^YYE>kvm(lxFf}C z>>Rqg^{Os1zURNIF$^2L$QAS&njiHH5FHjEG79oW+lke;c+AdalAr&nxJr1>+QWQe znYl_Av9`~UMJiKX&I&fxk+{QsFx->+OPh7yUd|~P-8xR{{PGJDfZeDBI{a}G3ucb;ycm1J(jpp;#zYx>7XSl;kNX>i@JGTk4 z6nUXu)`{sZ*&;_wdE`}=`+~qD)v=3N?{kA9gM)eY5K0hvGh}RO@M+bAiFE8& zGlA*nhl1Q$=IMi=C%#EpO%EOMp@B^@&{DK|%7HB2#o)SQ6mXT8Z@dlLx zk1OvjQVrH*-+cU3c9y^HnT~@*!M6UKQ>Qh|8B0;Xy`J2|N zB*GeOT?!mI*I^Xp2UA4TrmPV=88Aj@2o3{A&+_J!AnHY$Zol_vqJngPQI1=d*h1o& z&Cbbkez+vM8DRotL2??w34{Nd%LWHvI$DYpB$?0PB9Pi&5x%MNvECdC71Sy1n;#zf z@qW3Rq{UlvhIvxFj|EP=y9ErHp>Tg8tIzx8ht+yTk$or6MLN$~GqUii|^4mc0E zf7!8YF;6#c)E4=>cWi5Gn=n^roJgpzXjzOuE*@x=j&8PsxpH$;;+v|VtTf>-`g1T| zgjQd}MxaB~E90Vf@rrNkFdob$*b=mZYUl`z8TJHs*x%H`EHzswBX|w%>tg+Vog&mZ zdkK0704GKnI_Q2OFtvqj!CD&j!0MQH4j+vpnwg7J{@4M)>Xwj{-b_`oJ@HFsz=5l|M_O}OmM-S!IROGk{Qp$vi?FklYs9cH=sX; zKi$EMJz2gRt|^?FTlsA8JKN7IhYi?PL-$7}^iC3oQ|2YWv*-_R0BNN(3b4%@nDsMK zdU^&-m*F39L=()oqCO=ki;^~jkHgLpr)!<^O}rkbzKy)w#pD>H{6gfOdgzE;V>-PC zK$bXZ>=ebKb5V0ar|R9lG!2g%G=#O7Dm~Yd^n|3dh%fB5LwsR^jRHW@9J*0U(lQ}> zL~I~0WAby7Io_!TIm>MU{fV2(hx%<6wjQ)QzWtnd>AcQJN*72(H%KF|9sl75 z^dN|X>}^jmSFs=by9S->_DtXz=1YjVnbe20%joEq4s(Gv75jdlff}k6hC0AZyw5qW zTOLiAVW*F>Mkasu&(tKMAV`{zpg34h2iz zCmpN(a-!lJiK{s4c}Z6@muw6`>W=j;+JiaB;dG*9zQY$9V7DXO|F|4HExUK$I7?b< zy37c3eKrCFVjdY!A_!AM_y0}t1SWux!%Q_b8G3LZv4s!AS9-LnN+J~o>K`oxNE#j0mnf#BFnEqE{CKpJw{iYLP>JP5yLdU>5ql`;fNe-~;Loh9{sojS59 zP#=@gp3XQ9R(V?!b>#csfS~jd47ZDw=a_%!jT$@s8v#8si3YcF&*_G;>X6T4BWYq( zd(;rDbUGrBVhV}_jztpd%j~wQp#HK{t`5Y9uRbB#a_5HwwVI9}WnY*?UC zj1F7g=(KHGZ@KGj`zwyri)9Q#lSC8?U$B86rhJ)THUkA4l^Y4g+&nJqLAqa6@^Yul z#XVT4M(<-|1D(zjVlCKu(Z^sjdHC|>_P#%qb;FpJ@!$2f=(zw#iGi}^B$F!{%i5?4 ztM7QCWdQ0}bsPE^w)OWwZ&ZEljhX`$yi_dqotRTMZp)hx3l=8DFjm~c8-FoSWWWw? zB#m0N;3~G-(Bl4LGkY70xfGdpVM&2H5_{>OiV9)gM9-@F<(;I5Jp*H%P#bi+E*m+C z{+`DD1$hM?ONR4k{DM9nIZ!G3+xNhf2qlB;ehnmzluQuw{JBPY$}sSq=Bi19BM8Xr9m zC64MVKsK_EjNJTI%2ceB_i`pfQy1#+;@ZOev+uovwPC$?J_9x#j*v+Voxw(f>q(4c z6~i}j&!R}TGN*9FaFjP;>3u>~;u&B129IZmVh%;0devWSJ{u*S)_hRldM;J4sjB{) zf2u;YMB7<)hv%~2lH(ffVdFjc zzymIc#}+^IPl>RIhbS;P1J}l2sxAPSLgO?)-UgYlLZUZ($F?pddC%Sk) zQye^NfaHISsf=hxANG%#d)lZ0f3=Drf7k)X$$n*4T>pqHyN&;z$_9_fOSY|tmk%|@ z)8FW6uE8tVn#{Q=t@kRUN31^NR@Dj3L}n+RIw~R{`SJyu_)MbqZH|myP=lQLKcFje z=+}RKEua76To0d}?63_JxOvND9=sU%M!^hh{O$Gr4ILj3v~FXPdhb&u_wmW_2TWbe z84#=yFrlje_&18F5pn5VncRmQn@*>vWsQ5hpTq7~d%d_&l&Zi~oH>-gV#wtm--H`o zp6%Tr%tLwn)0&6JV=v~`A@ocf{K%%}sgq?SwkQp{?u1;slhyH^Nkg;XePAWBwXsi?Lu%4L zc&~mU-UwITQV~$@$n$2n4Yl^wuIPpD8^l2fhXj89-tYH%}E(R(}nTk_?-s3)?C0a$ae{n2m6L6KrWz}GFSv%ux`#Gw4s zH#n!b&ePMoQ{Z}FF#x&5i(--T7ort~ym4Si$B$uLnbb1UNTXf^H`@pmK>F9jQITGJSG&w?j0Wj@Mm@SWnD}jxt%G%^SR4FuFoHMi;o9YcV;=L`lx`wATVLbjR z_XZ!ib?2FS^XVK5m2wi?O9Jl++lCqCpXqJGL00s*Ylz?bD?T2(t^C$`iyUyLq}3hC zH#U>Lcc`JY86_0jEXhBBgdx0q{1CYlCkt=ecQ0N49?DT$m(KLd4M@}r+`J^|L>eJK z2j;jEv)TPfpU-l6oL4IwerTn5x_9QkzWf7#1NId_9W25S2(Cz&L-WPyG;Wm!u^y`DTb;b zILv}3tWW+SEQja|khfwT#l63;X^-Rqs{=u*8lw}%Mt|qFJ6bQHlBY0ibFP*e-1}b7 zY{EB|GkE@&DbRwyK=C5V%xOg;J!Wm)43+)t_>||T>?``;YzM?u8qY5YZVFg1!7ri) z&?SB?z2kxVnjXBXgD%t3S9#;$*U_h~ZuS|gMRYzL>GuXI?=T2%<^$$PBlCJ(F<75Q zRpwaQ1~uFb<#-yRCC&6=lD@W!9$O|`5}_NdZrIB+K|vIm;arnV#+2`1;>vNZTJhj{ zdz=C2G&;@4CYsO{9$_63^B)B@wf(ygL9g`e+Uq2X#6y!9celeLXuQ?4T#bc^Q;2EV z`hnkjT<0fGs}4RBp&_G}x^Ji^W^Dfp8Kp1<;L39jF6ZoU+#5Xqz z1dVBA*CeiM4F+HvNOf%D$1VmVexRLuDe_hTu@8f%v|Y?e>Is{>P$D`hDJvp$eWbN~&EpK@RG4cp1nhBw7+H(rnThHXMvJ()Ri2nf zrS;IKMr?I}gm7JOS*E-pE%Fe8qN{?9Sb&jV&^P840X)^#z-rQyT7Qy zOnMSoVq46ix8ZTz{%JdZXiX0khQT-(cMaz$bH)yf2rS#l2_h5*OI1h1dUVxyYYC2IE|n3aZXi|V$*Xl zVL$RJCSchvC4yTjA;c(FVUks?&(nU)0P+lfT#uciKHvE&hqxS9VXXO4^YsEBrwPCc zptQsbf}$y#>x?7)#6&xTg=&&;l(P3ya7WvXLbcVJScc{v;hzZ)1nr2AfS2EhQI2@I zqfwST4|TzPte?myfuo%sbWE{?l0s|+Tst5(6flbsb|xPT$9d}o1%>r+Ke_$e+E@A; zi0m0V2gZ>JV&c}VWH1Q*}fHYSd29TS5J3(k} z=V4L1_YYkh581XzRG+zOQSf$h=IA!}I0qR<@uQl#emj;mALM2j^pWR$#W|}Yh}Z|~ zO^DZzXfpU$UQot}`rwUKu(ft$4^yfX^DDHYWD>7dq_@gBJm~(z?P)Je;%2ZX5N$iB z+55bia;I0dL{HXKmj5QWt9v`JyeTV%);G(G{Ew-S6b%f%LhNZ`6x}%m&3%Fg5?*Lq zlgRFq&)hC{EN<~-h*AEp;J~i}5D4TMgkTD?IyXUt%1u+G=PsFU2|90i6nCWYTMuHo zYGg{DpMR#i-ck<+h#-UN=D3zj0!UJ%57qj$WlzvcQHPR~hMoj?K>wxV_D6^+FM(6g zIeSTBPm)`f^RP=9hv}W8(XjkIoe@nE7MNMXoXy#*BkDarw-;D1BX2o99^Rs_Og_(T zI0}r^6hQ+Sa-JqxF+5-U4fv>_b2_La*Ao}-Ufh{vMT{@dU+Nu`e@%nVbt|*Zn+zcp z-sFB@#d!DI=Dk}SD&wv)*|oTiQxQqZlcp|lPguVz`k z_P}n48mF8?2RE)LDDFR7DWi?{8DQ+yI{qYP{=I5m>0q(h*Z=Ds$7^g6sl z4s>h&RxPCUWGXai{Q0GY2P0YjR;|AF$A~>|KdpXU&s+6PxMv$B=5=KLQVa-}V+czC zF9vtrB1)cihKTP_WpTCVCp}3|&5imZ*Y(LE^v!b-M8>My z_GAuRl{suJ`6-%`)rlh-$A{{j9+4Z`vHj@OqO2|)6nybnU%qaF?PmPbk;#847{iR7 z`G96^JVFVdXk&lqHy8YTjP_l{=L1U=|7(4v2X2$PHxTP8?q{>>UQkWVRx!3QYZ zlGn;O-!J{UFsZs#&ezPHu4Oo%q`55sRvm`uFfQaT?x=cG){I|u8&@97{I7Bate8*G zF@R?h0WCk5q?s3@p;=K|3%S3hrMfhAgYAje#OMPnHMW?i)bzNK_mf+;wS%@eUCC9X2{F2gg{Y6g(Od%^ z^%?H8d~7iwo4rRnVJY%xKw!b!#MlpNjP2ri+ke_*l+0Pa`U?rv>=2;tV)Oo2-LCh5 zo(aYvA93t3=Z5EY=sSa(8mfGzjOyD1vb-9d)B(AFr2v?aABD}l!?2>n{lmFDZrho5 z_ziE$hWSLj3^QK2?hxK7f!H`uVCHT|9UIhfoFUw9*(04@6^%e!uHQLG(YyrG#!yb0b3820$^K4g@@Yw9=uJeq}Q=VBD}W3 zj2HNi&z1xuHoht_FHc#KB`8k8q=DAC-${*`nUu0$b3PaE_9nP53VZxIeq0Ls!^qRV zeZCh060cvFCO4Nq#x&Hr-y>^#xw7Vd!LhbL-XUJQ!@%?E?iP)=Zl=96vVG@bN~CfH zUOiw?Xz^MCeQLq|P(xlDw1m640flC+>2vpPBf4rDZd3l{&J#9sZ~C za<<-l)=s7)1KQBHJEg~EW!wgnYDaz26kk>Z?(5V~2i@z}aeTlF`Ud@SY3DeH2Z z@%!vNPG9U=rGl*fSsiuZ*wHiZafcW}o!Y&#%OuB*)zVGPF3wM!q1&IDZnq2Wf?i?2 zj{4LF4hI!OvYU5za~>GqSQ{byEzXDBK3rpRF5}p+)O@4jKqYRpdTs<<(L3;HyyO0Jd2eGqajZL735o~fC@jyGLZ_W+l&K@u z_deG-Dc0%QpY$l?(vR$}cP#1N>|I!}zHx?)X}91jBy&EmTcsvrXX6D{fTY3tI9F%F z^Mv%bBC_Je_aave=R-p-FE9jA%^BPj1Cp%~5NM}l0VRe=rI}>Kd`;V$P1a8Xidi~s zopsuFm&PyA$9FHtJEV4g00&T~UOStTcbhW*m6F^sc$b81RII5Sl4NM6-i=7)31>Tc z|A*R(W0St~tw=Uyd>Givb5DrE*xHgwm$4kye7&!tzfaab-{>Spa?DGFc_2|Zin;^! zvOgIuGKn;%XcpE_oJ?umw8XcS5JWyaixnt$YHT@k<9LbWztQb)$~9#@Zpxm;(2mFk zQh8qDhWQ3T{WeSew`Yb1_>9cZvSmuu!n4Z?MiCUJ8GHvW5?5xKkZHXFJL<-ev~vOY zq_01Fxi+Lwq`btZvvMpF8-{E@xy7wkQkvq}Y>3s(@hmIT(iw_Hh#LcLMJi4q9v4nJ z&vVqkNRJ_2c+Efa=w|hLNQ$bX$2_;#xc@oR_zIKR^Ofiwq5U$8kh-djS8s8ch780S zo#V`UZ6J$hDLyXcPAfh73vP`B&F1 z;Zg5wcdVa+q6o8$ww}w89erzP>0V1D&dGeNtZCguHt^RFpMR*`yZnIqWwVWJ&g92& zch9IN=1CxOC?iy@RhUG*?M&S5HqpwpRg_SJ=&L~4(-^1v_h;Wck$wC^fRpo?E%o<$ zag21*0}<;7A!m=|>g9F7jyE1#!Bn%o#5PL%11I-LjVV?;JyYwQ_Ok}L*NDQCPhLjz zn9L+zggi5{-(#?^&X4J!smlIOTr>*IWLL|~a*u30+Rl7;b@H0n>w;);zp8u(y=sj2BuV*(|BxOa(SH8Z8! z(s;bYO#SMh!*&s&FIh%5`H4ffI-iMrqKU>!2|;Fv&xE5%?jf5a| z1C@4R!p_OUj#UA|!X%l{mCH}#JHDB68IIhoe7*K#Nvzw2^%f$4Ig9&=xG1F5z`)as zEBlGbqOpU7z2Q-q`khK3#BfpQy^pf#o$`{wrmkPsl_14)wz!s2}i3zj($4)Fg(dUb)!5(_=QCd)$6v%*8UQ z>Wo9Jaog*_@_8NV$n?lTTyB!sNR*o2&xnWxm%`#W7a7Kc+88l`FXi0BsGv-q2& zl*mHl1qV;n1cANnr&dV*3g~@i%SucnLx4yKtK7_=Gm~rSS(szrRCTzTl8(&Aq!F>Q z^a9uu^ikBPBvZe}M|fM=YM^7zOk>F24d(^EkFhl@>Ac~(3e1xgZjZX#E*d1~=T3G8 zaO?|6;RRRWKY>n=A{B;Zel&F;xAbZPjSUfqM97SK$le zC#rE#>6lB*j~N0?BmYBUxjO`uI;BS^;dafA?&r@=8kYhb-k}%r$#+8J6Ajr#5B==? z#GGFYz#JW0Of=h?At|CqbOoN*ERR6FW!+uhns{kyH68k+N>%4_&ib^(Z|L=*B258X z7cI@~bp`0D^@>y)D0M=V;wgoUGw2B{BR2IPK|0s;^kx1o#X`?dNSu7Imah*fy$C5G&?;3LWjls+~q1Ixi(MKhbOFprz>~{S8fkX+=Cux*3kbEHeUo4QS_C#KzrXoB zz5~mSyfUm~7L?~@VYV*YByvdP5fY+Lq*#Aq~RFEDy(Nl`n%>0_Hk zTh7$P=Px3%L!wVg9~NC+UL6@8Av#Gq78Xb^uVqJT#$C`1mLqoK)j+Qu46~5CmOpEhP`h(dBrAq#@t~M7^4W}6d zVB=8MvKYc6wZ-H0zdpI`{^5pSI}pIzDu1um;^hb(ke&~qUF>7ou~IKa-XKPV`<&k) zz}rRjKA@I4-28&YpQG`z3s(9Fzr(xrenNvro-CoyP5D_ZB|icFfvJIDH}E>cgd$G` zRt5G(R|KmNUiTmVz2n&!Jd_^a@#12tq9)BW+bB;Tbx@Qz`it>(Wl}2YFJ$T|Kq{j| zvTCAEMh#o1vs&pVJqY9p`X9!q;s?r0k|OBuk5-&0@~^vJPGr1uwALF5lKZ|_^{jUH zJS@q1Ekm(iEP7`9CQ^buOn|3~Fk{KM>SC~-Z_^`_U zGJp^g%sD7|CZwvDj>XP4U<8s71Wj`baG%IZ3chn-S%mxxp@Nlj7v|p#spQ&U$cmZ$ z7?zS}NG!sR`xM!9n^Z6JO;5_6MukQGg1FwizYIv+6sr#R+m`{KywF(v{RUPhsfNs^ z+26-xVBqD&mCmi(md~OV*DD()lMVWEL;-fT=DO zvc8hd!gJ&TTf^;8o54*F$pJB{i~FGVCzE&IYu1z4{v&JNsv54KPUKZp2bhg4z zC4Ka4{{G+3EX;!4+)f)Px7`G=FCU2}l+jsfsH348MX`SwVk2h42+rr2Ok{BJnM4s-_zi%#Z^afK>O9)!@7fCqQ8+ydL zXsNbv)J3Rh?@2?gaZ+PgWbf=HF#${amvbM*+I=yu{XPg~n@OMKAG2~dcTe9qZO-G$ zo$YKBw_^;3P17bxVwtD2U!YGz`aU;ugZmPJ8f56ub7^)Yt2?BmgYLuck`qQ($*W}? zSLN=9Nj!gc*8INJ^gk-%-JwcTUCI7U?B+~qrFnzy&jL}^-%${Y!(SES2KCNTIBXiv z`Rb|n6&Jtkd@Gd2`zRPP)jMk!DfQy=cMb3cfbI>R00*lrR>l~bgTe1CoUp1VKBTI# z9_ZiC=X6b+ReV*U* ze4hXFynJ5#U;JP6Mn1RuzV7S3&htFZ<2cS4>yeZ3$YWA=t=3Z4+V`s0#F1(?{#D_t zBQ_6)YD!p)x^A`r%ue)f&*Qg#fUxp3ro{H^lxt5j z&51EaMMs$tk`c4$y%XC)KX$+~vKeTb`)96r`bp%2|Aag*qNx>#l{orPgzFBJ2;?P< z#sRpNJ^)c%l&qc#Kipd(h>9xm!EGlE2CWA=xOV49qoNAmy4?w0eKT}Gm>hhxX zPUdATT-DkVU&v#p2;d2ml`pObm5$l)^Tu*|s%7U3=y4r*c#Z^9G zN}zk`(qs@JC1!Z^z*3TDX#0SvFM#P0X=L_i= zE~UU&%hk+Y!UI74^mzm}hlZCA%RBHr54ABeS0`5(+Z3x@K4EdSIUx^T+_7Rl+u_UV zxvYN9XW)y*t-lcCZ^KE}gx$G*K8yv{Sj`}(H;3ub!}A$RQORbRj?vvq-U`er>O02e zvYD#G+e+p;Ww{M0g6ESRO`q+zr=+NzO(+8~P=GD5ERem(uS&lf?>2IwEniPc>@8}R zCWvI}V{^mrkZ=)~od9!n<6Kagj|QXoC^z(6E({N9Eh)UQ^~(O3v5Ii}3yGgT+&m;H z5EHwD1!AW4pN{25oNBP@ zIRqndPL#kk)bXVdn0ZlYyjIsSYvM~ukAr5#pNsnu%iHomdfTdVs^&tfShPd*xb~!@ z6|1`Nf7u1(KV>L*AO7Hm&Z>CgcVaJENmAQok2s(nv;WDT?cb4-{VGIB;Ho&h$irhZG<}ZG)2LHG=#Fu}3 zCV$iVUZvP_CnE%<;iY<(_z?r;ot15g#0H6()ISVk$=rMaqwKQ6zvP^V$t#67ctn;? z?>rin^qw)^P?_!HVC#zF9#}>2HnCwXQQy|ArH5phK5d(axpt^P^{Y*Yw?H}h#ap;&*5PE&QiW}z|C6C0Gb_}9B$%w zlFyW1N2d+W=nu>QSMZCcMeKK`aIEA)c=W~Hj`J7mX@e00$p$&9f`~RjvN!^B*&|rg z4{1J1wh1_swzhTgguJN8MfGRtJuhBOd~}wZQ~ZbQQ%8h_aajdeaWL=ZJpOJ>lHg}g z4jzWbQqPk$wMAJOWLHtRG88)JFJxcd*@nmq!Unsw3qFr<8K}6N<@ck=K3`V_e!3XR z)P6w4Gdor`zHRK!?429`i;O5CqV~v^K40e zWW`}wwx~Jj(fxslIe}>JGI8B+Z_}ER6p~FP{1+(;DOvQo$8aj6Vd4B53}>)XIi{O= zf70doA1=3!eiJpKO1JuQ;{frJM)k}2O5c#CYNln$zFMWq`jkr{s+R$E%ppjNdU{(~ z1@DESkCiohhDIe!6MnDXL%-GC?xxLqQcUN|eV7>S9h*G^Cm0M$dBZs7{4OnY-WqRs zJt{-66_v>67oj`jS%2dIsREv%q4FntQN!h^@ATB5 zsoCG=H&JDQmXspp0JI)klwx57a^Lo)|80ZbG7&Snr2sf}5{A!b*|l zUy|ysKdRES(^{a5mEpWG@im-l*t-cnoF`s|e?C(p$zQfr810XjGI;yj*+o z`pwhlzt_i$lGGUa-u^Vuug_ugCXRt+`&32rzz8CF$$a>y-@g#+G@|}=xyg`cHwB}# z9&@|P3$f`Q(^F6Pl8jxU)yTG)_f5JOvOVT|THk{dJ~>9M?PZZl`6of6#}g5-4jA#W zOw^CLYlFHKZBTe*Aq8)RAallowpsgezY%7U2hQbsH{=)3Jg)rHwlx4nd2xBtXEQ6BgLtnO{% z!`nC(Y7H*ibfUD$_KVsCPwnvRQT8(*{>f6v<({+lE#gQ@F%12I7kVaGbS_YrXo#J_ z8N|9Tg5zT;^Fo^ScEYDzcQg(mfY&#`LbrNJ=VxjTquo##Wn6R4uL+K;^O}`x=A5jb ze9r+E`~W2cNtLW?U9CpOS*TDyVuhs8GcyLo_P@W^PBY$o@+s-(;xXf~b;}D%Eb7@G zCPFjLaciqA);UXL_=Cu3b!`wv&Iz99dnarnDFMDZ4f%ohh~5n3#spc~j4qvVnRYBq z_vL_M?i%$2wsIEH9-SQp#Tgq!Q!nG?VwBB`{46mEiJpW2eZ{T>g(Z9A-@`M{%@bbX zZT!K)sg;+P!R(RIyB0hgnKx}`ChWBVWfVUQ??Ipwa5GvYFdHeHv!&7dsYvm8dsdQ9^v^ky8k1kuLf1P4Bk zrXTPv+NFOg*R3Dgd=*$KXr{&@++KbqRDoG0CHt2jg|cC6(&urS8-5bhbAj;C(J&9r z)+BbykZ5c0JIMSDFf>dAUNxxch#7(x(dlbnzH@Dgzd*sCrf}scF82@ljw^j}4sP4( zNHEh;V?JU7W9R{mty#jKyBGiPN>%?xw)BYV5i%~A!5P8dWJ$6k-$T54m6`T&!EC#R zU+U86#Yev`t*~-cC@&&0HjfeQwP34&&wdb-i$Fhyku@Nul<*c=;vrgVi?@W@59N|o zkoAs25hTgmR`0^+zLxr5Ui(3x%lA)bSW33X1LuEOB*@B@l}dXt{c@cb6g+l}?wono zEB7wu_qE$nr$t)eOl`1Atu3Xp;4$G-#4rP`~7c}fmW6{mn9NMtrnOPbl0dM5NQ1S=O?)2!DmH*~tZp!VIfP)&aE zwBA5pv0|h=rTV(pKUy-;hNqxEDqWjBl2pI<9u&{U$D@jj5ON}rHhaWn;|x&KJ(@V{ zB-NUQ?1<^kBwp>U#bjue(x#N!&n{6XQFMsfVREYMMYl$X2@kpm(fD z;;qXO?IeSrC2#%PL|1Bp=)o$2bxUysX*4D73}W>Id>smkmy!FF_f)<%yRWq!YE@4- zb0s;_V9_A8R#a6Eq#=S13EjY$j7KzriR@GiS7nmZaVv6p6~+;rbz3`R>+Nx!-~6W_x3(Q%gf*=aT??!qX|3JRyu?`1TeL+qgB7X6t& zGPUqa+eA_)chu`8Gts4H@}%zG%W9?*}1@5Ly4 zy6OStqbi%FInmXYK2BLr+`|060^GTkvY7KKu1I(*YYYvmIwW&MY7tX$z?u}wr>tIX zO>DFYvG#v3`(!#&Fg@UwUM+*Dw~%-Wc*wfn?g&@5FoMNv@w`~)4|PHF85_uwL*AuR*K||+j6WiH97fPkmhS@#PFsO_yKkCkFJoF{?y_*Ab_$zW8 zE#}>GKUva4l3XQX3Xq@su1oZE5JZz-7;JO2{9{9nn@#BeP2Ay&tCSYW=RHi&=E_{V zg|$=*(j5Y*4^x7Oo}h^cC{aEQrLip&{6;p+}GFVlS{lY8z*bY=Sc z1d^7)3-PsMasjyRkbzA08Pziuzbr{gjva4awYVdeF+KmEX~rcHJ{kx1MD?PuCO{eB*vNA#ZcH*ZTlymlA5bKQ_Q z(T%D~-&|UdCSEa(mK^}-Bu}7m(KPY4Qg>a|$QG1Ig!P{1l_SG5XXNZfCvwS8P@wESa02|9wx2yoK5u*<$-dS^$jG^euMqoi>J{ zC&G}y=YX?9D?MmNt+Zdx+4HDp^Hr(5mbk1h(zgF2Cvvz~5$1SaJ;~$IN6@Z zocfz|m78{Z<#$l_7v~fI=8}r{WY;0K;ILLC9lXj9(K;8QRcyae0Z%ejS#T_&;2?}J94;nxW3ylf1 zi?IAq5N`TtM(~Gj;{NFBY2;WpsSvm?!EpDFj1WK))_q0f$&cQzXSD86Gu}bKt&1ln z9t=cgS0mcDZ0gH& zry9(TEhDmkZW zazB93+r~i17jV=(d6^XSOC?Sl8&{B- zdC+&@2fvkX8ltzrE&~w#T{BsZ4hl=^pU`0*15`?Jiv7Kr}nDwc&?p^HfJLT7&)}`i2D5s;}YCK2L!0eh41T5}l zI$mzUG}gzgy0OSah)t~`sF=Id-tN2PCnxt;4hLdMpkU&uUfek_gO|d}s(%*KCBht_a zM$=!>t(b3xS0{g*o0!Y4NpFjtpN>Q?$NORc(zY(lT2~ugpd?ULWl>BkO zlkmL8I4y$2IMsRNL(g;_|Mf;V>qi3{3(Bt%H-Vn}HMK6x{mgp&Nez{>{4fi0Gx)s3 z0OGgPHGjO}L|#UYHUO12j1b-kMyFSjf@aB_EqMK&2xacPA2=rG>=Wu|Z5Q1>&@9?L zzd2}!dMu8G&VZt{(E~rF@ant%rsRq;mOQ-8_Q`6=)_8IH-G37{H<;Zi6Ru(-Mws7FoUoV_qt_!naR4P@1) zLSPJkA@Wm~LAgnehaH56xY&nMn4z_WSLLGtWn8hPdK^2$CcCHs-Bs7k^I zk`T2oTR^7&rq%ZehcTI%z9fG||NBw3rR*=mLeytpsAvRkfh9H%XNzl*?yL3>u_$sU z?p;I<0wa>UPK01$V&XCH4r>3-Abl4&_cQ`#V&}uCD)887cRsi?O={m6y3 zTs>~5!-lQ*>YsksPKjd9XUR^qJ8AYckMM&NWr#Lt#$C!gWmoVFWddWp_g+ylBuqbm zgO4xmO7NvXn~Vi|qg^yz-Q2X}+q(6W5Z^ffGKJpsyD{Hx)Q8UIgQ)}7b;gwt@G4*-PKN(7c+G@hYqD#0H!b(i9RIh4Qnw<11R9buLY`)Nyu6% zxFAw^ulLj^`#LSJjic^@AUhD*kl2<;ytEUoEbfC9A2_ovVEoCmrSiq*rB5}} zwK~1Zr+Tc%_7JVeqkCF$sEq|f;4j5ZFd%b%aw13*V-t@KLHP{8GgedTX%#Qo`Awf( z9p!KO=SMwW>|VF0Rv*gdfum2iK5c>^gMaq+C+>%o1P)(m1PjjWa`+c3%;YxRazkEd z7QAAs+tGIh)`yPTpj6@I9_#s#c>FHt6!_=E;C(;3Kr*G1 z@*Vlh2Bd_{$`46*f=6xGgSpo%_K$hO%3bSB1(s&bpYl`~oM$}c-*+QU_;fdXAIw1k z_j}0@jUa)PV)%pjB6#?-KtfZ;+wH;_x+X*zS^GHF4+-)oi3WGh2~(m`7@09NUUK}q zN6{U@nyyVd5f5hFUq_73{S^jskWt?I_(Nb@LWrE$oay8Z79<+vD0JRh+%(hURF}qcR9<48#c-3OEqlgT`O_mQQNzr} z^pmH1=zNh{V5J#GQRH3**h0I=QrHX_1;63Hfp4t3? zr=guY;i}@e(zu5tQ~j(lq5F(V=OOKCoco zz2QN83f!z~*X2fNf^ z%zb#-rAZ0>xyMd>&h+?3)I^BquU`fjfT^IfU|hgRtrX?FLkBlH(rUMH9?OmYeZzL1 zb7B3iP39{$GW&e1951jkevQ|AbQ`$#`rPs0`mNw_+xj+JV=ox2Fi1~Jy1j8+fFg6( zwZ3F`^-VHU8BcE*lM=FfhEz~{?zLZDMP(cysklzev5!;5rQ;9yyi3fUZO*i>_Y+(M zf6Xkje{x!;FLcGgoKZ)5J0Y-*4L$Gz;p>USt99fL=`pB{^?|<-tulz87@1R?U^wB3 z>*@Fli4DW5mNwk3>4P#xBEDs3XjjHhZtQt)ShIkqW(3+2<$sUQWu@14^cV8x^X2M; zk&2IrSwko9u|2NiKK@r=0FWU?5(gdL0AITUG7;u1sEtIRD^!_d95}QH&DU!ye*B1H zKYk6oM{*Et=a%r@F%3JNs2Ihi`2^{df%@dh)ApezD>R>$D()+>%9pZDBC)>U%zX-* z-g`v(6bMJ^VL{PqrYfw87M zdIGq8wb{#B_TMD94T@DZWescM=|qB>%J@Zk)8j(rk=$V84j$2Bwc2lbByJg8LNp%M z9#w8qd`&?>ZnOGTNYKPNWoIj|6F%-ycB*42xnz7ye6ZNdoQKA5wq%?_(5o1q;o*CF>JK}oow6B~^JktXX zi)W5GY}E)1e6=zVY7RhIuDvPkvvpFeG2bEPKeJ@t?ws&?@uG@g!dM}*gO1x z7<)gh;WDRlHQ=p%vlT}#3Fv2)BlJ1N;-E;L~DX9z9p*kx`=OZpk?P|9fb1^6Dpdm_t zANDmvv(gsJA~j(&yyI3QskkkhF}M5MIF0{pTnT84yi0g<_K~mfx#2C@kNOvabccm3 zb`>twRwVnFuf$((9=_pwqkO5CmiGC81!(2}zq!9o8*}G}nvmGr= z-pl0=jfqI~7H4+@B5bEg@5_u|;RBj7Z{K>G8eIlacIe|XWxe~dV}L03KktF==`yz{ z{{!~6li_01Ibas)5U`cKT`y@#y`7FY7(-<~Lbl?q#Y{RTiZyH>F1ZC7V*Q2V>%n6Ah4gk18lHh=vP|*^F9)50IaQ&2e@P5{>@zf z%VnbwPo-DLY}%wp(7kCGY1Qz*ZUrg#H6sCOpU>ol2=H)_x@76B-&q3vC>}I9A?lbQ zZ`k{#$p|Ut7-mBRDZruz ziBt!xw#e=B7xH-G$aMC3A>SkHJq=yX&$+#Vf0X7eUuyA)GgRP>?jK4SYH%OBteoWj zDcyyjhb0Vzj&wFW%aSmQ8^|kYuW3JL)%lIpbZO<2rv=MGHp`cI`;xjSnace6exc@q zqvpgP^{F#reaN z=3c-ouwdGWh*AAyGJ$@@S)YYxn}oWiL}#1q5t=7$|v(NVkV+q>Nt zg7EjbUZ{zNZN*S7e`%12`uP(u_lx=I9wh^F!8$7AAuiGaXH_~UtC$|qq{po8A(8jZ zWhkGPuDfsY6V9I-ZE@IR6JvvWFh6anHbUj*p2ytyrqq#}B!iG;AGK}OU)My@l z3ciHWB&(Ce0m0&a(R5ldZlmMSx71gryZ2Gb`fapn049;aIhb*T_x_+vtfWBKE6d5& zO0m&!TCv$dpdrT@!)@(JoEs)od+iI26>-Xu5|gQ#I@|CP5P_Y}AnbM`Clx4vdZ_Po zY;@rQ!XL5XJm1E=Nmlp!qa;228T>A$aiUY$rzwz=72KE@JpLX2g_zJ)TH~fT z@~Fy=Z)UEwN>h9FWyOX3;r$Xxj&5;D#ou%7KiQ+Uw(QIn)IZ_ALe6H5(hw`rZ1EMr z9ocmQofOON21)Wb@5sCw&mYK(Qn-YMO9IC~|*Yoi$)Ib73MnhM5jtcB#+Xi-!u>IVPA0R=fabSC`%G}%S z1A0rj7lT9B1f;StB(876o08Qt^1DWJ!`v~ly}j*4kVlF;5i$s_nSz$8{K<8k=FG4- z$syCY!Qm7g?T(tT5^kfF2gZ%ELopDw9>`aXH*q>X-By@s>4(_v%frHhUr_tzp-teZ zpc^Ho19$?#Bcp`Jh|eHZ1vi{y-*~oWN&Qa9abVRv8Qf)Ip>7Ljsj0p`Gt(X_kBPqh z`7$44&U1)RxUJs+W+I$=VKOMTlL{TF*)SYdA~~je3??NJAJDhYL&2+@XS2m!BxK@B zT>;M`A(zu~Dy_;dul6Y)t5+ZUlvbc=0Jw<}qA4EMKF?eO;|w&Mkd4jM>SRXR7p$i< zZ@IYNxbt?lMB&*&#fg?M9Aa|}hip|0X~F%4gm??xz~49VD;;gfesKSJaO#>j&5@W3 zo`eulwHcf)u_%5(CFRNyst#g1V?$O31O}j3M zS(BCKg!A(32(@I4vv4TgcuN;IOEVzS9F&Vi(Gd|nuUtmR8lNZOFSTIbQFZ2z0^&Qu zw#CB+5suKXIsVb~2O~f;_-cCV{!;p;?xbq{GobuMQ5i@$SeGzgExxWZ9-*JES%E*3 zz@j?t{Ojo>Qzw@bS+^HMU;-4eFB%-`0$KtRo;i#~c{FOPkitRw>v*}=G#DpUm>Am* zoYwu}w{A>n2Nd~;*9U|GcIT4EW*L6t0obT+fp_sv?a0_;eF`cm@(_^TTGmjUxu3!ADdo%mJ zat<|TVhtJ+%-~0hfosok;v$lAdc{~IGryK3tu>abR9Ue`V_29vjAr%nof}@JO1AwP zaCA8fHez-9iV-vEJ~@~x)_#=N@c+H!ilTo}9ci*^f6Za)(O$z?nyM8D~nQL-ivvF~?u*#}(l+4O?IeG9Y z?^)X6^=J{#)kmQ${x5=iVW;xO5Y_1zWTeLS7S(Aoo&VUIa+wyBJvnbe>?c`LM>8V3 zwoFLopypH3iK|%q?5hWlFRA5YH(;h`{zVUq_lDt&rc|W@p~Mn2GB%iPv?g}`)TA78fKExXyzygGT7=6+LE-KeBv zZ9}NiM}O02Z-2cW>dB=Ky-bTmvLM>5f@#;Y_`)8HN&5te8104`e5nch@q@TzoE{;3 zuv^&vu#^fYnz#pjY%wi@xIe!m(fvH@OvT88L}!N(Te)lKADi^FIUB<$T@Zd862la; zzD$P?bl9-=zU;5>C0w))4oe(v7Vn@63@;$`6M4G?E^ z3970t`knENNfB8Q zchG<2wrub4Rp;Q`6DXLSs?fx!3v-jD5u9S{_QkC8TH5&AQbXj>BO2qH zdHPk=E6#!zLo!=dIRo!JwnRSlWHtKICWI9hK&0QG7L7q}vhf>AftW7&lDWQzP?}Yl z9bWIc%pY5f9si7~{QUW{v0(@>^ev4yZOejA`0z;|NHXbr?Qr7U&8;o(_6^$zo4>5;%m@^KLGHBkVTO6JMu2S{-W%mtPq?_#ydg zXu#=0WW2V}bHNCqdKK~gjmNmTBy502&A3dXq{WowtBxS|lye>rN*M-J;Pl-H`X)9! zn|AFmSqGypW;U|*PV~&<>GNifydNzr33q^(cnvt~&74{xEC-382U;nD562}u+!xZQ zRK1sp{a1sH+enqgN3n8`87UB=0?uR}c&emJDzh-h%1pZ_!ln!FC;$F2Aj0O>xiHDN zFMT5L3eLAf(4-ER9pOE|);TLT>6UiOcGZkIyK~~9C|u%MLheum&@Qn98|L6AfX!sa zr+Nb&MoldQ?F=uN&?FmCLM!Z)X7I=t0F-A?36a{WvPs9i(e6p?{*eFP>m^d7@+ zIX7DQ-i>vabYW&3Wv({k*D=q>TS)$svZo~$;Nd;A9PIV>8)|~GM>p~Qk!00}?HwX8 zmr4LcT2P*rx-{`)NSU6p#nqCxtZW&?i&Q>6#XP@u{k&iVJpj}e28dUsHw#M0K}7p6 zejnRk_WNT|jMd2n`G^fEw~r^sg3NUzXi>7u^HpGIOG;btS)xZF@DD#Iul!7G)ChmJ zoF>Tg-s%}2v)U3A;vjsm&%3omG9-2o3~-1@Wn;`di#ah-x4s#!uWi+s6Dj`w_tIK2 zpVyXFXz=;qesJ=Hdk^rto*`acFwCOMSeMj!q8ID$nXdIGR9j(jMTX`m3G911O0mzt zbc-9O&4{u}k@GyWcS&T`NV&&Dj~OX5ci(-|*K73Z+kh!b@G#1*0*;WWwxP-}`yNV# zE*b3L&?vGlu5OEe0Z=etbks9JiDo>$zn)UNqnMnRQe013)XHHr&JGbWMoC}6kxjZh5S&jhMR$=9E`lZ`R{S-aCy^aEZ@kc;t% z=_BJ1&Sv4zWDiKE5?+S7FZbnJagBY%u@GSiq9o3nT6aa zY=JMc&8++*e8B%gSQ`Cc9mf&bxWW$8(~{i{T0c>vQBE}<-+K_@IQY(F>{DuZ$44-?6q)I5FI z4SX!6Ltu;&R0V(p$thn)$JUuGj4LZF_FU&@@B1F-LA>@5sDlvSOt%cm!LKNjO_70O z#EzWxThz1g?kuLF3sP>2tHYYGrF?e?Oj_YMgAz7UJ)xOWcAH&bMt< z)+(S&U|-U%PO~4&^J9V8*x=W;`nf01;S)gh4*ND&LQ)~0Z5|6WbbUsCh>bQ{5740> za61utA--8~`KbscN6B<(j**hxA{eOH#G566(~Qmoz8v*+Rq7YAA9QlS821Gr~(^U1W|Jjn||A9yM)afal$mBL#Ngzo-a<#k$}4eB!3w339&fN2v>vLytMk zoRHkdn_t;)7cu5Mp&2ysjZ&7&CnELI^~b^Mu{3yRyM;V&Ztp>4IsuG%z!*y#oJsbH zjVz8JF^p_CSp4lbOkbcT0=isYXrUNjX?f@&?nw?Us?3+h?>xMsB zp5&dr(dfzPk4vXJRJepPY+?tmMV9DR(o&28!vGtk@+t0RZjZE8-|WJeP0j5wLQMC@ zwq-3=sStvaEljJFiJOuOsa1&+J~b#O{RrC__+ zHl?Oe{EjrK@W|u=&MC6H5sYt_lz}S@5E3UBNLu$0rQO?~eAX=K9G>jwumM<>C~8Sd z(hYQKqdx?=b%ACnbsl zgL9l){*CR8lq&o_?5!fF_dPrM%eU$T-jq+|E;X?%|1$ALZRxH2y6+K6vHp)rx~`tm z5`H7hki!=34LT3=t<87Z4c9H+9d~~yVW^TyA@C%1z9)TMSQ1g$Xrs~-H?Y8Za)xBp zu}~%jBA-Wlys%kc*V&!3H`Mumb|S(T-%_xOCxXOxHU_4o(SN9=)1Ya=(xiA;Q1?N# zGcP-5VIQeKw6Cw!F&$_kU>slj(eX^@%kd;Rl_FqWufAbRewKxpl_uy(fD=?m9U(zAyp}n(}>|?U_mhUX|RkMrB$?$5E z@xdMs4~{npd~dH_Ncd+s1QzzU0a5Y;Ee6`L`4)!t{pTl6h-};78ACj^Ic44xe<;jZ z6&+mW9D-2X@!4T=Zn+X6?^6aU%ZN+!XMG91in#|guiHup5~92UCDAM3smJwNZPsE(1~biH15 zFLHls=e|1*6@NF;a>h@vOR>SVq34Z^zMhd0pVc2T&0P*fj!p-v?D*%amyKy2F#`n( z7o_G$4=}-YI(H_034faLPBAMJfd;rbbASB?nP0ezP5B&AY(J5L&uJE4@7R?1e857P zovBhI4kHK6QUj)yDLa;Pt>1FVe8NGiv43tn^wzio{pcxr`nfzD zDl`y=&XlbFL#t6SR86q!cXM>F6yaN+&TAjX>nXgRTv*;d{XMVrd=*++VYP<^) zEED__c6J+q{%FHb_Qa8}4> zFTItfX_LpEL=;GDvtelB#;s`84>#S!xQT0wY^$4ZZMhknJn=hEJc_LBhXfjfBK`>| zMys5n-=V{>S6%};&6$aT;}O3|>gjZn%Z94>_aUhRwg_P{e?*Y$hlg}Xl(Pz563Tuw z2!7CW2RELCi8ZMmsxFbdG&K`&$KL&IuKS1cYW!#~eLOFKI(XM!xHK%MyOd1o9F`$% z%^y&i*;Mywz<8X0R4`<_ic)Lc3*z}27`xxsWsWq(fXOD^Zx!ty_y}|f^kOwmh&FMA z6M~m0r;mqq!GwBzoJxq5RWA14W{T~Pf9!sPHdsaV1hTz4?(2Y6(YJe?T3>iH)FQmf zBZuDREJ&`pa=`zaZMaz(a*7`0)cmTVfqJmY9WocGGfM$~KhfXw^X!1k+M<+znNt|e zU=}Oi-oEVdodt&dftiPd+oQk3TIL42j&C+6PCpk=Q2%+VCoktzvwhb*tFrjQVtk+u zreFH;E^6!}>27MuBo9A#S>9@`u<(fF{+86w`TG@hR&S@LyG0VNuW|v~=UpWNFQwXh zXR^GbGy8P#^)-v~D2ap9Qj#k%WBJaXFjoBZ7j+)oVO5aTBCJu$WuY@STN~2SaT9k& zFA(9FC$joZmZ+#sv7ojKYDzDFj;C0efE=@d2A;vucU|Ey;PJboJ@Mr}54_LV1+xEV zLU^i-g$a}RCh#UE4BTjkg>hPF2V0?*bJm@|V0M}<>g*u>?|Iz46KtCX=s+(Jm9mc? zUJOhJn*#L&;kFYme~!9M0)F#pC=;1XL4(0(B9$W&Zq*x?_r6X44@}GJQFZnyKZH6e zi*#?W`i~-ELDi?fAm3Bx(*66(4^H>4WI(bzGgt~^0%66@6TQpkt~|pQzakqHQ*Dm~ z$N@z4g&i(Ln*_qyhSSdjKzrM{xV^`3pS9H9Ho0q}`UsyD9-7SmifT-fG;0yZS-)N= zyI#G+?mVveCk4^RwBeJgRX;2LZwkXjBEaHLz9Q=75vA4*P5@NG?_(Edr_s;fdSVeW zZ{w~ceXNj2zi@$}-)BLH-+i30Y|%EH2mQ`78y^I$uWV!UH~(x~FK z^QjT)!fzhXFBg(I`UrivPU;z4WNTxYDq+5JNOme_b>T;nx_?U!Iw{c=6RcDL#hE3Z zVveH}>H}Vm?6FrK`8*C5S+iH}63jm2y=9ZVKV!+a@Xo26kM-t1g!lh`S^D(fV6dM% z#LIPUpp4+4;nYb?;ArH6aO2YVO%rr{lDNDeC$~UGBiujN>6&iL!yQH4{94w6b+sX1YEY0dFO(E zxA3h{pYj3m=e?RkN_=>2x)V!5W|K);MUgi8^@$6YSGlzy0gT1vM7TCy|A28@o6~;(4H!Sq^09u!od{u8jikpR2L`4P3x`oK zbOsQi>f;8m-I2k{7Xy4&I)dETpC`z3c1;b9GQa-Oiku7(7j(NdENH3ZSLSB>AgS#H z-Ce`Ms|gSSck}`*(wdhTYfF&kBqHullxcwkGR+EgTT=^I<}(Ebfwxu{s1n(l4hD!- z7W*Z3E56b9cxus)O_+NBL}5Te%TWk#wL|6vgWf=RV+y=6O4i3~Jsy|xfu6b1oRI$Y z@y(io`d-#MFG`oH5?UFL*+A>R3b?~V)=!W+s5GFt+ofQRtuk5XdiEY<^BgR(JHl{@Q@oD5cp{ia$BVd;{<%h}j}Lt$!h0es(k6^Bl62TUG)c zowwy>NalIhKNwnN<}>;;B}2&Y{j3jMI&>~)oh!RTu6~X?Nt<&bTa7u5>%+_X*_&C; zz)blpv~A}|G>hTC8sUMt=xiZ29zPNm)~RBEQAObuckWMkcz+D2|6BtPf*RByB<*VS zq96WBB~&Aqu87tfy|`{;N)`qUb{%x$e>K>XS5<|8Z)f^Q`}wELeC92^IY-$z8NvJ8<8a~@Nw zw8q@h)~D?XbTow+ltWq7>2v8!-%&?~v-x$vn6Ycuo$y&jsA<>2)L}PImAN3=tpq5^ zjc965QYA~JpiylD%tS@(mh|@i>e<=Zy9`=c=GHE{v_rl>CWYUlO*36lRRd%xSVD_9 zUaM0@iU9D)>e^vtX`!qt*^*u!<~mcl>0hOCKYqTp`E-|6(WFNKNxJ%{7c)g`^kV&!WoYb_UfUp3Wv&{ZZ~ z%wbyO`UUW7U})?3QDCapCxfzh`rn%w_H7Fy{dn;LtfJD`#qzjx5c!?_h&l3 zFceo0gbLprqOt^@YN{M58%Sso(xOXQO*hppei83Fe)!<@yKmxKfh7**rgZ2Sqaj9~xzA}wdM&$ar1d4ci zxp@>%ua4aJ@OI1`mra^8NcPe4W~+I~FY3g-3TC_VB*w!Snsm@ZelkY~m$_!FQYFsS zCupScp7S}OJ(#r0;&0OlWeI#pq@WvNk#iSuK8fKneT^R=5`YzJ!0p#!DU>DDjBAd_^zdKq_MO%8e7^mW z)`QRd%whr} z6L-XaAbdnl;CD0({<-89 zUumU^o4w^97o`CG||l4i>~`EF^yQ(TQ_uSm;3QE05k?O<2ShIm4 zj-fbjiF)`EGeamX zO5n{es~I(OiO;=BOws*>jQAo;Wjn2B`Tdz++itr_zBOYF+OfdftCJoQy*l*roOfk= z-s~XLP17cP$Qn{l{vYPvJR0iv{~sRNvS;6oLS)UB5;Luqyd@N6o61f|!pN8r*<&h2 zh#^~L8T-!IB_VsrGNWWSBQcrf{k!^n&-vcp-|yV_ea`*g{m1;lamH)8uIKe!9*dQd z(pfc~Y)GUi}N-PFmCqmC!e?#$b5 zR>hMIAU7igKCCg>rZfpYI6i8pn(Vwx4LN%+!+1MeVmeT1oIjyD~b?MI){DA=mbS=iNEB- zO-5Q5Vk)g0(j=#rIPwTGAXtyT6vUSAS%X$Y&^F6cBzl zubQJ)UFT*`&VruV&Qkd~pb>$y@(EnqCa%96+fsi!Wm$b#x$*J*wC8M>o)0n6|NFc9 zfxX1ZHebez7Aq1%xsxCgydb@P;JhEVixXN;K6~EJaG3Ym)qhIyibL!TH~gM2sh%{D zI!J4KMD-*mb)S;5@7a#aIX-d$Y2PE2Q;qQ>@%VoHX30*I>AW+v`<%7ST>9!r^x(-e z@4@cGOWZ^m*HH7q?hqBp|CESsv_VrCvrbW+CK7MzP<1aAxTKD>@J4=5+!aVfKntnL zOTIh{;yfy7ai1nsvS0am;XjkZSdl`3klKV2R`JbqX>ZVimSD{is2o?ctx#hXI=`tT7 zK=erW2vBUQYBc*XUVDhUJ^tP7Qpy6u^Rsx+6V{`a^5aC9F|wJ|+eni_ruT%#nUW(m z2MhK`Z<+=K?hQC{-1b`%DqV8_;v|j|WqWm4JM+Ml*o{7l?Z~`Mv7FC}DE)K0AC?LX zQ+bQG?kK)6;q=!Dm3i|#<<)oeQL$?CzIHfWD7`OLM0Euz>-_qGiO7_wAMJ|EI=nhG87A*t zm+86=lL;R%xmn~}1|#`~uzpS+e~)}4diy*l=lgYs97A$I68#LY$;s-qlM}3>EN7S8 zoSk+^*e2CynIkhT*VsOorXRiEou~1NR@9IMkCrn`0%`z##H_jFgDsXMOGn#r^|E`K zsUKfDiMVqQ*?aLdps0%i4osV#YXhy#d*$ML&q9|iOqGW+n;!-H4_NdlM(DZQ%pg4f zc!fyed$93xye(vO3CS6H&WabAWLrC$Gt=pvxz}m&!cI9f;Kli7uiLqqu-fN#eCTHka-{<%Y=}v7g!APOF%8~UQ^X!$Q z_s~-lGBTA(JiNa0%S!7H^+b`u9XZjlQcXqqOSq9tgUVO zb?wiK?y(GUud}fRctuE1Zy5fNZikUkNyEi#6FV25`#(b3=;bc&eIqP4Vg8QI&gg%B z?&4>3oDGPGWTT6u6JO0QiB&Ti2bWpCEfA1-(W_sI6KN@XcSc~A9CYv-r=Eh`cVrk!P_E8(xQ_nwOjPy$*I`*TAi^Zx4NuOr!x96I06&M$M^_LtF207? zy@F7S`xRzYe?mS+hschgy$@|lE@p0AuiExT?mTD=-%tCXsf}K)_cJgq-+J*Vz~H@q z{pxxsVbPtMf{C}Fxp;}$HO(i9rE5k92g(+tg|eeR)t}_#XCpeGuTGy7Jum-$FnB>2 zs2pHBVMlYm`M|izAqgUZul3>LYv&!WJ&U?^H$m}2Vhi7AbLG=E9Nx^+J0~Wr$1~0NR{c2L{+IB+i ztNe^&6UF@ANzjNd2;+gNEWef3bVwAXU1P4l6 zxr?vJC(|lvH{0S`Mfi&1Nk_+zsQ%Dc?k<^4r^?}fD9$NJB^$jH4dJIJ-%lW3^*mT_ zcF@Ntp`SQ}EQdB*Y_IvR{|gZUSBd&(0wuWUXsPJ5l6K$OXQr-x+32JZ?deHH>sKBR zKHPlK&XUqts)oll&0)I`iJ{_6mdSA`E$nQ`L0+=URc~G!t%p7P;na5w01f4%Bs`x^ zY+7{Ys8fR};oWUIXZ zKeE|@8brb00I>m^j0*GkGP^34uG;S>Vz(^q#XDSU@!<1d=a1pZh83HEnd0I8exH!g z{l+Y8jg5U%a^t@FEt6qq|EooHt-p8c>gpH0eW2pZqf}cj&g&amXQw-T z%TgC{i%}S5dLoEc-=(v4?sPv(mL%SiV&97pa->`Tg;XJ!f`i9tG=5{}S}l#7V1Kc%eDfkw1#CJCm2WJLe~8Jx?j$vti+_^hmzNkc>QQ1U5LHi}+f&`D08 z5}8BheUjx1Bt&eBWjUgYBai&^Jiz{*&60TH;6C8k6L#mJz|fzDKXRv}uU=F;VcYQ$ zKh|Q5eDwOms}m}{PF0qsRWHLHHgEl2_b<-EE?b-bk;@8p}I>l1xH+y3{vj zPWpOjUhWoVyQfYja1I2sU2BjE`dpHnhIv1P^`6k%W?8R5Pf9I3&FKq>-9pKF z7hVqkY5L2l@cWKlxIut~scl2~nckpGxDSD4ogjqfMR>W$R5C{<+{bGMp49)!PdXvG zEA%n+tSULaPqM$4<0?C=E*(R0?6bqvaHNxeb~^aS1cG6c$7IgFowxnQsM2_#|5@pu zmStFVh{egX?HsjeE|e`r;AzXnB?1?+tqW-WnLg1f+uTwfnZM{A%u7}$e@asR{N%m- zdyOc=%*0>qk3hX#l)j{2IUJiuzA^M%X+Ik}@Noajr^<~v!b_90b(>3)!y+hcvRpH9 z8;g@NGJSWGfit1-&YWFMhduP$d(8eU5%KC~)g~HtTX)71t+_S7_s$V%^Pjr$nO|)_ zFKDx}K4o0?L4QWn)Pu^)2NMS;rz8D&l2n9NfpuuJ+H&W5^e?~D-;bD1MjD4Csx2>I zG4;?Gf+yf2qeZE~B`Xd&dGD!D&fRXRIq}1E`O{tLs-O-@&_sXnwxw?`iTbO6IS3?T zYXxN2?Zh3@aPErMt*sf>3X(D^y<3Kv&18Y4<5{$GsN-Ky5e2uLRu4idUbNUvxrM#2 zvVZzhCintI3bHE&I^+MF0-kCav)Hq+PkP?@qR$JnN(D>#b3=XH`NrnP><$2pt%jpM zUXl)oYvY`1u?pi&7i+rwa%!}P-=L{CCSGz$ksGq2g6CnRf*F~W0CSQlI;p=P;B9bl zxh(^!{vCTw<<{%md&vcqXqVOhc!&M<5Bq=q*bf;ZHXDf%_IqVni$;&+igM%=3T)q1 zT2;%YEe9=x&PjkF;K;ar6nYX<3r6P{H$Q>%!^tzEO1GQ-YY`w*+RrI6w~ zZ3HC&#Q>tQv`bwwv3^3G*qU-iyV33vKTF?zK8WuKc-dIe_F=C!WAg8<7?P~c?9Gww zBYlCMA;452qVM-QLIw$t9-L$arwO^OA>$wubBSt4hXs$L8+niA(fWIg8};KGTU%Qi zx0_;pv;5i~YJZd@4FxvvXLNx6=m{De-GktUdz8er0w~k)URQbVL@<{T?OE|VJB1&d zU+tSl{>z9)#M()@51RA{(~gUI1Som6juuG_xM_?>+WeH9Yt&kUWuOt|pz6X811l4%Bz}}{M6Xi!mfX<_gcS!2juf70p@0B?JQ|>Nb_npdK zWG?^A|3KsaPh>t=7X#pb2w*d<0EigSwEGO_aiQh5{>!|hY2aR%nTFgC zzM7?mA+-yU+^S_;#hyj4+2_Vj^@g!obGdQ51&!Dno9~JyQ^b4g2z+!-l7Og7hsGda zP3Z4dbR};A%^7hcx5wvXWktaguEGL@BziJOU;=fK$T$w{#_PtmVcirTb_?Bme9vog zPEKt7Cjk8oLJ(_&0eTeBe+6O)9&jkqu`_cy?)_RQTK*i_((Rh!?fxJ1*_20bCt!}h}Xb}ixvPZ0aud#mGcLtCk>{$Vz^|Adui zGLhb15D)?X&kt)f6%NeX(>vgMtd(Gf!{r?O0C5=p{RLH-*`9_tII3paiB?63Ij-rH zzxv<}ze~tzFDmi5?fujbd+Mb5oN%!Hmg+kYtYGoLWK{}{|7D=^wknrm_dJ;7OAxsH z?)>q3?*Q3vd#VeqDdOtyzK5`tKq8Xw-Sz>y^1q(IWp`&}iCGmz2^;rwTt3O6Ey^Ox zS1e%OLd5-2N`8aQ0B%19;xlqHL{7~SSc&<}Kt^jFL&kEWg5C}L*nCoM%;V^||xlUQ&ipU#4%uNsd*e=NaE3A=0lGdAZl#{f(_3jihN{ z?AsaYM>6&_Bb&wt7(okqkig1Nj0;|SyzW__;;Fh$t23{wK8n1$c17?R=oSwGccr0%l&_ z`u%4CpraiOx{tv(rRGilU;f;be$gql)r!uA**VI{cl<~q5Jd>g4b1-B|GW~FeJYOvavm41E6_mPyNJ3R?UN*#aoKh$-L8Pzv9JtH0r7B|x=`+e;2m z02(g;eSHrxohLAiuYg|GwCR=4JX&r(U(949{5irLDf=FX`0smfnlluutB6vTczCW@ zuJ&%?!9Um-9VWEI@T-AZ|E*toyR9^ZoI2IQhc6FA@p=Qs=QnfKCVA3pUs|zRN(6~S z9+7dgNhaQ`n_)ZgF#-%z8#DT3`G#GgWNqa~?XJu^v9YDPAc zn(w{wS1tI!FmI>(n8VMa;d+=_U~yg23FrHiw9Q35uB(|Resg-9%VT)_k2O=&bdPJY zi1C`>2PO%u`X_h|?aBfbGw*)9&r{?UkKNA$Zx5BYidlTdj2F{bj9K-yUsLWHmime^C2K~Lk~Pn3#NypWzpL{jgE^trZ_uKD zLctVM6A-_5soKotY=F5dI|sD|UvdNfeW1k}XsMk-9GvmZ1^GWnt@>Y}^npr~0p{SA zaS*^v-mvah59IJ9`|;oLT(`>fkbJ{0 zyQt3TF|g?p46rH7MR362zSdq%oC#8WPU-PA3=+ zv7cNjMwM?)F3WepRdZw?NC>WYvF`dX5BPL?NjpEfL-r^&RcDx@)-NzeU9<|BGX#!W zEQ_y;ZXKmIfxL%Rsc&jRl`AQzNxhf!P{SRp z@dU|GT!?ciy7mu9z1W6bafX5^h-?IGV~t(hQTf@!$~eVn6QGlr5Z^mep(NcJKx`We5 zYR8JIXwdDrkbtS4N<<0buk7!&L0f1tFkhj|r7KqHxN|osD|`?nbQ43fSdr6bG@-@j zz2)H1w$!jfr`0Lp*4F7$r*_o81FDB4=4BS!j$`ls=^uXm1Xg?*fB856of#;?VHp-vm+R1WoCy zqyG6-X{V}#GjF}ozMd?g(W$NYu;(F^2kr}f0=yviJGaHx_H+leVjq@-Vx8h{)Otl- zuuk%4?!4q1C1?S{Nd}=Blp$UL$#QBSpp6dDT)U*$EFU~oDeNo}yfo#CFV`Z3ka(aE%LbqjT;UXxCuvjevi51NTTyYajzdvZ^GJW8p|!MDk{ z{g0-fA}`BUaf8B_sTYKeLfTov6AV!r#!r4XW`tLZhGw4b-PS!@y>JE($~BMm_k_E6 zs*3T?PGj9$ex~hms_u?AKX+cXJtp?J;q-hduiJjEz!)@fL81jpw&)zC#`R`ymd6z4 z%~UxV3@U_NcPX9kYy$^bTq_+Sg_~whmospuY4&@rn+Hp0dQej!XxX${Y|pF{(dkQ# zTV-vMWv$JhMt*wqzDbIeU~1%@-{Mf)5@1CsF*0DvNN!p2&KYP_?I8EfmKqEFgd+Kr zX2K*8co(pzT2P8Ra5D6hIr8MIt$yX+1sfrc+mKnb^5R#p-sVTt<~=wg+UPt@h3-uW zh*vj4_y#XVIJ+nEaS2)J3!_(VeWe$^baQ)@Ni&J$CKIsGIs$P18~Xk5Ux)-U!*$|Q zEc0{4({E1ijCqbRzC^6g;*FNP4W{=P`L1E%2mXDMdxPjl=phW*GT9n-8d*;g=!+Rc zxdw)6H&i_K(CPkl_Qmiq+*Welb;@upB=nIuU36@bzwDh-Sr4V&d;j>>ftYR z0r1rssnvTR&~3#xjg0PcD66Z@cR5bg(op12fwi?wkPvbNkL|~3Wol6Assnhqbd9{b zqgKavP7`Y~=Q^{?OHV%FH(YMJ0)o97vXedyfF`JP%fP~~PUpjX3aj1?Im_U4{Sv)c zx4_h;7SV-|#t0gnUBrs4RZsgKP%pX)IqK%er4HB(okqM4#p{g%)D(uTb|J|Q2yW~$ z?lOPbHnd2g44zG&b?86imv=EG-w06RM8Imivr%5FHRDPw%YKUI`_Y>!bMj*xZ~2ra z3+I~ew!jlag=q&NCJ+g~UO|qjq+ESc`MGKAY{v4NqrBUx(fx{neIhKIZAR1>;&7~- zWD`Xou{Oz4z_iwRbZ*{E(6r(=th4QEE4Z&l=I~@D;p>#`q`y4x4g9e&nO@_*2gVeH z7IWI==s4z{z-;qHs-k{$5z^pd!%aZGLJLY z@i)M_a-b)TmgX`d!HG7*pG4HSIZR&ajg^@H=)@&-dO1kKu47#$TK@Np@9)ntPrn?R z`T>o`{LmNw7xMeBJd0&AqU6;d)5V29mX`q~_hHk&kTKV5oZl8|UCPM1S4JKlrxN8- zkH6XH*CT$&*GULTo(mb^(RR%@#qPspiZ$|PCCdCqH<7& zsm^$(m1=72Pp5H>HwF2owcaEXnImpc&GWnEaf@+u5%I83jg4L}`KeQMw7`M`22HnNP?t z&$@xz;KPsxMd3F)=jgth_!;}Xa5A9>`xvy1csM%Yf<8>Ck#bFqt%=N&nwJTK@{oNu zP7VT$u=sMJTA&M;G9#W`YbE8yQ!>9YlUz~tywGjuv7KwX(GEaUy8QO@=sQ2U6|7;h zBP|P?(N&)^;1@dm<*!Z!_aE^dkXzXaOoL*ksk`Z+!+d_3o>k?R-Yn+}fe($n#9cfW zc%z1QwG zJ_*r~e!$uKs1|*JIgR7p4fyK2R3FLIqRThO{WH3a=e3_P&TdD(dKt9>QQm)W)JX1$ z{WP>5x2bQ?iJ6uIFbn_hvupFpi9D7%M61Cg*#;S$4cHKYauwDRYAY=Q=(j54&EHa! zdIbkcE`$tw7hRgN;@XYKfY9u*%n3xTK^JsJ9FXh_LB#`4^b2-;S3{k56#E>ijmJTc zpZX~}#B7IFcy`SaqNLy{OnD?)Knx)FDcR=>CUlQ=2*0?dGCY6hmzdEn7khAQoM1d| z(I$7bivtF;_%uAOY%}BbWKEKNz|p)Ah*`eTow^^7WPa8{A_T$80F_HBYz!b0HNpT1 zgPUg8rV1YXWlTTL8<49O6jNedS`Oc&daYMkk)?iy;DDNr83)?V zCfoFy?-o1`;C04m&{eqT@27erR5zdWi$#BIjye8vhi5~)P)J5un>c!KG7xHy!@ZQ_t!)MhifC6DN}ozRAEYZyZC`I z7&dGRtaWnGINlfO-R}B5 z@_PbD7aiC`H&5DqTzmBSh_0V_)oO~uQ20LUg6Qw!$1UN+?F6(`4TUr2cerzaX_NDa z^Ylkq>DjL|DaxSjo;4%;cfjHL*dJs}%-gYlTx?!D=w+O4zaxLP%J8Q?1?c*fx}ZUW zv_3SR$=*|1_kJ-uFr!pgCD>m3PensV``t#9KHEeH!|`uh3=dZLVkvaT0}Y-TeiqJ< zsohgT{VhPj=1JA1C^OI?sXTKkm2o3BPugKnimyZZPtej7>yo z*Bw}GRJmHj--um`L zFQy=N9MHP|LZ(gwG%@NW%RyOqm>AB}czm-;!nz=2_nMCH0GbCqPTMAZA=mdlQ0>}eD7#Yl$Gg*|Kie$MR{0&x@J|SN#;+s7I&_~MU5wJBb^s}X zgW!puQ7x9m)Xd3|Zr#D+fn{}nlMA?uiS#H&6UI{uf>LMTnUYkCE(fXA1OD1QgSJ|s zYOa6M?_vATNV=%~&RR_0_`PT~y=_JqYvFoZp5*=RO1=2uD&6h-4V;Px*EkENaHzY~ z-;0>YN0(FHFJ(W_;QzFTsXnj%!7S`lS|95dsa+}h4-kOA3c!YoFW{Vib?u4=c}Fgm0FRQ}>>xg$Eo) zzLK_y(pAq4$&;*WI1Ky6?w|W8k!`txS>Q3DHYec@c?8;JLGnGFqZTf%QZb;`BBXlzyBdE!BmW#U zx6Ke-^m`N=Su3$z?~Hyk!@<$}AKN+aG)q11_C?UoPLiA0Rqj|wk>jTB6E9K3qx3)O zmz}QbKZU9SyL|4sfNDf?m{lkAtHR=T7dCbhCfiJQNiUx_t}4%aw{|4KqwytvXcjvs zIRxqDP$S{KewqW^XB_oJXfjjOOZZBTD8b-_5%`&CB6Me(XErvbMa^wZx}4itEO*24IVC+^&Q4 z#9Ck)LGs+R7Js4_z~z&63N2ajs(9S5+*7-zO6X9X;i-wa$nebHpvr(&fm?gx!d*R5 zMi4U)@LbgFVOcYcDGGe6dr#VsfTk{%MX{78UGIb|_T=2&aS`aJ(W( z)|7ets5|dh>X;R%E0>spR6Ou#1KrRlC+0`A6t$YnXj4L&RLY$&JMO1@VGQ3N9`5CO zCY0;Ti;Z^vH&BO7;8BFD3W!AMk~P|nQBAw{(Bc&S=r9)VnveA7dRK`j&n>C;m?`Ac zuKb}hvo_zd(1)L%AsKqXiI$z$R=cB2 z7b@-ng$75PG0KW6jz5#=mTA7}ie|~=_)5dfUntuJ!i;9)TARDgJV~?f?RXrKGq>D> zBIENFak-+)2sXI)aNI(T#oGF!rHiHW!?#Jn%G2|D7p7-e6kdd6?qI1WFre3W9a=?* zPn*H0*-`lQe9sm0(b`d+u;;P#9#e2A4^kCC=H$EQ{H3rdx_=U7hN;uy;XNtD^5evT zyzOt`TUAlif2{lIR)^qu_0RVqzmI1l_&}(;#f13);u+gbYS8)S7Xvpaegn5!yESS@ zr^R(*6!PwUU~uK~&ytGiA`6SAon;|QCJhvsgXMhx9wld~>2ihtDq2hngW;_30C+T_ zM(jhTRcy;ujo*#%iPfe8lNoQT`;Qv}+NUy@UAPKN*LEz96J=aRP3^LS9i^mHddvR) zG5H?y`r@+69U(P02n(3%F1O?}x$n@#skU8Ez5;41dEElriB=%*Qml|o*LG~Yl469| zWz_OT&qyrkvFw3-0-xL_L=|83uEmP4Yp* zg3_WabPF7z+YlT-^^ zjpb*my=t*Bomn)oHef^bn8C>k2q{d53Yrx(-ui)$Yf^8U$Qa#J?#<+ao8yG*_bueZ<_$~VU=(JV{Byaf^GMKw!4ICy2c&*x+Ar=^{G zuJ>#BL}#B=)UAYFh_HFVs$Hg7x@XbXb7^&_o)sK951mWVXSrco0jM{KFB^2ryfrD4 ze1RD~8ZAu2|CqA$Qm|?lg$*!7JU3Zr|9Hs()OJ(~3O1?Hg5k&~&zdz=zIHMVv5cvP z{8MWBPxLs6aGt3LS{4BOH+`0bu3cn28t8VovJy&`Zss!RRlLcihP!r{zWz&L$$n!N zI6)K0HUgY<-7!6-db0cOBv)WD*UdN~O~mq07=F|$0nUy(N|xR`N6jM=*j6p_SGMzg z#7*X$aXNZzx^8cFt^Zu({74{Q*-n<@p~}*9kW(&{I)ZPW_rmNC>vHLzuUek`u{KqY zh>5UTgadPk4PeH^iHI|;q@;hVJRsUbIR);w2Jw>P+|6Y5%VL$Xi#Sla-jj&K|QL}4!&xOEXRhrd!0h%f4hYqiZ| z(MRt{d>d)#|EMa^7I+T&ut(KO$XvSgc>`~G+|kD?=gYN7Q@wb0Iv;speg4$c=0go5 z*>|1+Id;r7l^@s&d?se`B|)qfkwB=LXl<(vQu?tF7f?7lO0xSl$o-+^b`DjbeuW){ z35S4J5d*IHoPM%ak5pqF2+W=tLSKgzYy)_n%ru^!Q`@m}icn!)FM(6l(t+M-Y1#Af z->>C8X0+VLWK7@eZ*jJ%ty{4o_q!H^==$Cy0iLzJrg`kc=C{q&&kPOfEBEx5GK)9z zX=m5w1q1j77D3H@x#)FE;mI0roxsS>bD?g*L%yVdud_E>7|;KO_-_l+d6I7V*>)H% z>E86XwpQ-?>rP;FWY2@-lwuK|v_ zg4W-F3b!5if^xR7e*g}G^#b0xtItc*g)d7!e`UcgVcq{D2JHq|pztA82{sqBl@BqQRYN#_@|zmqDE1Nj}i0TXNQ%W7ddco~JB)1vCW$8~U9}&-kzWkhwgq4G7MbumL(7pqCwD0Nng!&Dd zQ=Ls&y-S;iHue#9X>XZwDs@SKs>0d9=X!D6uhv;}OGDL6b5l`3CNTi<>~h2uD5cr< zsH|a&6yg|p`)cC1%Re-}yT^>aam8f3+1kd95{}b7Y49~@wdv;UBA(63#qyo^se{d! z-qb*C<>1nE?oZlZqZ?#G*+3I{eso>t@?6y0r)jQ{oMoso3Yd@hk`;(1twC?+kSP%X zHuZO36eZuA63pl4&fpw)?qp9Fjoyo#0I`#!io6#cvggp3ygzexyR!N5Vjdj`yU*_?&O-;JRog!0j(a~}-i#{HSXROQ-4Xh89_Ls6R zhjb(bL~g^hQ5IviJqKHj5!%(xwmNTB>*Q<%=0hUp9!co zra6n-W=CD-ul4s!Gcgu*q8H%GA|#izB)!qBt>mH zJkxfDQA1eFY7?Y3E^^_~QbNflCJNABVAwHVg~0l~q!gC+o~DlRMhQ@#led{d7%ir- ztjE6)JLV`8uux%R50rQ41cUvo!1|CM!%qCK{-fd>dKaDyxBv4RD)?|gkS;~Rc4Ce= zB7b#1`qp-oVp;yU^s7&(>Yjo2w~N{fGAtK5t(R=tTuv{hZD%iHWA;uV>II(uwDj$+ zGRDW%Y?|Bog{)nA{=K1z-_NRB4ni?$B!!IW7e#SaG`>=Z+Psy?B@Qt= zc1zURjQv&QqCI|)EbUDGnb>mv107D;m=~y|@Xh*2rKQ5sMd?tVI|<6sK_?OxAa$W9 z6wdnEc0<|Rr>2Nl94TqGkuLtB{Jqk0zR+;s`;$>O<3ns7bLnh-wBh*Xw|g#dK}$$&2GG;^t=4NkF23LX}pr}kZ= zXlC=p=4Qr}TARktTzISh4;%JgOvvrs{=4Ritiw!MDl5Q_2*dc07?Lc$d-K6CiNDd6 zzt&GA9ZFG*8$5KQ)>q%ANdC!Df48+zk9SDmQx;QKO!GpW&Dp+15fQha;V+CZyyZI; z*dy}XI9ISUuiY%=>PkB|U5stY-Ib>8`^Y|4yh98wTpJFLJB(_miNCkxNG+@1qNyUH zTko>|;PiYg9?4iQr+{UXg%d_@cg$;kF}lQ*1d?2QJ|IqF(1HdWQP$K_#B7?wNJHIRDr>W>a3QzLu9 zVnCLL1Faw&ml40dC!^~kR#1pg;wVvc= zuw!|u+5dS@ksdPTlbv8vhIPG6MoSWkXkt{|KMavOvjkcWp)gJA_7aA7%^oVaLDwhV zsHv{6qlOncf0`1v>8$S8@9Al|H&A1!YWj>{;@JbWNw_;!j9StIgN&0Xe&tQ+4tAA6 z#b4Lt6h^;~;q}#&4wIeCh2Nrj+`e|Ju;=^KlWPI#9^A>IZ&}3NZ1KuDVwGN#H(qO= zzH2HviG1PbOTuijhLM+{LTgNEGCsD3$IpbkvTriKZ{5)F;!?81B5GYq{ZjT5F%QYp z60qncph`~8Kv`1*mY|zfmEfvmjp++_X|R7GwTz;PG791T&feDCzT@dWSzkCmwYuhTs5QI3`1I*hm%7gR9!OYGHVH3`)VfBU zCU5O`KZC#>#KhItpe6hE+pq?kQ;0nOsoF_$b4x>^ZpILsYC`@S*c zc8^1SVy#vjWu8&@>pEul7GLgjha(Ns$G)>_hE_^?C{9&ULl=1;U5#5Bdk5xm&Xj;; zjhtCq3j17>=J<|-iff|CQ5%)67e{1Xn5&x)T85H!Hxbo3^kdA~SuAgH9J~(Jk8Acn z`l}_IIa?R7H#h%GPJ?1=N=XNer#24Cd=kZ=dkmSz1SSy`EzVbno@2`veP*v2sa}^= z+;|IucIZ9Tnd`L?a3?A#PJvNKda{uziBGv_%3vm>)D!wK?*2;^*!p-Cftc5TON{-{?*ZFp?T z2(hV^OQ}hGmWQ5~l*^vAteE4wiX&<9bp%kjY3;gRh#9eTrqQV8=5kf5IokurvTjPr zmGYD-NKd>9^)%ju;@N&01>9v>O+E2*UM{NSduEwd@uhheR%wNaH;_10irEK)qsFoP zCcXh3a%>b6Z0NgrYH^=E>_l^Oy`$GAsUoHK)3-z2O&1DZH~as{7=}RDz3s=aeVt&G6@|gp5r{L89Q`08xw7#zu zK@5%E{)Zx)-PEDq`rSsi&(P%aqHCZ{!Lu8$LyN(7jHbZqDHq#er+}h>Zs{EIif?_A zRqe=&(HZxg=|$y3cJ7#L^Shr9JFz>Zo+}Mo=+3r4j4_WQM-lOIM=ASH9%>otliR9^ zrwdcB)Yk8qxHTS8z@4;csh<(|fb_{sjUagO{=r&tZE{U5QyVQeg7jZrJy_0`NSUXp z!cmD_kdr?lOF3#W6K!1753|k`(cu`F(t?+ieY~dB+AmAcU>U#MQu^4r%*BM`$X8Bp z{@m{|lKS>mWS3_0+n=C=yA1)x@Q1xIUd-8+#snq%nnaGn*rP6rFTw;PEsMFqEgmk? zoq6{*k>fuw?!Qp({|}#A^|ZKkn{j}-_$&9`>-c)$K7JTMRY&xilBYm5L!8V$!5pPn zF1|p1p)YvEM?b~X9<`!c%_fi6@+nn;g&7*|1^2qq%8%0^drOUyN0&1A=QPjI)p2}ebI zi0ieYDq(xvsKI4x4%l94oJ9F6UnRPOxS=cK<;xM%2Ky5 zvXXx1cK^o9UEoF34kw%9llyvcY_(R9h2dsfa^din;P7WZcSSs7huB9_>jjE13p;2W z2mIR@VYjx#bunOLpJK7+4DRCb+SGwSR4~XEV+~us*H!u0J=?0cxW#;UH|Sr8`nPxR zt;|(I04@?Eh*m&P!?{~5iC9jbruoN3W$`WJ>clw_Y5(c0z+nm7)Xj5pW1#s9y5Fgq z6jKIj8ng1FJetXc18!61WT4pxn(nJ^y9BZ|F0M_9ild#N#&t1;NBn;?{KJ#%QhNIh zJ`vq*x&>}?l@Y!QcS!Ab;ilEvU1TY+)S$feG;61_o*0r}+Oa8Hoh`YwcBUT7t}OZ? zro;DPRtY{zj=L@SR+(RLXSLuL@t8XBicpyhal9@7I&gSHYdkw;L)&M%!UPw3rRG<7 zYg^VoKM=@UU*pOZ7$!t6kS#FWbOcb}hf2(#Zc_AX9<^nQ4mi%f6^Z#or(m<*Rw$6m zvAuAzIRZjG+ZEbGNN76+G%rRp#-+2eh*F)2W~_l4DdvY-_KBuRaPMuZGIq8_GMv1H ziI?NNHE(goFS^Cd{KwnpEb?-NhZi$3JsP2VIp7T>nio@H(6}A?S+Yd7drRhu;id0# zQl|HLT;H8D{K7d6uZDIEbI=7D=_u=VwrA~j6=k&Em?`?=WCxa&+sn^i!TVQ zEf}(oR)RvGobWhZGnlK)VTxv=k#iqm5HcUuYx?sXG>C2mswY1Zp2qHsSt-H~4O5R9 z(33D?nDqm2x}VP^n6w?eW%V8@&>fOiskg7YIwg2nh_|{jIg<5T>VMkKifvrTUyDOA zl;cntq^%9PrG7u;g+qD1SeMR+fd&Bhx=&wW?IZYY|G6U2X|=zLW1R3|*ie+|Nsn-# zOZhJ(SBehCKvC(;Az(+Yl>+0Tc9EwpG*ColuAeNZnC@Hd56~0JPzbqN$RF5O7;%fH zPPd?@Q{om=D&M0l$S>PODO%Zqeta4AUYAC~TwOm@0+;|@g%xySj_NvvPuoSga> zaxs$t(8S)ue+&W=5PMn(%t{>oDd;7?gdal7(`6}I-GAtQL|NOelWTAfn!?(V4Pn19 z^V#9kAL;9xxEapN1d4L|g50Wub3y>F46406)LO0|N?|I&PT02ka#(mI)#k*|ski z^W{EfB?gaWaxn)VVfwJMwj_f{kOxFqblUfay&GoRgM_7;rAw+jFl9Fn=xo~ocW;T3 zrPKqMmy7loJ%E^fu}i$(p>vo`>WQP&rJa}ltYh+tB!b_<*J6@b2Bm0hX{x#kP}wcB(2KUNG?Q61du14FF5NdnxH=%r2{G<~mboF2`!7C4)}T zE&3OtIgdh8Nmg{FdLiSUl#DZc>JJ(^($1Z>{1EaXgw76$ zxcs+kG{^*wva>r z5@@n-=y`AoNWcTk3}}y-PQX0}25`bAIks0ThvK)#0?)~Tcu01rs~OxzM@$M#H! z*7W|8?9zIaH(z1xL|1}GeX0lb(?=NH-#BcUV59cSbnhG^6|F`$XB2uca+#;VjQ-Ai zL2{sLp;@z1*p+)$sZ>GnT%%O%BZg)!FI)y$HQbKoo1~k+^W1Q-d^uIkzGkDRer}&c zIMF7J#P22iUFgXA?y*(aSvY?SYvJd2w<|-QYHnAh} zs!i$M?{LXdclR~1OR8q{^_}#^VY(KEdK{NXzm-eqQp3qnFWP0h#r_Mi;}HEgx>Oeq4-^ zN%;-h&uptyQHt{34Mq{L;KT@0EpDWD8NT83=1e$Ne|*#=p}w2e%wf~@m2ysIfi9}& zxi>^r0}eBO@ZFlJbvDFWDq{qZ1EvHabtV;#|wzODekTqoq}vw;;pr zKTx-nbFzh@wwt0IKQ5Qc4WS!RpzQ-%Neg6K0cjAPa^%~{6%)a}{qlrr)8l zPTu;w``LRx?2N`{jJ6Q7fAIX*v`)?JE2QG2!5|~v5T7xTYfS!dq8TO}&q>|DwD3Y`H5@@36yLKxoZ>47~3D2h_)Af>+32bjQG9c&=y^dn}(A`JO+_ z!8ELP-GCNv;I=7nu41}pwuJ5vCi>r!8p-$;05w4VUyv0^VOx~zg*I-r(iCyBGD0I~ z?PK!Dibi$7+11!RAKKAjBC-Q12xRYaNJa=q^9|e5^K@#ISNpw`Psh{QWj~GGofV+< z_ES8l$!Z|J$o!@8G8i7UF~_b#8OiW&jWB_2)XpV=N`0_I_*@2%abR3cVUQ^ewyp|2 zVZT&aX20^Nq%AGEl{=*d@j_&7^gEsp%}eV?63s$g<^g9Gbv~^X&2nIIo>ZSm3`1`` z`toB=#P|A0Ej%2{4R^yq(m%2ObGk@^Z%3kzID1P&Lfc2bMBNVgyD9YF(s~B8BnD6p zB>qihuy*hXA;UdrLZ)*{%#s7WzTN?%j`FHesm64#7%mx#WYQfhx6Jb2fwm4rrvui6 zF=ekETdb~|dU9N}h<nZP*QqiLcna@ebMiW-rsHXGRoC8jFuz^Z;GA~6_)?gvoKer`rf;t@_t^na zMo-k24bNY1tuM%Q3PZZN4k`>Kjn#Th;F>1<@(AO1D@ZfppEqos$1;?wSub=`q)!|R z64@W^Ze#%2GdFxlqi8kJB~kC_n%?Q5$180Ojc+?;bqe!X>vKP%>Gq;*>9XcEDcq9; zScuVUSVE)D{n`sNbqcv_GZry6=X<8RC&ho4z#-Y)unwS_g+(0KzB##;sS)Mt?yOWs zt5tTjILo267q(0)H3p-t!mC4R@?gO^Z6PT^=wdc2QS8OHI|J{o3%VM3{>nAzoHFhP z`0Y_D541((JP)lB?JObRLZj-;;O_D^HwkwDaHdM>6qwUG8I!KW()Cc2tK99q$af$| zf9G#RQHFj!htTvh#c+nhr6BvkRqD3SzY#=|H8bqDSh#v1e|dT9^3M+;LCe@Ljna@E ziIe;s%!;6K2NZl5ErcMkHuv>x%W_AzdPF&tJJqQZ0Gapt@ zI@CM2#k^9z1q^vsQtCI2ez2YTP%UK83jpLkmLUxB5^hg*6Hfawirqhkt8)JVjP#_C zEl6IjnBoCuxf}rH%V!^M}1~1wX zh=JCDWTO)RWGxrk&=W8X<_cjzjzDrJyV6>SexX70y2t~E39qs-<3_Vsn2%%MJ+$vC zwhZyq7sr6^!g4-_9b2st(yMSR*z?!WG_Agr6(n+Ir*~*qZDpK5BcG2LOs7Yv%_0OYg))CST>lAA^Z8+IBdmZA?ZK1p7Unb?v}r^&K=(U zDtomn8#{L)gYZ&echbW(bvbaIJ&`dzz+G7 ziy-fkwNdynbv*l8rgN@G{^0hoMb=R{i70sv#CQ0d`VK4Dh(u7@u{MoL0cAJXQq9k> zPU4cO-m>x`vd!o}|iM(aEPiC-$D@KNNAnjnwD_9=M_h z@s*lUiYWd$WY~UGgp#^UcU4b5w`D-eCy(N_H(I^jL)TcU(O=-@%U`fzSZ*ETQiyTB zh9bTHjLhyB==LJ!X<%Vn&%jlGsk1xkzen3hSV$flfnQ$&v9O8zI*?c`Z#ZC_I2n+) z)+-Ex77`v8t*Kt{mpJ@c?r05)+eEi_pk8|3zX={)TSMW~Y-!Z1Ao>;g%z>i76ZW23 zJ`X}Ys>>9zI{+*2RIPzz<%a^cYa!rla+3NClwFxtB}Y)-(oR6IRz0~P%E=Gbx|YVaG#0{!#dqQmBRoA!xE6C}Px5+A)e0^{pCfomWDJZAW6IaO*2qD`zD=Bi7`2 zP0vNdqICw0Xr43yw;B+>Fo>qJ*Prh|NN=LAY{W?^*BjknTQ1WoZ6P0_Mo}CIUNJ%$ z?X&R%fsJQ}*it8~FE{@P{c-T?t1f9z+0=%X+iI?jrXZvXouXWC-K8 z8OM;TbfUl{fN`>D0T0QsP+nqV;OpdGP!LujI0gxXe9e~toSreCw1F#yFkfsXXQS2M z|J-yqCoC~5F8hu3m~NCBy$|h3OOegmY*|4|n)BKhRvU#yR*H&zj{pq)7hQrczB;nO zg@6bAF5ku9A1*vT_u|m+-Rz4p;cGS#=s*fbKOC^1$K;xA9dP*pip%X{Y$F7I+oZ^4 zBc9cav$9Pe7L$|dzcPW)2s7_obzlk}%4l&Ab&?;DXual`KG}GAD()708k5dxF_(27 zX0fH)K|taTF@f#}no!UF#XDZX#a~bK8ci}V7|(k{RxdEkVn4Y@7kb-G1+R9cM_3y@ zA$W>t{;;-MP05)dQ8?~vpSY>*oN}xLUoq0YJPKS5{ zob?y6M2p@%I1;3Hf3E&T_ot>$8*B=hg9CyCNEWd%2n;-DkLj?J3KQ?B$7TEQ81jh6w6wJt4X?aDrIb9A=ac*Moyjk5aJG&B$GO%=V|zRq2g` zTqNjouaL?Tivc_K%Uu^MYd%-KxHF$}_u99Xp<{+9RWLIcp(7-odBkQQF|QTPPei{j zb+x@~6W4X1XgN;>u+fdg)`B(sd8Xz3vBb8t5jp=O*{W3e z#sjdG<$}$8r^~g{Lz?U}^p<2G6BD2P@G1O4lZS}QE1vT(3G_6!d^rs7gaq+1hZ>9( zxh9mbvmabx{%J^x<2!2c#tHgg+xMv;U1Q8Ax9IRK-Jj0xERl=+a z`-}#0Z=X9QeYbC%w5G}cJJZXzSvH&q57?Z4f`$faDsa<0paRYan4}&cs4|IF@VQqQ zPLv6;g=|UOilW%$POk_J@oSnXB&;Y#MhwRp!ZF8hNLG>5qPTD-tiFHuhcH z5~t%<_@bRYfFuH7jRscER-yfM+L7@&PA-ZH^%+G0ESh{PqqZD19txeA?K>KmLh~QJ zO?s$bJPV!n`Vs_>g_rxszI&M)x!ba)9%u67goi8b*E033yxylAP7tDdm?3CFv*c08 ziWp>!-9*C%x#g&a^JlMIVk%eOzwm*jZgh=3;hDC_^M-eo?tJxJ#pdmKe~MJV>5(sx zL}SjUqZ8k0iV%LDQDJ)Ojhemo(n7lIn=8dfHs}&q`eLaUa~S@nCatbj?yYGe|Eq@WSdWLeaaWAG{Q} zzwtTl{zx^X<3tw_anUp`lri-^{I;E462ls>9Q8!`69gn39VJfwNYr_i##YGr?a8Ki zm>KLC1%7bl&&E=3lDn$i8;&H@v~-O5^zncGlvrehR03U3~l6w z@rS7?ISK#W(cdj~wTFjQ;JjYn>H#)X^cS7@pG|PL1ipQ`Xxq3MlmY5}if7)SuoJ!t zjICxm+MfTkx9AZikC||m!20|l@JrK}QTou?QWPSioyG-fJ&EOxwD(sX_B&EtSy%nL zN0YNB1a7#J8%sw4nlJdG5}F>U3lhM(qOZPx^57;IuT_+*f64;%}X4%u}#{X*McT@0aItC`t5l!tx=NQ!;OtmwNPa`dH#Y`|$B{hB~Ne{aBwpFqB)zOf1vP;^w2M zaijSC(?!czNCM#coI(^4NO!(OvwcQrNJkapQ@lrtN}Sej4yN5TSgT#aU%ebjYZ<;2q<(S=`m!XevV#fdlt>PL(tXAFk3QP-HXH79T8U!*g8A3vbb zq!LpW<%HOCP35d_2vno5`S0+yo5=9lkMJ+Br?Zujq5dAE@^x#{NO(Y6!qJVk<*&_; zxFR>F-cyRWfnZtrz<6!nz}42PiM3G2di4ht*ZDf-Ub|-Wx6oH)X}&RZGyEg)bwVkS z#4VsnQYJ#PLpwCp&h&`2PO=UbZF6`|DVM>;1 zk#d`|d#1D_uZfdwu%j5KX}CTK9TiB}bk;Rmvn+2kOJ#ClwiAnNECK6)L?Z{DatcGV z#`5Zaz{;AgPm<`<;`fsIlVCUJ@pkTWxXIt-GbI0ndY@d>edzoQvQ_k0`{1}ZbI=>X z@18H`KEY03-;D$i<2v#gI08@mAcin=Z!pXc6zs*w&D%d}Cm!9XA~>)^o?_+UX*n%WETf7(oo|qxpVIu0&(uTi}OJgm#iVepoAN(~`%z|AGbOWvvdN&9M z>Tdg295gFNQLLR~sZk3EwReS0q@7D9Ani%{PI1A!Uo{>KeIU${}~khhc8 zOWHtjN_gK_(>w1Ts)igo}b!W8!sR@6@owE4gJR&)HS!LiFm(|Wgk!jUS#JM#L0A*NY_ z85>8@B{3klAlRk}WBadjUydA3NS7;olO8`)r!`24RhU{P>M#Z#<@tPcD z&Ew4zTDu};u@_ln5$7(=rI`S8MPy|Mh4+RVn!N^cQwpi)%`nv+ZkzLs|JWmDBEsc~<8pD-(Jo|Cc@^&g`4vNMH5SAswA_0uJg`n z8`__uJBV?eg`{w?>YMymh*$7jG*RS~;G5K8`#kB~uSXdiAAS0k5*~UUR*2=L$pFZ_ zHx#&hLLZ1@D^DI3-MUg}AGvgt&b;&Nsd_I!Waeb0_)z1}5&^j8-R{Db3?X)kc~8IE zz4G?vNKPFwx%sV_Cmoxo!KfDrR%%|IJEA9y(ldRYxxe=N$=ir0sudx2BFQ1XZNUxm zTOoZEO==u)Noi^JQ(d!|4DeiPA32<%YTwgeeHC8+(}d#6xQu~L0jXO&isV9#=LF%h zV{R=7B_&~B;DQuUNP6B(u6;%rPwdIls~&9gC^Rmcfy*j?;=B?DKPtnxrt}o~%Y?!Q zrirN%_1Nwn(nKO60T zb)+F;ol`#E34qqdI#yg6Ht|z%+w|PO@e)Qa)@V<~ITp?!)4yvVQdEstEIS0YrRwkzvz!}xd_ZHn^;E4s zOH}BaoYu?QPlj}5yVBC6()?ID84!K`ufE=utRcrH)OSG6c@*Xpy8P=J0ee5KGU%1n zBFiTGf$uu9#wf;xh$O(neB)S4Jb!6bodYh~azfRYDNiG=#8+@@*;3t|INn(wvN)qJ zBGlRk+)(kXQ{CQq0T|N(MXA5&>=Y87J*>xW@m)hKfQ(ZU;;F5F6YeQmkZAORx8eaNF|?TgpqNlqYqIFlPI=XmUoIQSp%V(9*VaUA}$LEk@Y-2dDA(YH{f76{02 z%m%9y*wnadKg^>=*0s?IL|n5GZGWN-X;g98)7g6WE>n++k$d4_)uyVIvmLw9z)r>% zYv9NQy-!l5f7__mX4^7~l*|qJ-bep^Zrf;zDfN{|rwZXCmG= zh4HNHziX~rim%Zd z^8R0WF=t^up{o=Oy>>ZA-W5B2j;{fpu5Xk70VDDsG3x(!@4@&xgS^nc0Zx*GWaL1V z#Ba3&P{3qlQS;d}PYc7RsWZk*zqD^4t1ug7L?jsn85TmzO;6sGmKQVViC{1VapXxAINk5z)oj1q~LUw=ON^_F*4`HiEidJMp$xwn-wW+I~n z5$(YE2?|^&a))oS1{u}zpP9BJvdV?fiR;ENG4|;5EzVZ=wsza-!BIMp<9lcZP|m$t zJdlX=U2~GuYt8W)n;TuxsI9r9j?rSXje<;f>$AuYqnd4z6y9@}%8AB_RY}p>SDrAp znt@{@_I}Dwof?q*z_T+?q~a-H9^^Jcboc`rL=K_~4<_N7*yfLaLfm_`|rVx*2>UVz42jAfwUG8c17 zQs!Isa=H*c!M$?1cEInz#OZNRv+4~<*rr*oopr=f_iCCn_L}j~?CsjnXUwjXP$KUc z##9LbmREuPU+kr}7F;$~4{l+Ps0}ujlqwcE7rwif&G#uNHS5XZf`!J;GB0y)U>fZx z=lp>OHym~g#W|7-!6lmcTa9+~1{aTyNuBX%XML#WQ2<9bekK0I-vJJ0pyYAU)A~Vm zVb`CVp2sNVhNmKpVAu3KSZHn2NC$^(@3kpu)dk4^af)y760$X5P$y&g7T2=C|qfsBc49=j#|lLJeKmA>|89 zffQxr5mV)vYw6RaF`O)$l*7>IN{!JI4Dg$rcWGCO zcQ`a4Q9<+!XCFx;1ZiZNG!Cr_xJkm8dkTsO_fiAjBkB?diciG|ssm$X4tr4?>6N3`bSf$0DDK?gt zhkPOSIgCgPbQ6qk2OBqAVHEKpAZ!%}7%@wOQuQfuCR#C(?T?;Rw_ig%Qk`kMOMR=y zPN9e3-V2h0Xh|r_6kqSB31g$^eqa<`94vJVeRESjrA+Si-j`RWVKTT6@Y{e8^`zj7 z(s;&~38Na%ktYqaS?_O6YCjLS(qqHb{Ns#SNO<25zZ|Y@c3$%osPZKsTxX#5cTzM+P%Jc>CatQV!ynVj)43q4B(uOXP z73i5z^f-rrCfkojxJQ5L4he53vUF|zglAu&5QR@UUdF_8bSjko_l`CkhcG5hOEy*NNd;^0*7=Yyk4 z#1X@A0w&^~S8b+xk;s$7$1C9kxsSAM>~34&i}*?U45L@x&-2Wx3REvI=gD4we^yIw z?SZ*9fFqxeVV)ok=98ey8}J5cD(Mzx-3XjW^Z~bkC;_u9p~R2nmVv$epRWe{`xkcd z06w4d4Fhp=7Fp7a{>N7{-tSMv^f|#PM_7i7Fyb0A#h8D&kugQm4>prVgYu}Zo$wVE zER89AZ5W@D#cK)w`$shHI?gTdI4E;kEag!sK;KfKB?Kh^vIGDA!jQhvQEPL7aoQY90w}x$JQn!pUq2Gq z+zCdShJj)tjxw=7bCP*~gm(Kcg&dEiJcX#>dp1xTv3CKPfI9tl9P-%ihTZ&BUgI7o z?RHc=S(EETXVZ2mBMl}=>ZPGj3IJ@~gF}mS=xK9gbQkz`3bgc{?jx~GVu2Bn1~4+K z4XZCOo;5Lb$cCVRv5Yw}w4?t;AjdXC(gAh2f*4Ve5=D3PG75f-B!fqfj-|Q!}1JuZ?p5Os4L@^ z?7uhboHLF1G`wZGPj}vRr$qcdww0+F z*NC~h1)au5!QB)bI1fYFU*JarMnDqH*f7Jlgv6L4EM~Affnn=onXLQ|Xi|919gGhy zpoeBT;m-E-zA&Nn)9yYh$14X<2>&dNrpZ-2chy_HQIwizMxy2ltAegb#_$H-!CDi3PbhsDhN%{1+W#y|iXD#l2*5CG&>M z{Z8e*cc^=(WENj6>*e;XaRRLopHMo7nzC`S&eCUJ(TNA;^mjhv`;^c|p2}N!`!F9>2uK=ez%G>Hi<<)Bp3W@?kcE z0Axm(Jz%f{a)DBGEsO!vDFb->pXo z)x+gBI+HXLbW9hzS;@Y2Rij%dP5=2C8{gB9YbHeWGJn->oW0(2`6nCi++2!1{3*N& z4MX>1i4jR$>q5IxJ4klZVhP*n{@V1BCAnwu8!3^S@#WOPCRydsHngrz?&H-O*|_Y1 z48^(&SC#B*HR|@8#7O_cy)vSAvCY zGU32@4!vwqGl+XrpJn!0BO`|AUh|o2ZNhzoXA6C?q)qpuShE5~5E`d@w;0M*8IGK@Ry^DzCrrLgl*C3+f=PbA6s+t%xx=_k@4d$q zP2IYg>Mfu#Ch5^_CC(PUce)OjLSYa=rHg)gJ+Qmjg??`wkiD*xjde>kJ_qVe6cZ0?oN>Rc9*A8?^g7v8#`x%n!yZJX33 z8d>J!nM{92F8r`DgOqewS6Iu5RCYh(Q}NP#4kfw6X6YZUC8=lnxjh;GnR!ur*I{iF zs)Jq7jJb(&sAVC0zO^Yf+a)Jg!|&4R^~!X;3dFZn!2C1Ea6;N6?^rei9pA8D-d<^G zx^6Fg=#vlYRrSNkm#=H7cu4v?YDqYMMn=@aqiJx|ISimEoh2O#eecd=Z<06RW_3Z> zOq=)lLWG0pJ7=$C)xE58>(F>Bw(_qZ%yDN6zx0n<6@M|5vH%hG8 z!x|5f;iLnvzv%4w_eMK4@jR64BvS&e$;@)}RxPoyXkIk1*jMhg{HBeDfv?Ehli$kI zqiZACayGTJa0bNqC%6{ugxy7Av4iA@?=+)*zoOF2q+P8h!pnz=BoiE89K)MSzmS!>PnNUizbr5xusZPf(C6&735>QsfP@2gQ<&I`6To_s= zjNqvSzSK~hja~hWKy@XX-nV;P;unpk0TLeH5eYnrX+NU+1zFpvZc5m#!1q$?&%Ap!iX5V$;yLQ! zL=J;elnX>Ey+zqgStr-ZU))KYnE57kQ7jzwt7bUiCKb1NqKfDh`u$7?S4PJ^Q!4MZ zy0IB4>xK8gdBz$_A<5}O=7fwhX^asf)tR%6beI`InL+F! zbf1t?Y`q)jQ?G5ru>gZse~sQimpk+4xIDe?7(}_+i}uI<;I0d&dvwFfYo7DyN~1)U z>E{u7g{^x7iji-eBo^S#uWvrP+!)XI!lf^hgkXnL)c89*_&d_iY!a11x%UT+hpc^y zW_xl4m*Pfd)GW_Wi;hPV^m90KikeF>?$%XtugxCABj8;1hE8`1Z>^TRcgyt6nlzK= zuCx#sFMalWDhdcNkM3sfiK2K&MPwJqu+;qtGEPUKWHpm5GqJ&&u-l){>Cf7$Fx0Y% z_Pdy6A2u(~M&E0}s8><(Nz*U+>fKaES-HLIY})r++&LZ^8(7m6ND0_*#{q|5!ZDHW z-rKD{jB~P-ljyFpDL3NvmAJQ1_MRd>e-0x6T{j%vxh4~@Ljn&(4bG{T_gdFz04;z+95s-zwp??i1r`H;kG z@~Pqs+j)Y?TEGVwbK{v6q-Z0|8p5~q&)ALK>XcV-FKjKFn(P#Q_BiF{i$hE)1ULQ0_R;0xXI5P^bj0jBlGi+} zJ<9_z&mZ@=TkbMRzjCm+GQp&T_8r~nxyKncNw0J9@RbZn>l%>+knV*E2V{|TiBdSg z7gnE+iv2+f&W_QmaYcQW;#%u=Zy3JBCVGtD;8*n}@k1e>$-#WT6AK(mC_)f8<&?J% zwr7izIMNo5__DHFbRZk}mG@F?)kwXECz#A?RgUHyHF(V1%QWGRtVDwJ`TZwJ@xV^I zo2O;w)Fn2pblM^y_0pJ%cBG|f^NKs6H@3F4h-6Gwfy($}t6m#zIS>!M zd%t3V9J4oZIsU<8n!!}Z`&jCdrrB40d%`D{&`ta?$12F4h-`Uch)<|D&7)S+CPq%b zaZ?X=H+NH68W>-4cJga(c@^D%^~|C3{JQhym4J3HCQ&==QQ2^T6-bZsoPtFZ(!8{8 zItuS=j9gE;Ah(2!n2{w(VGs26=j@_ULGJ<_ZxM$Vm&fbf3-x{c(%R%NNmXZ`UQD64 zxg@)B=E8HvLnY|1vm6iV&ytiD%zg;rqZMab+ALOA#omy8RTk?|ZrLpplj@ zld#dqmVL!6w69;BjjP_od%ilo*8%B|B7L1@g>3lUj-qd+(hDOpQ4*sbaKimnA@p++ zYrBTOl7~0-{akN!`XKeg?$M*&_kYWVn1A+?%vLGLnH#Njg>#_J5)w>&*;TKYnzkJ{ zOWKGg?ceO3@YIqw?$zuG)@I`XnxPhWbUKEcqA`)sP(Km8S@OMFDDkBat+xHA)TF%9 z*xWCHhroOzOs*Jr81h@bNG(MxxE){iNPm~-x<8e|Sz80g*ZA+38ypYzJGy~!<>WRd zBt_O_aL#Ap4y)}Rvo*#P*sykuio0)J!M45GRvfyS}aF`!Qy1WEvsuaT7YX5T~HXDK@*Rr#GTw zlk2AaumKn>xU^~fHZF0q$US>|Q@gnRECZ@;=vM)oejB~dN~YYR5(nhoKBGd-E}_Z8 zVjNDCle)?AC}R&M>-@4kh&_4ox^Z8u1+dK8`MUsQ$)>gm8ceA3%W;a@Gfj0PU7G zns8d1ZCg(|LGin5vPV_q1zbDZ7he`AWU1WW-RuGy|{V!H4jCy zna1|{$%KY)%t}1_XE+MP<{Oq@*WNEmq65ed^Yd_PECwXMvik~NzHwR6`H6BtZm5og zsiGe5q_d%q{%J7@G(<5vlKk8C#36+HLWAPg{N`?JSDn+ahx`UgHCQ_8o&3c(*D`r2 z>8JTT-JOoFo7bl(W<>$v3;Z3gHJco7jiC4mo9Djt1teL-8Gq8q81?7*-fzr!`4BXq zEPR|b%sS-BHGW;67Vt#dcrMB%l2=$s$rjH|;`ifL{Arh?Po#LJ8^s$L9G5tp<#*FN zC7!&#zJwWHZy7}&r@lv@a~pj!y%nuf#K|duQ&-4e=u)q2f~tJ} z-@-{2Q*z0}XbyDk%cTbr{>dS@58xtTwFvN zx%z)nc599mKb&>o`DDa*{#x9tOP=HkW~#m8F}Ys+{mL{1SI`)|4ON@dio{VlXoNAAae#2$NP}syc@JT6T_Q>Pe z(AmQj)wvQyb!)Y{i;2dJhioo=PYfQ9QJ?LjM`mIwdx0r%J4Q@T1B&WM22sZ4OI3Dd zvJ*%H#g#jBvh7F1w9I3v@`a_Icgkzh=jU%k9)4EWDeiI8yi+1OEaa>Lu=Ry@SjP>U8j zs(S|-0lzpb3Us0gvI9GTWYatDpt2&76@1-3gn$bYcq0y-J5E!avH}bSrfO>V4xDo> z9V1X5e0$`&yi=v9BEkE_lrlMkrGRBoY;QY@Th=(Vo{xn5&PtCm_xuD9AG9gX~3YsuJfhl{sXWo2&$Ya=wR*#%2IQ_ALhu4R zw_3GF0(Xa%PN+`X&n-NXzQ|GyuuOaSCgVb|9buQkZAZv{&Xj7`J%705f%EN$sPLuB zRx1|@_k|YWbMbg)6np{3AJEkyH2hR3Vdm*aJGa$`p(BD4Q?}F6WyOWD) zMqu0!QJJqBW}{uqjCw8zZ`Af=EXv2N6f>mkEaA=Zj$V`JTHsZq;RSVNQ3tTr3BTc? zN;jByoUSM*>EtrY?@}BvsaGOwC4vOf^*8S6$8KG!<}q`6yuy}V@!`0!(b`!?tu;9k zFk;DD0R2LT9?Ryeu6mw6X`OB_`}0gdis)65sEd<}li244 zdvn2!?cLzA7J-GHXNlg~bfTB^cJ}HqbGg_mm&AmT&$+!StI4SY>~co4UIN<^k?<1l zKQb-eX$Vj5B^6t#s2Ub@9eT}>_T^N2J!jhsn)*QyD9^@t334Y|HPb}1wxrAH5s z*MX5?o4!LOo0$1K^qIOzb^hN31Lu4)fl=q-;L-U4>L-Ab-rr67V|GBvl$a$8RsKac zzQVu>yc;%SNQ~rS5R)v>cz{?DO8f8bwScL?Pigrepf_ zZvv#=;8#PhA8h-197cLNGkE2M#6!6 zfavYJMQSw~JuJRwOgey?l*~!~LM^R7cOZe%Gjp+sI=F7%=p1D_z(uZV3GGs*@!pEZ zK9u|Q%j=JD&Ya|atzoEMk}cVd+*)C`l4<-}L~cy=SnDhQ`3n48sl&N?lJTZfP+pgu zuqY#=#ue6UQe zg5DKVPEcQ=To%08Iz6bke4Ao&k~b{t_?1`{D{*nQpix5aW(6s(2`;Lq-o1c`n$Vqz zau4HT?r@oI|ND`Ss8G>loX<74yREpC+>* z0NWwE5l20N!Jk>dRj>Ay6DJJU_?0>rX3F7nX)umTycl{oeN?;ZtyP)plLr&el&`$W z=wUV&X6b7*{=~QcXRS~szK)88J0^*m1TvUvWQk~w45%#Tr8wy)2ch0Y_4P5&j#-iM zQRGU<%IAZ_gkeEIfpN0kT6!{UI@QSHM(;^$#*Vv(ah;$O5t{~_o9ZOzk$Ht9+X%?m zG0Wuk?;|?%70R<6h<8E$6t3&7s)QbD_Q^7fD^08NGc>kn#!BOW0 zXv!pBJuYH;Rp~8E;oyV(I?S#)D{K+jgNUfyv?9Ny=08yI70yVzMnc!?^`AW}TW*pr zK5nJ8Z?z4iP!ovY13ijy*cgvaHmvb5|9d@yQ39O8^J`65X;Pe!Hg&AfS5-iPx|A@C zv5;Zh-6hPnbTblM!nb?bAa}EH(*gOD*12I!tgqOPds*t5=wuRmC;iFF19*2YhRF## z-Nc0X_0#k^hdMi(!IBz&hAlpt+&qhs`Stjy&In)h6p$CP@lw2{F*k6r*l565GK80&I6Mlr#@p6z;;o}R z_mfxRb(+1UqM55_Z*8tZqa)alEEL{4JLq8HD_RU?@Uc9~z7`+|J7&6UZs$(@be%q; z<&l%DzswSPE29k#$y+_$nPp#d5Fxs>m>Jt7u8o-L)@DgOeC8zgMQS00^8zypWWJg9 zUM6i~iNk$?PF2$15}R80fGQb5;VUBNMa{FsEvdCS-;p82UV1G|B zYri%g#Sz^zfjOKPsd)TcUOBp3s8#O>(JZ3${leK3c5y{n-z$$Foyw6pEdRqNKAz1A zQL9 z2s_{&fahR}1cZo+k^Ft^rsJIHcD223De9)X=6K$mK`r5G^M-0H)f&uCz$5H@Z-W^_ zPyR(`*mYhEP#2g$Df49GHu(OQBe+f%(u6jJshNp5kQ}pHAs+`${vxw&OU(s>8f_jh zefic$^t!wttVF7VCpdO>T6q4=b|w(fER>J7AS+?fN;DnXSfhk?L`%l7&gI8n<3#q% zkJLXi-{3t;x8vRmHi}K%`Ge0@W{Q;Ndr!;aO#646!uwJMXjR)v8^CvuIdEtP!)G0U zTo8T!N@MqtLcPPuviR1Mj~?o*bczeEwG`ybZDR@879|dfEK%_#!HZ)xeIK3X;`uQ; zH=a|KHLRDqq?3xcHM+n_;mezA3@2jA(d9#^b50yD-R5<5klk*cq2eYj<>zD*4{b1p zPG!7{@GK)u+p{7u~_t@E#alXkgb)%^3#izJi~-B#mqOtYDu7Uk6N z=1EZVYBH;r1Nr{OkmBQ!>Q7HoTHZHVF~*nDx4XZi#)G9K?~m)!S=ykO0MRPCRLw>Q zUfB6i@_21vqD2u;EurSsqr9r%odbK6%7C6cz#Y%_`?_0~GOYSc+nJmS>+2QbH@-d= z20vwpRsub$lHs*kG{?2FYw#DH==%ml1}hz{TMn{P%BZ>MyxpJqq7cU$qAWE_$2EtfTF>9&aiZ*{Off-RE;1 z^tlRG3Cdwj3fkx}QdCX&5zkd%QS zZIKga%Uyj|X2)qp{(EU1N-vi@7sMPiHkZ%#3gCb+^{EPMZ!e9u$BX$Q$Vs7eg5z*3 zWd>uhH|V$V6}3JEH9bC%E%Qh9i1GD{(-`Qc<~#=P()K6{BonB0Y!cCVf7mros~Q;+{&`%9MzY!|J~d3~;jlmww^{;6U7cldIPf2ECp<`7CGBNa->T zntdPVGKC=D9S9aiBO=>F_gH{KuSBdoF86VUdkjKVnb zm^B!AiI|b(rK3u)w+Q?xG02064fGJYYisU)PNY@v<%>gB6V?lS+fo184tca=Uop!| zz_r!#8i7MLBDFwR;(1p7@bk^Q+Sckdcg&33E$oyZ}R9m7Bp+tz312#lx$kR!iZ2X`c4J4KsCQ zme`g@mH6x5e3Qm|TQjtcqfx5)6vR+5il?-ycS5bJL|d+_E?~Jgi@jke;hDi_a+I-L z9UW0_tX`Qgsl)=goAf>YFYLW%RFmEN?->N?MS2rZDS}9oDlMSWM2e_%2+|?aq(dMS z0qFt{ARwU9rG(xg^dbV%A=Cr`=?OJL$TRn|X3aUj-5zch~le<#IfyB!c=gS z7S{7tyi6*_l4AuHXL8phF0EHFJU#doD+E#$4SNjgRp9>+TE7E2PEJPB`mQGMF*sreqA;uY$DMPE zA+5W6LWlcupXDxLRJQkS?k{!M)dTnnHcf*FRc?X-wsJPv%^Szw+r-OVy}<3F2#*XM zY`xCG2)tF_c=3T~_Z3bMR$s#&?lbpZbmSp0I;S2t7|<4AB&cmH$PI+vNfNOpc;xND-pI78Q8gC zivt~t-2ttCnk4lw}oow z_CMg3J6!%G=LNtXAC|(XXX}BSbG$W{AxRMX7N6?SI#d6reh9Ia`s>zXFG{}`ED7Jf zv^XfKi)Nnzb-TmtXK%145R%jJsO@0btogJ&*kh*HH@Gc6Yrefn+>@onyNv?~0)t+v z3gH>P;m5l=*{2%d-1;ypbgsIrige$sBFyuT_nlhd;I}`Lisc<_3?7cN?#URkDckkr zgJdg}Fq@e;_P#uDJ5IC7rFhB3?F|ZM+!(?6%Jq|0+g~}Z7g-fs(g;n{TimM)SF-1nORI6H}~k- zH?U~wQ1snvP0wp{ploPvY?ZII>XmUd>vBe^Jrt0n;HLl(k9hWq9ikK#i0y{}#0z`a zL(!#exf|OlrWS*qLxz@5zx{9k^5zovKG)p#w(iydd8(4*0c{|qQ{VV|qBwE-(up9% zE+@}tcd0sF8H;X+P_GIT}i5@s!WL zxi$luUw;Q6>NMUYZHwW&uBL#n2kro_gXgT=0di#HaWJp-SYn*q#@^M=@5_?|jtc#vAG1Y0n8)25zE;G&O3HGLvdyGO+0qf1WH{rJ zw(=N&znevJ5N^{a%a!$CNm8f@ ze`a8u0y|ZPu4539fG2SY1^{uL=Fi_=#lm@k9zx-SZ`0bGPoX939!{^clDK&YMQa?{877UQa1sh&Bf05 zF3I!PkfhFeH*j)gXeA{1t$5we_wi9F%1E)vO}3sN6%Y9%=&94od9UFG<)YSk{0DgN z;g|!EeW-z4wiKDw*Sbok`}|I?nI07-i}5p=&F6ihKD{T*d3+H%lHH#lxrL>R>8sQq zK^J}lWSR5jubHj;okuh3lTJRpuYOAV#j}Kaw+1)Yl_PP3fpR4P`ZA|G2$+He&=79J z+9)CI$^|z0#?-qQ?X2gjmJ!rtWQ^AN8rNzz00oD1SvNx*LRn{Bbestv51AVKIOq1J zaW=zq&xVcWl`Z?`+aG3GAJ^QwSYx~{rAe_(=#rTG0FJ#lAi0am-3P&FVSf8ZN|(*s zvjelIifq^A?VX|#?q9=d)~#vqB;S6+Ic)H07zSQze2F!ea;r?Wjss7_x4xsnTSs<} zFRQtfKV^zFZ?uw>t$`!n;%NptA0nIPhzn>BHtW%E%K@R7eyXISmx+R2s(XJq#B)=h z?^+jVkSa^`=GP(OlvxW(qJZqT$*akB8X88o5;{driM=(kwms}h&G*lohURjfit^W5 zllfpbTX7VL^$0G!U7rUU!ftA&`tdx~ta77cBS#W06rg+Ctx8^0w2Z9DP#f{=Yq;rA zeiVevgUCzJ!i^_17_g0F61Dn0=d~2a?K3dTl^G7)a&mHv!X#eliz5$hp);jl!!!PO z>TS_5AWgHLhMii=Pb3&t)>=tuw=};8eB#wP0f29^ZQI3~stQafc|E^Jf(g&$W@N=C z=DNCf7()BV!qppw1F`B>qYW7aG&wwb6~2F5;U zxB|@$*3uBaOdPm)M1#rY-~M;?{lt>lN+&F*R&Cdg<;-JJi&nJ+aLt1zs!I3 z7`IZ636|ho#%Dt>N$q}FnhG{2up8f}^c!Cu0KD^dj{ZSu2O5PmHE7&spP#gRnJXd8BN1R30X_V|+U;#P6xpjVTb^LEc=z_FZC`LycrU9JiW)I4#0V^tVeNYI=GhKEm}*go=^URbaGFa zD*x5H2Kd5y7w;`NC(LQKq}h}Yhn_;>?{1u*lA;V4BpZLRSBSwGF`4`@&!K}cLk;f@uoj|^+J>@AIN6` zGq|2)4}1f>Rt3J+F79CCl_(qL(v zwS2e~dJ*0>EzWQBy1Hp-_-^et5JlDX(3k)NB4q*KKH8CF1s~)(Xmc+SBV)s%+|QOX z5e-CI7zoS02kfN|_u!-fL5rGAu)rzdWtDE8UmuQE^?^LDDlmQ`5{Tmn1hJpyOMv4< zu__(goZlI)%ItF#tw)&BJKNPL7%+p#x~{5}!IyHdM{_c8PD^eET6~hi4B{-cv2H+r zz!Ur~+Qz_L^jzFD6>~T4r5V5HtCWGqq|=N`CS9xb05*ud3&7xCb_924{~(ehTfOhk zicn^ce)^PJ{=ts7mw#K0hi@NPAg;3m@=JzD#VbT)JESC_sh_LzFTi*d_3Z0wX6+)_ zFo)+C^>O8FyabU7j5RRAr(EhT1hiD1BZgUnXjQ^Ce| zyMJuMz+B+93OLnaCm(ToZbjfyiB7Z+s}bU%s`}!dunx28=TfjH>jmE>P-f7rPNc`j ztU+Sj+qKpcMSHV1b4vhVj0-9OBl521v(a;d``nxyr~Sbt``0fO@zQ4*SQ$WxogUsC zCZ7cjq*_!d{5XJLY-;`uJ?^yzp2=-%I23#)-`7l^Z6SKLOU@L&eUwTE@(vXT{#gxs z0U)4Bf{S3{4O6mU8p!IPU7_g?8|~CHDoXC#;oLc5Lu*_YQ$*@Wbgl&uO|0Q9m%)kd zDEc&-%t1ka)M?ossf2suUm+Icfibxzv5;dO-_Pzp70}yXoqQ6!*!<2D#XLR8ad&gS zIls$fhA4s^$XYj`cQvQ4bIwE=7r39z$xSk3XwScn`Q=QCTPISH;I_H}#DcFn4q>LLnC;@z+uSSEv?;Q#2gXXdssLFu68QQt{cC8LP?(0TU4xi!j4}PyN08$uQ*@AcZ;gzojt$ zj1Z~&0jyXqhVKIR57$BzQp#6jvZvy7c@y6A{0zco0GLdT^ZVyIHWCdok;Oc9xqpL- zJi!fsoATcv?W>YdNmpK#cHGrjq{`Bbo;NmAMh*sI*S5SqFn@9?0)Z-BVlfmraHWt! z0A$%405mFAj)f4ZCV=~^TVuGm0>Y|Kh9OAvmDJYRD0s0S;AKBo-dd$v_ZiPPAVvOI^HOkM2^tD%vIo<~|MrKiF*paj zo6#S5aYm~O+z*1v8H?!;{YgpI8`6Fggj3_-yMc5=E|m8mSq@L=NY_+jFo$%Dt-WkD zuW4P+B?#8$57w#zD3s@Bkqt0Kd*CgNU{g&-XJGwuRbyHE%(=f7n8l>?+1*Pe`O`8O zwDC8{(F4Fg_5KYCC*G;U$;bMNz`POWk{Kwa_fL#JXe-M7Nd4t|9SIb`t{?Vrs45X| z;qHc2$%@E0%a(hQTKC>a|4K4b%kqI?dC(k0$OPu9BoJ`_j*levZzE59UR?l5$m^8A zFp$Rs55)#}LIp<667#@2f@-)ZbeNA3K`^sh4<2XI^)KlNZ)l#Hylyy_*yicC^=m+C zr863Tj!orDLQ=NtuIVN|)w{#vx@6I?_ngZ7cC*~0RFdsoHNI3ZR#W27!%BZ*{{rxp zveWQxH#d8O_J;YRlV6)`pY)|%#9dsTsR6&w%LP)a)fd0YZi>eo_oXho9NM_K z%bbM2lrC^z;%WIKmEezfa)ki42L?2ub1UpqZ)9_J6PZmWe>v66xY;quybfDRc$fIy zQz=OU6zD-=dAZz-G9Vtuu%RtACYQ-`AMcxkDzo{aPo(UJv;jq{e#6P3fyklcV&G$zn-! zLOZ5Fl$P-i7tvo8Oc29K>_IQ}#%Z4qyQ7Q?TZ`iZUhd_Q2;o2S;s+*G)Dgd{+Vjt1 zu0FO?TLGcUvd0<#sy|%I?}JmCSwmMu8nEuo zcOZ=}s2ZtvX?${nzjyQAnG8X&Mo1vl8U@fmB>}SZ#gH9_A*nx$KQP!*UJK*PiXF!)}LA~6pRlDHl-V(!X>)|^vR>O^1b7udr^EQ4I zpvRg@yJSBR;@B8;OzE^y2_tBLfPq5hg&ptXdHfWo6@S{{Z<^D+d!rhtqZ!Ez5cqgw z{EjGd*yro(2{k8(FoKysL*FXu@;MNc$tl+>nP0`{?w>*zJ{~kij^FFOdl2)-zsMX81u zN!7Wzm1MyBN97W%6x0@yzFRjW1Iw3-1o0kDU;8mdnK%0#Eq*77M~X@7URRh!Ak9?iCCYi>%=XDH+-b0mcQ~C#daE{*VQOsHX8{~XMZ0meV=)H=7yNDVr zNUvYI<_R@5CqHU$O4FTTQy9)zawk(AD}f3R?Iz3}n)FHB+|H*ZFjfYXn;mPW7n*l0 z4@XGzzn^w^|9p78A@nuj5!PwX|I(eH+mzF@qR@dgNIY#hdoWni{@m(E@W)@&ZR4e{ zvq7g4s9hqpH?|cXk$o14JvL42>%2Qxu`9ws8TN9qBLjJIv%U}84Jzh;pF7T8no_aI9lW+FxX0%={mPiK7>GDiE4QeCyvJ!>f-~!&ZuX>=4&X$%EU`Q5*4i>h&g6)t3ant3~=gnxcPb zX;lU1t7T`6UodnMI&D+?9u1k&=S#^s7*%?B7PE~b# zm5#`TZ|YxpFxM`otZ4iN?%_ZmTPE%f`!LFr^`M&kYD+%f zUEE81!arLt-Lj`De^n?J(*vQ#&vnZOX*Q4}s$9%1*T>nPFUDuB>l6sx1wD!~VwanhuNf7fn3yq5CqOshNhsd@z)N9*_!MgPwnK0^ z-s*1B?P_gaXHxsBhqq35t=Rji1jy8On(oikCoir)`Qb6X%L(^_h4G%eVnBZ~w zJir7k9}Oe*!i@T>HP66w#q((rMPI(RsPMljOK{Yropn`L_0Vg6ST(g?LP(K#57?-= z@?Gc&!UorQ_$((qb;5KxmbfcZtg2)`!C(O z&js8Q^q1CYCB5c~-e}x3o){#1nsTRVIuT?D+N1bfCd_$6Ob1kz;WMFe6m*0NiyU}`l1AGnVJv1sF`b{%$ z&_7Sh(;HjWaGZ?$)Y7-n)q$L3*GP6q3i($EN|;>s{)+(QPv2lV_r99vz9p7LeNWE0 zKcr-TBdTD3gz`feknii$=RP|46nDP2;w!Xh3Qb?*XW+DG{xDYmgzDparafeby{7dQ-9%<#wem*iyJvp)f|&Uw zKJ#deIN6@GI_hyyb;g}sjmQK1ZE1S*uFa^DK=A%gswWVY1OQIC?d{}V^WAN;soAW+ ziPW2-ODy%RroQWW?;)KFugulAP?@EGI)jFpQ#TQrHWeW{P1ba``USDab&o12Nj8uD zC3j4CYSg$_Snvx7e0fhkCnCdxcH3W-aqiG3SN!}{hHeP>d)x{Yf5w5{ukb7jZ}EvR zNea06Btfra*~;AIg=?#QDx`kK>pbKDsC^rynfW<%1n@{c z$oZ;%9dP)>=(%zS2iski{$*xC7pDkP1SxQI4T#Q#inK!*xYx$}9eo;WC)#rvEOV1s zeHMRDs_cdEH(U4*|CZ-c_nuewT`!eE(g)P|=mq!s5&0+HgihTL+Q25w_O)ij9?Iq7 zo-FDtd3~T)G#(v{t&owR?JeI$Rb`c!Q$FA7Ox>`nCks!leMst}LZu=F5!~@(0wQ%a z{=lB)eUnT4-ylBJjv|JQ?7{q>&{uKZ<7~KrRTJ5nfL@&%9AuCWh><_W$uL^uot@?9 z&N56~xlrpb;<&wdH^8E;PbO<4KfTp@`r3N}U?J5I^e!J;1xP<@p7fQVmUwpyN5++# z_6H#_9(bRY?Ar$IYdZ=O?@!054tl_)&2P7-qz}7`vR~o4Av-*4*Pd9>r<*$&*xl&H z@$k)>&0JW3Q!kf9G$3b2urB^K_sz?00yDUkG)I4zO*Hq0-#fV1PJXZKbnU!~q1)sG zpwJ+z>^K(E{tGFdHsrB!P0i%f`}rO% zE!t}+&BuZ7qPlNJW+y+z>8E4#7*F+RUe?UMX{^yL=YE$bbjyk4!w0zHpRGapM$}jV zR=P(nk7vR+nbq3GXot)9Jl0(2zRXGV>}`R3Crw^h$q&QN>&`3w60p@UW3GSbAqEK- zs$W7rTk88*-P_sWqn(#c%pTL|+4$^I)Yo>+-ED#2e zCf1;5JHF4{Z=3L;+{2-V+|fpo!dP|&8VbfDqpPai8sJPWT(3qOT9?P^_D>Ak4W(Jk zOr@`tnwdT3r81K44>Z^Vq-H`{*ePOn{^hIPYln{}WRZ)+^-JUL-!C>-(KGdpP`|hh z?n_Oy*$4TM!h|HXyR%`c(G$MQ@_pHF6q8a)&3Ce4y;4>S&+0U8othY_6X+bb^KZ0^ zRYJX;QSexw;49mke_BHt2u7A#No@tj_rZ)1>21Y>DD24KY6G|Vf-X3$KKI`Ei_XcD z0m*@;aTlq!B(_J&O+KdaZu~B{uzH?IW|&kZ9x&PI(F?GEfw9FI${L?L(~1M&dYSuJ zDxu_Gx|<&Z^V=L(FA<;I$188OoXy3;96kp;m=(>x0nK)g@(}&wHCkph=$^E?e0_-Q z@@1=5>FE%i6mnOPLg%yhznfN)`mW@EyD4 zIO*qT`rwk1DfJCYUGo<7TT@ah?zQ)ByaX@a$p}Y=Vg*?GIhAig&sy!}Cscj}?6%%o zjKPTdb@J2#MTs7agj-lW&MX8L{BVCqb38NLwaH-tM>q=T%aiGyQd^kD;+$ZHM511E-H4J-gY^Oo`G9~gp zzHeNg_F)|)nBvRe*vmmUtP9-bjrQ7o6`R`@hW?uNoGIkWFZ-Ny^ZpMcXiPoqMT(^f(5*4w@ zd*OgpIXMe5->^*!nf&s3d@Oy|Z39`0GRK8<6`gJhg+g}KcI;dN0lcmhSb`K)T(5Y{Jb-6xkyDa zeBPZPy7^o624GvtM$xnE;=>oULSFx~ftAJ!-C?w~&)z6OT9M)Pr9RP~w%N0&b%3D* zN+7{jMXs-M!QLWqNolVu@aZrs-=b&Pg;YxF#ab+IyTr$ljY5g>xG-J$B1skgb%TB8 zH6Xm!n0>#MSZ15H)6_Uyjp`sse!r8UmgwCu&&(3Iw5LESiyB#n)2pLPVH!fnqCxbZ zb+fNC*=JHNcO<{pjKcw-tXpZEpJ-Qlf2h3K3q9^cf&aL2!8Wz{9K zSxDqa=dSB6hu0sJl{67V0rr6&8d$P|Iy*3wYm=Bcp%j^2m-W~U>9JU1lWW-`C!aLq z(5@P99U8&mtSVgxMKbIL`L#J$Hk3@cuQw;~JsQ#;HLAm>qj|JmHcYuGzxcZBD(|i5 z;o(GIKr<>+eQ=y|L(VMC?f~TA*XR`qkt4`W$s(vim*|RJQ87`!Iy$iebL<%_ER(DH z340yy9!(8QeK+e0yoMY(N|iESt@U_!GQx0;@B}En|7xa-iNSfir-sN-&(RP2-W&Kb zG{jR)uWGmBa((=B6)YH?17_6tY=99HqVs$H@uWrKt3}Dh6ALdwxx0(e%H0eT({+ve zV_PI@J34!!$LoNk7a(E1y80`t@|5)@#*av?#zeZ%#*H@HmfFOfjy`_>{wM_nm@eV{U0xb;CnMI=J`678`swFZgr zl*Y+N*VFkwYwMgicp@1e@{}s+3*TR?D}!Dz69k{uqxF2IfsetJ z{o2kBzc;$PIB!V6TTysr=}+7@(xfpCKZNkrr;~DSNCJ|NR?ZJQ%n<9A@gU{i$LZ~} zEmG33z-?=qz%o}qzUqYMF8j28O+>}!n(4e9{|JVf;mRMi_a|@NPmz1Y6DA-kPfgkN~<06;VqAfzO znbk@^3lJ1(PUz0TvK}|)V>1VV{XZJ<-svZiGvE##FUFLoUAAl~q{Of;;HK=if!UOb zI7%Im*kERgG13V_762X0ZOQx)>OC!ka=P1z z^~4F63H)b~>;F%E?gtJWa~}plj~YClxy{bySKuaC)Tf0n@V(c2B!s;APd2XmR;XCO zcAY}1gxR;;{dH|&PWQOINIg$qK&OlS%v(Obb*JR73WtoGt~;A-mbLMf5G}Zu*R5b&F*;%pZSv=CzfoLn=0? z9(PIpp9jtc!)(@MO4C1o=z?T!2JFEWCYBOK8q{h>rs!&gODdm2f_Al zcX^lg`J~3whP%i7h?wGx^=Hk;g$8xQ_ccZ6XKX6} zvX$%#|CGQB4T=L5RQ=Ad7Mzf!<2Q=gSM>YhAz1W7X#qA=XjIi=L^&j2ed-SP(5sj?ZS7q{te%%mUbAhA!lda2Q7asJCOxhB zXQm;Kd(Mtf+w4pRq8;^bHEV_*m>z!r76I{Ee>m#<^0^uZTPWGj3T3xad@Bu~M4w{f zCpmVLJfD~!5kiiMa%}UGR!M4PTd4s>NUV6ww}5dSoYiP@4r1GQ3T3wZr%JiBSlGcT zys`06?4gbJAesH_DGN!Y!wVO=j6xsjr6zuhbX*nKSUUQ4Fg>Z19kC$ zs;GC@48jpG28;p4=$Au4cX)VrT2@!0VN7*Gd6LQ@1btq-1wxyW`~)dfoyCE+2qtSh z6a!n26lanb;nk$UiCN+?&*3f}K%YEODWCh5zUE^hxa zT6$3LHA?=6qdtJ1R;7vn2C}uW%2eKKqmT8?+NHhiu5tt;v~guki*+Nk?;a!;aFX!_ zRbp17I@xe@F>mTsytRSEtwD`_u*L&L-8aA6USZNrwG$|~)5CuqTfJf-=>hJ`KLSXF zXTxv&=Hi;)&1AFf!CX~2W>~y`e0WXM-$SY{Ik$EmSswy_6ipn_p}ER? zR;wjj%yLWWM}olPdXV;;Pu|&15vr7UZgj`#@?kVg-lf3GYLP}at1>=1PZAK1v=9GT z^sZTX+gCGOg^2_8VCBP445C+KdVH}a< zZXX{i^|G&%AA11-WGXin-4y2x+3$$hc3KQCzjKr~EAY6deq2RDs~Hq`egRAhtVa)$ z3OG6SkH`4Xkx%w&wz}|cF1mcMtdC@psrXf&&>LZys%5_Z25FCY7ZiLurd!UY|BjAF zr%V1`k(E7!-#cHxT|*(+SxHkPXoH0W!^fw}H;y^H;lf@AldQ*`rl+@20>_4eV%Nqr zwm<2X^{>A;3}}6PNO5lg4Rv8|F|TT{QH9LiUT4i>eYFjR%Qt$-xljR6R5cG9?@ z4r+*gKD)SI>F{mc>%22*mntxkPHb{_4E8W#?|xC+jD#=u z{^Ms$O_Fz(Ek`6i^d+dWsTHo1Z8Zd4v5;)>(p}|-@&u5YHO}~eX?vj|&YW(%7O_=q zKN#!ZGif=zV;tb88Dy1D>;wU3!$Qrdu@3ZnRfATnP4*7eW@GUCXs??CcOkOm2H!7Z zR78IQx^5F|fX*tx9KZ_WF8$5+7H`F$q;N7c^*v57A(Ah%?mBfk8SAYCub1uWSc7is zd)Tp*&pRkCh|7g0xDI8uaj8*8)8yk%LYT2txX*XDMRsvQ?Itpe$bpBT6LXiFqJ08< z`rAL~`3gIxLNiH8iqc1v-_eseDzHin# z{XVL6zzH{TPh{c(PZ?CSzvYtBf_!v_O}i~|B30ubCx^8U9UJmK}Nvc)? zQ=1XgTX6lIA2Nu-t?wUuh0spk&HLh4mMXc(6Os5OOP8ce#u-ix6GX3Gx57Uff6}DN z)fODnJmns5*)Ni(nd-LUV*6OBrhCl^L=tZ006g0~>Z^QJKA7_C>lq&jdJVTyivle- z<#T4soR_boP06ZySZ~=eMsZ+@aNsTv68vDFHxz}4nRGKkvx%0yZqB~muEFuDS$o;Y zlcbA@oCDLgBTSlyWdKwY3mM^lWlM2S$^R|_4P>V-l6)`7|IlbtlFo>ueg3Y=YC zAd|=ct3?pW|2wvT|FfH@|Izl?d>VooaTt3yGIerTe82eT8*%>hLeUm$GX8`hMiW5I z=MU&)ehNuk65u`U1wDU*j`UP-0z&SRLZFsbz}LNFN5R#4Ox}t<<4^ARGe8lr>`7T7I&uGLTy!~iR`}PSJ!}23*;aHNPGQTn`1@ciw5!4kGxqxjHGpo z7%l~0T_*x%Z3itt!RRx{Iq;5&3e05*G0*h)!SH|fjjENpO`8ZyZIMn{eOet z$#|2j39XqjKSI~Z_`kL+_}4oX{eEytV?hdPX^vYOzZHF3H0t=~$*XZA&j8+Z+y6_x z;K%QRe|p#^$mw5y8=%wpuN}#{>GS;5)e0&3-+u}P=y+%QpZi%~HSO92O79+WO<&&R zJMIBru9=`8CdURmYvYEHWA*hvZEzo>2$kjgvx5lQjxlP>#Seg(>R%t#zjpSEkR*mb zXud0~@PD})pmSq{Pyj^*2a<4Tx?3*Zcl& z29VyF0vOO&oOc!MdBVp5jnrYd7C{5XcLhQc7=R=M!`>iaHJsr;?ne1}Ah49j_%~?v zT5=7bMO5YoMBdl=aI9GP$u&TV1SU>{>-dgC$-w{7ka@Xk7fB4z0tz*$RR<{u*kmG} z1>R=@TO- zCW=HvK-K3YXM3G5DUS&^BtN3WDM3Ge}ldhIT%MNbk~QS)BKB#6oT5~!52C_o{H!F5;wM=@3Nn*kao8GJSk2aYi()Mb=fi0kzNRk z{fzKpP0Mbl2!5PC(P=HX>38=pHc_~+ zzH4e@o^>)TTV5?}vf7k_pyYi(JM>GTLKW9OHp4|vL&BkS$c zufLa_4T!`Xv&9KMu%hm#V^h)l1wMbrL+tXJ2Fs zhHg9}{q}=})j{wrHI|$IP75-ER~Cneg_*6FLvCz0PRyI`OsXMxi?+Ds7@zbkvkzNs zAG57!KU7#;DUJ}+#d!1|QsZM~^mH|TDoeWsjHp(UG-mH$&}=I4jDH&MMGI7eQl&mQ z7U7}LZvoyIioTEsTTeT~c3R6_GZa&r#GC{iR|H6Z+$5(h*-*ymAGtw`fH#uBY^MyA zmv7>rT?4*macMw}s*$#`v=Z(a*t^Pl zv0}*Ke%~nTh!mOK!5|<1EgO31e5j!5j&V~yH9?&`xzyFTRd8J=#KX?rp*dEdKA<V6w3?qX3VO-r-_8I_0KiNJlYc(i?^``5b5pB(?JxdhwN zf@WtD1l|~DFZxbh! zPx$5 znU;g+g^wZ~;lXmnGuuRzDRqIWBuAJw*{`(=!%yd-YA-X_-Ae+=o63HNQg_TLs}wBj zIeGbCpN4vi_AEL`E~aEstM7c*SD=VHrQz-*Oy!d;R>-}BVBz94c5 zCdJEvdAIVWx>bd+=Y8ZE-cE3|ivXMaq))VqspPxJqeEtKO>Ttw`5`@>4h&pO_xh`w zrFowO7s)&6JzmFEw}pO7Znt8Je7vm(UXLh^FEMPn+Q^I|GdIp>4w-3Al@jZl-)@5r za;T`$U9IPttZzk$?}@jA5hEBY$6q6uTyYMc3C}xq5kk#sCL?O{X-QQU2#%_Jv@6^e z_5xp2U~n|1a@9xx3ZV*mQ<5T%PxZBa@G%0;1zgyuhjOL3H#(@jGH*-Nn>oG z2$!XZ|1Hv}=fiiy6sKhaz#n-i`tm7ucppx_@hP`5*AwbKK=?|Ls@3@Q%9P zp!5BQ16o1HZ3Z+homBWMbgpPOB`TzDd(ImlZxCSu^`Zwgru>!4_CiEK#q9A`k^d^Y zvRfIRp?h-mH;VQAy{uWG1r!S9))9v_*}#ME4A4+cU(=^hrY|(%#3uJC8pJG_CbAE( zn-gn11*aQ240UQQVUCHwEPnWSzTvvDaX0h6Nq-iWC z79KvQP=Ta{&aQCHD1)(Q9`(zvXBX5~vh#+oMHrKqOt(B8?NW}`!UC&i8z-OkKFqZ} zHo~i1ip_7cJG>t?9K*LkH`Jv>oghn}vG#yf{$apG?AOg&;;1HfyOyh57AQ8^ZERyUT#=V}4=ms|ivIufpIs z8Tw%7C*jeAvM2^YK8}u=ThFb}LGDvl%{oOX55^gW1-^YV+;yLRDq6;skF&w;MZfu3 z>{>pBE@pg>%6RofN2X!Yce3VtH@g`f>;&Q}3bEhhK^C$mnAw;zR|qGO24e1VZena6 zT2eY3=uLYcwLG{D1%!nm_sI9e~*deV0}XuXfivFMB0 zRb627s|V`BFFr2tXw%Lc6@|KsrgFdp&Alr1KUpqG4FA}Ny9gs2MzMwaqca`p@o%&J zJai9_fI^zg0f}U;S;VC*H17eHHEm(pV$kdQ<*H>ow1X=EVjsV;=Oqxgme>MH^1lI{ zj7cKs6##zjvMO5LP3%PewhSfvi4$E!LzOAoK5&8mrCtF5kEfa1R2WY+z{d3ZLu$|a zbwy5k+2fTR0!w!bG?QNWg%>+WEgn#~grrn?dNt=6n6Kc=y#am{5EXJe$V4A9=JN6w zFSkENpT<_4islGtny5vkXTe4jUNUxQaZ=sguOdv-f}#qqPWW)JSU1> z?9Rwotq+aD08aE`9a#A&&8T1(!M%&O=CVKbu+0xNkq4&{W|V+W(aj-&kjjVpXDlQIhk@%t4%G|=KsAm4>3in^1e zBWYInfo+TAF|TJ!RlWg!wA>~1{?B?acYq><8tySPd*QHW{?5NzN8u5M>gl(SzwT9N z(U!Q7)LB1V1M(4~IcFyL;Ij~>ZDl<)m_-L8_!IGAQP-ztNB)^@UznR?oY9a4C^trV zvkk#+1t{xy2(Q*?Xc(w>QS`~5cdPM{FQ$DLrJWioX9&%AjFk!<{W%@}_?7)h^7QLl z!y5IO1SuTPw13LQr2i2+goYmGWc_ZKe*Wo?-S7IwtG_{2(M!mir~A4XiNtmu^f<|A z( zNeOC>(WkW9~m_keGQM6f#qp^d-1RC3pH~t;X9jo48Fm zetOpM7YS<#T*qSI-iw12tlfZdU4YuI^b)?wH2;$MRYLdKqM7*cQg|0#3zhxd=&r3# zb2FYHHwC!Jd`uIrw(~)$CvDzQWY!>~XTu$ruLm z<=;pYgnUOBLimj8xrZ>&<6c+dr`4|HB91M1wjYt_rPUG<+B&)trHq{1qwBdH`R%p=-@ zb3{r+*XtlEPs*3w(N7DF6=AQ7(m$@f)uvX;5CBPA@>Q*0{DsKx^=`yM#iHgfX!(aQ3&9isp)?wVxFy2Ct%0*)??r)90=A=v%#XvE3yRP^!&GfIz{^jJ#)bkk--o6Ku~?yag| z-JE+UZ-ATxXMm2fov`UxbL0QQ-g`&G8Mb?)BLq={=ruv~5;f`&K@cSfK^O_56D``9 z5z%`I5)wlYQ75987`-K;6Wt(snNbHblka)oZ|`q?@80Wtd;fFJI_s?Shc#wJp1Gg< zx#qg=tNeaH3PpA+KPQ~CEtj5N+m-7W#ho&jl7bPRoE3!{f?cUtDuI`YM6wB_@ zSJke~ubKiuO&lpX9y5$gr%n)&K%Ij3tz()SoS{>!UEbHUoUn&WHX|^e`5?tETJ+W) z*`bw0b)KK9aduL$apnU~OrPtr&&Q=0b>0c_6G;3b%Y^^Jy&so;mft5T1Hd&IZWv>l zM^!T+-eihDrsLnP&3aI$<3pN_=@Bf*NGaa-@O|as*+Qms&U!+Tje}1DuBPSH;;U)R z6@bf}5fD!qS5-vsG2;!9OV)J&J@eQfPjtitcbi9g>PzaE`B(IYi-Q8Ah-_ON&TlHA zQY%t1>e3Tm`gf&Ny8Pa3NRz4Bmf;vUOC|KNcOhgu zkEdm%aJ0lwhNTGz+G%+Tzh(9ltIX5VsUku~*$pe9sLJqR9Jp*@hnJfuIt(u!F0_;Q zoWfO~(cCu1xa?%BM^o}eJtmb-;{r$%@kxr1lXU>L%whQxOa+_~0bsEHw3HmTfzJ5B zUTslL*gamUVTK}f>^2EmVpSkWM?;AD_^bnd&f$gH83JVGrkaN|$`Tr1>=s#kM{@?PRqF)^dqv@`g4PuX)&|i=qe|dn}e{LML<)vU& zH~p{PurNwm84~JdX*g40EtUVC^s_!tMq0SqfW13DkYwa+>%itIt(zEsH;=>^RH9^d z$t^HqacmtRAapW!CH)~rmZUQhl1?7tH^7Y4ZWk__%`5aQ+?uvLOON%Gcpki-8u98@ z#Lt&XcR|Nr{0X7PlXB&>`iuJt-%tN1l%gniz&dvK+O1bkYCe2O0gVAuf3IK}3; zTTbB+_gg`&y6?3O^jSA`80BB6rj7VaV0GqRmum;R5yv6)cKZg8Cr%WaleAh%H72Ob zJ}hS*%8+zy%(ie!@orjPM8`QWuOHIE%_eR~G~F9m=UJ>fCuYo~8~o z;Wh&t<&C}b^~`Vo_$sY5bK1N+koeRX)zBq+jiEZRl%3+RC?}Lef*FC_nc`s`m8uIf zUAA_edL(zolqt?|!|?-14}aCBt||F&RxH*ADwCha$u^jl3KxZGn7!$m4QTrwP^s&Z zP`04SUEA<9+a*k}5KsY>kV`($PvJ!Ee+ zk=5yav}C@$m*)V_@{nan#1X^1r_xf~kWFA7YU) zy8%NS8+$pn+re2!=bk(#1MIWy(rY~V7L_lhC=avCy3K(xH268=Ur=osTvQFv)#X+p z!d46a?G<_VQwlCJ*pfi@KmYDJ5eTnej^Vfm{s-rm$tnN#+PV@CUSaqHIc6{exK00p z*0e75OAZT1fsX3GuInfs=zzxRX2M6bjsJn+Pov)xMGU3@eWA!0BY?rOar1xuw)0C& z$P#OjoxdHTjtC1_vvWJD`~DVYqnLZEz*mNZ`mj{#IR#HGyqkZ}68~?v@jsV$vh;7u z0(z*d+ZNLpn}pyj(+b}k$16?oKQJ3{$HT0yLt#a>)IYRyZ2WGOO9~_nDx z@g})-qSkW9ImZEK);VQIH|j9%0qxZo60i9=SDa`0d{{hb;eDLICH8fXhq>Q;ms{Dm zermb2wv4pNlVeh~A-#L!_y5(Q=`#P69k`_{`T)yaRo4DiJ@QorFm8oIJjA|wPbm1u z%E1Z`R3|MUd5XfTG$iP^DV+(gOaJMM9RY>!5X+?62X$dAJA2LkNC8*HEG)o%Ug#4I z!t{5Q++B^IE>PJH7i+@-ZAgB@X+kiH7@gTZ9R@I4=;R_Vj^G4ZF{;0yuGD{Z`bWO= zy*5!dVv1odcymH5+->*N>Rpf zTRdKlP};^r90R0a?NSQ1)x~ffWe~0MVZm1F&pX*hgZ=p*=c@bUx7$en!Mp38601%% z0syJ-?m1%IcE7|n89iYb<)U~ta!^1=KK-eS| zVFF;wj04vCD-09_Ks1T|NZ>XiZ1In=*}$lnOG<)uU@YFOd?$I%VkotjU;3R7C1EP~Lkrcj~@8VPi5JKLkrtx~^ON#5iPtfaY?-URbWN z?vA6u$xDE2MC|NY(texJwmQ$Kf7rd->6FNlXi1yE3>u&71tnJ z44}_-qJ4hZ&0FK#6=q=8k^>-Zn@9GX^0v`G2E^WVi2n>0xR|am`BxLY@qh82IcZgj z`8STWGo&xJP9b2twp`C!lE%#{(x>!AJGaqQkjE%QjR;_WDogf!3WI8%j{EER+Y0GV zL?z`nrpk>AWpReS?#v6h;=Yc+iIJCJfniSMYpB+8r7>g_FXfg*R~lFhoN{jIW|BQO zEON}#7y`kP9$682f(1tWr!o`tJqyKu`s&Tf=8XJ@Z8=90sl-;296C_n6qD*aDa=|% zPaa%FWd=LRYKsTwNxvNQmAS?YxFxb-tXIP-d=0PbcAxGntAXwh$KjwuXT(Cvnt3+i$ z2^IW$A9j7m()w{!c=qKZi4%DBHJMFI{+CPbZkLRF76P z!AW3ZLBkfnXP4mSI;Y!2Cin4*S9?F*8hg?DsyOm-%$UH=6fWC52uk_%HJkrSil~+Tw1ZGpD>cK~ zUT-~B`q|c9Ik`mE#`RPI_c&dnNpPMp$rXao)ZjVS_FJOI8we$~UPbT0o>yK`ZPkj6V>rXSd!`4nN zoTfu>;YDF~{L<7P?bZ9oQ+CDaFDMCbhMDDNT%LR47V`9By(~iT`7_EmE`hIt#vg?Q z?1`Hx)+?UmnLPyo%Oyc1bpOII9_G7Yc7ZrNsb~E1do6T)4~5i_A4j zs2;p*nCr9{F&MvD|1+9&-DLiCTA!KGc>45DI;Or0ukvEzFpMhcBydV<$zoKXKIcMs z^~sUA4gW2{r)7D#d0kWYnlSI4#jC&rpcQ(8B~!mO)~fGSIiud5HRLL1^4`4He64`0 zi$;%~E61xCGu@MS6XyU>9&Vd$4wShn=OL7`ru6R1*v#p1G9BH#r9%mO>L5UKR+Zb^ zMhOU)ra(z6=M22Ea;`2+oau>42U4Rq-iVTK`E>^y6454<1n&jY5Gi+Ra5>vktELmJ>MxQxZNgA) zZ^;$4?jYW=pw&0ZaxOS~bL5Epm{;G6=Hy)c;Lp@53`K5|s(R#CgFZMWE^{0$xT9IB z!!Yo67*Bm`bxJo6bC~G9;@hUWoKS5O^Hfrg=kzLm+6WgNvKPi`xha#%x{5fg4G^l?7;B?#-#E|Ay{=W* zJz(#>Gvqzh@0<}7NHp^91@QQ;OBV_uKXC<~<%>`_E{r@O)VsuXplJJ9)ke&(!@>pc z#xln%3(-d8zM8%CuEjw-#J*KJ)Jl_J12C^&Z2hE%u_kyBtD4voJ?}twC&Jk7s)`Lp z8_?(S?j#Dw+XRdse^);pb6I$&KYS;)^icyDz4(T}tDPr@Z8pKgPf7So}Psz1{I&0F{Wx$kHG2vEP8*g!GibBQ-Of=#H~@T$Bt!kvfe z@Oo*2(xp;1!TDni?sf0J!piqwbLfRGyfN19d(i3O=Ebix_s_A+xkEo+KxALeg;Sfbzo4hiTpSrjXF$KcBPA~OP0O_K#ticsQA_9&0*m^@T=YO!|3_KaQ%rowQK~Q zE@t?#MG+IQd(m*TKE0lSntLzmiM;s ze#EW4xw@}KiC+cI1g1ygDNU{o^a)M18kpg3RxLs~aBt88E#uL}E8Nzn#3_q(V?q9$ z1m+Q7c9<7O%9r=iHsg zlxvW~9dMI4I(}l2E^qb8HbKrSe*SH{unygbuZZ0EA?XjOVAakdJlne!V9gyYmV3{+1|6sZwuKR_udsKb7 z>3r15wy9U6y$beYxgjp5ODf))mRJmUb{_Gzmez*GSiKw9HnbQ)tFB_vI?G`*v;s*ibc9@0e2aMKX@W}Q(;?F@?|ye!0mn(I z^WyEWp2@{)zg?4j-1EOQBt^cMQ7};aSpwT?NXg}=lvdhs?}5o;Zx>bu@pszsw#7W0 zI4NhB=iIY%4|q4%!T9+=QuZ)AFqI?^G)!K#jks0<31@}VAGgH9=58lXQ3v?{WLS4P zU-Kma%Vs@)X)F(LQFCn{F5(OzBVN19Sf1A&r_o{yK(nnS;{GE6k3mJLpYen3Tnzn6 zC9XtBUcN66)3+&qQJUx(A>E3n0GOS`Z z;z=GQ%{>eU;2wTvt^#{76b7`T>bfdnV2CM++y|p?S7Wxf@7!!@$(pP}zHqp1pH;tI zq{5UQbeA+rX;l%;+dZV!!y}0Gjy~`%nc^Fl@Z7Hq5E~LTR6tLepwl1NGTjwP-Oj%r zR&LM@(C%n>;~9`HtT`^zP$eh91nX`6^(nMKvOH8dslUBfx}EDygvcTVE^sADYK0rd`si>u>`I+JF^8{w@+dcz=lsBOkPcR5x{@ODVcBKuFB_#<+Oz`jdggMn1+X8!w+lp?&>q!2s?90#ycx zUjWS>)4AcW5}RO|0k@x{cjk=;t<2MgUy2RaZQ}32-E0P+N(y)5Yj6BiV7$+}?dWOjA|6C)_q&n65zQvR@IiV()Gz4YEh9 z-Ly!0!_3x8$-Gemb)P)G)~W)_lU5gJolUCz_@@#T^Et+FMmF+Aed}TSxxp1h6|cVM z?oHz`>ZR=qaLOO7Q7QEfQKtTqySw?hbi}W#Nrmxu-yF^bRtv29s9kl~fl~o~u%yeH zF6zrRk+ZIP)N6PD#*C)I<*4^9nS+Jyry^)A$JJCBnzm3E^3$djDJERkN?QS%m)@NL z=WRSH&ew`U*_b!xxxR2dT+zJ!*j(H(Bf{wAovG?pHLQUhW(d*qW0={lr=leCvvEyj zWpjH~#H2tw*M}Oe4;c}CTp-bd#7ZH|a8KV}MA9&PQ@*obP0e9&`So%}9TL z@_BCk+meaW63%7D-xI7e?SxCq$3+~jtj|c{C7iwDt98Z78Y48Io!El`@dx%XuOY^Z zwtdf>)qWPm793p;ka*~Z`Q6mD;Mi?lXHQJy~(I&<62 zzk-fkORd{7>)@VX)Dn$}*B0ImxvoQ5DN-sn&#GSaW?-Ra*8m3GBs~?uQai0!WZsItRF2*eH1Ii(4-4cQ9nVvsp^ld~b z=gqzP_!ueN765gEpvx{h`s@x4$)-YN44JR>tXsn)eeXSqcOP{4c?A;7^vkojNkrWa z;@jx9MVv8<y^0IE3#TBR=Q*zb;w<%N&A{Y)o-Dp+0M_VwDWYbp_gFR1-}x2VA;;l zf|qtxENo3x`j*p(!e&Q$);&lm6C(ObsX{+YKQ0PVw9D=r4O;30_#(XI%0)bmFV*A` z{gL`V)(r*H!ws&-=Yv2zb$w82j2tf$47AN*-x?}a=bn95&yfgHiocIafAGR2=vkBI zF~1yPXC`_hww`7`r z)UB+W;O~8h?tP_eF{r5O9hx@oy#x{B;aUGl_2 z&WD4Eu-!ZKC+Q6&=9icd3wReeMj4O?)&q2)x5u_HOFN&JV2Hxr0e8wcD=nw5b~+0` zt}Bxyi}v+^Nw#5<8C@ZHaiQ++6`LF}b9;po{Lb}=JGCAu4QYo>T{Nkok1yx*?0tYy z_cl>C4nz+-UeKr9E-0eY_GaB(sS^oCTgeqgJqv|ugu7opoe4Hq~Hm!h>BRAlA?ZA z-E0F6uUxLze6*qus9KJ03exG#P1yVVn(kZzE#MTptbb*feXkpyt8>2`k(hZ8EnRnh ztVFs#`&+GMBqz310l7{QIdExc9J;)NVAIAjX;x39@$4^kcwf02^!AT^`qT3~KjG~n z&`?qdJMxQMk|9(JeJOEoCg(<5ok46a=q=sr*Bgn6)9-^E+9B8RSMy7v{3V?4`E(Gs(p)i8QYV{oW%!bL9L)ZQ<@<0B^EQZ+XZqIO_cZ!G&<)W)Y9iKoPzR+r$7 zaJhm@@!kP<_fDRyqyQO z2s)OhlF=VM0`N|KePj)|D0C7JeQXXSWlw$}Z!E}juQ1wOOEJehw0~Br_ilyV}&_-Qgri@58p9>U>>x6;j)QS3farXEI3Z9>G946Yt?3+ny}kN z{FADq5|M{TlI`JfCST`oyzLNpkz=~kaI$`tcLw|uh)A4;!5w5v?h}f!M-72%lUXsLDn@A&@pKTU}ZRKlN%GLBS;)P8?KwZTJ?k}ng=n^bA32ONA;zfr0*Jc5Pi@4QW-`}L+U`DaC zPpd?=KYu~~e?g-7D4_UbhSM?z5SJeGaUqlQBJxvdSI4ch*bf&Cl8R&6=2N;yC|p;# zZHQE~n2mc)*r`&rL}5$` zX5AO4n0-}ScXR^Ip1gm*2=GyDa~fxCInj)9BbAejR64H}G- zkyw%UL7Jv!Xy&vZh6r(nO_Mgceq7v!w5B_SW>0*OH} z_=96Oh9m1d>0B6Hc@$HJzu9lP+JZDyGmhR=(|@BukBoyam-5z1rGT9gF?Mv5 zsuWcUF~MPF-@pG%AoD#)yl=x6aCc*@Z5BCnfRlJP+$j~aN-Xq+ZvuMMflgS~(@DZ- zo~{+%u2L_5<16Q*b2Hs3ZqZJr4C^lQUKv6@ciFZn^Z~l#UxaVZ38^sFtt(GHVJT8h zo= zrG2Nm5WK?-Pl7gdKATl5nssXK5EAY;{?Ns%0~9qu#6@22g z5$iwnKj@MO2S3%RRx$|0y2I7$&XH*PBt%``-crSONJ*F2 z4kU($wBbV+_Ybni>hWVTH4kvTuRvG-QDMA-ODT7UV2q6;lH`6t>{z5i&i{g*m`>z{ z`Thn+g^gdb1Q~U8AifQE*@BJZ_~VXUB#VUawCR~Y*bVz}C!aoA)e75i+CYc|hGxuh zh(K4YSi2>YxXePn1$AE8H_>vP#oUQE5a*!jRaAmHKkVlz_<9?OYki*Z)QObfo(FPT zw9JM{Ea^wIk&f(yrS|2spnF}t08cz`carR*IKh}pmUXmZ;%>T}3_tzLL0TvRD2m%) zCDscuQoYu4hqAL<&A&EDRE5*|p3wLB;YKKW0LtAYb(KwU9Ysn2I0klOJOB}EdF9{| zV%Yq!3#3TpGz(16;z-x!FX5Oj6D&_T=B#TvPwHEO5%&J+BfU~<4e2x^bMMD%7`o6L zd>7zC0^oyMZ8AVJr4&G4x>@cBf|!?=>J|5F3t79bf*Wg0)?NY$THEE^`j#?w1C>KuidVbM9#iRf_$bF|exSG#6rqlG=lqVwrELZ3m zIyhp?g?QXr?ZxBo&NtG4^$CjO;`U?5J2om$vN+v?vV;pYqaK@pKp`M8Gy*3Mi4>Pd z%tVYo%@H7ujGn~Ow*Eek=x68FJO+#W1%=0h4*r6?bB7Z`kR6bEC_jMM&_o!uW<#>J zR?jOQvePoWU>6KL9(Kc@05=vav?spEgeAcV@g!bQoPL1MI-3S%;uqfR$JqNcjJ08L z%quo1W`I9MdgXiDt8}y~cbJE5vfh)zEcw5nK6A?q;GdY_Yw1u;APh{vF9b{|Hu0hL zk_X>kBPx~Sg-KTcA!g&fzB1U*olO`$MlDh~JEiVq>Dot{IClom_&Eyk-5Dz7m&O3u zRuYEw^a%Sag9Y^kYE^^zv(|sxP&AldS3~DsW-?>`B~xF>MZ;;{6xewwnj<-Fbt&Oh zv$A`Aa?EWDdJd13{nOPn34U7|q?9Rxj7jj7#UOVok zR$Lb*{XqDD@{~$MP=j5?z!AK)G%uGHwizqGS5+~vJWlJ|&-D+|{a7Lm?CTzMWt%IvP3cuRc%qs@wa%TDEKqIT{6zfcd%$Ji~CLwY+Yy`QVddVO*t(f z6du#hgrW_Bo@PLgqN&LBCtI8CY71SYbrku_>7gL^X|cmxA~kL0Q(VldP$w^GYelnT znU|d^zWC3_=K~j4!SC~F>964~PxlB##{t;sNKQjcN_5pghHVKgdKZDNL+DPiETz3qeHn=nmNc#QRJ?ECx@@X(xy$~Urr2;RMn2T7t?7W=XE6?1b*AE^M=zDLRbF z<_rMaqPaDS8(v-N8Rm4ef4W2U&Z_9o+hLX3$go!=kP7jFw-GXw9pvHa4*?cxFE*4r znAR->R`-l0Wi^Sxe91-=AsRH$o+Ta3mB_3{r?lSMaH>iuCkoAO41IEwC^pO$%S zqdn&}bvBOo@@xk`LE9bCdR)`fa60gdgJ=W=UbAdxkq(oT6;-|+N}LY#8e_@!^L}=8 z2gI^LdIhBfU(Q1vrVS?|_O}ri)^ABJ`ox%%`nO5s&k2`TV3R0D^}8@&euJr1WM%J# zgqHT`vyZ#^29Aln7MnUsq(Xo0uA?+={{@)|U4BLhgO@YGhe2?F(r9OK<1ffS8x+z2 zNM!rrrcWIRpIdJh{2umNq|WN(+|wW5Eda5zfNm2K` z#A(gb?B4^b?j265NsYrs%G25_QWUTyJ8DejQn&jsN1uxIJ?|=i%Ed+92jTNS{*ZOF zZ<8$aRr>MLsLGZA{awtHA(&W#f4YjT(dnri|7+2?EZP$qzZ2vXf}yu2DAY{Xq>(r9#!L=%@IJjSF}(n_oPaIJ z0lX<29bn8OFb7_H5-Jh6>RMX~h+Wz$&*Mkm{=5U$>IB{Cdul>4HnjxQKbP}ElGF1< zy<@N2xCyErkL0;KWl(&Kf_@KQO=Lp!e$P1NBsA9zC*(;^5O3fu#xj6_%;aCW{9KlC zk?eBY1R(COq9AjOKZ4_+HW$2nYn}bsMwv0ECHe%3C6n%JbSj9y^F-oY2%zP zb@b@!3f(>8T#>?tqtTw_eEuZ1q%YgUyDGy-0pp{`diQeEqoaBnx*&8<++iGTW5|qd zOH23cx;H;~WuQC1#x@Ez#WoN)iOSlSTPP~2(R&Mm3oF+^Yc%(L5S3vCSp9uNty5Vn z^Ut5wR;{e!$$d=2Eo;in#IKlWk+HQn$7uug6I z^IMC`K!68Kb@MH9v(f`iCe6%I6Qjq8uWIF)v??brQB-WAaBbBRvu3rwC>w8 z#6%2^cgfyw6B?;X+RJR+XOPsiMe?k6rb}z80?}fC2S{EbVxg}H@8rHKc0yR%80Qyf zUDE_V1onzZ+`hv{Ria`gaF<)t#D%nxkc;vJC&K@c?(7%?QU1oA#U;pK{b_S!(m$j8 zyIOmJG}OazY2zuBf%_^d99-YGdwFRVGzK669Di?tuh*AZ_pC^$^VWSVO8q(L}r|#x!|!mhrhZ(*x0vuCLx|>5}XNz;WC^Wwg9hw5*eGIo%? zr@#Rn%tEiKmYOrPfI_`h<419xmwr$O!h7{QPoQ#y;@wXzU)!8);Edhz6=i*HKbnA{ z(P0V@^M?F|vp3nRDQp*`xQ|W&^9_3tIf(fj3s_$PCMmiA!`j8eAC&4A@}l;uwt~91 zfdJ)8=L zNh^Bsz(97vt?g!uxfttvF`4k;L%xpvYeILqEXS5OS`7kM4sZerh|(n$gFwl+lEN0Q zED=@L44g!W6rM4#odX_;a!lg1sE@izLvA_ft@9f5@ z#6;}^WR*E8CApP8K&DIW&$pgtnb2#uK>QAO=^F3uKnmg1>Oz}fHX^SJprSfC(lRFh zfDF@s6(I^dn|Zs&uq~EOsAy$>wt6swT9Hio(w*LZA@TD^tFvQTucHo6q|6%#AH0JF zY7<%pWyJzXw}7jJ|3@o<2AUvE@nCzkZ}0Igq@VvePlkv^aDd(6@jN70`Gv$q?vb0{ zLxuPDpiMXWg1PjXzWE(iBlQU_e?gwPfF4Arg+jCK2kYVFfXXhTuc-)0K`!=4R`^FHLDn{c2|5WG%6%L^ zYH4_=tA*l0rwCdc>FFh|si13>boCunRMPzEtSeJ3%R%j7;g7>PuP_0n6XZR@Td)_L zcMHyL+(AD}|IkpXmu`iV_K6NZE2D#GGR0tEO7l*~R(uC9#*&}`q5w?(ItaW4WJoJw zf24pzOztGg)jO_@6q}WV8MnrKZa{C4R9P#geWn&|g+{T>YQdMxNiIdObE`(LFTqf$ zsaEe5C92=9qkoGlfr&4!|Kj|py}h_^_F|6(w!C;2-CoNEFu6L5Ynh!`m>m}B#lMyHz|sY5EH@3bHfv&{JX)DBtnD1~ zkp|3l^$Bhu&G0AP6+>%ktM(*O2 z2-4pWWOFXL)hvY*rSCq7&lAJh`Vd|hY)vnb1{iQsAK)G+0IW4bfi(Ht1X&=9o8DyY zNqJ4YirXl*a?Ml0{Aq~Y^(eV~+^Q(P&7VQ$b(M`L53G zfn_@gCVj7~u$sH!E_3x!Ub_iJJn%?mTKz6HrJ{(un<8jhX*`>RFI}Sa+4WW!a`gGifhp=80!HA{ykZS?RKA3%%iIno)}^LAYQfd(N{jvzCu zldR@o+0cM+?cJQ@F)SgHm7fD!?y^*yvS)c25Y%7^fAd0$B#5Ke?0TyY2lls~SUHQY z^sC;x!>eXHe?gpZcld8Ji*b2Kd`$`GE71nu78X}^^iHaJxW#lI3MV3m;v!xTDr`SPk=6Y+ zds5@V-9$-6r>zP=0(DX05Mr^)jP zB`Qxp*Ro2O210rqoK{r=E)*+YzF@kli&^+m7fH?7Kq2Go^6U3RVl|($P;nTL`ICiD zoMc=oY&YMob`NkRWL6Aj4FquBWN7AJCY|%~{?!&ph>>#-GayohVb!-C=>-!nT1V2= zcR0h}^SUgC zRp|03S@ZB41HiA#41j=udhYOy->K46-FNzq9BW(mKqmS%iU<#U{284Qev-9>34_7p3KP^6L#!^ss{$ns z?iVWMR?pH`F7L*%Q@#-6_EyX*N#_mIq(t_i*&R#pnlUS{eQK zn-r>;LiV9xg+dH4s7iqz62U)dQ;9KaOHw`ezx%m94WlH?^n=PsMP#_xkM}FrHLNU= zN)c<^quj{KT2Jw)u|dZ7QBr#>!|^?BSUpgZ1Ak@R+Y7wZTiAwCV%u_AlCabd68l%K zig3@0%&y)1!>z{-fhOZ{D%~g^NwBjqP8ih<4E;TE8FRn*(uVq0{Z*?wn-OjHN~zaa za{xI#Oa&W4(ctN^^rqOUC^V;pN>6jbfZ$Xq(*wt}A|)6#GyCZkl#Dukkd`qPj%PmV zq5jgD&ER_Roq+dg>_2kQ`noz33yuPX+&F@FXw@Yt8?)b^vJNO0n$74ctB*%3?;YmK zIP-Se1cY6^B!$gQ^VZRQtibv8-zA#de3Yus`c{6FBvuWop~Qf z2IAWz;y<$PuXv-Hx+|Q)PLq#UjYxQ&Hy9)3NVYlp_FS>xVBeh>(=JL>PGD2sKYO*n znL+&dh8LrJk-Bi|_i3C2-9>hdwv0GIX; zwEpxvRlOlIzytQ-HGyNVQasDzetxrJr1UaIQUj~-0g_mia+{K4t%k8uz%U5j9 zB`Td$eNEKQKk(}KQsA$kbT>iB*F~#iED@4=Lc1}K;{!5Nq9j-D%z?dfStP)*b2GnFZ?doGI7RpQ>6 z1uqvPzOlvu$8@0f66DvdPVSi7W5<@Ro7|~;?etx)>&6lV(vnJ8-1H!#2EONtp*YRQ0&+QW1QjD(Y(H05h3lwpsr2K% z0R=id{eE>jvJBX_UT>V}MuPgC9Ui{=ai}}Gg;uuR!g{D3vtH=cn6>xnb+*bdX8D-; zQWlZ}$Jjv5h2XUCT`?eVeVPha%RQyR_5n_v;fa+ikOo8fKfAyv>qjI2i4^bMamM-U z{0#eq)qoVC{B@Kn;EpLC5k3HtB=CehzEL@LRoxYU4)oEu+z&Fcn8vI%4{-_s8XgLv z*9VXqI4O874o6RHSAbmH6gylx)y704RuDLVPpC=?tq0&G0%)y?SA9|i8Q)r5B(0@{ zfDR5e0BecSEH+%#O&?To)OrO~WPhGBl1kn({@`lL+EiikQ3R3`Df|@xau8S9v^xJ^|5ah0T6=*u(6apx zA0!9wR4WI_^mu_!Z5M&#a+Lo6tNri0`+vi}Jtj}FZ?18R7@T+Wq^x%zWjycY>xp#2 zek_Pn@k>m1cPBFJL5SfO*+de9zo70>Ku-P?2{3Ji0q;P7fvmW!r@Rz?1lS9jXA-E< z(3irOZ=mpoyqFZ=t9wi_fx=Khh_{o2&Pem7@bX1LISckva^(^hS5>macKqLKo{+p| z_8t30t3IpZ{)E52A<^?efktfM2uXV-|La23gU+r*F~k4M z+t^5XdFO;%VcfDh=NEB`b51GSqyCH`j^AfCULFZGjPUzKBY%tjJ9FpwFbp(PH; z9?t=iNw|{EK#qyotkz=5Y10-x2had&jhZ8ExK!1IqQws0SM(ROuXu)JMqdAk@+zDZ?7Lts%1QTih5H+ex zxiThlKsbuiTK9t)Y9f_NKJ;l*?R&gb#Sr)0Fh@GoJACe2-aV2dhk46w??|8l0q!LN zqo6zqD{2atI*qXpvwh}2FEkDet1`^#unbX#k* z!c%wUggCDv$bNJ?Rnu;M==%4;a4P%~a6XuU58T3q{RKU10w3!k$EdK#S%*_IjPsri zn!vAvExM>+$#EKJ3E#35q0Sbo-4{O6L@e0+W}5-cu@erxhI&6Q+3{q~aesFbD0RD)P*7^T|dnCH^kAm6_ra=42( zDbxoe7!!wAM9vtytLl$y9F_g>z?~PS=?#9BKL=dkRSn#N^W_8lBaC7PGRChYc3(ou z&rc#DIIV8}opz%1@~&-9ie}5~_nK}%{o6QNF{gE!gEs-MTkzzk(T=pKJqhvpl1SgQ z`~keid6yMae)_D1H~}yXs+X?F^7xou`C)-JRA`)?8VS9;tPc=~J&vF>&8>;Y*%nLr2TU3r~pZyrIf} z2_ntUr|*~gozbTZjIZH@;XNpvet^3zx!8Mom7sOh+xyYRbz+`Be9~_qI#G5NNY4|o zyOMh^3=&*N^lpqwI|cCHe9f)Nd}s-ja&Gf>LQ-I=t=OpD=C;?Bkh|%}TYh};f%|{h zjo8lkj)X$A`J&(FnQuR=Ds^mX zLxJgxtFDAXe|W4#8y8M$xx62*Sz+*{_*Z^&=(T$fWYRP>6yW~$eOJ`1LZ)_mACOpO#S7=t_krhbkB#?$F=hb7eG{-h$G(Wf3+SEV z@wee~G7&lD6!Csm!pZ91es>vjK328V_%2%B2{2 zf$~|sS!k+6MhcOJ0xOU3!L#FX(GVJ!J1$4K|3TY(M>X}e-J(H|DpdugMWrerf)u4i zRGJ8gqI9C7QX(KAJtPRykt(7fgd&1cBGOwTBA_Cmv{0mk-bpASkm6mw-}n2@Iq!Gw zy<^-n?jH^zBka9b*=s#(KJ%IL`e^Hq_U_RAgGXO#s7m|aQvS*z_j(6gKvyq-@&%&J zd!Nm==TQ_M*V1kdv)f)Q7JD7W;+$x_FHEguQAK(dgnuhaw)f4eVfmSmfElz)pSKFZD)Fbu-yLDc-Ka?;MF%y86I}00V)!T5!^R zsJ8jkBICi*A+v`VL~YQ(z3P|p6Jp^$oagE1vE4~jg``y%Vp5E#f6Cv)<8!T_6-_k% zvLII)G11`qJI*^Tcj{w1%=d?&6JV2x&;#nB{&SnHxRYd#$kNmHs*68A_8ei`cyG=L zzYt&@3eLuC6;vpKgtK7hF|##j1Nl+AersdpxA!>Q*X3{Cn3Hq09F?95JGU-Lm%vfM zw#`-vvYv$(30lqAXhlB-<7@Zd1X`(Bu30~loU2cw%0O4|5E~~VA0cJPvPJAkQ=xor z!tG+iY-sln7N$*p(a`}oVM8^9j1(Mig zbd1TMhaB1Por_XVM$skJCaiRv^^XyL?)q(dcdUTPR>jO~X5EJh3|M#}oskP)}dK(!ODRz()_&cAcBKWcS>|HqF& zNHBqeX3A`v;6=NK*g)Gdk%J=xAA|h&x?fLeKZmbX9E*0KNP(4T7Qi)9)~)suPF&l7 z6GKh<$+W2ew z>zV;S)<>5=>7q#3EF528eh&qlfWz~tgM@b9@)S@tp_yzH>f?V|i1_7ay>ol-1&6&% zhBHYyY>Ph%CH#RjAEj0jDrQh&ahk{U{^#PgP!nAfoy-l35nqZD)f>DwqndFmMOA}k z0v#Tui``On2Fn#6IsJOhNi7(6j zzH@g{M;Hm&+bnTbjFzw_J6g+-7n61Mv3^udnq=U?NtZUj9S7T=uls5lHS32+g_A?U zXl@Ylvy{OzU^$mM9g=M3*(hehG)CqC`*g)x* zyvvNHc-bx}3AL|E)#_QjH5h-ZAvY&?PAYEa(-}xE|2KRDX5$s~Feqc|`U7F6SufS# z|3Ho@88Mq@02W{tm=q1Wi;ZTAFbb5J>chX6J*IgGFHG*fSmn)Cm2LlmvhmE;bKi+? zyw_BnIev8|om)c-i1|V2WniR~H>Z0NV5VM-qe?!KvV#+pBjCdD*)lupM3VMpYZxrF9zUV~s^}Jn znD6HqAMa(GbufYsR5YdCn2w&h+{?43AtogUU4Ptz8`+zn1T$XfNicr|A7}^&S3_5J zJ>nRtRw&x^T=VV=ne<+PNcOXkjp)J?%**&laLNz_`MsY~*q{92z#GI^=O8d*vxhN+ z(p#B*u#d2Cm^(xBQU!>In*`@IJ+BMde&X<-d6@J(*t(SJrC&p;jjrkv{qc1$ zGqlS8!z=sKmWNKwOx~O1>u1>o-M+sbh~jt=;S97BdO^i{SWmkqb(3;>@OUK8S0>k^ z_Z-rU#5aFWS1?xQps?Lx>@&H6$t1%UCLZ{Y;AWnlQehAPL@nrTSrR+U-6*tO`%FFAQA5ET=w>fQe6_8xs zutOxDvus$?4?!u#%`{Fm7)^Wn1J zRz2DEw~lSI`LX#Z5U~P)-lr$-;NJI}>f4T!Ce6Mn%DQkJ$-fr0Ypz$H%MbX#lli!# zRBz%R$Vfno2A=&`RW%uzCgrYygGzl*T<(I)xfgNCIYf4NDp1hIBCIAKX>fXI&`EGU z!dJO$V_%_N_^EPopV7mu4$)sUgb64qvYJloyJbG?40PS#2;}6>d8vxS+v`|(YzvSV4D*V<-;`2`7^$VX;{yenDHZc(t7OU z#!wdnTtbJpi%(-p$L8OrXeS`2`GL)R>nrv)n9zbn zME`;^vmnqNhXdnV{XeGKbsd$v$??8KqDZnseHC8mML#%Cgst?vdt049tFi!7-bmMdjMs%>-wh2~j;^nPak;r41e{#BKCm~R3 z*$zg!+8MKLG1@mB%BbY>xL4E9{k`cOZ}DaQo;r5CDM}G}jFi#7&fys7+#akW+MNQ^ zb7)fY8vYf1sxzCiwIkqDGbpkdD!oFrKogMQ<9S;n@PXgIEVX`j!V0FFZBeR4d& zFv=}xR|C;RmlnsfCgk@$r|jTPCe3}_4|bbsFFh-+R~(YU7FrIVCljE2v;8g}QXRlh zy6Rh&(%sMJO(Y|k4$Kp{6hjos zx+`l7cpI@8EsMM%tfWMLxE;OdR7qoJ_U0U+_R;hL zZKVcQoyqLaoQ1RB*_f)9Ry4T_==J(mhp|D??t2!NePw>}Jn>@7f#oY}&aQ96?;S`z ze&p0AlDp1Ez1P2v-8>tVvip?flUQu|GzHAna6kGfMpkt(UH^_(z?8+Ijx@=VQq}sfoVU`f z+@akTd55>;l8`+4$g3n&2@x$gEL)Jjl&s#|)%e=;#zKg|BmETv2ogLSTQv|waWtaX zlDr}(38i9R{Cfh#8?1Biu54G=vtM;_twPMepJ4vduA}gk>ursHO~!4=JWj)0 z2F=+6Upxf(3XSgy2p`cKD_MRIQP^;9fo;6%a%SSnVOvpn+vQJ>dxi>#%ru&@1JWaE z-d;~7$0%hgq)H>mwW%P6-aHWily+Fw2XDfr$hfP~%{;%yXK)cA)gKS_!+ z3f55?Bfpo8+BH2Cz8%VNeYh#@WPcZ?s5!Q{>6gnNga0>9$XhTIir^g8{PCENs6s}H z=T7j8Cu(*L`bRpoPNXNZED62@`3%G_v>WtqWKn$@OB^$8*A(p+5f$)uS1WlFX+C#L zyVY=b*66Br!>;_DjaQdCYTa%fWefSq(F$0QSD_;Xtxy3yHrE`sem?|)UxfNV>fWIB z^`}9T*OB!HVS$i~uOCZ|H_K6a8C7&`nlfFZq2WWn+UkvV!UfrkH%HkbU*_CPv6?xN zu-R_T5lqxmL$}DI1d-RtG958$ZrjTHmNJBn-glHqFN@DHRWw=ysXxCk%6l|7Y%&$b zrL%uZUL7w8UmDkLdh#Wf?`@|<{hgl;r%gk7ocl!~JN!e-QyqyYO}aoI(q+0?sP6*4 ze{80zrizbs^Z;9O)QsnOsZq&laFhmdo+)3z2c!f1+eFWh1T_PJ@tRI{q%QkrO_6KCRQd+x{jcK4lea8C*fJCin zmg5LJ^vW_>c)s-rHOc(ZO>U)u;jJbEItVXR zOoY|t7sg88@2M)xAN3f%(<6b{zGai@I)47JljeR~n|lJm;R;i*I5^1!8wKo5JP0vO zDrE>KNOOaKs;gQNFT2UNp`!gPy0y;W(EXQiAwtO_!9Kylb54XiI(5*3DWC0il?|&> zuJ(4Gcz#_lSYA$gYYL|WUaI3umPhqY*`8(bY5(pKT|O}$j#hU7!eJB+KT%hSVx^!{Jt}fm4agr`? ztgV>;&n(q_wNg2t{sll#BJ?*&lkUls zSu%1aG)1wzSKD{y>pimtXKDJE`2**$NLHW}1t(;#{wERw3u_4tVZC@ z*mzc~H<8T!^aH4v;ggQ?? zT~u(HmSHC8;1KLJ4-Nmx1}MSNtJ-8ZC6|#)4}I_8etLrI@y^R(`%c%mYmHN!U&8ho zU$B|UB(D$mF|Y4VPweduz_z#<=cu-Tae&$11a%{#Ib2DAHuvFsD`%&6K!9_n%9>eF zbOc!ZG0{v;MxGx$p~0`aW?ZR1CrBlRcGHg5k&?2U?yNIU3$eE`3phXmUj&Ph% z8gfM+O*hx=Lj8gC==gsyQR}WS5PcEg8Fn#V(r2Psa%gs%+TRLkNdraX5>*8NDL);m)P zxj-Q8FleR$XeJ!V@&^L9gEJ%GrPY&8DvUTBlsZ9){OgXODLO)}y&5PTE`R!$2UpA; z&EpAa>ze>EL`}eJtbu0XJ-i_Glo_;j1eHg0bc`FD7*nWhUzkR^vp|HRkLB!6$L*Pd zG_?Q@O7|ok&M9*%b{#)Kq7-m{oBgfk)9!QL+VCrz3!7k@1GSPA@fj|zb2NbHb#zf+ zS=69I&*8O>rrH-KNA zAQ&Ik1vKf}#J*u9hw+I<$BW;vcYeYBnygU)>-(I;G@Kua^C2h|XRBPw&tqTXA7F;0 zh%PHF!G2!+L%-d}rR8tXTyA+wS48w1Lb$_d`$Cr&_8>=O(-Gi`|~wFglj#%F08r zr)Cr3P~;KPJuUX;JMYKr7H-|08yWEnI(9AO2LLG<@)I3+Z}49vVM zw{X3TbdcD9vN6A*jt_1p@wo2|=INDq)_m+J{{Bof*W**zd%cG{J-%%Sx+)_M1*OYT zr?K=~zdY6Jba2^4OnZvdNYlq-)7`ZX)$UQ0 zBvq(;){F2~=rblawQ;hM{~*=ti-u-{N5B5JgT=0is+Rq)OZc9O{-S1(@L-Z0(K#MM zOcGfX-W!bfSCMI&&RhB3nto>}5kXw_Vsdtn=0dLrJ%))<(=PSR!PHZRHKAh@8#AH$ zxjf(q1X!WBelm5bzQl|c5A7@D?mTb#ntqwkUPC#5h>=)c$9Gkm4US2~ddR`^AH&Aj zL}b-BFQXspo$shY%ufs#Q3R>>G-dOVq-eFpZJ40eLl4AFZ^b2k%Oemrjv<$ETt*!Y^sIo5aTyw$7CbL*AA4fMuw_E8

a_FzSoYKQ9sh-&&d4|6%D?hN7@o@qOo? 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 Date: Mon, 14 Apr 2025 01:16:06 +0900 Subject: [PATCH 036/663] 1.1.23 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- lib/src/widgets/pdf_viewer.dart | 10 +--------- pubspec.yaml | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef7fbf2..a83fd3b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.23 + +- Minor internal change + # 1.1.22 - PdfDocumentFactory refactoring to improve the code integrity diff --git a/README.md b/README.md index 32525639..83d81bf5 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.22 + pdfrx: ^1.1.23 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 963ac471..666f9f64 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.22" + version: "1.1.23" pdfrx_wasm: dependency: "direct main" description: diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 3255ed2a..2c4eb516 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -2099,16 +2099,9 @@ class _CanvasLinkPainter { } class _PdfViewerKeyHandler extends StatefulWidget { - const _PdfViewerKeyHandler({ - required this.child, - required this.onKeyRepeat, - required this.params, - this.onFocusChange, - super.key, - }); + const _PdfViewerKeyHandler({required this.child, required this.onKeyRepeat, required this.params}); final void Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; - final void Function(PdfViewerKeyHandlerParams, bool)? onFocusChange; final PdfViewerKeyHandlerParams params; final Widget child; @@ -2151,7 +2144,6 @@ class _PdfViewerKeyHandlerState extends State<_PdfViewerKeyHandler> { parentNode: widget.params.parentNode, autofocus: widget.params.autofocus, canRequestFocus: widget.params.canRequestFocus, - onFocusChange: widget.onFocusChange != null ? (value) => widget.onFocusChange!(widget.params, value) : null, onKeyEvent: (node, event) { if (event is KeyDownEvent) { // Key pressed down diff --git a/pubspec.yaml b/pubspec.yaml index 6f098392..4f1bbeb1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.22 +version: 1.1.23 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 8f7ebb7a465957bc7a193f4016158be74b1ec36d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Apr 2025 17:42:55 +0900 Subject: [PATCH 037/663] WIP --- lib/src/pdfium/pdfrx_pdfium.dart | 2 +- lib/src/web/pdfrx_web.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 058b842c..1b1f4b1c 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -25,7 +25,7 @@ PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= PdfD /// Get [PdfDocumentFactory] backed by PDF.js. /// -/// Only supported on Flutter Web. +/// It throws [UnsupportedError] on non-Web platforms. PdfDocumentFactory getPdfjsDocumentFactory() => throw UnsupportedError('Pdf.js is only supported on Web'); /// Get the default [PdfDocumentFactory]. diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index cfd0a2b5..c67ec9f0 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -18,7 +18,7 @@ PdfDocumentFactory getPdfiumDocumentFactory() { /// Get [PdfDocumentFactory] backed by PDF.js. /// -/// Only supported on Flutter Web. +/// It throws [UnsupportedError] on non-Web platforms. PdfDocumentFactory getPdfjsDocumentFactory() { _init(); return _pdfjsDocumentFactory ??= PdfDocumentFactoryJsImpl(); From 534acf7d72053d5e996a7fc35763ecc8ea77cf7d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 21 Apr 2025 13:43:06 +0900 Subject: [PATCH 038/663] PdfViewerParams.onKey can return true/false/null now #344 --- lib/src/widgets/pdf_viewer.dart | 32 ++++++++++++++++++++------ lib/src/widgets/pdf_viewer_params.dart | 14 ++++++----- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 2c4eb516..1c282136 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -497,36 +497,50 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Last page number that is explicitly requested to go to. int? _gotoTargetPageNumber; - void _onKey(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress) { - if (widget.params.onKey?.call(params, key, isRealKeyPress) == true) return; + bool _onKey(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress) { + final result = widget.params.onKey?.call(params, key, isRealKeyPress); + if (result != null) { + return result; + } switch (key) { case LogicalKeyboardKey.pageUp: _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1); + return true; case LogicalKeyboardKey.pageDown: case LogicalKeyboardKey.space: _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1); + return true; case LogicalKeyboardKey.home: _goToPage(pageNumber: 1); + return true; case LogicalKeyboardKey.end: _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd); + return true; case LogicalKeyboardKey.equal: if (isCommandKeyPressed) { _zoomUp(); + return true; } case LogicalKeyboardKey.minus: if (isCommandKeyPressed) { _zoomDown(); + return true; } case LogicalKeyboardKey.arrowDown: _goToManipulated((m) => m.translate(0.0, -widget.params.scrollByArrowKey)); + return true; case LogicalKeyboardKey.arrowUp: _goToManipulated((m) => m.translate(0.0, widget.params.scrollByArrowKey)); + return true; case LogicalKeyboardKey.arrowLeft: _goToManipulated((m) => m.translate(widget.params.scrollByArrowKey, 0.0)); + return true; case LogicalKeyboardKey.arrowRight: _goToManipulated((m) => m.translate(-widget.params.scrollByArrowKey, 0.0)); + return true; } + return false; } Future _goToManipulated(void Function(Matrix4 m) manipulate) async { @@ -2101,7 +2115,10 @@ class _CanvasLinkPainter { class _PdfViewerKeyHandler extends StatefulWidget { const _PdfViewerKeyHandler({required this.child, required this.onKeyRepeat, required this.params}); - final void Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; + /// Called on every key repeat. + /// + /// See [PdfViewerOnKeyCallback] for the parameters. + final bool Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; final PdfViewerKeyHandlerParams params; final Widget child; @@ -2148,16 +2165,17 @@ class _PdfViewerKeyHandlerState extends State<_PdfViewerKeyHandler> { if (event is KeyDownEvent) { // Key pressed down if (_currentKey == null) { - _startRepeating(node, event.logicalKey); - widget.onKeyRepeat(widget.params, event.logicalKey, true); // Immediate first response + if (widget.onKeyRepeat(widget.params, event.logicalKey, true)) { + _startRepeating(node, event.logicalKey); + return KeyEventResult.handled; + } } - return KeyEventResult.handled; } else if (event is KeyUpEvent) { // Key released if (_currentKey == event.logicalKey) { _stopRepeating(); + return KeyEventResult.handled; } - return KeyEventResult.handled; } return KeyEventResult.ignored; }, diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index d8025e26..92fcf35b 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -495,10 +495,7 @@ class PdfViewerParams { /// Function to handle key events. /// - /// The function should return true if it processes the key event; otherwise, it returns false. - /// - /// By default, [PdfViewer] handles key events such as arrow keys and page up/down keys. You can even override the - /// default behavior by returning true from this function. + /// See [PdfViewerOnKeyCallback] for the details. final PdfViewerOnKeyCallback? onKey; /// Parameters to customize key handling. @@ -851,14 +848,19 @@ typedef PdfLinkCustomPagePainter = void Function(ui.Canvas canvas, Rect pageRect /// Function to handle key events. /// -/// The function should return true if it processes the key event; otherwise, it returns false. +/// The function can return one of the following values: +/// Returned value | Description +/// -------------- | ----------- +/// true | The key event is not handled by any other handlers. +/// false | The key event is ignored and propagated to other handlers. +/// null | The key event is handled by the default handler which handles several key events such as arrow keys and page up/down keys. The other keys are just ignored and propagated to other handlers. /// /// [params] is the key handler parameters. /// [key] is the key event. /// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated /// by key repeat feature. typedef PdfViewerOnKeyCallback = - bool Function(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress); + bool? Function(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress); /// Parameters for the built-in key handler. /// From 4594d6bfac64ee95b95062058d7bcdf310ed5688 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Apr 2025 00:19:58 +0900 Subject: [PATCH 039/663] PdfViewerParams.scrollHorizontallyByMouseWheel --- lib/src/widgets/pdf_viewer.dart | 56 +++++++++++++++++--------- lib/src/widgets/pdf_viewer_params.dart | 7 ++++ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 1c282136..b4ccda15 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -502,29 +502,29 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (result != null) { return result; } - + final duration = widget.params.keyHandlerParams.repeatInterval; switch (key) { case LogicalKeyboardKey.pageUp: - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1); + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1, duration: duration); return true; case LogicalKeyboardKey.pageDown: case LogicalKeyboardKey.space: - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1); + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1, duration: duration); return true; case LogicalKeyboardKey.home: - _goToPage(pageNumber: 1); + _goToPage(pageNumber: 1, duration: duration); return true; case LogicalKeyboardKey.end: - _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd); + _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd, duration: duration); return true; case LogicalKeyboardKey.equal: if (isCommandKeyPressed) { - _zoomUp(); + _zoomUp(duration: duration); return true; } case LogicalKeyboardKey.minus: if (isCommandKeyPressed) { - _zoomDown(); + _zoomDown(duration: duration); return true; } case LogicalKeyboardKey.arrowDown: @@ -543,7 +543,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return false; } - Future _goToManipulated(void Function(Matrix4 m) manipulate) async { + void _goToManipulated(void Function(Matrix4 m) manipulate) { final m = _txController.value.clone(); manipulate(m); _txController.value = m; @@ -1132,7 +1132,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onWheelDelta(Offset delta) { _startInteraction(); final m = _txController.value.clone(); - m.translate(-delta.dx * widget.params.scrollByMouseWheel!, -delta.dy * widget.params.scrollByMouseWheel!); + final dx = -delta.dx * widget.params.scrollByMouseWheel!; + final dy = -delta.dy * widget.params.scrollByMouseWheel!; + if (widget.params.scrollHorizontallyByMouseWheel) { + m.translate(dy, dx); + } else { + m.translate(dx, dy); + } _txController.value = m; _stopInteraction(); } @@ -1428,16 +1434,23 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix double _getNextZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: true, loop: loop); double _getPreviousZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: false, loop: loop); - Future _setZoom(Offset position, double zoom) => - _goTo(_calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!)); + Future _setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => + _goTo(_calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!), duration: duration); Offset get _centerPosition => _txController.value.calcPosition(_viewSize!); - Future _zoomUp({bool loop = false, Offset? zoomCenter}) => - _setZoom(zoomCenter ?? _centerPosition, _getNextZoom(loop: loop)); + Future _zoomUp({ + bool loop = false, + Offset? zoomCenter, + Duration duration = const Duration(milliseconds: 200), + }) => _setZoom(zoomCenter ?? _centerPosition, _getNextZoom(loop: loop), duration: duration); - Future _zoomDown({bool loop = false, Offset? zoomCenter}) async { - await _setZoom(zoomCenter ?? _centerPosition, _getPreviousZoom(loop: loop)); + Future _zoomDown({ + bool loop = false, + Offset? zoomCenter, + Duration duration = const Duration(milliseconds: 200), + }) async { + await _setZoom(zoomCenter ?? _centerPosition, _getPreviousZoom(loop: loop), duration: duration); } RenderBox? get _renderBox { @@ -1852,12 +1865,17 @@ class PdfViewerController extends ValueListenable { double get currentZoom => value.zoom; - Future setZoom(Offset position, double zoom) => _state._setZoom(position, zoom); + Future setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => + _state._setZoom(position, zoom, duration: duration); - Future zoomUp({bool loop = false, Offset? zoomCenter}) => _state._zoomUp(loop: loop, zoomCenter: zoomCenter); + Future zoomUp({bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200)}) => + _state._zoomUp(loop: loop, zoomCenter: zoomCenter, duration: duration); - Future zoomDown({bool loop = false, Offset? zoomCenter}) => - _state._zoomDown(loop: loop, zoomCenter: zoomCenter); + Future zoomDown({ + bool loop = false, + Offset? zoomCenter, + Duration duration = const Duration(milliseconds: 200), + }) => _state._zoomDown(loop: loop, zoomCenter: zoomCenter, duration: duration); RenderBox? get renderBox => _state._renderBox; diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index 92fcf35b..34a6c48f 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -43,6 +43,7 @@ class PdfViewerParams { this.onPageChanged, this.getPageRenderingScale, this.scrollByMouseWheel = 0.2, + this.scrollHorizontallyByMouseWheel = false, this.enableKeyboardNavigation = true, this.scrollByArrowKey = 25.0, this.maxImageBytesCachedOnMemory = 100 * 1024 * 1024, @@ -324,6 +325,9 @@ class PdfViewerParams { /// null to disable scroll-by-mouse-wheel. final double? scrollByMouseWheel; + /// If true, the scroll direction is horizontal when the mouse wheel is scrolled in primary direction. + final bool scrollHorizontallyByMouseWheel; + /// Enable keyboard navigation. The default is true. final bool enableKeyboardNavigation; @@ -534,6 +538,7 @@ class PdfViewerParams { other.scaleEnabled != scaleEnabled || other.interactionEndFrictionCoefficient != interactionEndFrictionCoefficient || other.scrollByMouseWheel != scrollByMouseWheel || + other.scrollHorizontallyByMouseWheel != scrollHorizontallyByMouseWheel || other.enableKeyboardNavigation != enableKeyboardNavigation || other.scrollByArrowKey != scrollByArrowKey || other.horizontalCacheExtent != horizontalCacheExtent || @@ -574,6 +579,7 @@ class PdfViewerParams { other.onPageChanged == onPageChanged && other.getPageRenderingScale == getPageRenderingScale && other.scrollByMouseWheel == scrollByMouseWheel && + other.scrollHorizontallyByMouseWheel == scrollHorizontallyByMouseWheel && other.enableKeyboardNavigation == enableKeyboardNavigation && other.scrollByArrowKey == scrollByArrowKey && other.horizontalCacheExtent == horizontalCacheExtent && @@ -625,6 +631,7 @@ class PdfViewerParams { onPageChanged.hashCode ^ getPageRenderingScale.hashCode ^ scrollByMouseWheel.hashCode ^ + scrollHorizontallyByMouseWheel.hashCode ^ enableKeyboardNavigation.hashCode ^ scrollByArrowKey.hashCode ^ horizontalCacheExtent.hashCode ^ From 4532dd698f59e4023f9bb289d47f36c7fa90705a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Apr 2025 02:10:13 +0900 Subject: [PATCH 040/663] WIP: mmm..... --- lib/src/widgets/pdf_viewer.dart | 253 ++++++++++++++++---------------- 1 file changed, 130 insertions(+), 123 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index b4ccda15..e6ff0546 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -253,11 +253,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (widget.params.annotationRenderingMode != oldWidget?.params.annotationRenderingMode) { _releaseAllImages(); } - _relayoutPages(); - - if (mounted) { - setState(() {}); - } + _updateLayout(_viewSize!); } return; } else { @@ -290,14 +286,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _pageImagesPartial.clear(); } - void _relayout() { - _relayoutPages(); - _releaseAllImages(); - if (mounted) { - setState(() {}); - } - } - void _onDocumentChanged() async { _layout = null; @@ -322,8 +310,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _document = document; - _relayoutPages(); - _controller ??= widget.controller ?? PdfViewerController(); _controller!._attach(this); _txController.addListener(_onMatrixChanged); @@ -380,93 +366,105 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix child: widget.params.loadingBannerBuilder?.call(context, listenable.bytesDownloaded, listenable.totalBytes), ); } - return LayoutBuilder( - builder: (context, constraints) { - if (_updateViewSizeAndCoverScale(Size(constraints.maxWidth, constraints.maxHeight))) { - if (_initialized) { - Future.microtask(() { - if (_initialized && mounted) { - _goTo(_makeMatrixInSafeRange(_txController.value)); - } - }); - } + Widget selectableRegionInjector(Widget child) => + widget.params.selectableRegionInjector?.call(context, child) ?? + (widget.params.enableTextSelection ? SelectionArea(child: child) : child); + + return Container( + color: widget.params.backgroundColor, + child: _PdfViewerKeyHandler( + onKeyRepeat: _onKey, + params: widget.params.keyHandlerParams, + child: StreamBuilder( + stream: _updateStream, + builder: (context, snapshot) { + return selectableRegionInjector( + LayoutBuilder( + builder: (context, constraints) { + _updateLayout(Size(constraints.maxWidth, constraints.maxHeight)); + return Stack( + children: [ + iv.InteractiveViewer( + transformationController: _txController, + constrained: false, + boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), + maxScale: widget.params.maxScale, + minScale: _alternativeFitScale != null ? _alternativeFitScale! / 2 : minScale, + panAxis: widget.params.panAxis, + panEnabled: widget.params.panEnabled, + scaleEnabled: widget.params.scaleEnabled, + onInteractionEnd: _onInteractionEnd, + onInteractionStart: _onInteractionStart, + onInteractionUpdate: widget.params.onInteractionUpdate, + interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, + onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + // PDF pages + child: CustomPaint( + foregroundPainter: _CustomPainter.fromFunctions(_customPaint), + size: _layout!.documentSize, + ), + ), + ..._buildPageOverlayWidgets(context), + if (_canvasLinkPainter.isEnabled) + SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(_viewSize!)), + if (widget.params.viewerOverlayBuilder != null) + ...widget.params.viewerOverlayBuilder!(context, _viewSize!, _canvasLinkPainter._handleLinkTap) + .map((e) => SelectionContainer.disabled(child: e)), + ], + ); + }, + ), + ); + }, + ), + ), + ); + } + + void _updateLayout(Size viewSize) { + final currentPageNumber = _guessCurrentPageNumber(); + final oldSize = _viewSize; + final isViewSizeChanged = oldSize != viewSize; + _viewSize = viewSize; + final isLayoutChanged = _relayoutPages(); + + _calcCoverScale(); + _calcAlternativeFitScale(); + _calcZoomStopTable(); + + void callOnViewerSizeChanged() { + if (isViewSizeChanged) { + if (_controller != null && widget.params.onViewSizeChanged != null) { + widget.params.onViewSizeChanged!(viewSize, oldSize, _controller!); } + } + } - if (!_initialized && _layout != null) { - _initialized = true; - Future.microtask(() async { - if (mounted) { - final initialPageNumber = - widget.params.calculateInitialPageNumber?.call(_document!, _controller!) ?? widget.initialPageNumber; - await _goToPage(pageNumber: initialPageNumber, duration: Duration.zero); + if (!_initialized && _layout != null) { + _initialized = true; + Future.microtask(() async { + if (mounted) { + await _goToPage(pageNumber: _calcInitialPageNumber(), duration: Duration.zero); - if (mounted && _document != null && _controller != null) { - widget.params.onViewerReady?.call(_document!, _controller!); - } - } - }); + if (mounted && _document != null && _controller != null) { + widget.params.onViewerReady?.call(_document!, _controller!); + } + + callOnViewerSizeChanged(); + } + }); + } else if (isLayoutChanged || isViewSizeChanged) { + Future.microtask(() async { + if (mounted) { + await _goToPage(pageNumber: currentPageNumber ?? _calcInitialPageNumber(), duration: Duration.zero); + callOnViewerSizeChanged(); } + }); + } + } - Widget selectableRegionInjector(Widget child) => - widget.params.selectableRegionInjector?.call(context, child) ?? - (widget.params.enableTextSelection ? SelectionArea(child: child) : child); - - return Container( - color: widget.params.backgroundColor, - child: _PdfViewerKeyHandler( - onKeyRepeat: _onKey, - params: widget.params.keyHandlerParams, - child: StreamBuilder( - stream: _updateStream, - builder: (context, snapshot) { - _relayoutPages(); - _determineCurrentPage(); - _calcAlternativeFitScale(); - _calcZoomStopTable(); - return selectableRegionInjector( - Builder( - builder: (context) { - return Stack( - children: [ - iv.InteractiveViewer( - transformationController: _txController, - constrained: false, - boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), - maxScale: widget.params.maxScale, - minScale: _alternativeFitScale != null ? _alternativeFitScale! / 2 : minScale, - panAxis: widget.params.panAxis, - panEnabled: widget.params.panEnabled, - scaleEnabled: widget.params.scaleEnabled, - onInteractionEnd: _onInteractionEnd, - onInteractionStart: _onInteractionStart, - onInteractionUpdate: widget.params.onInteractionUpdate, - interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, - onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, - // PDF pages - child: CustomPaint( - foregroundPainter: _CustomPainter.fromFunctions(_customPaint), - size: _layout!.documentSize, - ), - ), - ..._buildPageOverlayWidgets(context), - if (_canvasLinkPainter.isEnabled) - SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(_viewSize!)), - if (widget.params.viewerOverlayBuilder != null) - ...widget - .params - .viewerOverlayBuilder!(context, _viewSize!, _canvasLinkPainter._handleLinkTap) - .map((e) => SelectionContainer.disabled(child: e)), - ], - ); - }, - ), - ); - }, - ), - ), - ); - }, - ); + int _calcInitialPageNumber() { + return widget.params.calculateInitialPageNumber?.call(_document!, _controller!) ?? widget.initialPageNumber; } void _startInteraction() { @@ -549,21 +547,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _txController.value = m; } - bool _updateViewSizeAndCoverScale(Size viewSize) { - if (_viewSize != viewSize) { - final oldSize = _viewSize; - _viewSize = viewSize; - final s1 = viewSize.width / _layout!.documentSize.width; - final s2 = viewSize.height / _layout!.documentSize.height; - _coverScale = max(s1, s2); - if (_controller != null && widget.params.onViewSizeChanged != null) { - widget.params.onViewSizeChanged!(viewSize, oldSize, _controller!); - } - return true; - } - return false; - } - Rect get _visibleRect => _txController.value.calcVisibleRect(_viewSize!); /// Set the current page number. @@ -575,7 +558,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } void _determineCurrentPage() { - _setCurrentPageNumberInternal(_guessCurrentPage()); + _setCurrentPageNumberInternal(_guessCurrentPageNumber()); } void _setCurrentPageNumberInternal(int? pageNumber, {bool doSetState = false}) { @@ -590,7 +573,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - int? _guessCurrentPage() { + int? _guessCurrentPageNumber() { if (widget.params.calculateCurrentPageNumber != null) { return widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); } @@ -624,6 +607,29 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return pageNumber; } + /// Returns true if page layouts are changed. + bool _relayoutPages() { + if (_document == null) { + _layout = null; + return false; + } + final newLayout = (widget.params.layoutPages ?? _layoutPages)(_document!.pages, widget.params); + if (_layout == newLayout) { + return false; + } + + _layout = newLayout; + return true; + } + + void _calcCoverScale() { + if (_viewSize != null) { + final s1 = _viewSize!.width / _layout!.documentSize.width; + final s2 = _viewSize!.height / _layout!.documentSize.height; + _coverScale = max(s1, s2); + } + } + bool _calcAlternativeFitScale() { if (_pageNumber != null) { final params = widget.params; @@ -974,11 +980,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - void _relayoutPages() { - if (_document == null) return; - _layout = (widget.params.layoutPages ?? _layoutPages)(_document!.pages, widget.params); - } - PdfPageLayout _layoutPages(List pages, PdfViewerParams params) { final width = pages.fold(0.0, (w, p) => max(w, p.width)) + params.margin * 2; @@ -1145,7 +1146,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Restrict matrix to the safe range. Matrix4 _makeMatrixInSafeRange(Matrix4 newValue) { - _updateViewSizeAndCoverScale(_viewSize!); if (widget.params.normalizeMatrix != null) { return widget.params.normalizeMatrix!(newValue, _viewSize!, _layout!, _controller); } @@ -1537,6 +1537,16 @@ class PdfPageLayout { PdfPageLayout({required this.pageLayouts, required this.documentSize}); final List pageLayouts; final Size documentSize; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! PdfPageLayout) return false; + return listEquals(pageLayouts, other.pageLayouts) && documentSize == other.documentSize; + } + + @override + int get hashCode => pageLayouts.hashCode ^ documentSize.hashCode; } /// Represents the result of the hit test on the page. @@ -1681,9 +1691,6 @@ class PdfViewerController extends ValueListenable { addListener(handler); } - /// Forcibly relayout the pages. - void relayout() => _state._relayout(); - /// Go to the specified area. /// /// [anchor] specifies how the page is positioned if the page is larger than the view. From db57b3459d2eda704c7d0a3076983f6b184cba1c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Apr 2025 01:43:40 +0900 Subject: [PATCH 041/663] #215: FPDFText_GetText is not suitable to extract text corresponding to the char rects --- lib/src/pdfium/pdfrx_pdfium.dart | 29 ++++++++++--------------- wasm/pdfrx_wasm/assets/pdfium_worker.js | 21 ++++++++++++------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 1b1f4b1c..2e2d98c9 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -890,9 +890,9 @@ class PdfPageTextPdfium extends PdfPageText { int lineStart = 0, wordStart = 0; int? lastChar; for (int i = 0; i < length; i++) { - final char = fullText.codeUnitAt(from + i); + final char = fullText.codeUnitAt(i); if (char == _charCR) { - if (i + 1 < length && fullText.codeUnitAt(from + i + 1) == _charLF) { + if (i + 1 < length && fullText.codeUnitAt(i + 1) == _charLF) { lastChar = char; continue; } @@ -957,19 +957,6 @@ class PdfPageTextPdfium extends PdfPageText { return sb.toString(); } - static String escapeString(String s) { - final sb = StringBuffer(); - for (int i = 0; i < s.length; i++) { - final char = s.codeUnitAt(i); - if (char >= 0x20 && char < 0x7f) { - sb.writeCharCode(char); - } else { - sb.write('\\u{${char.toRadixString(16).padLeft(4, '0')}}'); - } - } - return sb.toString(); - } - /// return true if any meaningful characters in the line (start -> end) static bool _makeLineFlat(List rects, int start, int end, StringBuffer sb) { if (start >= end) return false; @@ -992,9 +979,15 @@ class PdfPageTextPdfium extends PdfPageText { } static String _getText(pdfium_bindings.FPDF_TEXTPAGE textPage, int from, int length, Arena arena) { - final buffer = arena.allocate((length + 1) * sizeOf()); - pdfium.FPDFText_GetText(textPage, from, length, buffer.cast()); - return String.fromCharCodes(buffer.asTypedList(length)); + final count = pdfium.FPDFText_CountChars(textPage); + final sb = StringBuffer(); + for (int i = 0; i < count; i++) { + sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); + } + return sb.toString(); + // final buffer = arena.allocate((length + 1) * sizeOf()); + // pdfium.FPDFText_GetText(textPage, from, length, buffer.cast()); + // return String.fromCharCodes(buffer.asTypedList(length)); } } diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index d461ae2f..7f953d44 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -812,9 +812,9 @@ function _loadTextInternal(textPage, from, length, charRects, fragments) { let lineStart = 0, wordStart = 0; let lastChar; for (let i = 0; i < length; i++) { - const char = fullText.charCodeAt(from + i); + const char = fullText.charCodeAt(i); if (char == CR) { - if (i + 1 < length && fullText.codePointAt(from + i + 1) == LF) { + if (i + 1 < length && fullText.codePointAt(i + 1) == LF) { lastChar = char; continue; } @@ -918,11 +918,18 @@ function _boundingRect(rects, start, end) { } function _getText(textPage, from, length) { - const textBuffer = Pdfium.wasmExports.malloc(length * 2 + 2); - const count = Pdfium.wasmExports.FPDFText_GetText(textPage, from, length, textBuffer); - const text = StringUtils.utf16BytesToString(new Uint8Array(Pdfium.memory.buffer, textBuffer, count * 2)); - Pdfium.wasmExports.free(textBuffer); - return text; + const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); + let sb = ''; + for (let i = 0; i < count; i++) { + sb += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); + } + return sb; + + // const textBuffer = Pdfium.wasmExports.malloc(length * 2 + 2); + // const count = Pdfium.wasmExports.FPDFText_GetText(textPage, from, length, textBuffer); + // const text = StringUtils.utf16BytesToString(new Uint8Array(Pdfium.memory.buffer, textBuffer, count * 2)); + // Pdfium.wasmExports.free(textBuffer); + // return text; } From 6373901c08cbe171a428ccdb4babc610e4cbe994 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Apr 2025 01:53:33 +0900 Subject: [PATCH 042/663] #215 --- wasm/pdfrx_wasm/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml index 1da81ab3..ccb2c7a1 100644 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ b/wasm/pdfrx_wasm/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_wasm description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.8 +version: 1.1.9 homepage: https://github.com/espresso3389/pdfrx environment: From a601d9ad7c5c3c6b2068ebaed5ed685cead339f1 Mon Sep 17 00:00:00 2001 From: Abbas Hosseini Date: Sun, 27 Apr 2025 14:53:02 +0330 Subject: [PATCH 043/663] Implement error handling in PdfPageView for image creation and log errors --- lib/src/widgets/pdf_widgets.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/widgets/pdf_widgets.dart b/lib/src/widgets/pdf_widgets.dart index d1060cc8..f903cbe5 100644 --- a/lib/src/widgets/pdf_widgets.dart +++ b/lib/src/widgets/pdf_widgets.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer' as developer; import 'dart:math'; import 'dart:ui' as ui; @@ -210,7 +211,6 @@ class _PdfPageViewState extends State { @override void dispose() { - _image?.dispose(); _cancellationToken?.cancel(); super.dispose(); } @@ -292,13 +292,16 @@ class _PdfPageViewState extends State { cancellationToken: _cancellationToken, ); if (pageImage == null) return; - final newImage = await pageImage.createImage(); - pageImage.dispose(); - final oldImage = _image; - _image = newImage; - oldImage?.dispose(); - if (mounted) { - setState(() {}); + try { + final newImage = await pageImage.createImage(); + pageImage.dispose(); + _image = newImage; + if (mounted) { + setState(() {}); + } + } catch (e) { + developer.log('Error creating image: $e'); + pageImage.dispose(); } } } From c9f33f8b78ee11660c5b65560cb246d2d6f9999d Mon Sep 17 00:00:00 2001 From: Abbas Hosseini Date: Sun, 27 Apr 2025 14:57:56 +0330 Subject: [PATCH 044/663] Dispose image resource in PdfPageView to prevent memory leaks --- lib/src/widgets/pdf_widgets.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/widgets/pdf_widgets.dart b/lib/src/widgets/pdf_widgets.dart index f903cbe5..3872e0e2 100644 --- a/lib/src/widgets/pdf_widgets.dart +++ b/lib/src/widgets/pdf_widgets.dart @@ -211,6 +211,7 @@ class _PdfPageViewState extends State { @override void dispose() { + _image?.dispose(); _cancellationToken?.cancel(); super.dispose(); } From 256434b97a3be71e7ca899aa6578aa5548238880 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 01:40:41 +0900 Subject: [PATCH 045/663] WIP --- .vscode/launch.json | 4 +- example/viewer/lib/main.dart | 7 +++- example/viewer/pubspec.lock | 2 +- lib/src/pdfium/worker.dart | 3 +- lib/src/web/pdfjs.dart | 5 ++- lib/src/web/pdfrx_web.dart | 4 +- lib/src/widgets/pdf_viewer.dart | 66 ++++++++++++++++----------------- 7 files changed, 49 insertions(+), 42 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index dfe3c507..3dd4e510 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "request": "launch", "type": "dart", "program": "example/viewer/lib/main.dart", - "args": ["-d", "chrome", "--wasm"] + "args": ["-d", "chrome", "--wasm", "--dart-define=pdfrx.enablePdfiumWasm=true"] } ] -} \ No newline at end of file +} diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index e912f2e1..604e1769 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -10,10 +10,15 @@ import 'password_dialog.dart'; import 'search_view.dart'; import 'thumbnails_view.dart'; +const isWasmEnabled = bool.fromEnvironment('pdfrx.enablePdfiumWasm'); + void main() { // NOTE: // For Pdfium WASM support, see https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support - // Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; + if (isWasmEnabled) { + Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; + } + runApp(const MyApp()); } diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 666f9f64..de006dc4 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -322,7 +322,7 @@ packages: path: "../../wasm/pdfrx_wasm" relative: true source: path - version: "1.1.8" + version: "1.1.9" platform: dependency: transitive description: diff --git a/lib/src/pdfium/worker.dart b/lib/src/pdfium/worker.dart index 3dee429f..e4233858 100644 --- a/lib/src/pdfium/worker.dart +++ b/lib/src/pdfium/worker.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer' as developer; import 'dart:isolate'; import 'package:ffi/ffi.dart'; @@ -60,7 +61,7 @@ class BackgroundWorker { _sendPort.send(null); _receivePort.close(); } catch (e) { - debugPrint('Failed to dispose worker (possible double-dispose?): $e'); + developer.log('Failed to dispose worker (possible double-dispose?): $e'); } } } diff --git a/lib/src/web/pdfjs.dart b/lib/src/web/pdfjs.dart index 27df102a..69cd52d0 100644 --- a/lib/src/web/pdfjs.dart +++ b/lib/src/web/pdfjs.dart @@ -4,6 +4,7 @@ library; import 'dart:async'; +import 'dart:developer' as developer; import 'dart:js_interop'; import 'dart:typed_data'; @@ -325,13 +326,13 @@ Future ensurePdfjsInitialized() async { return; } - debugPrint( + developer.log( 'pdfrx Web status:\n' '- Running WASM: $kIsWasm\n' '- SharedArrayBuffer: $isSharedArrayBufferSupported', ); if (kIsWasm && !isSharedArrayBufferSupported) { - debugPrint( + developer.log( 'WARNING: SharedArrayBuffer is not enabled and WASM is running in single thread mode. Enable SharedArrayBuffer by setting the following HTTP header on your server:\n' ' Cross-Origin-Embedder-Policy: require-corp|credentialless\n' ' Cross-Origin-Opener-Policy: same-origin\n', diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index c67ec9f0..0ecd39be 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -38,12 +38,12 @@ bool _initialized = false; void _init() { if (_initialized) return; - Pdfrx.webRuntimeType = _isWasmEnabled() ? PdfrxWebRuntimeType.pdfiumWasm : PdfrxWebRuntimeType.pdfjs; + if (_isWasmShouldBeEnabled()) Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; Pdfrx.pdfiumWasmModulesUrl = _pdfiumWasmModulesUrlFromMetaTag(); _initialized = true; } -bool _isWasmEnabled() { +bool _isWasmShouldBeEnabled() { final meta = web.document.querySelector('meta[name="pdfrx-pdfium-wasm"]') as web.HTMLMetaElement?; return meta?.content == 'enabled'; } diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index e6ff0546..56c5ad0c 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -428,8 +428,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _viewSize = viewSize; final isLayoutChanged = _relayoutPages(); - _calcCoverScale(); - _calcAlternativeFitScale(); + _calcCoverFitScale(); _calcZoomStopTable(); void callOnViewerSizeChanged() { @@ -557,10 +556,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _setCurrentPageNumberInternal(_gotoTargetPageNumber, doSetState: true); } - void _determineCurrentPage() { - _setCurrentPageNumberInternal(_guessCurrentPageNumber()); - } - void _setCurrentPageNumberInternal(int? pageNumber, {bool doSetState = false}) { if (pageNumber != null && _pageNumber != pageNumber) { _pageNumber = pageNumber; @@ -622,15 +617,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return true; } - void _calcCoverScale() { + void _calcCoverFitScale() { if (_viewSize != null) { final s1 = _viewSize!.width / _layout!.documentSize.width; final s2 = _viewSize!.height / _layout!.documentSize.height; _coverScale = max(s1, s2); } - } - - bool _calcAlternativeFitScale() { if (_pageNumber != null) { final params = widget.params; final rect = _layout!.pageLayouts[_pageNumber! - 1]; @@ -641,16 +633,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } if (_coverScale == null) { _minScale = _defaultMinScale; - return false; + return; } - _minScale = !widget.params.useAlternativeFitScaleAsMinScale ? widget.params.minScale : _alternativeFitScale == null ? _coverScale! : min(_coverScale!, _alternativeFitScale!); - return _alternativeFitScale != null; } void _calcZoomStopTable() { @@ -1149,7 +1139,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (widget.params.normalizeMatrix != null) { return widget.params.normalizeMatrix!(newValue, _viewSize!, _layout!, _controller); } - return _normalizeMatrix(newValue); + return newValue; + //return _normalizeMatrix(newValue); } Matrix4 _normalizeMatrix(Matrix4 newValue) { @@ -1188,40 +1179,49 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return _calcMatrixFor(rect.center, zoom: zoom, viewSize: _viewSize!); } - Matrix4 _calcMatrixForArea({required Rect rect, PdfPageAnchor? anchor}) { - anchor ??= widget.params.pageAnchor; - final visibleRect = _visibleRect; - final w = min(rect.width, visibleRect.width); - final h = min(rect.height, visibleRect.height); + Matrix4 _calcMatrixForArea({required Rect rect, double? zoomMax, double? margin, PdfPageAnchor? anchor}) => + _calcMatrixForRect( + _calcRectForArea(rect: rect, anchor: anchor ?? widget.params.pageAnchor), + zoomMax: zoomMax, + margin: margin, + ); + + // The function calculate the rectangle which should be shown in the view. + // + // If the rect is smaller than the view size, it will + Rect _calcRectForArea({required Rect rect, required PdfPageAnchor anchor}) { + final viewSize = _visibleRect.size; + final w = min(rect.width, viewSize.width); + final h = min(rect.height, viewSize.height); switch (anchor) { case PdfPageAnchor.top: - return _calcMatrixForRect((rect.topLeft) & Size(rect.width, h)); + return Rect.fromLTWH(rect.left, rect.top, rect.width, h); case PdfPageAnchor.left: - return _calcMatrixForRect((rect.topLeft) & Size(w, rect.height)); + return Rect.fromLTWH(rect.left, rect.top, w, rect.height); case PdfPageAnchor.right: - return _calcMatrixForRect(Rect.fromLTWH(rect.right - w, rect.top, w, rect.height)); + return Rect.fromLTWH(rect.right - w, rect.top, w, rect.height); case PdfPageAnchor.bottom: - return _calcMatrixForRect(Rect.fromLTWH(rect.left, rect.bottom - h, rect.width, h)); + return Rect.fromLTWH(rect.left, rect.bottom - h, rect.width, h); case PdfPageAnchor.topLeft: - return _calcMatrixForRect((rect.topLeft) & visibleRect.size); + return Rect.fromLTWH(rect.left, rect.top, viewSize.width, viewSize.height); case PdfPageAnchor.topCenter: - return _calcMatrixForRect(rect.topCenter & visibleRect.size); + return Rect.fromLTWH(rect.topCenter.dx - w / 2, rect.top, viewSize.width, viewSize.height); case PdfPageAnchor.topRight: - return _calcMatrixForRect((rect.topRight) & visibleRect.size); + return Rect.fromLTWH(rect.topRight.dx - w, rect.top, viewSize.width, viewSize.height); case PdfPageAnchor.centerLeft: - return _calcMatrixForRect(rect.centerLeft & visibleRect.size); + return Rect.fromLTWH(rect.left, rect.center.dy - h / 2, viewSize.width, viewSize.height); case PdfPageAnchor.center: - return _calcMatrixForRect(rect.center & visibleRect.size); + return Rect.fromLTWH(rect.center.dx - w / 2, rect.center.dy - h / 2, viewSize.width, viewSize.height); case PdfPageAnchor.centerRight: - return _calcMatrixForRect(rect.centerRight & visibleRect.size); + return Rect.fromLTWH(rect.right - w, rect.center.dy - h / 2, viewSize.width, viewSize.height); case PdfPageAnchor.bottomLeft: - return _calcMatrixForRect((rect.bottomLeft) & visibleRect.size); + return Rect.fromLTWH(rect.left, rect.bottom - h, viewSize.width, viewSize.height); case PdfPageAnchor.bottomCenter: - return _calcMatrixForRect(rect.bottomCenter & visibleRect.size); + return Rect.fromLTWH(rect.center.dx - w / 2, rect.bottom - h, viewSize.width, viewSize.height); case PdfPageAnchor.bottomRight: - return _calcMatrixForRect((rect.bottomRight) & visibleRect.size); + return Rect.fromLTWH(rect.right - w, rect.bottom - h, viewSize.width, viewSize.height); case PdfPageAnchor.all: - return _calcMatrixForRect(rect); + return rect; } } From bb495a4b0a4d37e51a3c953c4a71b09bd531d933 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 01:47:59 +0900 Subject: [PATCH 046/663] WIP --- example/viewer/lib/main.dart | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index 604e1769..5bf5e3e5 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -112,6 +114,10 @@ class _MainPageState extends State with WidgetsBindingObserver { FilledButton(onPressed: () => openUri(), child: Text('Open URL')), Spacer(), ], + IconButton( + visualDensity: visualDensity, + onPressed: documentRef == null ? null : () => _changeLayoutType(), + icon: Icon(Icons.pages)), IconButton( visualDensity: visualDensity, icon: const Icon( @@ -311,6 +317,10 @@ class _MainPageState extends State with WidgetsBindingObserver { //passwordProvider: () => passwordDialog(context), controller: controller, params: PdfViewerParams( + layoutPages: _layoutPages[_layoutTypeIndex], + scrollHorizontallyByMouseWheel: _layoutTypeIndex == 1, + pageAnchor: _layoutTypeIndex == 1 ? PdfPageAnchor.left : PdfPageAnchor.top, + pageAnchorEnd: _layoutTypeIndex == 1 ? PdfPageAnchor.right : PdfPageAnchor.bottom, enableTextSelection: true, maxScale: 8, // facing pages algorithm @@ -504,6 +514,82 @@ class _MainPageState extends State with WidgetsBindingObserver { } } + int _layoutTypeIndex = 0; + + /// Change the layout logic; see [_layoutPages] for the logics + void _changeLayoutType() { + setState(() { + _layoutTypeIndex = (_layoutTypeIndex + 1) % _layoutPages.length; + }); + } + + /// Page reading order; true to L-to-R that is commonly used by books like manga or such + var isRightToLeftReadingOrder = false; + + /// Use the first page as cover page + var needCoverPage = true; + + late final List _layoutPages = [ + // The default layout + null, + // Horizontal layout + (pages, params) { + final height = pages.fold(0.0, (prev, page) => max(prev, page.height)) + params.margin * 2; + final pageLayouts = []; + double x = params.margin; + for (var page in pages) { + pageLayouts.add( + Rect.fromLTWH( + x, + (height - page.height) / 2, // center vertically + page.width, + page.height, + ), + ); + x += page.width + params.margin; + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size(x, height), + ); + }, + // Facing pages layout + (pages, params) { + final width = pages.fold(0.0, (prev, page) => max(prev, page.width)); + + final pageLayouts = []; + final offset = needCoverPage ? 1 : 0; + double y = params.margin; + for (int i = 0; i < pages.length; i++) { + final page = pages[i]; + final pos = i + offset; + final isLeft = isRightToLeftReadingOrder ? (pos & 1) == 1 : (pos & 1) == 0; + + final otherSide = (pos ^ 1) - offset; + final h = 0 <= otherSide && otherSide < pages.length ? max(page.height, pages[otherSide].height) : page.height; + + pageLayouts.add( + Rect.fromLTWH( + isLeft ? width + params.margin - page.width : params.margin * 2 + width, + y + (h - page.height) / 2, + page.width, + page.height, + ), + ); + if (pos & 1 == 1 || i + 1 == pages.length) { + y += h + params.margin; + } + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size( + (params.margin + width) * 2 + params.margin, + y, + ), + ); + }, + ]; + void _addCurrentSelectionToMarkers(Color color) { if (controller.isReady && textSelections != null) { for (final selectedText in textSelections!) { From 8cdf3189435bc43b69a9b1f00f66de63bbb99ee1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 01:54:09 +0900 Subject: [PATCH 047/663] FIXED: #336 --- lib/src/widgets/pdf_viewer.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 56c5ad0c..cbfeacdf 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -1139,8 +1139,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (widget.params.normalizeMatrix != null) { return widget.params.normalizeMatrix!(newValue, _viewSize!, _layout!, _controller); } - return newValue; - //return _normalizeMatrix(newValue); + return _normalizeMatrix(newValue); } Matrix4 _normalizeMatrix(Matrix4 newValue) { @@ -1148,7 +1147,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final viewSize = _viewSize; if (layout == null || viewSize == null) return newValue; final position = newValue.calcPosition(viewSize); - final newZoom = widget.params.boundaryMargin != null ? newValue.zoom : max(newValue.zoom, minScale); + final newZoom = max(newValue.zoom, minScale); final hw = viewSize.width / 2 / newZoom; final hh = viewSize.height / 2 / newZoom; final x = position.dx.range(hw, layout.documentSize.width - hw); From 120a6a76111bc5b6b9933642ea9864c70a1a7946 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 02:06:15 +0900 Subject: [PATCH 048/663] WIP --- lib/src/widgets/pdf_viewer.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index cbfeacdf..4a117154 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -253,7 +253,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (widget.params.annotationRenderingMode != oldWidget?.params.annotationRenderingMode) { _releaseAllImages(); } - _updateLayout(_viewSize!); } return; } else { From 2cf79b80c5689f6b98a86395d29ad6a09a9f3c52 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 02:15:37 +0900 Subject: [PATCH 049/663] 1.1.24 --- CHANGELOG.md | 9 +++++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83fd3b9..1badd90e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.1.24 + +- FIXED: #336 zoom out does not cover entire page after changing layout + - Updates to viewer example to support page layout switching + - Minor goToPage and other goTo functions behavior changes (normalizeMatrix and other) +- MERGED: PR #349 that fixes resource leaks on PdfPageView +- FIXED: #215 Wrong link highlight position on searching a word +- FIXED: #344 New "key event handling" feature in version 1.1.22 prevents TextFormField in page overlay from receiving key events + # 1.1.23 - Minor internal change diff --git a/README.md b/README.md index 83d81bf5..ddb5c595 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.23 + pdfrx: ^1.1.24 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index de006dc4..ccae8d61 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.23" + version: "1.1.24" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 4f1bbeb1..f2d01908 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.23 +version: 1.1.24 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 09769a6a5caed1a6793c391a284e6134076d3a6e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Apr 2025 10:55:15 +0900 Subject: [PATCH 050/663] WIP: document update --- .vscode/settings.json | 5 +++-- lib/src/pdf_api.dart | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c079e946..729ebeeb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -284,5 +284,6 @@ "editor.rulers": [ 120 ] - } -} \ No newline at end of file + }, + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable" +} diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 84f275dd..40e00c5b 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -157,6 +157,7 @@ PdfPasswordProvider createSimplePasswordProvider(String? password) { /// Handles PDF document loaded on memory. abstract class PdfDocument { + /// Constructor to force initialization of sourceName. PdfDocument({required this.sourceName}); /// File path, `asset:[ASSET_PATH]` or `memory:` depending on the content opened. @@ -168,6 +169,7 @@ abstract class PdfDocument { /// Determine whether the PDF file is encrypted or not. bool get isEncrypted; + /// PdfDocument must have [dispose] function. Future dispose(); /// Opening the specified file. @@ -930,8 +932,10 @@ enum PdfDestCommand { const PdfDestCommand(this.name); + /// Command name. final String name; + /// Parse the command name to [PdfDestCommand]. factory PdfDestCommand.parse(String name) { final nameLow = name.toLowerCase(); return PdfDestCommand.values.firstWhere((e) => e.name == nameLow, orElse: () => PdfDestCommand.unknown); From a3051e64b17938b5a4babff4bc2446f2b3334f67 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 30 Apr 2025 01:21:51 +0900 Subject: [PATCH 051/663] Related: #350 fixing the issue?? --- lib/src/widgets/pdf_viewer.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 4a117154..e605d230 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -458,6 +458,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix callOnViewerSizeChanged(); } }); + } else if (currentPageNumber != null && _pageNumber != currentPageNumber) { + _setCurrentPageNumber(currentPageNumber); } } From bbf4774ea96f8d60082bf78ae6b20ee0fa1d9e6d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 30 Apr 2025 01:26:20 +0900 Subject: [PATCH 052/663] 1.1.25 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1badd90e..9206de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.25 + +- FIXED: #350 callback onPageChanged no longer called? + # 1.1.24 - FIXED: #336 zoom out does not cover entire page after changing layout diff --git a/README.md b/README.md index ddb5c595..9ff52cd8 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.24 + pdfrx: ^1.1.25 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index ccae8d61..54c260fb 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.24" + version: "1.1.25" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f2d01908..5c9dfdf5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.24 +version: 1.1.25 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From bbc461658865cc2e54f88f9ea385f96f1f7b29eb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 1 May 2025 02:10:28 +0900 Subject: [PATCH 053/663] WIP: Introducing PdfPoint. --- lib/src/pdf_api.dart | 88 +++++++++++++++++++++- lib/src/widgets/pdf_page_text_overlay.dart | 22 +----- lib/src/widgets/pdf_viewer.dart | 6 +- 3 files changed, 93 insertions(+), 23 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 40e00c5b..a6d3cd9b 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -799,10 +799,11 @@ class PdfRect { } /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool contains(double x, double y) => x >= left && x <= right && y >= bottom && y <= top; + bool containsXy(double x, double y, {double margin = 0}) => + x >= left - margin && x <= right + margin && y >= bottom - margin && y <= top + margin; /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool containsOffset(Offset offset) => contains(offset.dx, offset.dy); + bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); /// Empty rectangle. static const empty = PdfRect(0, 0, 0, 0); @@ -1003,3 +1004,86 @@ class PdfException implements Exception { class PdfPasswordException extends PdfException { const PdfPasswordException(super.message); } + +/// PDF page coordinates point. +/// +/// In Pdf page coordinates, the origin is at the bottom-left corner and Y-axis is pointing upward. +/// The unit is normally in points (1/72 inch). +class PdfPoint { + const PdfPoint(this.x, this.y); + + /// X coordinate. + final double x; + + /// Y coordinate. + final double y; + + @override + String toString() => 'PdfOffset($x, $y)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPoint && other.x == x && other.y == y; + } + + @override + int get hashCode => x.hashCode ^ y.hashCode; + + /// Convert to [Offset] in Flutter coordinate. + /// [page] is the page to convert the rectangle. + /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. + /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. + Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final rotated = rotate(rotation ?? page.rotation.index, page); + final orig = rotated.rotateReverse(rotation ?? page.rotation.index, page); + print('this=$this, rotated=$rotated, orig=$orig'); + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + return Offset(rotated.x * scale, (page.height - rotated.y) * scale); + } + + PdfPoint rotate(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfPoint(y, width - x); + case 2: + return PdfPoint(width - x, height - y); + case 3: + return PdfPoint(height - y, x); + default: + throw ArgumentError.value(rotate, 'rotate'); + } + } + + PdfPoint rotateReverse(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfPoint(width - y, x); + case 2: + return PdfPoint(width - x, height - y); + case 3: + return PdfPoint(y, height - x); + default: + throw ArgumentError.value(rotate, 'rotate'); + } + } +} + +extension OffsetPdfPointExt on Offset { + /// Convert to [PdfPoint] in PDF page coordinates. + PdfPoint toPdfPoint({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; + return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); + } +} diff --git a/lib/src/widgets/pdf_page_text_overlay.dart b/lib/src/widgets/pdf_page_text_overlay.dart index 5a123904..bd99cead 100644 --- a/lib/src/widgets/pdf_page_text_overlay.dart +++ b/lib/src/widgets/pdf_page_text_overlay.dart @@ -129,7 +129,7 @@ class _PdfPageTextOverlayState extends State { } void _onHover(PointerHoverEvent event) { - final point = event.localPosition.toPdfPoint(widget.page, widget.pageRect); + final point = event.localPosition.toPdfPoint(page: widget.page, scaledPageSize: widget.pageRect.size); final selectionShouldBeEnabled = isPointOnText(point); if (this.selectionShouldBeEnabled != selectionShouldBeEnabled) { @@ -140,28 +140,14 @@ class _PdfPageTextOverlayState extends State { } } - bool isPointOnText(Offset point, {double margin = 5}) { + bool isPointOnText(PdfPoint point, {double margin = 5}) { for (final fragment in fragments!) { - if (pdfRectContains(fragment.bounds, point, margin)) { + if (fragment.bounds.containsPoint(point, margin: margin)) { return true; } } return false; } - - static bool pdfRectContains(PdfRect rect, Offset point, double margin) { - return rect.left - margin <= point.dx && - rect.right + margin >= point.dx && - rect.bottom - margin <= point.dy && - rect.top + margin >= point.dy; - } -} - -extension _OffsetExt on Offset { - Offset toPdfPoint(PdfPage page, Rect pageRect) { - final scale = page.height / pageRect.height; - return Offset(dx * scale, page.height - dy * scale); - } } /// The code is based on the code on [Making a widget selectable](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html#widgets).SelectableRegion.2] @@ -231,7 +217,7 @@ class _PdfTextRenderBox extends RenderBox with PdfPageTextSelectable, Selectable @override bool hitTestSelf(Offset position) { - final point = position.toPdfPoint(_page, _pageRect); + final point = position.toPdfPoint(page: _page, scaledPageSize: _pageRect.size); return _textWidget._state.isPointOnText(point); } diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index e605d230..b51011d1 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -1424,7 +1424,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (pageRect.contains(offset)) { return PdfPageHitTestResult( page: page, - offset: Offset(offset.dx - pageRect.left, pageRect.bottom - offset.dy) * page.height / pageRect.height, + offset: offset.translate(-pageRect.left, -pageRect.top).toPdfPoint(page: page, scaledPageSize: pageRect.size), ); } } @@ -1557,7 +1557,7 @@ class PdfPageHitTestResult { final PdfPage page; /// The offset in the PDF page coordinates; the origin is at the bottom-left corner. - final Offset offset; + final PdfPoint offset; } /// Controls associated [PdfViewer]. @@ -2058,7 +2058,7 @@ class _CanvasLinkPainter { if (links == null) return null; for (final link in links) { for (final rect in link.rects) { - if (rect.containsOffset(hitResult.offset)) { + if (rect.containsPoint(hitResult.offset)) { return link; } } From a32e647b0fcf73ae20b9c68c02458bd05d5a1e09 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 1 May 2025 02:31:40 +0900 Subject: [PATCH 054/663] FIXED: #352 --- lib/src/pdf_api.dart | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index a6d3cd9b..2ad0b59c 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -845,6 +845,24 @@ class PdfRect { } } + PdfRect rotateReverse(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfRect(width - top, right, width - bottom, left); + case 2: + return PdfRect(width - right, height - bottom, width - left, height - top); + case 3: + return PdfRect(bottom, height - left, top, height - right); + default: + throw ArgumentError.value(rotate, 'rotate'); + } + } + PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); @override @@ -863,6 +881,18 @@ class PdfRect { } } +extension RectPdfRectExt on Rect { + PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + return PdfRect( + left / scale, + page.height - top / scale, + right / scale, + page.height - bottom / scale, + ).rotateReverse(rotation ?? page.rotation.index, page); + } +} + /// Extension methods for List of [PdfRect]. extension PdfRectsExt on Iterable { /// Merge all rectangles to calculate bounding rectangle. From e490e5b4269f4c6548b0b817597a00bc3c463e2c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 1 May 2025 02:37:42 +0900 Subject: [PATCH 055/663] WIP --- lib/src/pdf_api.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 2ad0b59c..24081f9b 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -827,6 +827,7 @@ class PdfRect { Rect toRectInPageRect({required PdfPage page, required Rect pageRect}) => toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); + /// Rotate the rectangle. PdfRect rotate(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; final width = swap ? page.height : page.width; @@ -845,6 +846,7 @@ class PdfRect { } } + /// Rotate the rectangle in reverse direction. PdfRect rotateReverse(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; final width = swap ? page.height : page.width; @@ -882,6 +884,7 @@ class PdfRect { } extension RectPdfRectExt on Rect { + /// Convert to [PdfRect] in PDF page coordinates. PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return PdfRect( @@ -1073,6 +1076,7 @@ class PdfPoint { return Offset(rotated.x * scale, (page.height - rotated.y) * scale); } + /// Rotate the point. PdfPoint rotate(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; final width = swap ? page.height : page.width; @@ -1091,6 +1095,7 @@ class PdfPoint { } } + /// Rotate the point in reverse direction. PdfPoint rotateReverse(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; final width = swap ? page.height : page.width; From 898b9221d060b2c6610f67f5d2a83eea03522d9c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 1 May 2025 02:45:10 +0900 Subject: [PATCH 056/663] 1.1.26 --- CHANGELOG.md | 5 +++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9206de29..8c9be2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.26 + +- Introduces PdfPoint, which work with Offset for conversion between PDF page coordinates and Flutter coordinates +- FIXED: #352 Link click/text selection are completely broken if PDF page is rotated + # 1.1.25 - FIXED: #350 callback onPageChanged no longer called? diff --git a/README.md b/README.md index 9ff52cd8..1727e3cd 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.25 + pdfrx: ^1.1.26 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 54c260fb..5b844970 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.25" + version: "1.1.26" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5c9dfdf5..6abed4bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.25 +version: 1.1.26 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 54c73f4ca0e190308dc587c4d32794e9ceabda96 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 2 May 2025 14:00:34 +0900 Subject: [PATCH 057/663] #134: I'm not sure whether the proposed fix works correctly or not --- lib/src/widgets/pdf_viewer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index b51011d1..5e48baf4 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -1154,7 +1154,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final x = position.dx.range(hw, layout.documentSize.width - hw); final y = position.dy.range(hh, layout.documentSize.height - hh); - return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize); + return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize).scaled(1.0, 1.0, newZoom); } /// Calculate matrix to center the specified position. @@ -1330,7 +1330,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } on TickerCanceled { // expected } finally { - _animGoTo!.removeListener(update); + _animGoTo?.removeListener(update); } } From 261db65fbe67e49b3c26536cba554157edd50366 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 2 May 2025 14:03:12 +0900 Subject: [PATCH 058/663] 1.1.27 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c9be2ef..02488c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.27 + +- Apply a proposed fix for #134; but I' not sure if it works well or not. Personally, I don't feel any difference... + # 1.1.26 - Introduces PdfPoint, which work with Offset for conversion between PDF page coordinates and Flutter coordinates diff --git a/README.md b/README.md index 1727e3cd..2a66abd2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.26 + pdfrx: ^1.1.27 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 5b844970..576f6b61 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.26" + version: "1.1.27" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6abed4bd..7455f8e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.26 +version: 1.1.27 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From c97c7eb6dd043bf14b2c86f6b97b08bd03b4b83b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 2 May 2025 17:43:13 +0900 Subject: [PATCH 059/663] WIP: zoom ratio calculation updates --- lib/src/widgets/pdf_viewer.dart | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 5e48baf4..2b409047 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -552,12 +552,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Set the current page number. /// /// Please note that the function does not scroll/zoom to the specified page but changes the current page number. - void _setCurrentPageNumber(int pageNumber) { + void _setCurrentPageNumber(int? pageNumber, {bool doSetState = false}) { _gotoTargetPageNumber = pageNumber; - _setCurrentPageNumberInternal(_gotoTargetPageNumber, doSetState: true); - } - - void _setCurrentPageNumberInternal(int? pageNumber, {bool doSetState = false}) { if (pageNumber != null && _pageNumber != pageNumber) { _pageNumber = pageNumber; if (doSetState) { @@ -624,9 +620,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final s2 = _viewSize!.height / _layout!.documentSize.height; _coverScale = max(s1, s2); } - if (_pageNumber != null) { + final pageNumber = _pageNumber ?? _gotoTargetPageNumber; + if (pageNumber != null) { final params = widget.params; - final rect = _layout!.pageLayouts[_pageNumber! - 1]; + final rect = _layout!.pageLayouts[pageNumber - 1]; final m2 = params.margin * 2; _alternativeFitScale = min((_viewSize!.width - m2) / rect.width, (_viewSize!.height - m2) / rect.height); } else { @@ -637,11 +634,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return; } _minScale = - !widget.params.useAlternativeFitScaleAsMinScale + !widget.params.useAlternativeFitScaleAsMinScale || _alternativeFitScale == null ? widget.params.minScale - : _alternativeFitScale == null - ? _coverScale! - : min(_coverScale!, _alternativeFitScale!); + : _alternativeFitScale!; } void _calcZoomStopTable() { @@ -1379,11 +1374,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (pageNumber < 1) { targetPageNumber = 1; } else if (pageNumber != 1 && pageNumber >= pageCount) { - targetPageNumber = pageCount; + targetPageNumber = pageNumber = pageCount; anchor ??= widget.params.pageAnchorEnd; } else { targetPageNumber = pageNumber; } + _gotoTargetPageNumber = pageNumber; + await _goTo(_calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), duration: duration); _setCurrentPageNumber(targetPageNumber); } @@ -1394,6 +1391,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfPageAnchor? anchor, Duration duration = const Duration(milliseconds: 200), }) async { + _gotoTargetPageNumber = pageNumber; await _goTo(_calcMatrixForRectInsidePage(pageNumber: pageNumber, rect: rect, anchor: anchor), duration: duration); _setCurrentPageNumber(pageNumber); } @@ -1401,6 +1399,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Future _goToDest(PdfDest? dest, {Duration duration = const Duration(milliseconds: 200)}) async { final m = _calcMatrixForDest(dest); if (m == null) return false; + if (dest != null) { + _gotoTargetPageNumber = dest.pageNumber; + } await _goTo(m, duration: duration); if (dest != null) { _setCurrentPageNumber(dest.pageNumber); From d46ad243eb36cbd766497172b7992e2bef130ae6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 4 May 2025 23:32:42 +0900 Subject: [PATCH 060/663] WIP --- example/viewer/lib/main.dart | 81 ++++++++++----------------------- lib/src/widgets/pdf_viewer.dart | 4 +- 2 files changed, 27 insertions(+), 58 deletions(-) diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index 5bf5e3e5..a0c57425 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -318,57 +318,12 @@ class _MainPageState extends State with WidgetsBindingObserver { controller: controller, params: PdfViewerParams( layoutPages: _layoutPages[_layoutTypeIndex], - scrollHorizontallyByMouseWheel: _layoutTypeIndex == 1, - pageAnchor: _layoutTypeIndex == 1 ? PdfPageAnchor.left : PdfPageAnchor.top, - pageAnchorEnd: _layoutTypeIndex == 1 ? PdfPageAnchor.right : PdfPageAnchor.bottom, + scrollHorizontallyByMouseWheel: isHorizontalLayout, + pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, + pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, enableTextSelection: true, + useAlternativeFitScaleAsMinScale: false, maxScale: 8, - // facing pages algorithm - // layoutPages: (pages, params) { - // // They should be moved outside function - // const isRightToLeftReadingOrder = false; - // const needCoverPage = true; - // final width = pages.fold( - // 0.0, (prev, page) => max(prev, page.width)); - - // final pageLayouts = []; - // double y = params.margin; - // for (int i = 0; i < pages.length; i++) { - // const offset = needCoverPage ? 1 : 0; - // final page = pages[i]; - // final pos = i + offset; - // final isLeft = isRightToLeftReadingOrder - // ? (pos & 1) == 1 - // : (pos & 1) == 0; - - // final otherSide = (pos ^ 1) - offset; - // final h = 0 <= otherSide && otherSide < pages.length - // ? max(page.height, pages[otherSide].height) - // : page.height; - - // pageLayouts.add( - // Rect.fromLTWH( - // isLeft - // ? width + params.margin - page.width - // : params.margin * 2 + width, - // y + (h - page.height) / 2, - // page.width, - // page.height, - // ), - // ); - // if (pos & 1 == 1 || i + 1 == pages.length) { - // y += h + params.margin; - // } - // } - // return PdfPageLayout( - // pageLayouts: pageLayouts, - // documentSize: Size( - // (params.margin + width) * 2 + params.margin, - // y, - // ), - // ); - // }, - // onViewSizeChanged: (viewSize, oldViewSize, controller) { if (oldViewSize != null) { // @@ -418,21 +373,31 @@ class _MainPageState extends State with WidgetsBindingObserver { thumbSize: const Size(40, 25), thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( color: Colors.black, - child: Center( - child: Text( - pageNumber.toString(), - style: const TextStyle(color: Colors.white), - ), - ), + child: isHorizontalLayout + ? null + : Center( + child: Text( + pageNumber.toString(), + style: const TextStyle(color: Colors.white), + ), + ), ), ), // Just a simple horizontal scroll thumb on the bottom PdfViewerScrollThumb( controller: controller, orientation: ScrollbarOrientation.bottom, - thumbSize: const Size(80, 30), + thumbSize: const Size(40, 25), thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( - color: Colors.red, + color: Colors.black, + child: !isHorizontalLayout + ? null + : Center( + child: Text( + pageNumber.toString(), + style: const TextStyle(color: Colors.white), + ), + ), ), ), ], @@ -523,6 +488,8 @@ class _MainPageState extends State with WidgetsBindingObserver { }); } + bool get isHorizontalLayout => _layoutTypeIndex == 1; + /// Page reading order; true to L-to-R that is commonly used by books like manga or such var isRightToLeftReadingOrder = false; diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 2b409047..67bd439c 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -580,7 +580,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return area / (rect.width * rect.height); } - if (_gotoTargetPageNumber != null) { + if (_gotoTargetPageNumber != null && + _gotoTargetPageNumber! > 0 && + _gotoTargetPageNumber! <= _document!.pages.length) { final ratio = calcIntersectionArea(_gotoTargetPageNumber!); if (ratio > .2) return _gotoTargetPageNumber; } From fcbd8e197fc7d20069be78eed1b62cbb35eeb95c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 5 May 2025 21:51:02 +0900 Subject: [PATCH 061/663] PDFium WASM 138.0.7162.0 --- wasm/pdfrx_wasm/assets/pdfium.wasm | Bin 3867983 -> 3960317 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm/pdfrx_wasm/assets/pdfium.wasm b/wasm/pdfrx_wasm/assets/pdfium.wasm index edcf7c1ac88333f40c74a034656192ce6abbd298..72c415b66e5243d7c575f9e33854a359c2c6858b 100644 GIT binary patch delta 738143 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

oqIiuG`NHzP}+>0PM@w^-MGKCSBX@ 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%0DJpLUsOAadK))60x;_xvg10Yr0YW#BJdDF65 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 delta 672386 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 uP{t5mB#vhQs0kmJzUAr@*1 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%0ATSDn1k42((Ihy=chDw&Sz5*aY}~AbUiP_2={;` zK?aWEET)&)+JocL;kqh2Qi_I*c^Rg^rBbbp)uYLBK(`&(Ad-Pfy zf9)TC9T%J>su6Byl_-JIlsXN$8ISlDy@tI9^Mk{iTPh*=*z2#FGW~~02E@DN zzx2k+Y&xc$0Hn4K5VMzqIz;OK$I|?anf#1q(+49CXV==8Q}6<9rPKZU?VYpSjfV#N z)}sr(v^&(ow{T`ywaXE?AY<61epCnDmN8Q8)Xpj*QAh_)->5q*6X;alG z=;va8`8FxjmJq7bu15b(_?sW`wiP`642K0 zD9Sw(%+;kj?IHeH@_L_s@vDNp>oqo9zbE>i zlMR=vnrygC)nvn^x|=K;?yAe_vf<8=5}_NWpjGtM5E-hI&VQ#2Ow+W+fb-uJx)0`G zBa0}JckD6XAN~GO^pApn^!dkh|G*z^>0;x%kFka%D?BS91*))-=yxAGCC*?UWmi;7gJ-DGdEar% zrCmu%Ep=}>JkSBd7&Lhg8pQv`eiE7bdY&y?^=sB> zEj%Sh?@%l`WJx?~w!LQYo(#JN% zwREv&rG%oGUo74n{WKN4lZIN=P~auCAbMxkckv5O%DB6Z3@UZOMJBhTdbTG)$<1)2*`OZ`Xl7V}a;8^> zl^RiwDt=8=A_ps>OH`-w8S>k0CUb4mCS2hkyZOg*|5)Z9OYKn)clAG)_{T2(vCuz8 z{A00y4Ex6-|H#Tq7WU0?faR(6VPq>~-_zU5^n$GnYvu}>kj*g9Ss6W1+c|^b&1tEh zl-)0m3XYo*GGxVrhz(ylKiA6nxk|xZ_|ER4+j?{`5FUqo2G-<<2>)QK`Hg-xuZNxx zFo#Wxqk~=RH+C3SGRu}{olUQN=&?S{2a*I_LVpKcHUC=H9OZ##%L>I|wZ6y-r7fe; z$^yD23y`>YueaZLboMob)X=92gpJ4g&78Vua<|!>SV-2;5ZTx(Rzuy43OHEL;4<75 z%G8_L>X0t27>rU#t-8NrYqkC6u&s8;s+~?fSXRuPG<7B!v<>hWdC`)a40mZqxnwzQKL-m3ZPTA)_EC?Z)#ts-$@(DxV34eL_dSzatv4wu>7MC9>d4CW!>1 z(*+NuhuCNI2X>K^ol(AS2DD*1$EXCf9u-~JG@&#fEhg$vrzt53QIcHU3x=~ysx!f` zYOf|3R_xUT!`b7nL*uV=#$V^!tI35sHRM7Lv@A>jQihEz(aFO?kHe897jmW7%6cMU zT~Zz|7}`Z&c|ID6U~9 z~TZ>V-e0^+`vGCl#|>F;RhZ-`I42p+rt9R070|`@rS1YG}G|h#rWE zVdvLA%b+K1+>ORUV-N<=Po!=V}ro~F$J`=NTQw@3*1BpYj?jmHaMZ)?t#$4_<_iVa3CDEb;sC; z4Jz!&2oNLGVt$Mqz>lQu^eE$jY|ha4VM+W3!4dsL+J@&S#iZ@7aJsGw#m+8iyK7w9 z-lMB#(soz4lSo~cq~&9H%#oPf6(R|);W1lcGC@8hCU=EZiOF5zEO*_SU@yeLaH5%A zIpN6pZ%GEU6%GXw8mxdkJ*-4m}0ssU$l5f$0@(kj@pN&3rL3J$$Gm{*@KB*8c!nZ%@N>O`b% zkPOob8dL*eQyc?oMs<)Wc9TTRWJZn>5Y@*M_9TaA5=aB=N$Z;9h=uLxIZKO;#NGeu zVESk;_$kGqlLOkGqZr9H^(2wgl{@0>+!3w0sG>EB%$W|! z%BA{P))HIM)sfn&0`#=RwuH(E9YkPB$g^Xn!S_vJq# z(ONQ%Zb}x#KsPY@ONH%`#HXRK)YDc()KXYYJ&=)Z3rd3(Sk=c!L_1Az0h%ZS2>S45 zv)>rn){Waq;GwL9Ou#R7NfHLvyePBL!WYn(z?n)f3#$O5YKT^*4V^5aq>Gr^53aT# zz@Q#B>Z+n~Sf;*8a+>@7ahwj9eH^(!8WM^Tgqi{^y6;eKKS^mw;%Ob4N3G-<()$)c z3uWDA)rZBZjVM4319miyP z&MMIhNlWnO2^tgjU9T~rthq(&gSkY{Nyi!yYx-mvP|8)<9ZIA|LQFsV&^7sg!@ft{$z$GuGQRvxAYE)dx>qxRc zF{8o~I>{p=wx!8~BGdi`Ls}f6fs%1Qz=d%IVE{9;r;Hx<5y^-sDat@&5xDoC5X?|C zkbaW-iLz;W7CSnl)v~82oaEK28!tj$AB|JyYDyOTVcl_E=*9d7Ch0=BVJu86Cd4Ov z-SnjV$J#3$ZrN*t;Z|^!cm(dm*9P;e4Tl1aepDi|xwN85F>g6@L-^EdgE`3#_r2E! zyA&TU3+3?KUYNKVMumEv0-5q|@WfzdSIv9<%e5k`xz#5I3-A{E#P_D};XZX@FeAf= z0{6`mgTc-yBkmRC`^$;JeDYOJ3SM@!yb>eb?I*N^8wNjd!KfwxA`W@Wy-UWnq`&!6 z-_A@{6Ou{otM}?SFaF$<|5e%Da}xd;LoWBapud$a;8W%1zb@EgGIuD*dbA9* zg${W#iogmZ5DF?ClidpEoWc`I-HSb;6iD0m%eEGbk`A|6-`vx02o@H-gH?vEBZbRJ zGHJd}Zs{9?6^DUzG|GhI@56ee%b38{R+HYlDcS7ZMAz})!o;kV%N8n!~yT! z^dD~w4$7JnP^r@GvTq7@QUvu~-W2p@*)5Uq(%}w!Q!sDCljaK&r-?5~fVWxd1{D~2 zw;9e;B76!=WwJbDmMjlVgrbNJ68;P(m0Beb@XumO$pxH~BM5&9hMaegyeX(IVUX;0 zFxLZQk}6W6F_okxEdmDM^(V+vLaR9MmTm|}T65a}$-CEY2smBCiaKXQu((b<7()A5 z#BxgM*fs5u*jmFNCzcD(5*T=jtJq0G@i48gt@cWp-H4zy9e=GZ?@1ppQeh?fT9`xY#xXA;%bG}@(vzEB z@KdVkle=&<2FGbAdZbFrz_6y-F;sBZ2ohi&098k{@#lwj^+9Qp*AT6@FEucP7Qr^|^bYypXg3PSiIO8xAVU_SnZzEo$U zRM(4^@~(mVOX$*yU`+%K_rmT*9X&6hj@7CouB79oRMKIdy1x4zz9VazI+A-Sb>Oh5 z#bzKtnRD%L3Cg9|D{^wT-Cl1AhPz`=Ne@J~`7Ob&$=%%bZwU_C>sxF-EoN#~jr!R~ z;V~>g;eqC@YO;aaBubv~VIwl~23&AzuxQWkm~UlN1)DQg1q+%k{`4qgymN z4~UyK;{$Xgu1x5r0rqQ0S0=Deu(!<5e?FJqBN6cO-1Fz2x;(*|UF^-0`4wL||3GII z9>TF)#lh-^hzSO1a{`3*kq-`trf)sT#cGT_N>=MQUX_#I%TZ}~gR{1O$0z;_6^ zBC1nN4fNC)fO5oBlRi0QMKTJT8s^-6bxSZax*$k%s#~Uj-C?SqwmwDIDPBp%14+?V zd{1UiG*R6+*xEz^MX6IZQCP=qqImvRIHakfY&28$^s9@xoUhPO^cGrydjhM;=9eUC z+i{d07y%x??)=l_1KtDYm7ysCh|b=o;Zh$DQvpL>{Z=CL~}oY5qPk z@h4oyWMXxkMX^LbXZp2(tzM$0$;hHWCH*PqFLkfj8XSxR_BC6BIg(Jnwl%0`;RcdA z^X?B@g9F;N%oP-|$F2IeV2Gs(nDCkmLO?BfmX(lbcjF#Q=S3K@DNl_7CyCClVe z)tuA|OGq?FqpKqbAY(+O|6r zwQX7Yo;+{>F_m>TfV7{@?}oBAfh1&a(&}OZ*oAKc{}GxY1F1?k)A9(@!i7H|SEh<( z3nZkAMoBaynwBAUgb)MuCMM6cYInXTy-Ewt&=|tTVwHKxY#bxJv@J?5sisaIRm!5| zVu<7hWRfbg!yf*!YWrF#;KQubEc-pE8U8Z>;`62h5UBE-4%ozJ$-#$j+QcPxzkgfM z--Fnnf{;b$sPeA=?ZHlMT?(&vOW7Ut_F(r;KFY9#;St!M_x50A-3$xZ>xD;_w#KrC4U7;sxPXpzJJJUuuDnWX36gIPOo$Dl{Ql3p%RcWkLdbTlr4 zJnhfIt^4<2Syj&Z*!&PEWEe+!fRy@%Asd|{rS7x;9=slHU(zqV&O|%S)Xjy~cFIgL zZ3ZFRNMw{e{&SkqAwOb1F02QObhFrz^&n_Hj)*Ce5b8lG&>23d zc?=w8jZGU+R&(z1dT?mxt>y-=_$qgn-5=_~I>5emTQGZ#m;gCPHptlYMzoClH8T5x zM@)xqaIkkZuImNBIqlzQi>bxtLFxwI_SD8k&i(hcV5ffTFXf{C+S8b&`{%acDB3;p zUBR*guEE?338YS}11jBg6}FXKW9Cj~Q=ix9GZu3LkKu*~)Ysn?te8K}KU1{c zZPN_wc}B2ce}%C0!{SJq;#aP+Nsv__%u9hbanTqr8Up%-0Z+nT6O?W|BRIN#Et3EM zLk(m|e+WOXnMy58jA)Xg)skbuG37QDjb)`N^8pxAsF}z%X=HPP)N-__s+b;W@XQNr z_AARR@^m$C8k!~zEUY)n6q$)K1j9fegfoy+%%h zf;s6KwYQ*)=jOA5&&wQ)cH|kqmnXI8ko$ClVxGIoD@k=Tua&L6pj|@Nmmda* z%*YSG>4TPunM(!;!7lbDxYNTjVvA|n&A(GQwk@!iEXPC2ed4`=clWbSOQXT1!F}t! z!M^Q1EdE)-VAp?kP-YHmbwq8kUCl%M6CPYb5aQ8S?cicJkOBYpwvAGB1AefB;D5-s zDv|#f^xCev;1H%+=k&*4OmMk1AoU_pA|C$wlG-A(Rjodc<92Zhs)I5Zr`iX15ok-! zL}j6P&OLN?z}*`pDBZQ56U?g6Y^sWZMYcd1*s*DM`8h$qJN}$tPLaGXlw+rzVak%T zlNyNn$GnU?Yd4C;19DHF6CBX}Bqu3FNli1zx&7W3%-c(QsUGAkSI;j~wvAAeZAF8z zwG+*uMgq;A;j75F=g$jwAN2R$G+Q8^)`IvxpidGdY1n?+?_b8c|byyGOL zu!w|eY>G)HP9d6_KP>ARIfLeUKsooDbA!d0#s|*hxTIO?_jDW23kK#ZxH$gGX|)3Z zfjQD1s0klb&1^ubIt|L*c3!Yp4CPnn1^Z;QnP7+T{o5h$4;IxCzSY|bJQ-|~ki*Zc z-%H6Io0LOOe9RgoZXyS=@lE(Sfp#Rdc*kjn!jFQxwZuUcfSJmTP_dRo!nMCYn3?&w zx~dcE{%!a3gO#ZpIVO32@Mf0gW9J9E>F3}Ff>S%@<^&q8#xeJSVE5$oBT36$;RYCYIw2PFC@S| z3g-4;z{=Xf%8%rFdBOBi(+U3shM{GXQbJOq%u8}PGSjqhA-4w9Os%RRHs$~59ge_) z<$dCznto2s2$raBe=NH@T~JCrQ+7Xg!My4&^5kRz`&ERrOXtY8OEo1EOgc}#IhO?g zruYLCAVj%A ztt88>{U?1*>I-9`om(#r_DcPw?EZLZup)Q{LBzIAAUDa{yZ@76<^NuGwaeH(Jm0mpyLY5g}*omz06GGy>2gjy*CV%|S zM}qT`XN(ue+;+{jeYlH!4b*iY&Ud6Ffx6%R>?FJI(xx8EI-?wcmsmQZ2Y+1bz@K% z+kQjP*OCHtZo4_yM6r3Fv|=4`F8jkxO8GJC4x^&Rx?+%9MUOEVHb%$Mh znAq==$ye@`Ck1*WBWjr3Z*%L{_aVi->h55D+lL3~(#3;gU%Z=L=hmoyfA~spKq__d zpxgbP;NRoK+wTef(3ZO3ma$vz4_=k6KXS`=KnY=ne>mye-^B)-u7En z5L$V}b&rMWpfGp9PDK|KXn>M|(~H`zr9R>8E!Cvldrb&5!;>b+PF}U)aoz zicFFHh^FjB7E>d~{yZ2dpYy#4q%9IIJFFXVn%n;K;NbFe?}k6B{-zvp+E~k@@Rjmk z9^0{!IBo2bUj(Jh;AMk$_cMEsP>UJ#c{~149eAB5@|O*cee>5rUnX_=pnLSmV0qUS zgSF=qXb$XxUvPn&`&6)0pVmDUY)kR`;plhY?}Brce$DTKT~l}6;=cd8;AlM;JRQ72 z&ksEvoS^43PY3TU-+5Lv2sGAsm~ooB?DxTmi|_qSG=`=ejkH!T8tHD6LhVj%I1Rdm ze+W)b@x1X5!L53p@WguD|Fi#o$Df1ukv?|lUxLe%6UMmR{qdP# zp~g~uHn=o(!FKnBXZ`Odo(G_HV(f%Z=eQ<%rYVTmBvt)a|qW?z^4&M{tXN-}Vo>z;l=9{IKqO z&VN7rpMF@G=Yx0Z`^s!^xich9e-?+sc z)KL`N#_sMY-k-@``+XrWI|?A=H9f^3NxI(} z{w0B!bM>IBO)q{+M4`8@_-cs4DSgEcrcxgn97`38*)%V%b4KxUg`CdYm-yLi&%d?Xi!i?5p^`IwR7=CmqFF7>tUy>IdR$+0i& zQry>a$H}vn7H3;yaYE~%MRtCg3Ldn!cyr1fu@4upTst^+!9Kk_;Sk=r?|`7OT%LWuJ~^glE>CxUVP2Olyk2x zzIsB6qp}raPh4C4a;y6B=}-7J96cKtyX2syN2~tZTkb9*;?U6v4LKUBy4&w5K9lji~Ei}b8qp7EzKhuyYf55i>>JY z_rbjR(c*P+rvTIcufcr9?fLuSE{tf55&hf-6C;kMXL#(OzZS72{GYLB4D9if5#!TK zN1Qc5sXsgT;sog3GfSV&jlDYs(ee~=?^&hl+`oNMRLnDNi6ulSk_}jbh5uyi-<>t9 zv~yJ0WQ(yoW|dl!yCK?f_)FsYCic%F_1~wYc1TG#QrzFe(jo}yf=cNXnSLXrP*HYf zkmDt_VLvlf>GCDj-uRMm5@rKMM8pZ$jC*zW43rFGfs zz8U>WFXPuu-}1l4-mt9nu4MN4=;PSa%S+pn+3&lyoKg0d$kkXRmIc}#zOS>LGy$Eo%l>cNucunQc z7{u;w7Xb0t;LEo7)LA-&pQMjIwT&9DKD1O$p5bmkv~lIl5HcPwJRD)2BtO7{j&(87ZxKN5hvKBNK!WBA7b#FVy z%8p%gOzD(N_J;R(`Pc=oEPW&UrTEuxURiqA_^-{cD*a+S=lWNdcF$fK=e*|CrIWj9 z%xGQc$=|>_@MgB#eH{neXS;W-EA7YQj&-H8$E#fXn$pgx*)!Z1*OjKbi(bQ2H`?Y? z=zj8=(qR;ucUu)Y+O;_-}FsjyL(`@s5AVYecwDJ#5Ou&KRk)$63yIu{X za(_Odw5$3!|FxwdJ&$;8X||rHy|y&Gh&~%C4tu0k$LZfAR{#3Eo9TbASNkXN(|zN$ zrTu46NV~y8_Cy-$rad6c4}$Cb6H6xzs8W7-YgSa{6I7`TA3u=`5_$gm#3qQ{MDw(8 zYjmix0he2S`h5!;Q{(${-vZ13#N_lReEP2@r~k^Q|6@|RyXd6S5_RS)CzTdyR5tjh zedfo;GrOMGm5Lgb;lqOqa=8rFh}>r~$u@Qw@)yr^H8NB$#rY%uPkG+~kq48t%lvQo zEt4QmSL|5Nrq`ElTl@m`Kou^Y zsX)$HcV)a%JJ@+Indy#wL+RDQ=Lc)rYlQ{lOV2qll_!JF+lb6Cy#>ugll-!G$w5?4mw zRN8g-PenaY6F%BwXqe00*+3y0DHs~H8I6_t-4EVW+B^9(7i=h&R%YHBAMekOFdpi&|37x*GKT{}HwGtI^U;_dDVmYIW&H3hQN|@Fm#Y1rA7mRy zbx8zio(Ol!$%G+zD4G`c;gd_71cdfeN~3*KO`JRXl+vC@eR5Eh#S;Zq6H`UFt(t%I z$;Oxw7;@^KlHdEO=49PWV9B40l3noT();GT_cnP9U<|~7&Hg3k2wK85gB=Ra5OT>c z_P;0H-ES^!*!!w$e0@kIn0fFsV?Huoh@>OB4}2FP{t6J^cEl}5?b2ArJLN5<|Hs&u z07g-4Yftw~CX+&<1r$+GTmveCEQ%l$s30gR z3My_4hzed&5fH*6qJpB=1r-JN4dws7s-8(e-+TY%nx5|3PF0;cdsUq!%CY%6ipVq^ z?tl$;l*+OpL7&{xXNx*ifoX|m<^!7*gi~4LMVfr{jbs@dBauq*(U&P z5K)PZ@2D#Ym5WY!YXj%DGGavC63;RD*)u(3HDIVrH{eeHLg$sSk#=L~k*(O+Lk$0I zqO4UL0lD+bZvY z=6UhLym_PT>BzKjG{h>nINly@hx2s@Al$PHbZ`Uazed}&%ustRWE4ElyVmYsvPd1B zph@r^IFZoV1D;z;zJ9H}so^;-P~!WP=k*|92ocl+2^b`WLb+b?Kr%?~&8d!#{9!Pl zM|F6BbQS3h11gyLp2t%)!ff5bouQJlKsqw46=efarBCLMu?x>kE|0@nKq0k;b0I=1 zy2chN^a^IygtvkLafpOowLf;8BE)P#HjJ@PYW?#16chv*h?xZstIcSQ7A}0sI3}Qi z#Ou2;c3mh*W@M-9?Qq`}Wzqa6PxZQff8vmONN@>`1(oLco$F( zH0n`~zjqaSFt*cjH8bs}&vk3^+w1MtSu1}<^olm{%jgYuyOwXIrfro+t>yVQ*vA#W z2!XDPX`gb~bjcTQu*<6c&tdi^QXaekYBR>K^hSskc%FBo-SGqrMz$IZbhMEIBEekS zG$;r=&UDSx7d3Y_0)ua%YRbIJ4nPv%8r*iHT{(F6Yx)h7mqU${o0DDawBT@}fI7Ef zXFF0b49w1U99b19if^DUNM`0vacpte*)E#JL$%q)OG#JoSvT2t|I;Y_b`w^|@-i8_ z*)9nsd8q7jvwd3HOH@7OxLY7pFTL3wh@}DYz)G8~3>WWD?@3y%~u9- z5_eQ%BXtHG6qF$tXdAk^yKJ}xqUB3v@)11i)ZAfR<+^WRyM8YHeB|`Rr(TN3;~oO? z>m0zR@_0*E8Vz=SgU;B~%>>ipvcV0*XcqzFJVz{90NWF>*5lc%v*+cC=DS%4a9J=W3^zj4m7L72EV15$zd-Ga5r84c*li)C^}gB>$hLVa$q{}Y?N zS);ff00i8j?}rSoTs77{votwLYLy>0u=0p;_Q0%#A9Gx^fHifTz0lYzkGR!-2~iA) zYkwYxe_(7jFCrfS_Nf`&>9GrG-DaQQfGgqLttALuR0l8w5N!>EffB5*K{^@2E4M+z z+x&{RCa2$S4+||$zNL+~BcT^P`9xNa2LP3F^LSGDS$EhaH5Cpq7AGUX27@gh!1Rn1 z(I9%gus~O6~0~$qb3#C)ke}#uoX|opzU2o#F`B#wm|UQuCH)GSUMKNerifR>%W)+D<7J zN-@7%?t;DKy)S?`z=_23ciGPy_@Rl&yhpv3$4s+t zqYU@wGN2|7YX@Iz@Le}+BR3?=74 zE}v>ImhnH=khtg-bcz3{`>5efiegUxbzphV<=y;9L67i zg3+jjJRi)nYs_@-W1{5(`$Hr7CVzRzKDMM2?~%jCB}j*1B^r!Eq?(NY`$u8jdf1+3 zK3OKed>9QrR+gw;XkTaKV{+9^f7K++nQHYVUU>wQnLXz(9Qe&0d1MEB9<`6m(qFXQ zx%x5t4c7AGW9WQn&7a82eC88&uPpWEal(_Iu+PYP6jT~QGqFcL^@JTh@{uywv!M)u z$wQx{B8YP}(LTx^W3C6ByWKOYGt4lXLOyw^G^WNxLu5?CK}&h34<2(tgSwh1aQ1*(aZroJYmR zu-ybyek}eM-~#2SG*8C{4eg60)v5*2@*R>T(dudYC&T=yRF*ztziPm1{R~W~on+p# z_C<9+VVzPn^$xN|GN#I%&)a*^ zm&Cdk>>+6dQ_G@9N6!Y|I4c^Bo&h1kXH88Uz0y8EVBDQ}V3qx-sk78YFO41*{a5sS z3|$CW&iIe*WFVwxPp+Mc(4eEODP^2^KGEq)EL64QjqmEW(kZ-A0(6Pu z4fgE@m*d>GVSTGitb7|ZEfax01w^%3Wr_DT+7TnxDUSWR+)dpbD^*AADFkoF(at0W zibi{A`kmP7NW_@bXgjTcS2hR{EkpF9styY-Ct4O2!{V7%vliAj+L!tfK$Pgb&F;#k zu-d3isisns8&yrI_?1cTz+gQI+bcP#6u1$WgTV$3jc2L}W>#6S-ToJ}ha0xTI$kd2 zcDoZEZ*8|P!K1^w>f=f8+MPnwT?)`sdHcI|A7;^MpCb)#_73|l&fcOO_IW|fR*p4A zX1-_th#_0{zI}H9Gdk@L}Coi6;rNA@e#gavhDhdS!VZYad@ z;S)QezT*g1%vMC9%iZ$z-S&+=Sb}KLQSVks{svlP5M}tTenXAq0Fp|;Z%X$GmjB20 zL!o=#_qao=PaqSd-hTVUULH@rksssSVjiCW>2*>9TKf-7a~f9)%cPKn;1!jX^- z9%5vHiR2P@F8kcBb|xvr1&ggEpB-!P?*?33X-$$(eQuW=F}aKi?mwg>0c0IY5a|GE z%f|POXfHCiKR&nLF}rSiSc8o`T6Zod`#8a(37Aq<2$adZnqip;LU;e`#or`?U%`U| z47g;k-5N20=|jPK(-9MhA2gl|_kOImRYxdgy0D0tF`$0*dGyJmOF4s~knqie{ zBT=JKlF}jBYNK&%>?8u-*G0HsUs@`Z;p~*}?Dicz9noD44ok31_ghbwLkUdMNvhs$ zULcr!@;ke&*!{EaCWIO00Y`>DPIfH!e`nX#J@o)${k5pbR=K$o%O(yJ3>-#C(l#+a zz^r=9B5cba?Y7yoe&h!R27&YS{n4(^ zdWZ!)&Nux>l(}<1KWMk^Yb^7{_x!L_m-+2S`P)_1sUiHEGfP^j~e4%yss!c3TE< zV%D<}_>5^`kn)~%PU4PVwH3tp%`RcYuG$l6B8!sNtADe@#~)B7cjA*OYUy_x_wf#_ z@j+p_|3{zo5Do3hlxY2`iqM$OHNm<2wN&2on_X2W$AwbUZ08M=8ps<6C>wNHf-I z=1|GN2}m@l=9u{UEL6`#&`#b3$VH)oY~8nKacQzE!~U?t4NuHAT8yi6Q*BY|EC?!_!!j?nk91npLT8L z7pfXHlOO(RUzqvHZdR+N;e@}y+Ep`{qZj|OtBX>#0{o`F$OC`b(S|3J;9(rq<`!Tx zR+{Oq$7uQCg;qnNQeay}E2g_crR;8&0`U0qt<~R^VHmFuekT9nU{`9%UoQ#*nglAsMMkwgAflP&O4X(#TmBRf5uqt1HomKD+`fm#l-0O>nm8E( z|BY#)C%O0CK~bE^G@VDjpC(#)wQ*B}%m|8Z$nFNk(W(k=`1E1B94()JE4(y#XyK1g zs+g6tXK+Fq@P1(nlm?_WU5xAYHk3KN%>|xOy=?=b_Te}}r7uxiYe9p-m1o0)s+@Xr zwG&V7O&7~5;kgTv6KJns6cqvYRBg%=2rLAdf$T&W!nFAsvqZS<`glHd9aaVN*;j7-rNHo3(7OtHhjKwO_KeomX0Ykiz8 zN3}o})O)anXvKOfb3~gSPl6MW+kx-+DPF3xn=0@?kBFQBcO`5PJU|1P2>Kq?flI7k z%H*URaSNjw=zV~ZlZ8^3Z1IVKysepFDAl2|wzcd7?5jGii8E>( zD`5sKOWd5TZK{Zb>8t%1s1|vZAH&anmllZ%gOl957?+=6l7|fXa%FK&{;P{S9Db>4tNQlbpkKSa*J+X0{ z5QDRSQD1h0+5^I)9nraBED)Zznd1Ye@+R;oW!Lfp$@hKe0XowWM`nHX40D3Oqv(5% zxF~Ca`k>Z;Jg!7kWo>+xIn+{0Twfx3BMxsezOcY+rQ#|Q;b>Tt%b}$rQ(jai3Jc#~ z?e!N}0J@71F<2y-sjvu(D~3-bn3y$+m%z|8X+*G$HkOHCC|OqSEEB!Gy8AYLy(%pF z(RP@s3?4vM29keBWwRn8+&)EVKQ%7w#c*K*OlVUY*3s@nB)U~sFX8b$28wjkc%Tu2Nf$HQp%ZY3@aefFfc8kV-w-Pq7d*rCUM^S`l1pjbMT~dO*C_7ivS{#w}5&dg~F1v-?eUzw@Gh2&Opfc}iEh<7X*}H-&aY5)OZ{FpNReI_k zs1nzN_D+QPnKNA6Ms#Mi{o07j@k}Jzh%iEOlU3|$s|)?rR&=V_3AE50n2QSp9WZkb zuO1q}%F#Ax<=ySYSA^c00d4U;px?)V+E!I zGA9Q};Y^526e^hhkX&Soszs&FsjlKNfrbdiY0AliQZ$->UoFNQ|G9@E8gDDG_ZXG? zuZ-i*aC+;K)7(5Cc+e0a9_Q%+kD|+G+6#w%K}E@fXal!dc%&Vc7~DFeKyY)^?-bQ< zITTz0R3Cm_FGCT(fbPd}GLbIg@C&XZiKi#Og!u)0ARbD7iE{=XNKXPYsDr4>UOtvG z4V6Vq@yrh5xa?bD`cE;CFFJ@0p(n;657Wy!iqk`rJ_w$+2NnBt0?O&`xF(dFly@F-~Mp6YiuagKf z=PRAWFIhW(V@^$8Rv#f6svu!wfrHa4BJT&un2_Y91cM^Ta<$L;-+n)W}x*F-FVdbqNjYYw|XHF;G7%M<1FxUTMoj&1zRBV&8IiO!+LpLlQOb)sjN z@rdD6vN@Ols_To!{G;eOWWW}vPyon9U*p%&BiUY)2beh$l$JWTUf zQhNAckyv%4xGc0P8Ob#JND&s#HU+IHA&7)R1G?!*QOO#AIT8y!nN4=-F4~0-CPV2j z>#mVxVs}yMV16L`PQmCTiA*YJ>Xq)IeF3`g_mqR(MJ2qIl|4jF(WkF!ur(Dj=~Xdd zY8>7}91%)Y^=Jd5Y?fChjY^CDGEaizx9SO+*5QUA>l#q#| zUbKh+W0n`B_1*8Ff|JqKU z8;&q6R`$6Eip;AQvoPq3>cLl_=W0FXYHoUg|35oE2xsWR7aNt~`In01KIf#dq<{&nSh1I>q743nV$`*ZgN+G6;=>n-Yz!La?0Ai!wCmI9tqK|u)Nh?JdBpXu&e;Nju$V?-;r z8IX|9A^jy6qN*REGF~b9LaLX{r)69vzHT{2)P|DP$ecbRDgYg{=+t1F?%he_{9k=U zJfMnQfnTBNp1VZKK2VBNmAw9cR??a?*7{h{&{XKDW5q>HZ}xGbebd{?$B8S>Uf(2W zk-`nZkzyok5G9gw6;Ux1$2oz?v6}j>?ln;felLh*n)y@ehmkQ!kMfTf#}T8?Jzk^I z)Z@izWsQgg<`GB3kH;}{91I@ni*R7=p!(~0QD%p=DeFvr0+aY{448eh$J||#^25#lHawI2uz*8`v+{Fsy$N(YL{$kvqP_n{Xv=Bl$u zfN6f~cWk~MC?3jQ`7GB@b*i{K^nFsnPd`oUK5_gfETQ;yGgugCOpBgN!xW&@fW;9) zPjLVTzhHeo^0kL!SXavW*XeXo)(x~2I64OSNV2MbDhkV=acDc)@pPOaN3WSm#px<) zPto|(#i*=ZzYrKLD+JCE{a~RQbcPtq>D_vUsEYGk1#me;wE{kRMN_~${)nO(c7hc|PRHRb{fn`JyVix-2Wx$IfCKn=k7V17=#j zh1{2MVk|Z<$ZO9RD{}SD0)=HkShwWSgGF1~4TcUDZ5j~Y7XnBC4_jRD7w1H3Ss7Qe zs2^@9mS*-!{-aiT77qvKz9!iJ;@Q1_#ku&or+PNBjT^kFW4f-?mk$v=vVNb< zq0kHNuOZ^(h9_3>gI*eH{w`71o2A1IehG#x?MVbfnigVevj?@{VZIPQywI>@Z!@#p z`Di68#?yP#hGo%Ygy}$0h7EHdAcWv?9?dZ->1bHYL3Qa6lKYzs~oN5NLt(f zcMhBuP0Py4X{z4Zl%lfMmZqPZ%MQu3qbzHpfyQyhw0!5^qTu9>|4mLl=@;xUNiq8` zxb&%3YURok$CU|#!nix>t|#{Rl6e!!EkIo_6J1Vy#tV*7NNBq0NwNCnKUA=L+C~vrbAp}a&W=3FXVP8Q8=?_QHkl-hiVckpB zp@c)q1SI0f03uDGlN25At`$S2unu59kxIMMlRXFx>;&HD4EI$p*jtVqD*BzENIp<4 z2s%IYwsdS50iECCp}PZ^s6c!a6ImaSxZ6=!mbEB2)iQiAnbd#%HS|@a%fkh zo=SP{Fj0jm)nINy^Z+MV&KM@zoiXoKZ@LXwh7=~`Xo;EB7p~_6I+;nFI@p4nPn|cY zdidZ!#_`$-2jlHF#C#EkF&|=NVp~{=KOQECjuQmaDA*w@_$X zPG2?ksS?|oO1JHei+@xkV;C}2GW&ll)oR#Kmj#gEwDrh1; zZVFI_bmcYjCHDfzudaa9`vom{9=mIErKs$wKPV=w;lefoHT395jhh1<#jSvzO-&vH zgRNqit+8);Q#<)eaphoT#l%nmQIb*rLY<@te;oPL7&SUt`s^3lHxTPFiu{}oS|xbQNjKEqC&wrqd4tBi*Bi} zu#|zZ>T$50eO^*uvoHXa%bd4yliqo?I8$~LdCMqKbi#aZ@v~FuZV;V@GK{l( z)gU%68AKv_r;Y{mPP4>WacaeZ<)e?gMj;3SeJdHQ{RcfqYa8*IqeVB)_q5TXqTx*h zSmJ_gcP*Z$;rSMx1MyskCr;CL*W(G*-Q9pELml46lNYOO#FM9SY{C;f&~3!?JUln! zS&iowJge~Bif09$+whFy`3|1hcy7lNM`kuy8_O6Ohg19XEgpvZE>Z~54m=6bdw3F} z_wgh|AK*!dcH+qvcj3tvKg5$QeuO7m+>Iw&{1{I{{t2Fh{J(e-@=x*1ZiOG8;Rir* zKgSbY!7*%xJJBEs^I_P1fKa}Bt*EF#$_jWZAruh2r&tMbO+yo|_o!p^)y zHQ{sb$d&GOq9BLw06iHSa`APdT?>jI8L;$%8elH&zfN@RR!LfC;h0$fquneF4&q1K z6Lr)JO!I(CZ=R8LA_YXd9wJEzm_iJVrc)okY!| zY-j;11!&5*uXNlf41CvIFa8Zf#0S@lS4yYn$9>FIO@9*y8KcB8_PMujlP}yLdeB&Q z12F$voZ_oMyw^}Ls(jE=aQmaIzES)DAfq>lf>NF7tfoBY5!4~-Xk$6>CUG!#Mm~>> z&EPoC4Wsnnn?)Pr3pw#-adPGxYkbyUX}BGwQGRr@5QrrOKqz00h--MECKe=9ty_dJ z>Sfzo#EpF#LyWrVN!NwmHm>AbKP5V=p7zR~6wq7?cQ}H};2pL>${vlYRby}`SexXR zw}=QT%xDnd%y-|@3Dz#zsX^HId}4#>V*DV-Hi%dYFBQ^tC>E3>&j}aC047M6vgKC5 zixBghm_*_ZkMlLGcf<12259DZb{Q*1W7F5-v7*-c`468pDac*13Y_2MzOk?n)yu+h z;;4>4yvsx&Lx3lmp9V_wXc&S8Dpr}-s$^h(c6Zk%vhs4V)+gdK9xIMFqZ3IibAQMe zZx>@QqMgU1?)mcc@uI63jIcT2pbxy0HdNSNeeO2QQH5MMUi8mr@D5~l-e=vhn+ej2 zL)z2s5T^pPv+oc+F2wcw_uZ5XeJQkT{0EOb2pYS zR5f=Ro;IHMs3jhBr>mtMbZ6jMjqfva~-zRH#3CW>Mc&-*6AkMW0RbCqvT6z7LF>;T;$ zi$CH{QP1$HYwi?B^;hEwk&dmgo&wz?D}ik(NhtW;^1ds0i9ud|m{&OhBnxR+#(&)@ z9*(#0#mM-Gm&G7$xDEvu8*@~1X}HDRUSav#B=Im|xNNdG)(FX`CPOkwlqI%I7WWw? z*sB7jXPCXzf$jnBT`53a{2?by6}@>^DXvaWY?>-g%PgscJsD0^6(ouiRWsasL0YkQ z5l8VRj-D+FjUtrC#esf=g5g&S208Ip;_^9Sp+8j|sy`3!)x`^S@v6C^z^GvJC^I8=DajP+Co<(t=}$D-Go@ z4~iIdVrRZsW9|@g_k3}R_iQgOT!4U}9U`%Hfv7h4)p%H($>%u_!&^FgoBYqi;x_+a zoV(O_p{OprHD3`Px51i}kL`?g46B$R?_4Ne#@V(!>N|up%h7a&9Q252+i7q-3OOeY z8{~4R$6JtI(YZ8l^A{)tZ|=qc0fXb^I-gwfh*)OSBrbkbe23HCamXv%e=<;BOvna0FK%O}L`hS62Z#o~ux zB~}EE;-9ue3@|3j-AlxSNE!PiLjn^+pAtA7d17MG(-`-QiNzrNxW^jHKw+`wpu}SO zvXkGzCjp0nrKG-RC%;2rQQy%Kh058Gyb+Sls}p?c$orUIzWC!Yybx1AFj^Cb?ok1`P8%GH1Ih8bK;VWajnS2 zT4DgMdQRAd;2p5u(&aq8gbBEX+1f1UBI(?wq;K+=gxD2pb10^+n)1awN1-pu6K6`T zrpzyt>ddGogl>Z2{XMg^o);5w%D?e~Xy1dTMGRdv9{cC30AknyVcJ8zJi)sg*R+cJ z>q&}Eio@HWcruZypibygpxda4CAlpgDwowwp7H{g_mlzJ1;`5j~5V8X==Rk2r-NsarAs~&o{*EEq3M`;o%`#Djt04O?0~S@ve=ifAjd$h! z%S4a7!Pd%PJgCJQQ0TulC%#xFCg1>UHOF$)a>SI9KQ0%iWqj8SgQbQ%ZH0&%<7L$f zgyYrAVJk$HF+twDg4PTnSFI2iA~E)&xWL>g5;tNfIFtUqt)d5Bn$||Btw$SW~D%_mREB2?z_KSq~{_PkTcgZO#wNd*2Wp%mrb&<_*y!ZLqaE6qi@^Ei97G znp6tfKDD-|D3lFzgI8!z6DwVsd1gC13x)>o%qQ3S%%k(sv?jE z#@cX=g7RB6T&E9*<$3GHiJ9+fJ;AizlS|hNyFD6I@jNi`V4Tn$>JuR97B?pZ$+gZdfJhV_o+h7uTSUmJlTAvaYq>4kr>C{dQ2WXVh|Jz7 zF3N#?Pm0EiT};UIH*JJn0h*xPD0=vD44o_+QC=+fZWKe!hj5S&LEcqD2n?V2=Eug~TprD(9=PQp$|E>& zW!ZhDa`#rzE_X1m@qh*bzKeV1vZb?4oMU9m(c8rFomkxD)rs&fkvZ!saf&Ca#D$uC z;21Vk2`hO^R{6~~RV8H13N&TvDqFuJ&Iw@8Qb9Q$z9VV_Ev#LovShm`=}M%4&`-_F;$3IKC>|mBEi6EdC1b{UDjY;x zlaOND2j4<+=yuWnJZK4!KWNs;rYjMI3PNcBPFg;p(nIaR`7YVs+JoGPwv^K`>H(y# zcyeh|_VWgSBY-{-@Ldo%z#OC)tOFy7j4zL4Q>FrhP*3k)Gb7b zd*2n;878{b;yuwve>(j=F~kTEG3AQ)#dMtR%-pGpU0okXiI#Px=elX!5C_|{D4)ls-}C~@!CqOU)PVxSVg6$zGKeJeJg zuEaiZvB9)V(1i*_zWB1%$*S+5894HW@5EiF-I)(rQ~A+=j|TA3fMjDS6r-Pk!&hKE z8OBHYwipfa6kj+i=(r;KzN`y;6xjJMYUISsnd7obWaIh%v>H%nl<~#+2lJOKM_tme zo`(U80I4jh#w>Z^J*D~CY=J`z_dr;Vy{EJ=jajo~>|lN`2t40>FS-Mns(uhX%vE9e zuOC3*2g~<=5ZjRW^pB#yCk>fDiPq-Qux$4e#L#EL@{FHEFZ0>3oc$BR|L}bKCvgKL z$G$&2pKFtmWav&A*60f*n_cRXQv!uq*SjU&~UTkQ*LR zify_40J4eMS`n6y{Uu&7SA-LT4~q7ItQW&TYxK){F)U}n@#QQHyNt>~h)$|y)gDSV z`kkxLCpX|++-*xMl;X%kG6W5vgj6t;<94s#NTUN36BnrPnY~md%{&309Jj^lD)ZBv z;xo}KdZewgu;FRVSxcUo@0}TVlk=%fRk=*=K=I5XT5*e^bW=EP6mp5Ed zo+$%Cr!Wh443N#6G;OB4=A*XgJvPx!NW*(og9S%rUT;?Lk9jSgS6M- zfW~Kaceo-mw6rONR<&@FcqyGyVw=@duMnBE*Yp;~|7S&5+xKlq9TPj)pgx4 z%4_jB&Q#2eys)aM(5O$;2&cCpULx5jRLFv>%amu4V`F_GCp%8t>LnRwu)(+mxL2c= z5b)g~pAK~cA4WvHzM2$+7l0AyE63?)z8aQwB~C>Jtj}l+7y=~z?I-@m4~&B5VKF){ z2FnGX#q#C;cLSkJ2&Zb=wufZidF42UXF_=%I>F_S6I&tfK!^TNZtfH>kiV2TZW<_i zv-hLr-KEX|;SIdLK9iwsm<{WdOz0h*I>bukpJh%*2Lq$v(BeEcy6KxrD^S~qIpub! z2@E^#$eAdjRT1pPBKT9=sc8oznE+dj(rHq4!%lC4KD75$fg`Eh^A`k>J!Z1Akm_EQ zCx@L<6Mek!=lo)MYuG8KdZDr^LjiKKtLz%omvB;Z`6TRgqlh}cJg2QZHsaKW1_Nbr zL+Gn=O2moTp0-6I25l>t)V4yh|CfPs*(>T4MipqZwl(Qokjzp#SF@~iil}TFZDIj6 zL>&DqqE48=r)nkeDy!C}bWZc1azfte5_4+7j1t3Qjx^8#16cMzSmw=)707V8)Af)~ zxXB(iT5xaB8w|&TVTfdiDCDa(U%j9-luudi)EW=U4dqTLn)tHZIff!thYF{6$@h+S z0)6jz7_RBI&)vCAPOfl{H0R{WH!7S+Y)&3lDY@nR0nKnx=Y?Lcb- z-+B$>OCRfPzSOFeI%^|es?LqG5@`dDyENC-c(6SuV#wo~V-J1We56h8l zoSRKB|AT+!7iF^@ow6Z3W8Bk}2KKda>})ovzrU?ZBoo-YPS|*80zuIUn+{DNSURCG zm2gj6r#PD+>V(Z<`FvYvU_F&W3_p1><3cGbtPAUTSx3r~o%)EEy#~0o$4<;%?(84G zkjJ!hUNpfX^L}#*Gv3TcCXn_@Syt_QZ%md}d*@tpSy*1t-q~HYBdlcj0vM#>8nD*l z_IA>DykuKz<Ef zShBpvscCZhZG%Zwx*`q=Ew8O{+GT#N8BGWx`QjQ!G}M!%@`NX}en%FG5t86kQzs@s z1XvD))l0d(L(V#r0lQSbzRg2Mr zTCls517AW|)*j(JWxg-ur$;!Qon>K;BP`WP0hj_Ad>bDK*{QQLKlnu}gjrO|cRM@X zIV{w@AUk8KLYS&ptuw8KzWAB~mizoR`AV(R))*$gtaUCiSBB+)E)K7deWi=#eQq6BXfH?J$HQ`v7?;JnwUfJ(N1~!2nR?q z7zGXT$}IPIo@Qa^AMJQ%IWMDUmZPWUX$CaVcJ~G{p}D+zr_lT|Q$F)otb<&AmKBh% z%!pV(9W0TW#N+-?%zvRg$#2w_Y zCd*(ir#b^Dgkm;U!>cR7^au2EHlXoR7gI4<9_a!fUY3`*PIVS4ic-s48jQ<9*`-DD z85hw2gJrLl;SRHUJ3(WyJn^_-8x-!;+bQbG?4}Z{qe|BcL$#x?7t3^!qT!%mJnbg{ zA232o5!J~Ad2?@PSjJ%NJ7EA3H~2V5m~Z6CR>wGJp{-kvaR#Lg1`r?0AzuXwQ-=D7W-+I;Cq0Ia4k;*15;{w|wAOXVzu^ z)~eq`KW+hl{51`m0SqXgAWpXOU+mlg7j92Lx0XKuD}uPVFByaZVrc?aJjhyJmT6uV zWh=g_8;nwb^-W-ryLX4Y?>Hxteqf5}W?C=F)yF~j+>_=`#(T(m2}G2+w#@OUBK_$wSOUptyKZa4!nxG9TKXf*3e;qx86-A_a%T!{z}Y#;4lX z+4c1W2?p7yB9%|GUQa{hqqRmZIo`Q2@L?v%MP6U0COe7 zm(xygCSfW{PIP{PZz1m_$1=)UOrcjc%w1TPL_d8S3b9UC)2E*kD?qN0Hd|#X>tXp2%Pq*yXMK<+=Y*sJ7?dSApr<=$Dr3GDvb2tDRY~bv8L_%cysJd&546`Lp zJK5<;Q19vQ^ul9Hf2Vtk-|tT6diE=ujvORQN%=btaAMJeIt43T1wD|oBW59$f0w*! zfLi}|4p7i68sOBMd&Ba}0ZzY`-%xQ@I|ebh);9?4kSj-*3pr$~#ihhGb0 z{Hpy!^U(SpGu1rh5#DB??7v$MIn60BcZXCm1@?WP#b%%h7nG9aAV3YA#A7o-+5u!tzp$gFf z(ZqzCWcnG=G|&`;F{Okbx<*M=D8#rLuoz|qRjLZ{lNI{qkUbXX{^HY}f0=hrlj}}* z2AVUb${<K@_m(OteW_pNWT5l;#!{@3q z9b9sq7ZdXQGo2yjpPET;$9^^1-f(*5V2QSN%iU)>#jTZ4qUKYlC6_!=1{@Dljl1o? z56kdbP<-pbx!WulFbtu!mkhDNmO3vjFMK3~=x!|*p*;iyi^0aJr*!I-VG zv=aI9znwgfi_>4cT|P54*m~Aw4$ce<%l4N!Bg`Mea^7W5>-rN8HG`=@AdU-#T*_lR zGxf#gU`N^nWqlon+@a_NV)bO`a-6I(6SoLm?kqAt2ub&km=XqQ%_M8ZIzSMOH8)%S zdAYN<uouD~#-Yu=BbawGsILU7Vy>Rc&L8U`Z#TUbsR=Jeuo z<1nW(^PX=#kK3p6uVIR07Y_&P`dJ=1+^M3v4Q~Q=NaOA}%l{fnCEe!__JCu25~Yy~62L{4tK}g|15S=?5k% zdC+HlBFA0fRAmw^p)L5WkL7b$I9-7Zw>)aKl?7KiWyUA+h%24e_;k*dPDSisSclqb zAh2Sphg#zzEx5N#;q(`B-jz;C>sur0gNAf$L~-D1kPgLx;c7q!scXpHS31Q-DbP`= z2RaGMJsnC&SRlS0RSN*wlsiH9Qjsx>ON%rJlKej{HF@s{Fxz^$d4w~#_#s`;Z=zwg z;#WoE^5}8V_OoVygJIzugA>XKRvQIa5r3EE~nB>%d~$@!P=BMj&vO3we-HA>nl zx>EH;jg0OfR33fGj;_)Zy~$945tOr140Ys4r%ffBiZOCA$dOe+ij03Oh4YO7;&W)I z^{HGv5_rf4keBg`_^G1#n74oiK;%TW&9j41dF0hjacFD=n3k4p$Z1_l+sTHjoqU=z zA>>yjNisJ6VKlHL6&sd|uXY}U=yuXI&LG-y=UwC6STtC%dUc!tz=s%#^=bVmJC1VN z8$ZhPM`4{oxtcf1aRQ`1@}*JE{ix%t(YlVsqrrgw2+OZWE4_cgwaz?IfivOs;-XNP z>$%o^S#QgOFN6!_71ufGBste!=aiG=+<%=@Z>*FbT<17l=R&$*r#LR{eTYEe4pmPC z@n+0#tw*3R*{Bu*xrEz|aTxV`(irF5te*|&vYsgbOGgZIbx4zVd(-6-5P@{;J192=c zoQlJ;DxwniBJp=tGJ~%SnexC)xRa7cdSz;i4ggLX&A+
D0iRlF+Xu+UgpB*3{@z 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®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 N5S|Brr$%0mhzmaay`&-Eky#3q24z55 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 From c9cd992b013e4a4eee948552f0d6a8f3e754a248 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 May 2025 18:11:21 +0900 Subject: [PATCH 062/663] Woops; removing debug print... --- lib/src/pdf_api.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 24081f9b..4730617d 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -1070,8 +1070,6 @@ class PdfPoint { /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); - final orig = rotated.rotateReverse(rotation ?? page.rotation.index, page); - print('this=$this, rotated=$rotated, orig=$orig'); final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return Offset(rotated.x * scale, (page.height - rotated.y) * scale); } From 204b3007ff621bd68565725beaa08665c534e458 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 May 2025 18:15:33 +0900 Subject: [PATCH 063/663] 1.1.28 --- CHANGELOG.md | 7 +++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02488c61..7a748b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.1.28 + +- WIP: zoom ratio calculation updates +- goToPage throws array index out of bounds error if the page number is out of range +- PDFium WASM 138.0.7162.0 +- Remove debug print + # 1.1.27 - Apply a proposed fix for #134; but I' not sure if it works well or not. Personally, I don't feel any difference... diff --git a/README.md b/README.md index 2a66abd2..322d8b32 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.27 + pdfrx: ^1.1.28 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 576f6b61..a07d70c2 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.27" + version: "1.1.28" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7455f8e6..a257ee35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.27 +version: 1.1.28 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 69917b0b05245bc2fa74872fd775135bf44d8a09 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 May 2025 18:17:25 +0900 Subject: [PATCH 064/663] pdfrx_wasm 1.1.10 --- example/viewer/pubspec.lock | 2 +- wasm/pdfrx_wasm/CHANGELOG.md | 4 ++++ wasm/pdfrx_wasm/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index a07d70c2..a5c343e6 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -322,7 +322,7 @@ packages: path: "../../wasm/pdfrx_wasm" relative: true source: path - version: "1.1.9" + version: "1.1.10" platform: dependency: transitive description: diff --git a/wasm/pdfrx_wasm/CHANGELOG.md b/wasm/pdfrx_wasm/CHANGELOG.md index 2849487d..c3e940ae 100644 --- a/wasm/pdfrx_wasm/CHANGELOG.md +++ b/wasm/pdfrx_wasm/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.10 + +* PDFium WASM 138.0.7162.0 + ## 1.1.8 * FIXED: loadOutline is not implemented on Pdfium WASM diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml index ccb2c7a1..2b4cc103 100644 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ b/wasm/pdfrx_wasm/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_wasm description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.9 +version: 1.1.10 homepage: https://github.com/espresso3389/pdfrx environment: From 6f3e451cfef1a2e7881617bdb076425076f4bc7c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 7 May 2025 19:11:44 +0900 Subject: [PATCH 065/663] Give me a coffee! Thanks! --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..1039c520 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: + - espresso3389 From 31b280181fb0d268881d63d83752785a555b564c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 7 May 2025 23:32:56 +0900 Subject: [PATCH 066/663] WIP --- lib/src/pdfium/http_cache_control.dart | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/src/pdfium/http_cache_control.dart b/lib/src/pdfium/http_cache_control.dart index 21f2dda2..5dcec22f 100644 --- a/lib/src/pdfium/http_cache_control.dart +++ b/lib/src/pdfium/http_cache_control.dart @@ -54,20 +54,20 @@ class HttpCacheControl { @override String toString() { - final sb = StringBuffer(); - if (noCache) sb.write('no-cache,'); - if (mustRevalidate) sb.write('must-revalidate,'); - if (noStore) sb.write('no-store,'); - if (private) sb.write('private,'); - if (public) sb.write('public,'); - if (mustUnderstand) sb.write('must-understand,'); - if (noTransform) sb.write('no-transform,'); - if (immutable) sb.write('immutable,'); - if (staleWhileRevalidate) sb.write('stale-while-revalidate,'); - if (staleIfError) sb.write('stale-if-error,'); - if (maxAge != null) sb.write('max-age=$maxAge,'); - if (sMaxAge != null) sb.write('s-maxage=$sMaxAge,'); - return sb.toString(); + final sb = []; + if (noCache) sb.add('no-cache'); + if (mustRevalidate) sb.add('must-revalidate'); + if (noStore) sb.add('no-store'); + if (private) sb.add('private'); + if (public) sb.add('public'); + if (mustUnderstand) sb.add('must-understand'); + if (noTransform) sb.add('no-transform'); + if (immutable) sb.add('immutable'); + if (staleWhileRevalidate) sb.add('stale-while-revalidate'); + if (staleIfError) sb.add('stale-if-error'); + if (maxAge != null) sb.add('max-age=$maxAge'); + if (sMaxAge != null) sb.add('s-maxage=$sMaxAge'); + return sb.join(','); } @override @@ -96,8 +96,8 @@ class HttpCacheControlState { final expires = _parseHttpDateTime(headers['expires']); final etag = headers['etag']; final lastModified = _parseHttpDateTime(headers['last-modified']); - var noCache = cacheControl?.contains('no-cache') == true; - var noStore = cacheControl?.contains('no-store') == true; + var noCache = cacheControl?.contains('no-cache') ?? false; + var noStore = cacheControl?.contains('no-store') ?? false; var maxAge = int.tryParse( cacheControl?.firstWhere((e) => e.startsWith('max-age='), orElse: () => '********').substring(8) ?? '', ); From 524a2e98f4cc6d8c99e30bf3aedcf6b954b27099 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 01:14:24 +0000 Subject: [PATCH 067/663] Fix typo in README.md (informatin -> information) Co-Authored-By: Takashi Kawasaki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 322d8b32..7806b26e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Without this, you may encounter errors [like this](https://github.com/espresso33 ### Pdfium WASM support on Web -pdfrx now supports Pdfium WASM on Web, for more informatin, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). +pdfrx now supports Pdfium WASM on Web, for more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). ### Deal with Password Protected PDF Files From a0a978f126f097f0c39b905d5233f997bb617cd6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 01:17:47 +0000 Subject: [PATCH 068/663] Fix typo in CHANGELOG.md (I' -> I'm) Co-Authored-By: Takashi Kawasaki --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a748b54..aabc9f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ # 1.1.27 -- Apply a proposed fix for #134; but I' not sure if it works well or not. Personally, I don't feel any difference... +- Apply a proposed fix for #134; but I'm not sure if it works well or not. Personally, I don't feel any difference... # 1.1.26 From 93757a0fc832f0f6ff5fe71c7bf5af2bd0c96d48 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 14 May 2025 17:22:29 +0900 Subject: [PATCH 069/663] interactive_viewer.dart sync with flutter/flutter 5491c8c --- lib/src/widgets/interactive_viewer.dart | 170 ++++++++++-------------- 1 file changed, 71 insertions(+), 99 deletions(-) diff --git a/lib/src/widgets/interactive_viewer.dart b/lib/src/widgets/interactive_viewer.dart index 5cdba37d..8b0f784b 100644 --- a/lib/src/widgets/interactive_viewer.dart +++ b/lib/src/widgets/interactive_viewer.dart @@ -468,7 +468,7 @@ class InteractiveViewer extends StatefulWidget { } class _InteractiveViewerState extends State with TickerProviderStateMixin { - TransformationController? _transformationController; + late TransformationController _transformer = widget.transformationController ?? TransformationController(); final GlobalKey _childKey = GlobalKey(); final GlobalKey _parentKey = GlobalKey(); @@ -617,7 +617,7 @@ class _InteractiveViewerState extends State with TickerProvid // Don't allow a scale that results in an overall scale beyond min/max // scale. - final double currentScale = _transformationController!.value.getMaxScaleOnAxis(); + final double currentScale = _transformer.value.getMaxScaleOnAxis(); final double totalScale = math.max( currentScale * scale, // Ensure that the scale cannot make the child so big that it can't fit @@ -635,7 +635,7 @@ class _InteractiveViewerState extends State with TickerProvid if (rotation == 0) { return matrix.clone(); } - final Offset focalPointScene = _transformationController!.toScene(focalPoint); + final Offset focalPointScene = _transformer.toScene(focalPoint); return matrix.clone() ..translate(focalPointScene.dx, focalPointScene.dy) ..rotateZ(-rotation) @@ -675,29 +675,29 @@ class _InteractiveViewerState extends State with TickerProvid if (_controller.isAnimating) { _controller.stop(); _controller.reset(); - _animation?.removeListener(_onAnimate); + _animation?.removeListener(_handleInertiaAnimation); _animation = null; } if (_scaleController.isAnimating) { _scaleController.stop(); _scaleController.reset(); - _scaleAnimation?.removeListener(_onScaleAnimate); + _scaleAnimation?.removeListener(_handleScaleAnimation); _scaleAnimation = null; } _gestureType = null; _currentAxis = null; - _scaleStart = _transformationController!.value.getMaxScaleOnAxis(); - _referenceFocalPoint = _transformationController!.toScene(details.localFocalPoint); + _scaleStart = _transformer.value.getMaxScaleOnAxis(); + _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); _rotationStart = _currentRotation; } // Handle an update to an ongoing gesture. All of pan, scale, and rotate are // handled with GestureDetector's scale gesture. void _onScaleUpdate(ScaleUpdateDetails details) { - final double scale = _transformationController!.value.getMaxScaleOnAxis(); + final double scale = _transformer.value.getMaxScaleOnAxis(); _scaleAnimationFocalPoint = details.localFocalPoint; - final Offset focalPointScene = _transformationController!.toScene(details.localFocalPoint); + final Offset focalPointScene = _transformer.toScene(details.localFocalPoint); if (_gestureType == _GestureType.pan) { // When a gesture first starts, it sometimes has no change in scale and @@ -721,24 +721,21 @@ class _InteractiveViewerState extends State with TickerProvid // previous call to _onScaleUpdate. final double desiredScale = _scaleStart! * details.scale; final double scaleChange = desiredScale / scale; - _transformationController!.value = _matrixScale(_transformationController!.value, scaleChange); + _transformer.value = _matrixScale(_transformer.value, scaleChange); // While scaling, translate such that the user's two fingers stay on // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. - final Offset focalPointSceneScaled = _transformationController!.toScene(details.localFocalPoint); - _transformationController!.value = _matrixTranslate( - _transformationController!.value, - focalPointSceneScaled - _referenceFocalPoint!, - ); + final Offset focalPointSceneScaled = _transformer.toScene(details.localFocalPoint); + _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - _referenceFocalPoint!); // details.localFocalPoint should now be at the same location as the // original _referenceFocalPoint point. If it's not, that's because // the translate came in contact with a boundary. In that case, update // _referenceFocalPoint so subsequent updates happen in relation to // the new effective focal point. - final Offset focalPointSceneCheck = _transformationController!.toScene(details.localFocalPoint); + final Offset focalPointSceneCheck = _transformer.toScene(details.localFocalPoint); if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) { _referenceFocalPoint = focalPointSceneCheck; } @@ -749,8 +746,8 @@ class _InteractiveViewerState extends State with TickerProvid return; } final double desiredRotation = _rotationStart! + details.rotation; - _transformationController!.value = _matrixRotate( - _transformationController!.value, + _transformer.value = _matrixRotate( + _transformer.value, _currentRotation - desiredRotation, details.localFocalPoint, ); @@ -769,8 +766,8 @@ class _InteractiveViewerState extends State with TickerProvid // Translate so that the same point in the scene is underneath the // focal point before and after the movement. final Offset translationChange = focalPointScene - _referenceFocalPoint!; - _transformationController!.value = _matrixTranslate(_transformationController!.value, translationChange); - _referenceFocalPoint = _transformationController!.toScene(details.localFocalPoint); + _transformer.value = _matrixTranslate(_transformer.value, translationChange); + _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); } widget.onInteractionUpdate?.call(details); } @@ -783,8 +780,8 @@ class _InteractiveViewerState extends State with TickerProvid _rotationStart = null; _referenceFocalPoint = null; - _animation?.removeListener(_onAnimate); - _scaleAnimation?.removeListener(_onScaleAnimate); + _animation?.removeListener(_handleInertiaAnimation); + _scaleAnimation?.removeListener(_handleScaleAnimation); _controller.reset(); _scaleController.reset(); @@ -799,7 +796,7 @@ class _InteractiveViewerState extends State with TickerProvid _currentAxis = null; return; } - final Vector3 translationVector = _transformationController!.value.getTranslation(); + final Vector3 translationVector = _transformer.value.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); final FrictionSimulation frictionSimulationX = FrictionSimulation( widget.interactionEndFrictionCoefficient, @@ -820,14 +817,14 @@ class _InteractiveViewerState extends State with TickerProvid end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX), ).animate(CurvedAnimation(parent: _controller, curve: Curves.decelerate)); _controller.duration = Duration(milliseconds: (tFinal * 1000).round()); - _animation!.addListener(_onAnimate); + _animation!.addListener(_handleInertiaAnimation); _controller.forward(); case _GestureType.scale: if (details.scaleVelocity.abs() < 0.1) { _currentAxis = null; return; } - final double scale = _transformationController!.value.getMaxScaleOnAxis(); + final double scale = _transformer.value.getMaxScaleOnAxis(); final FrictionSimulation frictionSimulation = FrictionSimulation( widget.interactionEndFrictionCoefficient * widget.scaleFactor, scale, @@ -843,7 +840,7 @@ class _InteractiveViewerState extends State with TickerProvid end: frictionSimulation.x(tFinal), ).animate(CurvedAnimation(parent: _scaleController, curve: Curves.decelerate)); _scaleController.duration = Duration(milliseconds: (tFinal * 1000).round()); - _scaleAnimation!.addListener(_onScaleAnimate); + _scaleAnimation!.addListener(_handleScaleAnimation); _scaleController.forward(); case _GestureType.rotate || null: break; @@ -852,16 +849,16 @@ class _InteractiveViewerState extends State with TickerProvid // Handle mousewheel and web trackpad scroll events. void _receivedPointerSignal(PointerSignalEvent event) { + final Offset local = event.localPosition; + final Offset global = event.position; final double scaleChange; if (event is PointerScrollEvent) { if (event.kind == PointerDeviceKind.trackpad && !widget.trackpadScrollCausesScale) { // Trackpad scroll, so treat it as a pan. - widget.onInteractionStart?.call( - ScaleStartDetails(focalPoint: event.position, localFocalPoint: event.localPosition), - ); + widget.onInteractionStart?.call(ScaleStartDetails(focalPoint: global, localFocalPoint: local)); final Offset localDelta = PointerEvent.transformDeltaViaPositions( - untransformedEndPosition: event.position + event.scrollDelta, + untransformedEndPosition: global + event.scrollDelta, untransformedDelta: event.scrollDelta, transform: event.transform, ); @@ -869,8 +866,8 @@ class _InteractiveViewerState extends State with TickerProvid if (!_gestureIsSupported(_GestureType.pan)) { widget.onInteractionUpdate?.call( ScaleUpdateDetails( - focalPoint: event.position - event.scrollDelta, - localFocalPoint: event.localPosition - event.scrollDelta, + focalPoint: global - event.scrollDelta, + localFocalPoint: local - event.scrollDelta, focalPointDelta: -localDelta, ), ); @@ -878,19 +875,15 @@ class _InteractiveViewerState extends State with TickerProvid return; } - final Offset focalPointScene = _transformationController!.toScene(event.localPosition); + final Offset focalPointScene = _transformer.toScene(local); + final Offset newFocalPointScene = _transformer.toScene(local - localDelta); - final Offset newFocalPointScene = _transformationController!.toScene(event.localPosition - localDelta); - - _transformationController!.value = _matrixTranslate( - _transformationController!.value, - newFocalPointScene - focalPointScene, - ); + _transformer.value = _matrixTranslate(_transformer.value, newFocalPointScene - focalPointScene); widget.onInteractionUpdate?.call( ScaleUpdateDetails( - focalPoint: event.position - event.scrollDelta, - localFocalPoint: event.localPosition - localDelta, + focalPoint: global - event.scrollDelta, + localFocalPoint: local - localDelta, focalPointDelta: -localDelta, ), ); @@ -914,80 +907,69 @@ class _InteractiveViewerState extends State with TickerProvid } else { return; } - widget.onInteractionStart?.call( - ScaleStartDetails(focalPoint: event.position, localFocalPoint: event.localPosition), - ); + widget.onInteractionStart?.call(ScaleStartDetails(focalPoint: global, localFocalPoint: local)); if (!_gestureIsSupported(_GestureType.scale)) { widget.onInteractionUpdate?.call( - ScaleUpdateDetails(focalPoint: event.position, localFocalPoint: event.localPosition, scale: scaleChange), + ScaleUpdateDetails(focalPoint: global, localFocalPoint: local, scale: scaleChange), ); widget.onInteractionEnd?.call(ScaleEndDetails()); return; } - final Offset focalPointScene = _transformationController!.toScene(event.localPosition); - - _transformationController!.value = _matrixScale(_transformationController!.value, scaleChange); + final Offset focalPointScene = _transformer.toScene(local); + _transformer.value = _matrixScale(_transformer.value, scaleChange); // After scaling, translate such that the event's position is at the // same scene point before and after the scale. - final Offset focalPointSceneScaled = _transformationController!.toScene(event.localPosition); - _transformationController!.value = _matrixTranslate( - _transformationController!.value, - focalPointSceneScaled - focalPointScene, - ); + final Offset focalPointSceneScaled = _transformer.toScene(local); + _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - focalPointScene); widget.onInteractionUpdate?.call( - ScaleUpdateDetails(focalPoint: event.position, localFocalPoint: event.localPosition, scale: scaleChange), + ScaleUpdateDetails(focalPoint: global, localFocalPoint: local, scale: scaleChange), ); widget.onInteractionEnd?.call(ScaleEndDetails()); } - // Handle inertia drag animation. - void _onAnimate() { + void _handleInertiaAnimation() { if (!_controller.isAnimating) { _currentAxis = null; - _animation?.removeListener(_onAnimate); + _animation?.removeListener(_handleInertiaAnimation); _animation = null; _controller.reset(); return; } // Translate such that the resulting translation is _animation.value. - final Vector3 translationVector = _transformationController!.value.getTranslation(); + final Vector3 translationVector = _transformer.value.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); - final Offset translationScene = _transformationController!.toScene(translation); - final Offset animationScene = _transformationController!.toScene(_animation!.value); - final Offset translationChangeScene = animationScene - translationScene; - _transformationController!.value = _matrixTranslate(_transformationController!.value, translationChangeScene); + _transformer.value = _matrixTranslate( + _transformer.value, + _transformer.toScene(_animation!.value) - _transformer.toScene(translation), + ); } - // Handle inertia scale animation. - void _onScaleAnimate() { + void _handleScaleAnimation() { if (!_scaleController.isAnimating) { _currentAxis = null; - _scaleAnimation?.removeListener(_onScaleAnimate); + _scaleAnimation?.removeListener(_handleScaleAnimation); _scaleAnimation = null; _scaleController.reset(); return; } final double desiredScale = _scaleAnimation!.value; - final double scaleChange = desiredScale / _transformationController!.value.getMaxScaleOnAxis(); - final Offset referenceFocalPoint = _transformationController!.toScene(_scaleAnimationFocalPoint); - _transformationController!.value = _matrixScale(_transformationController!.value, scaleChange); + final double scaleChange = desiredScale / _transformer.value.getMaxScaleOnAxis(); + final Offset referenceFocalPoint = _transformer.toScene(_scaleAnimationFocalPoint); + _transformer.value = _matrixScale(_transformer.value, scaleChange); // While scaling, translate such that the user's two fingers stay on // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. - final Offset focalPointSceneScaled = _transformationController!.toScene(_scaleAnimationFocalPoint); - _transformationController!.value = _matrixTranslate( - _transformationController!.value, - focalPointSceneScaled - referenceFocalPoint, - ); + final Offset focalPointSceneScaled = _transformer.toScene(_scaleAnimationFocalPoint); + _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - referenceFocalPoint); } - void _onTransformationControllerChange() { + void _handleTransformation() { // A change to the TransformationController's value is a change to the // state. setState(() {}); @@ -996,45 +978,35 @@ class _InteractiveViewerState extends State with TickerProvid @override void initState() { super.initState(); - - _transformationController = widget.transformationController ?? TransformationController(); - _transformationController!.addListener(_onTransformationControllerChange); _controller = AnimationController(vsync: this); _scaleController = AnimationController(vsync: this); + + _transformer.addListener(_handleTransformation); } @override void didUpdateWidget(InteractiveViewer oldWidget) { super.didUpdateWidget(oldWidget); - // Handle all cases of needing to dispose and initialize - // transformationControllers. + + final TransformationController? newController = widget.transformationController; + if (newController == oldWidget.transformationController) { + return; + } + _transformer.removeListener(_handleTransformation); if (oldWidget.transformationController == null) { - if (widget.transformationController != null) { - _transformationController!.removeListener(_onTransformationControllerChange); - _transformationController!.dispose(); - _transformationController = widget.transformationController; - _transformationController!.addListener(_onTransformationControllerChange); - } - } else { - if (widget.transformationController == null) { - _transformationController!.removeListener(_onTransformationControllerChange); - _transformationController = TransformationController(); - _transformationController!.addListener(_onTransformationControllerChange); - } else if (widget.transformationController != oldWidget.transformationController) { - _transformationController!.removeListener(_onTransformationControllerChange); - _transformationController = widget.transformationController; - _transformationController!.addListener(_onTransformationControllerChange); - } + _transformer.dispose(); } + _transformer = newController ?? TransformationController(); + _transformer.addListener(_handleTransformation); } @override void dispose() { _controller.dispose(); _scaleController.dispose(); - _transformationController!.removeListener(_onTransformationControllerChange); + _transformer.removeListener(_handleTransformation); if (widget.transformationController == null) { - _transformationController!.dispose(); + _transformer.dispose(); } super.dispose(); } @@ -1047,7 +1019,7 @@ class _InteractiveViewerState extends State with TickerProvid childKey: _childKey, clipBehavior: widget.clipBehavior, constrained: widget.constrained, - matrix: _transformationController!.value, + matrix: _transformer.value, alignment: widget.alignment, child: widget.child!, ); @@ -1058,7 +1030,7 @@ class _InteractiveViewerState extends State with TickerProvid assert(!widget.constrained); child = LayoutBuilder( builder: (context, constraints) { - final Matrix4 matrix = _transformationController!.value; + final Matrix4 matrix = _transformer.value; return _InteractiveViewerBuilt( childKey: _childKey, clipBehavior: widget.clipBehavior, From 181bc183c0bf7b444e1f532d4e36d237767a9511 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 15 May 2025 09:50:18 +0900 Subject: [PATCH 070/663] Possible fix for #363 --- lib/src/web/pdfrx_wasm.dart | 25 ++++++++++--------------- lib/src/web/pdfrx_web.dart | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index bf52f546..1b9a8818 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -31,14 +31,14 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { Future _init() async { pdfiumWasmWorkerUrl = _getWorkerUrl(); - final moduleUrl = Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath; + final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); final script = web.document.createElement('script') as web.HTMLScriptElement ..type = 'text/javascript' ..charset = 'utf-8' ..async = true ..type = 'module' - ..src = '${moduleUrl}pdfium_client.js'; + ..src = _resolveUrl('pdfium_client.js', baseUrl: moduleUrl); web.document.querySelector('head')!.appendChild(script); final completer = Completer(); final sub1 = script.onLoad.listen((_) => completer.complete()); @@ -53,11 +53,11 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } } - /// Ugly workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments + /// Workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments String _getWorkerUrl() { - final moduleUrl = Pdfrx.pdfiumWasmModulesUrl ?? _appendComponents(web.window.location.href, defaultWasmModulePath); - final workerJsUrl = _appendComponents(moduleUrl, 'pdfium_worker.js'); - final pdfiumWasmUrl = _appendComponents(moduleUrl, 'pdfium.wasm'); + final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); + final workerJsUrl = _resolveUrl('pdfium_worker.js', baseUrl: moduleUrl); + final pdfiumWasmUrl = _resolveUrl('pdfium.wasm', baseUrl: moduleUrl); final content = 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; final blob = web.Blob( [content].jsify() as JSArray, @@ -66,15 +66,10 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { return web.URL.createObjectURL(blob); } - static String _appendComponents(String url, String additionalPath) { - var uri = Uri.parse(url); - final lastSlash = uri.path.lastIndexOf('/'); - if (lastSlash == -1) { - uri = uri.replace(path: '${uri.path}/$additionalPath'); - } else { - uri = uri.replace(path: uri.path.substring(0, lastSlash + 1) + additionalPath); - } - return uri.toString(); + /// Resolves the given [relativeUrl] against the [baseUrl]. + /// If [baseUrl] is null, it uses the current page URL. + static String _resolveUrl(String relativeUrl, {String? baseUrl}) { + return Uri.parse(baseUrl ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); } Future> sendCommand(String command, {Map? parameters}) async { diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart index 0ecd39be..021e6103 100644 --- a/lib/src/web/pdfrx_web.dart +++ b/lib/src/web/pdfrx_web.dart @@ -39,7 +39,7 @@ bool _initialized = false; void _init() { if (_initialized) return; if (_isWasmShouldBeEnabled()) Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; - Pdfrx.pdfiumWasmModulesUrl = _pdfiumWasmModulesUrlFromMetaTag(); + Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); _initialized = true; } From b83d3f95dc45b81a150bb321082a311490524435 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 15 May 2025 10:03:04 +0900 Subject: [PATCH 071/663] 1.1.29 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aabc9f5b..4c6ed29f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.1.29 + +- FIXED: [#363](https://github.com/espresso3389/pdfrx/issues/363) + - FIXED: pdfium-wasm-module-url on HTML meta tag overrides value explicitly set to Pdfrx.pdfiumWasmModulesUrl + - Improves pdfium_worker.js/pdfium.wasm loading path resolution logic to allow relative paths + # 1.1.28 - WIP: zoom ratio calculation updates diff --git a/README.md b/README.md index 7806b26e..f91d6866 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.28 + pdfrx: ^1.1.29 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index a5c343e6..8f408387 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.28" + version: "1.1.29" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a257ee35..c00cefe1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.28 +version: 1.1.29 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 5dde5a1509acca3a70c92f042eac4838320c508e Mon Sep 17 00:00:00 2001 From: DoLT Date: Thu, 15 May 2025 14:13:48 +0900 Subject: [PATCH 072/663] fix: blank pdf on Windows when restore window from minimize --- lib/src/widgets/pdf_viewer.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 67bd439c..6334244e 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -388,7 +388,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix constrained: false, boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), maxScale: widget.params.maxScale, - minScale: _alternativeFitScale != null ? _alternativeFitScale! / 2 : minScale, + minScale: minScale, panAxis: widget.params.panAxis, panEnabled: widget.params.panEnabled, scaleEnabled: widget.params.scaleEnabled, @@ -421,6 +421,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } void _updateLayout(Size viewSize) { + if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); final oldSize = _viewSize; final isViewSizeChanged = oldSize != viewSize; From 01a358d381bc6a61d78776dc2e6fa263b9208ebd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 15 May 2025 15:26:08 +0900 Subject: [PATCH 073/663] _alternativeFitScale should not be negative or 0 --- lib/src/widgets/pdf_viewer.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 6334244e..8a550af4 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -629,6 +629,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final rect = _layout!.pageLayouts[pageNumber - 1]; final m2 = params.margin * 2; _alternativeFitScale = min((_viewSize!.width - m2) / rect.width, (_viewSize!.height - m2) / rect.height); + if (_alternativeFitScale! <= 0) { + _alternativeFitScale = null; + } } else { _alternativeFitScale = null; } From e36e9c4251eb33e8b14850a4c79f326f878e907e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 18 May 2025 21:43:55 +0900 Subject: [PATCH 074/663] Fix typos --- .vscode/settings.json | 3 +++ android/CMakeLists.txt | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 729ebeeb..c246cc95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,11 +3,13 @@ "AACTION", "ACRO", "alloc", + "allprojects", "ANNOT", "Annots", "Antialiasing", "APPEARANCEMODE", "ARGB", + "armeabi", "autofocus", "bblanchon", "Bezier", @@ -15,6 +17,7 @@ "bgra", "BSTR", "buflen", + "buildscript", "BYTESTRING", "CALGRAY", "calloc", diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index b616f573..e771aec4 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -36,7 +36,7 @@ set(PDFIUM_DEST_LIB_FILENAME ${PDFIUM_LIBS_ARCH_DIR}/${PDFIUM_LIB_FILENAME}) set(PDFIUM_LATEST_DIR ${CMAKE_SOURCE_DIR}/.lib/latest) set(PDFIUM_LATEST_LIBS_ARCH_DIR ${PDFIUM_LATEST_DIR}/${ANDROID_ABI}) -set(PDFIUM_LASTEST_LIB_FILENAME ${PDFIUM_LATEST_LIBS_ARCH_DIR}/${PDFIUM_LIB_FILENAME}) +set(PDFIUM_LATEST_LIB_FILENAME ${PDFIUM_LATEST_LIBS_ARCH_DIR}/${PDFIUM_LIB_FILENAME}) if(NOT EXISTS ${PDFIUM_SRC_LIB_FILENAME}) message(STATUS "Download precompiled PDFium...") @@ -71,9 +71,9 @@ set(pdfrx_bundled_libraries # Defined in ../src/CMakeLists.txt. # This can be changed to accommodate different builds. $ - ${PDFIUM_LASTEST_LIB_FILENAME} + ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) target_include_directories(pdfrx PRIVATE ${PDFIUM_LATEST_DIR}/include) -target_link_libraries(pdfrx PRIVATE ${PDFIUM_LASTEST_LIB_FILENAME}) +target_link_libraries(pdfrx PRIVATE ${PDFIUM_LATEST_LIB_FILENAME}) From 0a2f187616dade20657ff190ddcad4311d52395e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 19 May 2025 10:29:34 +0900 Subject: [PATCH 075/663] Regarding #365 (16KB page size on Android), update example's app/build.gradle --- example/viewer/android/app/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/viewer/android/app/build.gradle b/example/viewer/android/app/build.gradle index b5d72326..85728c42 100644 --- a/example/viewer/android/app/build.gradle +++ b/example/viewer/android/app/build.gradle @@ -8,7 +8,9 @@ plugins { android { namespace "jp.espresso3389.pdfrx_example" compileSdkVersion flutter.compileSdkVersion - ndkVersion "27.0.12077973" + + // To support 16 KB pages, we need to set the NDK version to 28 or higher. + ndkVersion = "28.1.13356709" compileOptions { sourceCompatibility JavaVersion.VERSION_18 From 6cb0c8f63e446da56ab3f2b0a0c455ef1890e7f2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 19 May 2025 10:33:07 +0900 Subject: [PATCH 076/663] 1.1.30 --- CHANGELOG.md | 5 +++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c6ed29f..ad414369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.30 + +- MERGED: PR #364 fix: blank pdf on Windows when restore window from minimize +- Update example's app/build.gradle to support Android's 16KB page size + # 1.1.29 - FIXED: [#363](https://github.com/espresso3389/pdfrx/issues/363) diff --git a/README.md b/README.md index f91d6866..bfbbc6f2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.29 + pdfrx: ^1.1.30 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 8f408387..3e66bd68 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.29" + version: "1.1.30" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c00cefe1..9a62552e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.29 +version: 1.1.30 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From e8aa848ad04927a9a00d7ef607a2549c1f283338 Mon Sep 17 00:00:00 2001 From: Giovanni Accetta Date: Tue, 20 May 2025 00:11:35 -0400 Subject: [PATCH 077/663] Fix nullPointerException when laying out view and calculateCurrentPageNumber is overriden --- lib/src/widgets/pdf_viewer.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 8a550af4..3a4c1453 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -567,10 +567,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } int? _guessCurrentPageNumber() { + if (_layout == null || _size == null) return null; + if (widget.params.calculateCurrentPageNumber != null) { return widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); } - if (_layout == null) return null; final visibleRect = _visibleRect; double calcIntersectionArea(int pageNumber) { From c3478329fb34d1417904d9a5e5638bb8f339feeb Mon Sep 17 00:00:00 2001 From: Giovanni Accetta Date: Tue, 20 May 2025 00:19:45 -0400 Subject: [PATCH 078/663] fix wrong param name --- lib/src/widgets/pdf_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 3a4c1453..35d4f470 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -567,7 +567,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } int? _guessCurrentPageNumber() { - if (_layout == null || _size == null) return null; + if (_layout == null || _viewSize == null) return null; if (widget.params.calculateCurrentPageNumber != null) { return widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); From 2686aae2c01bad4900fc0305cc77a746a9185746 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 27 May 2025 09:51:55 +0900 Subject: [PATCH 079/663] Add Claude PR Assistant workflow --- .github/workflows/claude.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..bcd8ef59 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,37 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + From ed5c98af03f08ef77591303341f080307d405ea0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 27 May 2025 10:09:41 +0900 Subject: [PATCH 080/663] FLUTTER_VERSION is used by the framework and cannot be set using --dart-define or --dart-define-from-file. Use FlutterVersion to access it in Flutter code --- .github/workflows/github-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index e335b824..33c427d5 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -35,7 +35,7 @@ jobs: - name: Build Flutter Web App (WASM) run: | cd example/viewer/ - flutter build web --wasm --release --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION --dart-define=FLUTTER_VERSION=$FLUTTER_VERSION + flutter build web --wasm --release --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION sed -i \ -e 's|||' \ -e "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" \ From a9dcda8ea69409003070a4f2a2378f9bd2b809c5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 27 May 2025 22:54:26 +0900 Subject: [PATCH 081/663] PDFium 138.0.7202.0 --- android/CMakeLists.txt | 2 +- example/viewer/pubspec.lock | 20 +- lib/src/pdfium/pdfium_bindings.dart | 448 +++++++++++++++++++++++----- linux/CMakeLists.txt | 2 +- test/utils.dart | 2 +- windows/CMakeLists.txt | 2 +- 6 files changed, 387 insertions(+), 89 deletions(-) diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index e771aec4..63ebf84d 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -12,7 +12,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F6555) +set(PDFIUM_RELEASE chromium%2F7202) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 3e66bd68..d7430c52 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -193,18 +193,18 @@ packages: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -492,10 +492,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: diff --git a/lib/src/pdfium/pdfium_bindings.dart b/lib/src/pdfium/pdfium_bindings.dart index d8c0d2dd..e4b27714 100644 --- a/lib/src/pdfium/pdfium_bindings.dart +++ b/lib/src/pdfium/pdfium_bindings.dart @@ -537,6 +537,8 @@ class pdfium { /// Return value: /// Page width (excluding non-displayable area) measured in points. /// One point is 1/72 inch (around 0.3528 mm). + /// Comments: + /// Changing the rotation of |page| affects the return value. double FPDF_GetPageWidthF( FPDF_PAGE page, ) { @@ -561,6 +563,8 @@ class pdfium { /// Note: /// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the /// future. + /// Comments: + /// Changing the rotation of |page| affects the return value. double FPDF_GetPageWidth( FPDF_PAGE page, ) { @@ -583,6 +587,8 @@ class pdfium { /// Return value: /// Page height (excluding non-displayable area) measured in points. /// One point is 1/72 inch (around 0.3528 mm) + /// Comments: + /// Changing the rotation of |page| affects the return value. double FPDF_GetPageHeightF( FPDF_PAGE page, ) { @@ -607,6 +613,8 @@ class pdfium { /// Note: /// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the /// future. + /// Comments: + /// Changing the rotation of |page| affects the return value. double FPDF_GetPageHeight( FPDF_PAGE page, ) { @@ -733,8 +741,8 @@ class pdfium { /// flags - 0 for normal display, or combination of flags /// defined above. /// Return value: - /// None. - void FPDF_RenderPage( + /// Returns true if the page is rendered successfully, false otherwise. + int FPDF_RenderPage( HDC dc, FPDF_PAGE page, int start_x, @@ -758,10 +766,10 @@ class pdfium { late final _FPDF_RenderPagePtr = _lookup< ffi.NativeFunction< - ffi.Void Function(HDC, FPDF_PAGE, ffi.Int, ffi.Int, ffi.Int, ffi.Int, + FPDF_BOOL Function(HDC, FPDF_PAGE, ffi.Int, ffi.Int, ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('FPDF_RenderPage'); late final _FPDF_RenderPage = _FPDF_RenderPagePtr.asFunction< - void Function(HDC, FPDF_PAGE, int, int, int, int, int, int)>(); + int Function(HDC, FPDF_PAGE, int, int, int, int, int, int)>(); /// Function: FPDF_RenderPageBitmap /// Render contents of a page to a device independent bitmap. @@ -1200,7 +1208,7 @@ class pdfium { /// color - A 32-bit value specifing the color, in 8888 ARGB /// format. /// Return value: - /// None. + /// Returns whether the operation succeeded or not. /// Comments: /// This function sets the color and (optionally) alpha value in the /// specified region of the bitmap. @@ -1210,7 +1218,7 @@ class pdfium { /// background will be replaced by the source color and the alpha. /// /// If the alpha channel is not used, the alpha parameter is ignored. - void FPDFBitmap_FillRect( + int FPDFBitmap_FillRect( FPDF_BITMAP bitmap, int left, int top, @@ -1230,10 +1238,10 @@ class pdfium { late final _FPDFBitmap_FillRectPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(FPDF_BITMAP, ffi.Int, ffi.Int, ffi.Int, ffi.Int, + FPDF_BOOL Function(FPDF_BITMAP, ffi.Int, ffi.Int, ffi.Int, ffi.Int, FPDF_DWORD)>>('FPDFBitmap_FillRect'); late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction< - void Function(FPDF_BITMAP, int, int, int, int, int)>(); + int Function(FPDF_BITMAP, int, int, int, int, int)>(); /// Function: FPDFBitmap_GetBuffer /// Get data buffer of a bitmap. @@ -3434,7 +3442,7 @@ class pdfium { /// Experimental API. /// Set the color of an annotation. Fails when called on annotations with /// appearance streams already defined; instead use - /// FPDFPath_Set{Stroke|Fill}Color(). + /// FPDFPageObj_Set{Stroke|Fill}Color(). /// /// annot - handle to an annotation. /// type - type of the color to be set. @@ -3476,7 +3484,7 @@ class pdfium { /// Get the color of an annotation. If no color is specified, default to yellow /// for highlight annotation, black for all else. Fails when called on /// annotations with appearance streams already defined; instead use - /// FPDFPath_Get{Stroke|Fill}Color(). + /// FPDFPageObj_Get{Stroke|Fill}Color(). /// /// annot - handle to an annotation. /// type - type of the color requested. @@ -4252,6 +4260,34 @@ class pdfium { late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr .asFunction(); + /// Experimental API. + /// Sets the form field flags for an interactive form annotation. + /// + /// handle - the handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment(). + /// annot - handle to an interactive form annotation. + /// flags - the form field flags to be set. + /// + /// Returns true if successful. + int FPDFAnnot_SetFormFieldFlags( + FPDF_FORMHANDLE handle, + FPDF_ANNOTATION annot, + int flags, + ) { + return _FPDFAnnot_SetFormFieldFlags( + handle, + annot, + flags, + ); + } + + late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, + ffi.Int)>>('FPDFAnnot_SetFormFieldFlags'); + late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr + .asFunction(); + /// Experimental API. /// Retrieves an interactive form annotation whose rectangle contains a given /// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned @@ -4566,6 +4602,43 @@ class pdfium { late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction< int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer)>(); + /// Experimental API. + /// Set the text color of an annotation. + /// + /// handle - handle to the form fill module, returned by + /// FPDFDOC_InitFormFillEnvironment. + /// annot - handle to an annotation. + /// R - the red component for the text color. + /// G - the green component for the text color. + /// B - the blue component for the text color. + /// + /// Returns true if successful. + /// + /// Currently supported subtypes: freetext. + /// The range for the color components is 0 to 255. + int FPDFAnnot_SetFontColor( + FPDF_FORMHANDLE handle, + FPDF_ANNOTATION annot, + int R, + int G, + int B, + ) { + return _FPDFAnnot_SetFontColor( + handle, + annot, + R, + G, + B, + ); + } + + late final _FPDFAnnot_SetFontColorPtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.UnsignedInt, + ffi.UnsignedInt, ffi.UnsignedInt)>>('FPDFAnnot_SetFontColor'); + late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction< + int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, int, int)>(); + /// Experimental API. /// Get the RGB value of the font color for an |annot| with variable text. /// @@ -5015,6 +5088,33 @@ class pdfium { late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); + /// Experimental API. + /// Function: FPDFText_GetTextObject + /// Get the FPDF_PAGEOBJECT associated with a given character. + /// Parameters: + /// text_page - Handle to a text page information structure. + /// Returned by FPDFText_LoadPage function. + /// index - Zero-based index of the character. + /// Return value: + /// The associated text object for the character at |index|, or NULL on + /// error. The returned text object, if non-null, is of type + /// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. + FPDF_PAGEOBJECT FPDFText_GetTextObject( + FPDF_TEXTPAGE text_page, + int index, + ) { + return _FPDFText_GetTextObject( + text_page, + index, + ); + } + + late final _FPDFText_GetTextObjectPtr = _lookup< + ffi.NativeFunction>( + 'FPDFText_GetTextObject'); + late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction< + FPDF_PAGEOBJECT Function(FPDF_TEXTPAGE, int)>(); + /// Experimental API. /// Function: FPDFText_IsGenerated /// Get if a character in a page is generated by PDFium. @@ -5195,34 +5295,6 @@ class pdfium { late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); - /// Experimental API. - /// Function: FPDFText_GetTextRenderMode - /// Get text rendering mode of character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return Value: - /// On success, return the render mode value. A valid value is of type - /// FPDF_TEXT_RENDERMODE. If |text_page| is invalid, if |index| is out - /// of bounds, or if the text object is undefined, then return - /// FPDF_TEXTRENDERMODE_UNKNOWN. - FPDF_TEXT_RENDERMODE FPDFText_GetTextRenderMode( - FPDF_TEXTPAGE text_page, - int index, - ) { - return FPDF_TEXT_RENDERMODE.fromValue(_FPDFText_GetTextRenderMode( - text_page, - index, - )); - } - - late final _FPDFText_GetTextRenderModePtr = - _lookup>( - 'FPDFText_GetTextRenderMode'); - late final _FPDFText_GetTextRenderMode = _FPDFText_GetTextRenderModePtr - .asFunction(); - /// Experimental API. /// Function: FPDFText_GetFillColor /// Get the fill color of a particular character. @@ -7249,6 +7321,65 @@ class pdfium { late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); + /// Experimental API. + /// Gets active state for |page_object| within page. + /// + /// page_object - handle to a page object. + /// active - pointer to variable that will receive if the page object is + /// active. This is a required parameter. Not filled if FALSE + /// is returned. + /// + /// For page objects where |active| is filled with FALSE, the |page_object| is + /// treated as if it wasn't in the document even though it is still held + /// internally. + /// + /// Returns TRUE if the operation succeeded, FALSE if it failed. + int FPDFPageObj_GetIsActive( + FPDF_PAGEOBJECT page_object, + ffi.Pointer active, + ) { + return _FPDFPageObj_GetIsActive( + page_object, + active, + ); + } + + late final _FPDFPageObj_GetIsActivePtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function(FPDF_PAGEOBJECT, + ffi.Pointer)>>('FPDFPageObj_GetIsActive'); + late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction< + int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); + + /// Experimental API. + /// Sets if |page_object| is active within page. + /// + /// page_object - handle to a page object. + /// active - a boolean specifying if the object is active. + /// + /// Returns TRUE on success. + /// + /// Page objects all start in the active state by default, and remain in that + /// state unless this function is called. + /// + /// When |active| is false, this makes the |page_object| be treated as if it + /// wasn't in the document even though it is still held internally. + int FPDFPageObj_SetIsActive( + FPDF_PAGEOBJECT page_object, + int active, + ) { + return _FPDFPageObj_SetIsActive( + page_object, + active, + ); + } + + late final _FPDFPageObj_SetIsActivePtr = _lookup< + ffi.NativeFunction>( + 'FPDFPageObj_SetIsActive'); + late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction< + int Function(FPDF_PAGEOBJECT, int)>(); + /// Transform |page_object| by the given matrix. /// /// page_object - handle to a page object. @@ -7291,6 +7422,35 @@ class pdfium { void Function( FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); + /// Experimental API. + /// Transform |page_object| by the given matrix. + /// + /// page_object - handle to a page object. + /// matrix - the transform matrix. + /// + /// Returns TRUE on success. + /// + /// This can be used to scale, rotate, shear and translate the |page_object|. + /// It is an improved version of FPDFPageObj_Transform() that does not do + /// unnecessary double to float conversions, and only uses 1 parameter for the + /// matrix. It also returns whether the operation succeeded or not. + int FPDFPageObj_TransformF( + FPDF_PAGEOBJECT page_object, + ffi.Pointer matrix, + ) { + return _FPDFPageObj_TransformF( + page_object, + matrix, + ); + } + + late final _FPDFPageObj_TransformFPtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function(FPDF_PAGEOBJECT, + ffi.Pointer)>>('FPDFPageObj_TransformF'); + late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction< + int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); + /// Experimental API. /// Get the transform matrix of a page object. /// @@ -7416,6 +7576,27 @@ class pdfium { late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_DOCUMENT)>(); + /// Experimental API. + /// Get the marked content ID for the object. + /// + /// page_object - handle to a page object. + /// + /// Returns the page object's marked content ID, or -1 on error. + int FPDFPageObj_GetMarkedContentID( + FPDF_PAGEOBJECT page_object, + ) { + return _FPDFPageObj_GetMarkedContentID( + page_object, + ); + } + + late final _FPDFPageObj_GetMarkedContentIDPtr = + _lookup>( + 'FPDFPageObj_GetMarkedContentID'); + late final _FPDFPageObj_GetMarkedContentID = + _FPDFPageObj_GetMarkedContentIDPtr.asFunction< + int Function(FPDF_PAGEOBJECT)>(); + /// Experimental API. /// Get number of content marks in |page_object|. /// @@ -7521,17 +7702,18 @@ class pdfium { /// /// mark - handle to a content mark. /// buffer - buffer for holding the returned name in UTF-16LE. This is only - /// modified if |buflen| is longer than the length of the name. + /// modified if |buflen| is large enough to store the name. /// Optional, pass null to just retrieve the size of the buffer /// needed. - /// buflen - length of the buffer. + /// buflen - length of the buffer in bytes. /// out_buflen - pointer to variable that will receive the minimum buffer size - /// to contain the name. Not filled if FALSE is returned. + /// in bytes to contain the name. This is a required parameter. + /// Not filled if FALSE is returned. /// /// Returns TRUE if the operation succeeded, FALSE if it failed. int FPDFPageObjMark_GetName( FPDF_PAGEOBJECTMARK mark, - ffi.Pointer buffer, + ffi.Pointer buffer, int buflen, ffi.Pointer out_buflen, ) { @@ -7547,11 +7729,11 @@ class pdfium { ffi.NativeFunction< FPDF_BOOL Function( FPDF_PAGEOBJECTMARK, - ffi.Pointer, + ffi.Pointer, ffi.UnsignedLong, ffi.Pointer)>>('FPDFPageObjMark_GetName'); late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, ffi.Pointer, int, + int Function(FPDF_PAGEOBJECTMARK, ffi.Pointer, int, ffi.Pointer)>(); /// Experimental API. @@ -7581,18 +7763,19 @@ class pdfium { /// mark - handle to a content mark. /// index - index of the property. /// buffer - buffer for holding the returned key in UTF-16LE. This is only - /// modified if |buflen| is longer than the length of the key. + /// modified if |buflen| is large enough to store the key. /// Optional, pass null to just retrieve the size of the buffer /// needed. - /// buflen - length of the buffer. + /// buflen - length of the buffer in bytes. /// out_buflen - pointer to variable that will receive the minimum buffer size - /// to contain the key. Not filled if FALSE is returned. + /// in bytes to contain the name. This is a required parameter. + /// Not filled if FALSE is returned. /// /// Returns TRUE if the operation was successful, FALSE otherwise. int FPDFPageObjMark_GetParamKey( FPDF_PAGEOBJECTMARK mark, int index, - ffi.Pointer buffer, + ffi.Pointer buffer, int buflen, ffi.Pointer out_buflen, ) { @@ -7610,12 +7793,12 @@ class pdfium { FPDF_BOOL Function( FPDF_PAGEOBJECTMARK, ffi.UnsignedLong, - ffi.Pointer, + ffi.Pointer, ffi.UnsignedLong, ffi.Pointer)>>('FPDFPageObjMark_GetParamKey'); late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, int, ffi.Pointer, int, + int Function(FPDF_PAGEOBJECTMARK, int, ffi.Pointer, int, ffi.Pointer)>(); /// Experimental API. @@ -7681,19 +7864,19 @@ class pdfium { /// mark - handle to a content mark. /// key - string key of the property. /// buffer - buffer for holding the returned value in UTF-16LE. This is - /// only modified if |buflen| is longer than the length of the - /// value. + /// only modified if |buflen| is large enough to store the value. /// Optional, pass null to just retrieve the size of the buffer /// needed. - /// buflen - length of the buffer. + /// buflen - length of the buffer in bytes. /// out_buflen - pointer to variable that will receive the minimum buffer size - /// to contain the value. Not filled if FALSE is returned. + /// in bytes to contain the name. This is a required parameter. + /// Not filled if FALSE is returned. /// /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. int FPDFPageObjMark_GetParamStringValue( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, - ffi.Pointer buffer, + ffi.Pointer buffer, int buflen, ffi.Pointer out_buflen, ) { @@ -7711,14 +7894,14 @@ class pdfium { FPDF_BOOL Function( FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, + ffi.Pointer, ffi.UnsignedLong, ffi.Pointer)>>( 'FPDFPageObjMark_GetParamStringValue'); late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction< int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, int, ffi.Pointer)>(); + ffi.Pointer, int, ffi.Pointer)>(); /// Experimental API. /// Get the value of a blob property in a content mark by key. @@ -7726,18 +7909,19 @@ class pdfium { /// mark - handle to a content mark. /// key - string key of the property. /// buffer - buffer for holding the returned value. This is only modified - /// if |buflen| is at least as long as the length of the value. + /// if |buflen| is large enough to store the value. /// Optional, pass null to just retrieve the size of the buffer /// needed. - /// buflen - length of the buffer. + /// buflen - length of the buffer in bytes. /// out_buflen - pointer to variable that will receive the minimum buffer size - /// to contain the value. Not filled if FALSE is returned. + /// in bytes to contain the name. This is a required parameter. + /// Not filled if FALSE is returned. /// /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. int FPDFPageObjMark_GetParamBlobValue( FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, - ffi.Pointer buffer, + ffi.Pointer buffer, int buflen, ffi.Pointer out_buflen, ) { @@ -7755,14 +7939,18 @@ class pdfium { FPDF_BOOL Function( FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, + ffi.Pointer, ffi.UnsignedLong, ffi.Pointer)>>( 'FPDFPageObjMark_GetParamBlobValue'); late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, int, ffi.Pointer)>(); + int Function( + FPDF_PAGEOBJECTMARK, + FPDF_BYTESTRING, + ffi.Pointer, + int, + ffi.Pointer)>(); /// Experimental API. /// Set the value of an int property in a content mark by key. If a parameter @@ -7864,7 +8052,7 @@ class pdfium { FPDF_PAGEOBJECT page_object, FPDF_PAGEOBJECTMARK mark, FPDF_BYTESTRING key, - ffi.Pointer value, + ffi.Pointer value, int value_len, ) { return _FPDFPageObjMark_SetBlobParam( @@ -7884,12 +8072,12 @@ class pdfium { FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, + ffi.Pointer, ffi.UnsignedLong)>>('FPDFPageObjMark_SetBlobParam'); late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction< int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, ffi.Pointer, int)>(); + FPDF_BYTESTRING, ffi.Pointer, int)>(); /// Experimental API. /// Removes a property from a content mark by key. @@ -8311,6 +8499,53 @@ class pdfium { int Function(FPDF_PAGEOBJECT, ffi.Pointer, ffi.Pointer)>(); + /// Experimental API. + /// Get ICC profile decoded data of |image_object|. If the |image_object| is not + /// an image object or if it does not have an image, then the return value will + /// be false. It also returns false if the |image_object| has no ICC profile. + /// |buffer| is only modified if ICC profile exists and |buflen| is longer than + /// the length of the ICC profile decoded data. + /// + /// image_object - handle to an image object; must not be NULL. + /// page - handle to the page containing |image_object|; must not be + /// NULL. Required for retrieving the image's colorspace. + /// buffer - Buffer to receive ICC profile data; may be NULL if querying + /// required size via |out_buflen|. + /// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. + /// out_buflen - Pointer to receive the ICC profile data size in bytes; must + /// not be NULL. Will be set if this API returns true. + /// + /// Returns true if |out_buflen| is not null and an ICC profile exists for the + /// given |image_object|. + int FPDFImageObj_GetIccProfileDataDecoded( + FPDF_PAGEOBJECT image_object, + FPDF_PAGE page, + ffi.Pointer buffer, + int buflen, + ffi.Pointer out_buflen, + ) { + return _FPDFImageObj_GetIccProfileDataDecoded( + image_object, + page, + buffer, + buflen, + out_buflen, + ); + } + + late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function( + FPDF_PAGEOBJECT, + FPDF_PAGE, + ffi.Pointer, + ffi.Size, + ffi.Pointer)>>('FPDFImageObj_GetIccProfileDataDecoded'); + late final _FPDFImageObj_GetIccProfileDataDecoded = + _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction< + int Function(FPDF_PAGEOBJECT, FPDF_PAGE, ffi.Pointer, int, + ffi.Pointer)>(); + /// Create a new path object at an initial position. /// /// x - initial horizontal position. @@ -9545,35 +9780,68 @@ class pdfium { _FPDFTextObj_GetFontPtr.asFunction(); /// Experimental API. - /// Get the font name of a font. + /// Get the base name of a font. + /// + /// font - the handle to the font object. + /// buffer - the address of a buffer that receives the base font name. + /// length - the size, in bytes, of |buffer|. + /// + /// Returns the number of bytes in the base name (including the trailing NUL + /// character) on success, 0 on error. The base name is typically the font's + /// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. + /// + /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. + /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| + /// will not be modified. + int FPDFFont_GetBaseFontName( + FPDF_FONT font, + ffi.Pointer buffer, + int length, + ) { + return _FPDFFont_GetBaseFontName( + font, + buffer, + length, + ); + } + + late final _FPDFFont_GetBaseFontNamePtr = _lookup< + ffi.NativeFunction< + ffi.Size Function(FPDF_FONT, ffi.Pointer, + ffi.Size)>>('FPDFFont_GetBaseFontName'); + late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr + .asFunction, int)>(); + + /// Experimental API. + /// Get the family name of a font. /// /// font - the handle to the font object. /// buffer - the address of a buffer that receives the font name. /// length - the size, in bytes, of |buffer|. /// - /// Returns the number of bytes in the font name (including the trailing NUL + /// Returns the number of bytes in the family name (including the trailing NUL /// character) on success, 0 on error. /// /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| /// will not be modified. - int FPDFFont_GetFontName( + int FPDFFont_GetFamilyName( FPDF_FONT font, ffi.Pointer buffer, int length, ) { - return _FPDFFont_GetFontName( + return _FPDFFont_GetFamilyName( font, buffer, length, ); } - late final _FPDFFont_GetFontNamePtr = _lookup< + late final _FPDFFont_GetFamilyNamePtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_FONT, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFFont_GetFontName'); - late final _FPDFFont_GetFontName = _FPDFFont_GetFontNamePtr.asFunction< + ffi.Size Function(FPDF_FONT, ffi.Pointer, + ffi.Size)>>('FPDFFont_GetFamilyName'); + late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction< int Function(FPDF_FONT, ffi.Pointer, int)>(); /// Experimental API. @@ -9913,6 +10181,34 @@ class pdfium { FPDF_PAGEOBJECT, ffi.UnsignedLong)>>('FPDFFormObj_GetObject'); late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction< FPDF_PAGEOBJECT Function(FPDF_PAGEOBJECT, int)>(); + + /// Experimental API. + /// + /// Remove |page_object| from |form_object|. + /// + /// form_object - handle to a form object. + /// page_object - handle to a page object to be removed from the form. + /// + /// Returns TRUE on success. + /// + /// Ownership of the removed |page_object| is transferred to the caller. + /// Call FPDFPageObj_Destroy() on the removed page_object to free it. + int FPDFFormObj_RemoveObject( + FPDF_PAGEOBJECT form_object, + FPDF_PAGEOBJECT page_object, + ) { + return _FPDFFormObj_RemoveObject( + form_object, + page_object, + ); + } + + late final _FPDFFormObj_RemoveObjectPtr = _lookup< + ffi.NativeFunction< + FPDF_BOOL Function( + FPDF_PAGEOBJECT, FPDF_PAGEOBJECT)>>('FPDFFormObj_RemoveObject'); + late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr + .asFunction(); } /// PDF text rendering modes @@ -11725,6 +12021,8 @@ const int FPDFBitmap_BGRx = 3; const int FPDFBitmap_BGRA = 4; +const int FPDFBitmap_BGRA_Premul = 5; + const int FORMTYPE_NONE = 0; const int FORMTYPE_ACRO_FORM = 1; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 88d63b3d..5ec52403 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -29,7 +29,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F6555) +set(PDFIUM_RELEASE chromium%2F7202) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/test/utils.dart b/test/utils.dart index 0a690204..a5ff2d17 100644 --- a/test/utils.dart +++ b/test/utils.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:pdfrx/pdfrx.dart'; /// The release of pdfium to download. -const pdfiumRelease = 'chromium%2F6555'; +const pdfiumRelease = 'chromium%2F7202'; /// Temporary directory for testing. final tmpRoot = Directory('${Directory.current.path}/test/.tmp'); diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 4c99c4bf..ec65e0dc 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F6555) +set(PDFIUM_RELEASE chromium%2F7202) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) From 7aeb6bf5e2814e73e479faa6c5061aa1e2fca5be Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 27 May 2025 15:44:05 +0900 Subject: [PATCH 082/663] WIP: updating iOS/macOS pdfium build script --- .gitignore | 3 +++ darwin/pdfium/build-config.sh | 23 ++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 3f8f3a03..09d5833d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ migrate_working_dir/ # C++ .cxx +# For pdfium builds on macOS +darwin/pdfrx/.build/ + # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. diff --git a/darwin/pdfium/build-config.sh b/darwin/pdfium/build-config.sh index df1f4016..bec5362c 100755 --- a/darwin/pdfium/build-config.sh +++ b/darwin/pdfium/build-config.sh @@ -5,8 +5,12 @@ if [ "$2" = "" ]; then exit 1 fi +echo "**************************************************************" +echo " Building PDFium for $1/$2" +echo "**************************************************************" + # https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/6555 -LAST_KNOWN_GOOD_COMMIT=5a6a8741b0e111a6b5bd9ab4e1036377c98885dc +#LAST_KNOWN_GOOD_COMMIT=5a6a8741b0e111a6b5bd9ab4e1036377c98885dc SCRIPT_DIR=$(cd $(dirname $0) && pwd) @@ -58,7 +62,7 @@ else DEBUG_DIR_SUFFIX=/debug fi -if [[ "$TARGET_OS" == "macos" || "$TARGET_OS" == "ios" || "$TARGET_OS" == "android" ]]; then +if [[ "$TARGET_OS" == "mac" || "$TARGET_OS" == "ios" || "$TARGET_OS" == "android" ]]; then IS_CLANG=true else IS_CLANG=false @@ -73,17 +77,18 @@ PDFIUM_SRCDIR=$WORK_DIR/pdfium BUILDDIR=$PDFIUM_SRCDIR/out/$TARGET_OS_ORIG-$TARGET_ARCH-$REL_OR_DBG mkdir -p $BUILDDIR +pushd $PDFIUM_SRCDIR +git reset --hard if [[ "$LAST_KNOWN_GOOD_COMMIT" != "" ]]; then - pushd $PDFIUM_SRCDIR - git reset --hard git checkout $LAST_KNOWN_GOOD_COMMIT - cd $PDFIUM_SRCDIR/build - git reset --hard - cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo - git reset --hard - popd fi +cd $PDFIUM_SRCDIR/build +git reset --hard +cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo +git reset --hard +popd + INCLUDE_DIR=$DIST_DIR/include if [[ ! -d $INCLUDE_DIR ]]; then mkdir -p $INCLUDE_DIR From 1baaf8ffa1209a0c9f761f4fb1fdeb13c35a31d2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 28 May 2025 08:35:36 +0900 Subject: [PATCH 083/663] iOS: chromium/7202 --- darwin/pdfium/build-config.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/darwin/pdfium/build-config.sh b/darwin/pdfium/build-config.sh index bec5362c..70898979 100755 --- a/darwin/pdfium/build-config.sh +++ b/darwin/pdfium/build-config.sh @@ -9,8 +9,7 @@ echo "**************************************************************" echo " Building PDFium for $1/$2" echo "**************************************************************" -# https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/6555 -#LAST_KNOWN_GOOD_COMMIT=5a6a8741b0e111a6b5bd9ab4e1036377c98885dc +LAST_KNOWN_GOOD_COMMIT=chromium/7202 SCRIPT_DIR=$(cd $(dirname $0) && pwd) @@ -80,7 +79,7 @@ mkdir -p $BUILDDIR pushd $PDFIUM_SRCDIR git reset --hard if [[ "$LAST_KNOWN_GOOD_COMMIT" != "" ]]; then - git checkout $LAST_KNOWN_GOOD_COMMIT + git checkout "$LAST_KNOWN_GOOD_COMMIT" fi cd $PDFIUM_SRCDIR/build From 42f4fe60836ebb1f20a55fb299a1b80f6e0af3bb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 28 May 2025 08:51:01 +0900 Subject: [PATCH 084/663] WIP --- src/pdfium_interop.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pdfium_interop.cpp b/src/pdfium_interop.cpp index 58e71491..343edf11 100644 --- a/src/pdfium_interop.cpp +++ b/src/pdfium_interop.cpp @@ -200,7 +200,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFText_GetFontSize), reinterpret_cast(FPDFText_GetFontInfo), reinterpret_cast(FPDFText_GetFontWeight), - reinterpret_cast(FPDFText_GetTextRenderMode), + // reinterpret_cast(FPDFText_GetTextRenderMode), reinterpret_cast(FPDFText_GetFillColor), reinterpret_cast(FPDFText_GetStrokeColor), reinterpret_cast(FPDFText_GetCharAngle), @@ -347,7 +347,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFTextObj_GetText), reinterpret_cast(FPDFTextObj_GetRenderedBitmap), reinterpret_cast(FPDFTextObj_GetFont), - reinterpret_cast(FPDFFont_GetFontName), + // reinterpret_cast(FPDFFont_GetFontName), reinterpret_cast(FPDFFont_GetFontData), reinterpret_cast(FPDFFont_GetIsEmbedded), reinterpret_cast(FPDFFont_GetFlags), From 4f609c024e4e4599969a11c17da71fb8e7297a5c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 16:31:32 +0900 Subject: [PATCH 085/663] WIP: updating pdfium patches for darwin --- darwin/pdfium/patches/ios/pdfium.patch | 16 ++++++++-------- darwin/pdfium/patches/macos/pdfium.patch | 14 +++++++------- .../xcshareddata/xcschemes/Runner.xcscheme | 2 ++ .../xcshareddata/xcschemes/Runner.xcscheme | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/darwin/pdfium/patches/ios/pdfium.patch b/darwin/pdfium/patches/ios/pdfium.patch index 0071669a..673d3f5a 100644 --- a/darwin/pdfium/patches/ios/pdfium.patch +++ b/darwin/pdfium/patches/ios/pdfium.patch @@ -1,5 +1,5 @@ diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn -index 215a63b14..357d96735 100644 +index a07bcd089..3d1d197d2 100644 --- a/core/fxge/BUILD.gn +++ b/core/fxge/BUILD.gn @@ -164,7 +164,7 @@ source_set("fxge") { @@ -12,18 +12,18 @@ index 215a63b14..357d96735 100644 "apple/fx_apple_impl.cpp", "apple/fx_apple_platform.cpp", diff --git a/public/fpdfview.h b/public/fpdfview.h -index b374088b4..b1c896e26 100644 +index d96556b47..79b6f3ebb 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h -@@ -214,7 +214,7 @@ typedef int FPDF_OBJECT_TYPE; +@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(WIN32) #else - #if defined(FPDF_IMPLEMENTATION) --#define FPDF_EXPORT __attribute__((visibility("default"))) +-#define FPDF_EXPORT +#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #else - #define FPDF_EXPORT - #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(COMPONENT_BUILD) + + #if defined(WIN32) && defined(FPDFSDK_EXPORTS) diff --git a/testing/test.gni b/testing/test.gni index 6ad2c2d4a..bae5117d8 100644 --- a/testing/test.gni diff --git a/darwin/pdfium/patches/macos/pdfium.patch b/darwin/pdfium/patches/macos/pdfium.patch index ef254320..53ac1c23 100644 --- a/darwin/pdfium/patches/macos/pdfium.patch +++ b/darwin/pdfium/patches/macos/pdfium.patch @@ -1,13 +1,13 @@ diff --git a/public/fpdfview.h b/public/fpdfview.h -index b374088b4..b1c896e26 100644 +index d96556b47..79b6f3ebb 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h -@@ -214,7 +214,7 @@ typedef int FPDF_OBJECT_TYPE; +@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(WIN32) #else - #if defined(FPDF_IMPLEMENTATION) --#define FPDF_EXPORT __attribute__((visibility("default"))) +-#define FPDF_EXPORT +#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #else - #define FPDF_EXPORT - #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(COMPONENT_BUILD) + + #if defined(WIN32) && defined(FPDFSDK_EXPORTS) diff --git a/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada48..e3773d42 100644 --- a/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> From 0053f3a0e8f97981080dd4beac368d25a370d2fb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 16:59:52 +0900 Subject: [PATCH 086/663] WIP: pdfium-apple-v10-exp4 --- .github/workflows/pdfium-apple-release.yml | 31 ++++++++-- darwin/pdfium/build | 67 +++++++++++++--------- darwin/pdfrx.podspec | 16 +++--- example/viewer/ios/Podfile.lock | 4 +- example/viewer/macos/Podfile.lock | 4 +- 5 files changed, 78 insertions(+), 44 deletions(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index c0dac841..12b82fdd 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -6,11 +6,34 @@ on: jobs: build: runs-on: macos-latest + strategy: + matrix: + target: [ios, macos] steps: - name: Checkout uses: actions/checkout@v2 - - name: Build PDFium - run: ./darwin/pdfium/build + - name: Build PDFium ${{ matrix.target }} + run: ./darwin/pdfium/build ${{ matrix.target }} + - name: Upload PDFium artifact + uses: actions/upload-artifact@v3 + with: + name: pdfium-${{ matrix.target }} + path: ./darwin/pdfium/pdfium-${{ matrix.target }}.zip + + release: + needs: build + runs-on: macos-latest + steps: + - name: Download iOS artifact + uses: actions/download-artifact@v3 + with: + name: pdfium-ios + path: ./darwin/pdfium/ + - name: Download macOS artifact + uses: actions/download-artifact@v3 + with: + name: pdfium-macos + path: ./darwin/pdfium/ - name: Release PDFium uses: softprops/action-gh-release@v1 with: @@ -20,5 +43,5 @@ jobs: prerelease: false body: iOS/macOS PDFium prebuilt binary distribution for pdfrx (${{ github.ref_name }}). files: | - ./darwin/pdfium/pdfium-ios.tgz - ./darwin/pdfium/pdfium-macos.tgz + ./darwin/pdfium/pdfium-ios.zip + ./darwin/pdfium/pdfium-macos.zip diff --git a/darwin/pdfium/build b/darwin/pdfium/build index 1aa3940d..efa07305 100755 --- a/darwin/pdfium/build +++ b/darwin/pdfium/build @@ -3,32 +3,43 @@ SCRIPT_DIR=$(cd $(dirname $0) && pwd) cd $SCRIPT_DIR -# for iOS/iPhoneSimulator -if [[ ! -d ios/pdfium.xcframework ]]; then - ./build-config.sh ios arm64 - ./build-config.sh iossim arm64 - ./build-config.sh iossim x64 - - mkdir -p .tmp/out/lib/iossim-release - lipo -create .tmp/out/lib/iossim-arm64-release/libpdfium.a .tmp/out/lib/iossim-x64-release/libpdfium.a -output .tmp/out/lib/iossim-release/libpdfium.a - - mkdir -p ios/ - xcodebuild -create-xcframework -library .tmp/out/lib/ios-arm64-release/libpdfium.a -headers .tmp/out/include -library .tmp/out/lib/iossim-release/libpdfium.a -headers .tmp/out/include -output ios/pdfium.xcframework - - tar -czvf pdfium-ios.tgz ios -fi - -# for macOS -if [[ ! -d macos/pdfium.xcframework ]]; then - ./build-config.sh macos arm64 - ./build-config.sh macos x64 - - mkdir -p .tmp/out/lib/macos-release - lipo -create .tmp/out/lib/macos-arm64-release/libpdfium.a .tmp/out/lib/macos-x64-release/libpdfium.a -output .tmp/out/lib/macos-release/libpdfium.a - - mkdir -p macos/ - rm -rf macos/pdfium.xcframework - xcodebuild -create-xcframework -library .tmp/out/lib/macos-release/libpdfium.a -headers .tmp/out/include -output macos/pdfium.xcframework - - tar -czvf pdfium-macos.tgz macos +build_ios() { + if [[ ! -d ios/pdfium.xcframework ]]; then + ./build-config.sh ios arm64 + ./build-config.sh iossim arm64 + ./build-config.sh iossim x64 + + mkdir -p .tmp/out/lib/iossim-release + lipo -create .tmp/out/lib/iossim-arm64-release/libpdfium.a .tmp/out/lib/iossim-x64-release/libpdfium.a -output .tmp/out/lib/iossim-release/libpdfium.a + + mkdir -p ios/ + xcodebuild -create-xcframework -library .tmp/out/lib/ios-arm64-release/libpdfium.a -headers .tmp/out/include -library .tmp/out/lib/iossim-release/libpdfium.a -headers .tmp/out/include -output ios/pdfium.xcframework + + zip -r pdfium-ios.zip ios + fi +} + +build_macos() { + if [[ ! -d macos/pdfium.xcframework ]]; then + ./build-config.sh macos arm64 + ./build-config.sh macos x64 + + mkdir -p .tmp/out/lib/macos-release + lipo -create .tmp/out/lib/macos-arm64-release/libpdfium.a .tmp/out/lib/macos-x64-release/libpdfium.a -output .tmp/out/lib/macos-release/libpdfium.a + + mkdir -p macos/ + rm -rf macos/pdfium.xcframework + xcodebuild -create-xcframework -library .tmp/out/lib/macos-release/libpdfium.a -headers .tmp/out/include -output macos/pdfium.xcframework + + zip -r pdfium-macos.zip macos + fi +} + +if [[ "$1" == "macos" ]]; then + build_macos +elif [[ "$1" == "ios" ]]; then + build_ios +else + build_ios + build_macos fi diff --git a/darwin/pdfrx.podspec b/darwin/pdfrx.podspec index ba08aa0f..4487e511 100644 --- a/darwin/pdfrx.podspec +++ b/darwin/pdfrx.podspec @@ -2,11 +2,11 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint pdfrx.podspec` to validate before publishing. # -lib_tag = 'pdfium-apple-v9' +lib_tag = 'pdfium-apple-v10-exp4' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.3' + s.version = '0.0.4' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. @@ -36,17 +36,17 @@ Pod::Spec.new do |s| s.prepare_command = <<-CMD mkdir -p pdfium/.lib/#{lib_tag} cd pdfium/.lib/#{lib_tag} - if [ ! -f ios.tgz ]; then - curl -Lo ios.tgz https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.tgz + if [ ! -f ios.zip ]; then + curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip fi if [ ! -d ios ]; then - tar xzf ios.tgz + unzip -o ios.zip fi - if [ ! -f macos.tgz ]; then - curl -Lo macos.tgz https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.tgz + if [ ! -f macos.zip ]; then + curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip fi if [ ! -d macos ]; then - tar xzf macos.tgz + unzip -o macos.zip fi CMD diff --git a/example/viewer/ios/Podfile.lock b/example/viewer/ios/Podfile.lock index 1777a260..65721a1b 100644 --- a/example/viewer/ios/Podfile.lock +++ b/example/viewer/ios/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.3): + - pdfrx (0.0.4): - Flutter - FlutterMacOS - url_launcher_ios (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 07fc287c47ea8d027c4ed56457f8a1aa74d73594 + pdfrx: a8f2f736a6d80c10e35d49b0aec89f1c5a23d17f url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/example/viewer/macos/Podfile.lock b/example/viewer/macos/Podfile.lock index c8757c23..726ae108 100644 --- a/example/viewer/macos/Podfile.lock +++ b/example/viewer/macos/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.3): + - pdfrx (0.0.4): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 07fc287c47ea8d027c4ed56457f8a1aa74d73594 + pdfrx: a8f2f736a6d80c10e35d49b0aec89f1c5a23d17f url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 From fd15cba70140d8a486bf8b9a739b4c70a4ed4727 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 17:07:19 +0900 Subject: [PATCH 087/663] WIP --- .github/workflows/pdfium-apple-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 12b82fdd..a9db0fbe 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -15,7 +15,7 @@ jobs: - name: Build PDFium ${{ matrix.target }} run: ./darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pdfium-${{ matrix.target }} path: ./darwin/pdfium/pdfium-${{ matrix.target }}.zip @@ -25,12 +25,12 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pdfium-ios path: ./darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pdfium-macos path: ./darwin/pdfium/ From 3059d71ad160a253b23d2ffa3a1ccc48b117e81b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 17:27:08 +0900 Subject: [PATCH 088/663] WIP: working with pdfium-apple-v10-exp5 --- darwin/pdfrx.podspec | 4 ++-- example/viewer/macos/Podfile.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/darwin/pdfrx.podspec b/darwin/pdfrx.podspec index 4487e511..583c858a 100644 --- a/darwin/pdfrx.podspec +++ b/darwin/pdfrx.podspec @@ -2,11 +2,11 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint pdfrx.podspec` to validate before publishing. # -lib_tag = 'pdfium-apple-v10-exp4' +lib_tag = 'pdfium-apple-v10-exp5' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.4' + s.version = '0.0.5' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. diff --git a/example/viewer/macos/Podfile.lock b/example/viewer/macos/Podfile.lock index 726ae108..3a0085ec 100644 --- a/example/viewer/macos/Podfile.lock +++ b/example/viewer/macos/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.4): + - pdfrx (0.0.5): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: a8f2f736a6d80c10e35d49b0aec89f1c5a23d17f + pdfrx: e66aed16abf6488c2474de0b0e8d42b2a4aea137 url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 From f816ce588f910db838457218e2d7293a6498ce34 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 17:28:10 +0900 Subject: [PATCH 089/663] tag: pdfium-apple-v10 --- darwin/pdfrx.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darwin/pdfrx.podspec b/darwin/pdfrx.podspec index 583c858a..4ce16824 100644 --- a/darwin/pdfrx.podspec +++ b/darwin/pdfrx.podspec @@ -2,7 +2,7 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint pdfrx.podspec` to validate before publishing. # -lib_tag = 'pdfium-apple-v10-exp5' +lib_tag = 'pdfium-apple-v10' Pod::Spec.new do |s| s.name = 'pdfrx' From a1cbb0283bc81e9d782b7c549ab2eb8f333a1ddb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 18:54:08 +0900 Subject: [PATCH 090/663] SwiftPM support --- .gitignore | 3 +- .vscode/settings.json | 1 + darwin/pdfrx.podspec | 6 +-- darwin/pdfrx/Package.swift | 35 ++++++++++++++++ darwin/pdfrx/Sources/pdfrx/include/.gitkeep | 0 darwin/pdfrx/Sources/pdfrx/pdfrx.cpp | 3 ++ example/viewer/ios/Podfile.lock | 26 ------------ .../ios/Runner.xcodeproj/project.pbxproj | 40 ++++++++++--------- .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++++++++ example/viewer/macos/Podfile.lock | 26 ------------ .../macos/Runner.xcodeproj/project.pbxproj | 40 ++++++++++--------- .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++++++++ lib/src/pdfium/pdfium_interop.dart | 14 ++++++- lib/src/pdfium/pdfrx_pdfium.dart | 11 ++++- 14 files changed, 146 insertions(+), 95 deletions(-) create mode 100644 darwin/pdfrx/Package.swift create mode 100644 darwin/pdfrx/Sources/pdfrx/include/.gitkeep create mode 100644 darwin/pdfrx/Sources/pdfrx/pdfrx.cpp diff --git a/.gitignore b/.gitignore index 09d5833d..308b693f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,8 @@ migrate_working_dir/ .cxx # For pdfium builds on macOS -darwin/pdfrx/.build/ +.build/ +.swiftpm/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line diff --git a/.vscode/settings.json b/.vscode/settings.json index c246cc95..ef5e5c0d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -175,6 +175,7 @@ "sublist", "Subrect", "supercedes", + "swiftpm", "SYSTEMTIME", "TEXTONLY", "TEXTPAGE", diff --git a/darwin/pdfrx.podspec b/darwin/pdfrx.podspec index 4ce16824..dcf206af 100644 --- a/darwin/pdfrx.podspec +++ b/darwin/pdfrx.podspec @@ -6,7 +6,7 @@ lib_tag = 'pdfium-apple-v10' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.5' + s.version = '0.0.6' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'pdfrx/Sources/pdfrx/**/*' s.ios.deployment_target = '12.0' s.ios.dependency 'Flutter' @@ -27,7 +27,7 @@ Pod::Spec.new do |s| 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', } - s.osx.deployment_target = '10.11' + s.osx.deployment_target = '10.13' s.osx.dependency 'FlutterMacOS' s.osx.private_header_files = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework/macos-arm64_x86_64/Headers/*.h" s.osx.vendored_frameworks = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework" diff --git a/darwin/pdfrx/Package.swift b/darwin/pdfrx/Package.swift new file mode 100644 index 00000000..3259deef --- /dev/null +++ b/darwin/pdfrx/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version:5.6 +import PackageDescription + +let package = Package( + name: "pdfrx", + platforms: [ + .iOS(.v11), + .macOS(.v10_11) + ], + products: [ + .library( + name: "pdfrx", + targets: ["pdfrx"] + ), + ], + targets: [ + .target( + name: "pdfrx", + dependencies: [ + .target(name: "pdfium", condition: .when(platforms: [.iOS])), + .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])) + ] + ), + .binaryTarget( + name: "pdfium", + url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v10/pdfium-ios.zip", + checksum: "d716939a98f8a27a84eb463e62aee91c42a8f11ab50d49f4698c56195b728727" + ), + .binaryTarget( + name: "pdfium-macos", + url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v10/pdfium-macos.zip", + checksum: "dd7d79041554c1dafe24008cb7d5c4f3a18977953ef38fa8756338fa2b7bd9ab" + ) + ] +) diff --git a/darwin/pdfrx/Sources/pdfrx/include/.gitkeep b/darwin/pdfrx/Sources/pdfrx/include/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp b/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp new file mode 100644 index 00000000..815fc1a3 --- /dev/null +++ b/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp @@ -0,0 +1,3 @@ +// Relative import to be able to reuse the C sources. +// See the comment in ../{projectName}}.podspec for more information. +#include "../../../../src/pdfium_interop.cpp" diff --git a/example/viewer/ios/Podfile.lock b/example/viewer/ios/Podfile.lock index 65721a1b..a16608d4 100644 --- a/example/viewer/ios/Podfile.lock +++ b/example/viewer/ios/Podfile.lock @@ -1,41 +1,15 @@ PODS: - - file_selector_ios (0.0.1): - - Flutter - Flutter (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.0.4): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter DEPENDENCIES: - - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: - file_selector_ios: - :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - pdfrx: - :path: ".symlinks/plugins/pdfrx/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: a8f2f736a6d80c10e35d49b0aec89f1c5a23d17f - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 405dbda5..39b093b9 100644 --- a/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -80,6 +81,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, BE975C22D15CF1BBE8071C10 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -197,13 +199,15 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - F6C1F0DECDC2C464FB14B588 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -237,6 +241,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -344,23 +351,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - F6C1F0DECDC2C464FB14B588 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -731,6 +721,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d42..c3fedb29 100644 --- a/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - D724C1B933D3A4A79B1A27D3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -786,6 +776,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6228ad48..56aa5ca6 100644 --- a/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + ( diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 2e2d98c9..eaf0d3f1 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -43,8 +43,17 @@ String _getModuleFileName() { throw UnsupportedError('Unsupported platform'); } +DynamicLibrary _getModule() { + try { + return DynamicLibrary.open(_getModuleFileName()); + } catch (e) { + // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) + return DynamicLibrary.process(); + } +} + /// Loaded PDFium module. -final pdfium = pdfium_bindings.pdfium(DynamicLibrary.open(_getModuleFileName())); +final pdfium = pdfium_bindings.pdfium(_getModule()); bool _initialized = false; From e8bc4dc8bf233b31bdfedd15c3da2d9f8bb0000c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 23:33:35 +0900 Subject: [PATCH 091/663] 1.1.31 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad414369..b2bf0ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.1.31 + +- SwiftPM support for iOS/macOS +- PDFium 138.0.7202.0 +- FIXED: null assertion exception when laying out view and calculateCurrentPageNumber is overridden (#367) + # 1.1.30 - MERGED: PR #364 fix: blank pdf on Windows when restore window from minimize diff --git a/README.md b/README.md index bfbbc6f2..0fbe139c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.30 + pdfrx: ^1.1.31 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index d7430c52..15c925a8 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.30" + version: "1.1.31" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9a62552e..67bce5cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.30 +version: 1.1.31 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 80f4c96e2edc666e5eef989248b9fb29979bdd09 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 29 May 2025 23:42:12 +0900 Subject: [PATCH 092/663] Remove unused file --- darwin/Classes/pdfrx.cpp | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 darwin/Classes/pdfrx.cpp diff --git a/darwin/Classes/pdfrx.cpp b/darwin/Classes/pdfrx.cpp deleted file mode 100644 index 2e0ee1fb..00000000 --- a/darwin/Classes/pdfrx.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Relative import to be able to reuse the C sources. -// See the comment in ../{projectName}}.podspec for more information. -#include "../../src/pdfium_interop.cpp" From 48509b0f9b6f121b31ed35a2d782e250fb7af290 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 30 May 2025 12:20:07 +0900 Subject: [PATCH 093/663] 1.1.32 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- lib/src/widgets/pdf_viewer.dart | 1 - pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2bf0ec1..8c29f0a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.32 + +- Minor fixes + # 1.1.31 - SwiftPM support for iOS/macOS diff --git a/README.md b/README.md index 0fbe139c..c5b1dac1 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.31 + pdfrx: ^1.1.32 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 15c925a8..1439de9c 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.31" + version: "1.1.32" pdfrx_wasm: dependency: "direct main" description: diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 35d4f470..eae72897 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -568,7 +568,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix int? _guessCurrentPageNumber() { if (_layout == null || _viewSize == null) return null; - if (widget.params.calculateCurrentPageNumber != null) { return widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); } diff --git a/pubspec.yaml b/pubspec.yaml index 67bce5cf..b4c0bc87 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.31 +version: 1.1.32 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From d892fc10284ba9659fd63897b5e7ae6ced886ea2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 31 May 2025 00:25:06 +0900 Subject: [PATCH 094/663] #365 explicitly specify 16K page size rather than specifying NDK version --- android/CMakeLists.txt | 2 ++ example/viewer/android/app/build.gradle | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 63ebf84d..23349cd4 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -76,4 +76,6 @@ set(pdfrx_bundled_libraries ) target_include_directories(pdfrx PRIVATE ${PDFIUM_LATEST_DIR}/include) +# Support 16KB page size (#365) +target_link_options(pdfrx PRIVATE "-Wl,-z,max-page-size=16384") target_link_libraries(pdfrx PRIVATE ${PDFIUM_LATEST_LIB_FILENAME}) diff --git a/example/viewer/android/app/build.gradle b/example/viewer/android/app/build.gradle index 85728c42..e0edd28d 100644 --- a/example/viewer/android/app/build.gradle +++ b/example/viewer/android/app/build.gradle @@ -9,8 +9,7 @@ android { namespace "jp.espresso3389.pdfrx_example" compileSdkVersion flutter.compileSdkVersion - // To support 16 KB pages, we need to set the NDK version to 28 or higher. - ndkVersion = "28.1.13356709" + ndkVersion = android.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_18 From 1c311dc9b41d647dbda74b4b38543400930c5d99 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 31 May 2025 00:26:51 +0900 Subject: [PATCH 095/663] 1.1.33 --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c29f0a1..27aee0a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.33 + +- Explicitly specify 16KB page size on Android rather than specifying specific NDK version + # 1.1.32 - Minor fixes diff --git a/README.md b/README.md index c5b1dac1..867f9b57 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.32 + pdfrx: ^1.1.33 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 1439de9c..a80bab87 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.32" + version: "1.1.33" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b4c0bc87..a6d4cc20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.32 +version: 1.1.33 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From c53b15a35b00907bfd324e4a0ce1c92129a24d0d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 31 May 2025 11:21:03 +0900 Subject: [PATCH 096/663] WIP --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 867f9b57..1656636c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ A [demo site](https://espresso3389.github.io/pdfrx/) using Flutter Web - Windows - macOS - Linux (even on Raspberry PI) -- Web (\*using [PDF.js](https://mozilla.github.io/pdf.js/)) or Pdfium WASM (\*experimental) +- Web + - By default, pdfrx uses [PDF.js](https://mozilla.github.io/pdf.js/) but you can enable [Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support) ## Example Code @@ -62,11 +63,11 @@ dependencies: The build process internally uses *symbolic links* and it requires Developer Mode to be enabled. Without this, you may encounter errors [like this](https://github.com/espresso3389/pdfrx/issues/34). -### Pdfium WASM support on Web +## Customizations/Features -pdfrx now supports Pdfium WASM on Web, for more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). +You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). -### Deal with Password Protected PDF Files +## Deal with Password Protected PDF Files ```dart PdfViewer.asset( @@ -78,11 +79,7 @@ PdfViewer.asset( ), ``` -For more customization and considerations, see [Deal with Password Protected PDF Files using PasswordProvider](https://github.com/espresso3389/pdfrx/wiki/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider). - -## Customizations/Features - -You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). +See [Deal with Password Protected PDF Files using PasswordProvider](https://github.com/espresso3389/pdfrx/wiki/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider) for more information. ### Text Selection From 3498e1002eba16570dca307a16ef688a1467aace Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 4 Jun 2025 16:42:16 +0900 Subject: [PATCH 097/663] WIP --- lib/src/widgets/pdf_page_text_overlay.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/src/widgets/pdf_page_text_overlay.dart b/lib/src/widgets/pdf_page_text_overlay.dart index bd99cead..4a946cbf 100644 --- a/lib/src/widgets/pdf_page_text_overlay.dart +++ b/lib/src/widgets/pdf_page_text_overlay.dart @@ -222,17 +222,9 @@ class _PdfTextRenderBox extends RenderBox with PdfPageTextSelectable, Selectable } @override - bool get sizedByParent => true; - @override - double computeMinIntrinsicWidth(double height) => _pageRect.size.width; - @override - double computeMaxIntrinsicWidth(double height) => _pageRect.size.width; - @override - double computeMinIntrinsicHeight(double width) => _pageRect.size.height; - @override - double computeMaxIntrinsicHeight(double width) => _pageRect.size.height; - @override - Size computeDryLayout(BoxConstraints constraints) => constraints.constrain(_pageRect.size); + void performLayout() { + size = _textWidget._state.widget.pageRect.size; + } @override void addListener(VoidCallback listener) => _geometry.addListener(listener); From c8ea182800fc17e80d4e07dc91a0f7d3961bf3bf Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:48:16 +0900 Subject: [PATCH 098/663] Clarify password provider comment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1656636c..af29d9c2 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ You can customize the behaviors and the viewer look and feel by configuring [Pdf ```dart PdfViewer.asset( 'assets/test.pdf', - // Most easiest way to return some password + // The easiest way to supply a password passwordProvider: () => 'password', ... From f94738ade156cee23bf12581fe6f195afc2b2f4b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:48:39 +0900 Subject: [PATCH 099/663] Allow tests to use existing PDFium --- test/setup.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/setup.dart b/test/setup.dart index ba9238f1..0db128c5 100644 --- a/test/setup.dart +++ b/test/setup.dart @@ -1,3 +1,5 @@ +// Tests can skip PDFium download by setting the `PDFIUM_PATH` environment +// variable to an existing module file. import 'dart:io'; import 'package:archive/archive_io.dart'; @@ -12,7 +14,12 @@ final cacheRoot = Directory('${tmpRoot.path}/cache'); /// Sets up the test environment. Future setup() async { - Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath(); + final envPath = Platform.environment['PDFIUM_PATH']; + if (envPath != null && await File(envPath).exists()) { + Pdfrx.pdfiumModulePath = envPath; + } else { + Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath(); + } TestWidgetsFlutterBinding.ensureInitialized(); From f6847fd0f93a12826a4c17994cc56da656ec36a4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:48:58 +0900 Subject: [PATCH 100/663] Clarify password provider comment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1656636c..af29d9c2 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ You can customize the behaviors and the viewer look and feel by configuring [Pdf ```dart PdfViewer.asset( 'assets/test.pdf', - // Most easiest way to return some password + // The easiest way to supply a password passwordProvider: () => 'password', ... From 601c50c6338f96715b16a44c1749369b8fe363c5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:50:40 +0900 Subject: [PATCH 101/663] Fix ArgumentError parameter name in PdfRect --- lib/src/pdf_api.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 4730617d..a1e688cd 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -842,7 +842,7 @@ class PdfRect { case 3: return PdfRect(height - top, right, height - bottom, left); default: - throw ArgumentError.value(rotate, 'rotate'); + throw ArgumentError.value(rotation, 'rotation'); } } @@ -861,7 +861,7 @@ class PdfRect { case 3: return PdfRect(bottom, height - left, top, height - right); default: - throw ArgumentError.value(rotate, 'rotate'); + throw ArgumentError.value(rotation, 'rotation'); } } From cdfe864789b80599c11489b07754f0b2fcaca2d8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:51:17 +0900 Subject: [PATCH 102/663] fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27aee0a1..299bdba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,7 @@ # 1.1.13 -- Fix indefinite stack on loading PDF files from certain server; now it immediately return error (not actually fixed) (#311) +- Fix indefinite stuck on loading PDF files from certain server; now it immediately return error (not actually fixed) (#311) - FIXED: 2nd time loading of certain URL fails due to some cache error (#330) # 1.1.12 From a6a262e4c4ac11ab37935ebe2b5454bb8768aabc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 6 Jun 2025 00:53:01 +0900 Subject: [PATCH 103/663] fix: preserve null max-age in cache control --- lib/src/pdfium/http_cache_control.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/pdfium/http_cache_control.dart b/lib/src/pdfium/http_cache_control.dart index 5dcec22f..1bedc7e7 100644 --- a/lib/src/pdfium/http_cache_control.dart +++ b/lib/src/pdfium/http_cache_control.dart @@ -195,7 +195,7 @@ class HttpCacheControlState { int get hashCode => cacheControl.hashCode ^ date.hashCode ^ expires.hashCode ^ etag.hashCode ^ lastModified.hashCode; } -int _parseInt(String s) => s == 'null' ? 0 : int.parse(s); +int? _parseInt(String s) => s == 'null' ? null : int.parse(s); DateTime? _parseDateTime(String s) => s == 'null' ? null : DateTime.fromMillisecondsSinceEpoch(int.parse(s) * 1000); From 8f4a41c6f3ad1e1212ab43c7db1aaaeeaad1c464 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 9 Jun 2025 17:19:50 +0900 Subject: [PATCH 104/663] Add CLAUDE.md --- .gitignore | 1 - CLAUDE.md | 107 ++++++++++++++++++++++++++++ lib/src/pdfium/pdfium_bindings.dart | 96 ------------------------- pubspec.yaml | 16 +++-- 4 files changed, 116 insertions(+), 104 deletions(-) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 308b693f..7ead85d9 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,3 @@ build/ /test/.tmp -CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..412d717c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,107 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +pdfrx is a cross-platform PDF viewer plugin for Flutter that supports iOS, Android, Windows, macOS, Linux, and Web. It uses PDFium for native platforms and supports both PDF.js and PDFium WASM for web platforms. + +## Development Commands + +### Basic Flutter Commands +```bash +flutter pub get # Install dependencies +flutter analyze # Run static analysis +flutter test # Run all tests +flutter format . # Format code (120 char line width) +``` + +### Platform-Specific Builds +```bash +# Example app +cd example/viewer +flutter run # Run on connected device/emulator +flutter build apk # Build Android APK +flutter build ios # Build iOS (requires macOS) +flutter build web # Build for web +``` + +### FFI Bindings Generation + +- FFI bindings for PDFium are generated using `ffigen`. +- FFI bindings depends on the Pdfium headers installed on `example/viewer/build/linux/x64/release/.lib/latest/include` + - The headers are downloaded automatically during the build process; `flutter build linux` must be run at least once + +```bash +cd example/viewer && flutter build linux +dart run ffigen # Regenerate PDFium FFI bindings +``` + +## Architecture Overview + +### Platform Abstraction +The plugin uses conditional imports to support different platforms: +- `lib/src/pdfium/` - Native platform implementation using PDFium via FFI +- `lib/src/web/` - Web implementation supporting PDF.js (default) and PDFium WASM +- Platform-specific code determined at import time based on `dart:library.io` availability + +### Core Components + +1. **Document API** (`lib/src/pdf_api.dart`) + - `PdfDocument` - Main document interface + - `PdfPage` - Page representation + - `PdfDocumentRef` - Reference counting for document lifecycle + - Platform-agnostic interfaces implemented differently per platform + +2. **Widget Layer** (`lib/src/widgets/`) + - `PdfViewer` - Main viewer widget with multiple constructors + - `PdfPageView` - Single page display + - `PdfDocumentViewBuilder` - Safe document loading pattern + - Overlay widgets for text selection, links, search + +3. **Native Integration** + - Uses Flutter FFI for PDFium integration + - Native code in `src/pdfium_interop.cpp` + - Platform folders contain build configurations + +### Key Patterns + +- **Factory Pattern**: `PdfDocumentFactory` creates platform-specific implementations +- **Builder Pattern**: `PdfDocumentViewBuilder` for safe async document loading +- **Overlay System**: Composable overlays for text, links, annotations +- **Conditional Imports**: Web vs native determined at compile time + +## Testing + +Tests download PDFium binaries automatically for supported platforms. Run tests with: +```bash +flutter test +flutter test test/pdf_document_test.dart # Run specific test file +``` + +## Platform-Specific Notes + +### iOS/macOS +- Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) +- CocoaPods integration via `darwin/pdfrx.podspec` +- Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) + +### Android +- Uses CMake for native build +- Requires Android NDK +- Downloads PDFium binaries during build + +### Web +- Default: PDF.js for better compatibility +- Optional: PDFium WASM for better performance/compatibility + +### Windows/Linux +- CMake-based build +- Downloads PDFium binaries during build + +## Code Style + +- Single quotes for strings +- 120 character line width +- Relative imports within lib/ +- Follow flutter_lints with custom rules in analysis_options.yaml diff --git a/lib/src/pdfium/pdfium_bindings.dart b/lib/src/pdfium/pdfium_bindings.dart index e4b27714..661498e2 100644 --- a/lib/src/pdfium/pdfium_bindings.dart +++ b/lib/src/pdfium/pdfium_bindings.dart @@ -112,45 +112,6 @@ class pdfium { late final _FPDF_SetSandBoxPolicy = _FPDF_SetSandBoxPolicyPtr.asFunction(); - /// Experimental API. - /// Function: FPDF_SetPrintMode - /// Set printing mode when printing on Windows. - /// Parameters: - /// mode - FPDF_PRINTMODE_EMF to output EMF (default) - /// FPDF_PRINTMODE_TEXTONLY to output text only (for charstream - /// devices) - /// FPDF_PRINTMODE_POSTSCRIPT2 to output level 2 PostScript into - /// EMF as a series of GDI comments. - /// FPDF_PRINTMODE_POSTSCRIPT3 to output level 3 PostScript into - /// EMF as a series of GDI comments. - /// FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH to output level 2 - /// PostScript via ExtEscape() in PASSTHROUGH mode. - /// FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH to output level 3 - /// PostScript via ExtEscape() in PASSTHROUGH mode. - /// FPDF_PRINTMODE_EMF_IMAGE_MASKS to output EMF, with more - /// efficient processing of documents containing image masks. - /// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 to output level 3 - /// PostScript with embedded Type 42 fonts, when applicable, into - /// EMF as a series of GDI comments. - /// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH to output level - /// 3 PostScript with embedded Type 42 fonts, when applicable, - /// via ExtEscape() in PASSTHROUGH mode. - /// Return value: - /// True if successful, false if unsuccessful (typically invalid input). - int FPDF_SetPrintMode( - int mode, - ) { - return _FPDF_SetPrintMode( - mode, - ); - } - - late final _FPDF_SetPrintModePtr = - _lookup>( - 'FPDF_SetPrintMode'); - late final _FPDF_SetPrintMode = - _FPDF_SetPrintModePtr.asFunction(); - /// Function: FPDF_LoadDocument /// Open and load a PDF document. /// Parameters: @@ -721,56 +682,6 @@ class pdfium { int Function(FPDF_DOCUMENT, int, ffi.Pointer, ffi.Pointer)>(); - /// Function: FPDF_RenderPage - /// Render contents of a page to a device (screen, bitmap, or printer). - /// This function is only supported on Windows. - /// Parameters: - /// dc - Handle to the device context. - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// start_x - Left pixel position of the display area in - /// device coordinates. - /// start_y - Top pixel position of the display area in device - /// coordinates. - /// size_x - Horizontal size (in pixels) for displaying the page. - /// size_y - Vertical size (in pixels) for displaying the page. - /// rotate - Page orientation: - /// 0 (normal) - /// 1 (rotated 90 degrees clockwise) - /// 2 (rotated 180 degrees) - /// 3 (rotated 90 degrees counter-clockwise) - /// flags - 0 for normal display, or combination of flags - /// defined above. - /// Return value: - /// Returns true if the page is rendered successfully, false otherwise. - int FPDF_RenderPage( - HDC dc, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags, - ) { - return _FPDF_RenderPage( - dc, - page, - start_x, - start_y, - size_x, - size_y, - rotate, - flags, - ); - } - - late final _FPDF_RenderPagePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(HDC, FPDF_PAGE, ffi.Int, ffi.Int, ffi.Int, ffi.Int, - ffi.Int, ffi.Int)>>('FPDF_RenderPage'); - late final _FPDF_RenderPage = _FPDF_RenderPagePtr.asFunction< - int Function(HDC, FPDF_PAGE, int, int, int, int, int, int)>(); - /// Function: FPDF_RenderPageBitmap /// Render contents of a page to a device independent bitmap. /// Parameters: @@ -10780,13 +10691,6 @@ final class FPDF_COLORSCHEME_ extends ffi.Struct { /// Each should be a 32-bit value specifying the color, in 8888 ARGB format. typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; -final class HDC__ extends ffi.Struct { - @ffi.Int() - external int unused; -} - -typedef HDC = ffi.Pointer; - final class _IPDF_JsPlatform extends ffi.Struct { /// Version number of the interface. Currently must be 2. @ffi.Int() diff --git a/pubspec.yaml b/pubspec.yaml index a6d4cc20..e9ca18d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,20 +51,22 @@ flutter: ffiPlugin: true web: +# To generate the bindings, firstly you must build example/viewer on x64 linux and +# then run the following command: # dart run ffigen ffigen: output: bindings: "lib/src/pdfium/pdfium_bindings.dart" headers: entry-points: - - "example/viewer/build/windows/x64/.lib/latest/include/fpdfview.h" - - "example/viewer/build/windows/x64/.lib/latest/include/fpdf_annot.h" - - "example/viewer/build/windows/x64/.lib/latest/include/fpdf_text.h" - - "example/viewer/build/windows/x64/.lib/latest/include/fpdf_doc.h" - - "example/viewer/build/windows/x64/.lib/latest/include/fpdf_edit.h" - - "example/viewer/build/windows/x64/.lib/latest/include/fpdf_formfill.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdfview.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_annot.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_text.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_doc.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_edit.h" + - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_formfill.h" include-directives: - - "example/viewer/build/windows/x64/.lib/latest/include/**" + - "example/viewer/build/linux/x64/release/.lib/latest/include/**" preamble: | // ignore_for_file: unused_field // dart format off From 9ee3c2c92a2a148ba4b5b9aff23937479aa33a3d Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:36:24 +0000 Subject: [PATCH 105/663] feat: Add Windows ARM64 support - Replace hardcoded x64 architecture with dynamic detection - Support x64, x86, arm64, and arm architectures on Windows - Follow same pattern as Linux CMakeLists.txt for architecture detection - Enables Windows ARM64 builds by detecting ARM64/aarch64/ARM64EC processors Fixes #388 Co-authored-by: espresso3389 --- windows/CMakeLists.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index ec65e0dc..8932433a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -22,10 +22,24 @@ set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) file(MAKE_DIRECTORY ${PDFIUM_RELEASE_DIR}) +# Determine target processor name for Windows +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x64|x86_64|AMD64)") + SET(CPU_NAME "x64") +ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86)") + SET(CPU_NAME "x86") +ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|aarch64|ARM64EC|arm64ec|ARM64E|arm64e)") + SET(CPU_NAME "arm64") +ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM)") + SET(CPU_NAME "arm") +ELSE() + MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\"") +ENDIF() +message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) + set(PDFIUM_PLATFORM "win") set(PDFIUM_LIB_FILENAME "pdfium.dll") set(PDFIUM_LIB_DIR "bin") -set(PDFIUM_WIN_ABI "x64") +set(PDFIUM_WIN_ABI ${CPU_NAME}) set(PDFIUM_ARCHIVE_NAME pdfium-${PDFIUM_PLATFORM}-${PDFIUM_WIN_ABI}) set(PDFIUM_SRC_LIB_FILENAME ${PDFIUM_RELEASE_DIR}/${PDFIUM_LIB_DIR}/${PDFIUM_LIB_FILENAME}) From f002addd875b7fd83d7d901959ba684210728685 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:54:09 +0000 Subject: [PATCH 106/663] feat: Restrict Windows architecture support to x64 and arm64 only Remove support for x86 (32-bit Intel) and arm (32-bit ARM) architectures. Only x64 (AMD64) and arm64 (ARM64) are now supported on Windows. Co-authored-by: espresso3389 --- windows/CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 8932433a..a0a37e0c 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -25,14 +25,10 @@ file(MAKE_DIRECTORY ${PDFIUM_RELEASE_DIR}) # Determine target processor name for Windows IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x64|x86_64|AMD64)") SET(CPU_NAME "x64") -ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86)") - SET(CPU_NAME "x86") ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|aarch64|ARM64EC|arm64ec|ARM64E|arm64e)") SET(CPU_NAME "arm64") -ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM)") - SET(CPU_NAME "arm") ELSE() - MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\"") + MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only x64 (AMD64) and arm64 (ARM64) are supported.") ENDIF() message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) From f67ac480df358ffe6a89ba8ec2e8dd26346fa919 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:57:11 +0000 Subject: [PATCH 107/663] feat: Restrict Windows architecture support to x64 and arm64 only - Only accept AMD64 and ARM64 as valid CMAKE_SYSTEM_PROCESSOR values - Remove support for architecture aliases (x64, x86_64, aarch64, etc.) - Use exact string matching instead of regex patterns for strict validation Co-authored-by: espresso3389 --- windows/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index a0a37e0c..08fa46e8 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -23,12 +23,12 @@ set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) file(MAKE_DIRECTORY ${PDFIUM_RELEASE_DIR}) # Determine target processor name for Windows -IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x64|x86_64|AMD64)") +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") SET(CPU_NAME "x64") -ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|aarch64|ARM64EC|arm64ec|ARM64E|arm64e)") +ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") SET(CPU_NAME "arm64") ELSE() - MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only x64 (AMD64) and arm64 (ARM64) are supported.") + MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only AMD64 and ARM64 are supported.") ENDIF() message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) From 41f25651d72277a0589170b711c767a0c58a1555 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 9 Jun 2025 18:09:03 +0900 Subject: [PATCH 108/663] Format CHANGELOG.md --- CHANGELOG.md | 451 ++++++++++++++++++++++++++------------------------- 1 file changed, 229 insertions(+), 222 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 299bdba4..9fc4fb58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.1.34 + +- Add `CLAUDE.md` for Claude Code integration +- FIXED: preserve null `max-age` in cache control ([#387](https://github.com/espresso3389/pdfrx/pull/387)) +- FIXED: `ArgumentError` parameter name in `PdfRect` ([#385](https://github.com/espresso3389/pdfrx/pull/385)) +- Documentation updates and improvements + # 1.1.33 - Explicitly specify 16KB page size on Android rather than specifying specific NDK version @@ -10,47 +17,47 @@ - SwiftPM support for iOS/macOS - PDFium 138.0.7202.0 -- FIXED: null assertion exception when laying out view and calculateCurrentPageNumber is overridden (#367) +- FIXED: null assertion exception when laying out view and `calculateCurrentPageNumber` is overridden ([#367](https://github.com/espresso3389/pdfrx/issues/367)) # 1.1.30 -- MERGED: PR #364 fix: blank pdf on Windows when restore window from minimize -- Update example's app/build.gradle to support Android's 16KB page size +- MERGED: PR [#364](https://github.com/espresso3389/pdfrx/pull/364) fix: blank pdf on Windows when restore window from minimize +- Update example's `app/build.gradle` to support Android's 16KB page size # 1.1.29 - FIXED: [#363](https://github.com/espresso3389/pdfrx/issues/363) - - FIXED: pdfium-wasm-module-url on HTML meta tag overrides value explicitly set to Pdfrx.pdfiumWasmModulesUrl - - Improves pdfium_worker.js/pdfium.wasm loading path resolution logic to allow relative paths + - FIXED: `pdfium-wasm-module-url` on HTML meta tag overrides value explicitly set to `Pdfrx.pdfiumWasmModulesUrl` + - Improves `pdfium_worker.js`/`pdfium.wasm` loading path resolution logic to allow relative paths # 1.1.28 - WIP: zoom ratio calculation updates -- goToPage throws array index out of bounds error if the page number is out of range +- `goToPage` throws array index out of bounds error if the page number is out of range - PDFium WASM 138.0.7162.0 - Remove debug print # 1.1.27 -- Apply a proposed fix for #134; but I'm not sure if it works well or not. Personally, I don't feel any difference... +- Apply a proposed fix for [#134](https://github.com/espresso3389/pdfrx/issues/134); but I'm not sure if it works well or not. Personally, I don't feel any difference... # 1.1.26 -- Introduces PdfPoint, which work with Offset for conversion between PDF page coordinates and Flutter coordinates -- FIXED: #352 Link click/text selection are completely broken if PDF page is rotated +- Introduces `PdfPoint`, which work with `Offset` for conversion between PDF page coordinates and Flutter coordinates +- FIXED: [#352](https://github.com/espresso3389/pdfrx/issues/352) Link click/text selection are completely broken if PDF page is rotated # 1.1.25 -- FIXED: #350 callback onPageChanged no longer called? +- FIXED: [#350](https://github.com/espresso3389/pdfrx/issues/350) callback `onPageChanged` no longer called? # 1.1.24 -- FIXED: #336 zoom out does not cover entire page after changing layout +- FIXED: [#336](https://github.com/espresso3389/pdfrx/issues/336) zoom out does not cover entire page after changing layout - Updates to viewer example to support page layout switching - - Minor goToPage and other goTo functions behavior changes (normalizeMatrix and other) -- MERGED: PR #349 that fixes resource leaks on PdfPageView -- FIXED: #215 Wrong link highlight position on searching a word -- FIXED: #344 New "key event handling" feature in version 1.1.22 prevents TextFormField in page overlay from receiving key events + - Minor `goToPage` and other `goTo` functions behavior changes (`normalizeMatrix` and other) +- MERGED: PR [#349](https://github.com/espresso3389/pdfrx/pull/349) that fixes resource leaks on `PdfPageView` +- FIXED: [#215](https://github.com/espresso3389/pdfrx/issues/215) Wrong link highlight position on searching a word +- FIXED: [#344](https://github.com/espresso3389/pdfrx/issues/344) New "key event handling" feature in version 1.1.22 prevents `TextFormField` in page overlay from receiving key events # 1.1.23 @@ -58,13 +65,13 @@ # 1.1.22 -- PdfDocumentFactory refactoring to improve the code integrity - - Introduces getDocumentFactory/getPdfjsDocumentFactory/getPdffiumDocumentFactory to get the direct/internal document factory -- Introduces PdfViewerParams.onKey/PdfViewerKeyHandlerParams to handle key events on PdfViewer +- `PdfDocumentFactory` refactoring to improve the code integrity + - Introduces `getDocumentFactory`/`getPdfjsDocumentFactory`/`getPdffiumDocumentFactory` to get the direct/internal document factory +- Introduces `PdfViewerParams.onKey`/`PdfViewerKeyHandlerParams` to handle key events on `PdfViewer` # 1.1.21 -- FIXED: loadOutline is not implemented on Pdfium WASM +- FIXED: `loadOutline` is not implemented on Pdfium WASM # 1.1.20 @@ -76,7 +83,7 @@ # 1.1.18 -- Merge PR #338 from mtallenca/cache_expired_not_modified_fix +- Merge PR [#338](https://github.com/espresso3389/pdfrx/pull/338) from mtallenca/cache_expired_not_modified_fix # 1.1.17 @@ -84,20 +91,20 @@ # 1.1.14 -- Improve pdfium_worker.js/pdfium.wasm loading path resolution logic (#331) +- Improve `pdfium_worker.js`/`pdfium.wasm` loading path resolution logic ([#331](https://github.com/espresso3389/pdfrx/issues/331)) # 1.1.13 -- Fix indefinite stuck on loading PDF files from certain server; now it immediately return error (not actually fixed) (#311) -- FIXED: 2nd time loading of certain URL fails due to some cache error (#330) +- Fix indefinite stuck on loading PDF files from certain server; now it immediately return error (not actually fixed) ([#311](https://github.com/espresso3389/pdfrx/issues/311)) +- FIXED: 2nd time loading of certain URL fails due to some cache error ([#330](https://github.com/espresso3389/pdfrx/issues/330)) # 1.1.12 -- FIXED: WASM: could not open PDF files smaller than 1MB (#326) +- FIXED: WASM: could not open PDF files smaller than 1MB ([#326](https://github.com/espresso3389/pdfrx/issues/326)) # 1.1.11 -- Color.withOpacity -> Color.withValues, Color.value -> Color.toARGB32() +- `Color.withOpacity` -> `Color.withValues`, `Color.value` -> `Color.toARGB32()` # 1.1.10 @@ -106,7 +113,7 @@ # 1.1.9 -- Move back the example viewer to example directory +- Move back the example viewer to example directory # 1.1.8 @@ -114,8 +121,8 @@ # 1.1.7 -- Introducing allowDataOwnershipTransfer on PdfDocument.openData to allow transfer data ownership of the passed data; it is false by default to keep consistency with the previous behavior - - This actually fixes #303 but the drawback is that extra memory may be consumed on Flutter Web... +- Introducing `allowDataOwnershipTransfer` on `PdfDocument.openData` to allow transfer data ownership of the passed data; it is false by default to keep consistency with the previous behavior + - This actually fixes [#303](https://github.com/espresso3389/pdfrx/issues/303) but the drawback is that extra memory may be consumed on Flutter Web... # 1.1.6 @@ -123,7 +130,7 @@ # 1.1.5 -- Explicitly specify web support on pubspec.yaml +- Explicitly specify web support on `pubspec.yaml` # 1.1.4 @@ -141,39 +148,39 @@ # 1.1.1 -- Supporting Flutter 3.29.0/Dart 3.7.0 (Stable) with workaround for breaking changes on Flutter 3.29.0 (#295) +- Supporting Flutter 3.29.0/Dart 3.7.0 (Stable) with workaround for breaking changes on Flutter 3.29.0 ([#295](https://github.com/espresso3389/pdfrx/issues/295)) - It breaks compatibility with older stable Flutter versions :( # 1.0.103 -- Change the default CDN for pdf.js to `https://cdn.jsdelivr.net/npm/pdfjs-dist@/build/pdf.js` to deal with CORS error on loading CMAP files -- FIXED: pdfjsGetDocumentFromData, which is used by various PdfDocument open functions, does not propagate cMapUrl/cMapPacked to the pdf.js +- Change the default CDN for Pdf.js to `https://cdn.jsdelivr.net/npm/pdfjs-dist@/build/pdf.js` to deal with CORS error on loading CMAP files +- FIXED: `pdfjsGetDocumentFromData`, which is used by various `PdfDocument` open functions, does not propagate `cMapUrl`/`cMapPacked` to the Pdf.js # 1.0.102 - dart2wasm compatibility updates - Pdf.js 4.10.38 -- PdfTextSearcher correctly releases its listeners on dispose +- `PdfTextSearcher` correctly releases its listeners on dispose - Example viewer code updates # 1.0.101 -- Revert commit d66fb3f that breaks consistency; Color.withValues -> Color.withOpacity +- Revert commit d66fb3f that breaks consistency; `Color.withValues` -> `Color.withOpacity` - Update pdfium ffi bindings # 1.0.100 -- PdfTextSearcher introduces text caches (#293) -- PdfTextSearcher search reset issue (#291) +- `PdfTextSearcher` introduces text caches ([#293](https://github.com/espresso3389/pdfrx/issues/293)) +- `PdfTextSearcher` search reset issue ([#291](https://github.com/espresso3389/pdfrx/issues/291)) - collection's version spec. reverted to pre-1.0.95 # 1.0.99 -- Introduces Pdfrx.fontPaths to set pdfium font loading path (#140) +- Introduces `Pdfrx.fontPaths` to set pdfium font loading path ([#140](https://github.com/espresso3389/pdfrx/issues/140)) # 1.0.98 -- Introduces PdfViewerController.calcFitZoomMatrices to realize fit-to-width easier +- Introduces `PdfViewerController.calcFitZoomMatrices` to realize fit-to-width easier # 1.0.97 @@ -181,24 +188,24 @@ # 1.0.96 -- FIXED: #260 onTextSelectionChange callback cant be called +- FIXED: [#260](https://github.com/espresso3389/pdfrx/issues/260) `onTextSelectionChange` callback cant be called # 1.0.95 -- FIXED: #273; apart from the ream WASM support, it fixes several compilation issues with --wasm option +- FIXED: [#273](https://github.com/espresso3389/pdfrx/issues/273); apart from the ream WASM support, it fixes several compilation issues with `--wasm` option # 1.0.94 -- Merge PR #272; Fix minScale is not used +- Merge PR [#272](https://github.com/espresso3389/pdfrx/pull/272); Fix `minScale` is not used # 1.0.93 -- Merge PR #264; Check for non-existent zoom element in PdfDest.params in some PDFs -- FIXED: Widget tests starts to fail when using PdfViewer widget #263 +- Merge PR [#264](https://github.com/espresso3389/pdfrx/pull/264); Check for non-existent zoom element in `PdfDest.params` in some PDFs +- FIXED: Widget tests starts to fail when using `PdfViewer` widget [#263](https://github.com/espresso3389/pdfrx/issues/263) # 1.0.92 -- Merge PR #262; Remove redundant check that breaks building on some systems +- Merge PR [#262](https://github.com/espresso3389/pdfrx/pull/262); Remove redundant check that breaks building on some systems # 1.0.91 @@ -206,144 +213,144 @@ # 1.0.90 -- Introduces selectableRegionInjector/perPageSelectableRegionInjector (#256) +- Introduces `selectableRegionInjector`/`perPageSelectableRegionInjector` ([#256](https://github.com/espresso3389/pdfrx/issues/256)) # 1.0.89 -- web 1.1.0 support (#254) +- web 1.1.0 support ([#254](https://github.com/espresso3389/pdfrx/issues/254)) # 1.0.88 -- Merge PR #251 +- Merge PR [#251](https://github.com/espresso3389/pdfrx/pull/251) # 1.0.87 -- BREAKING CHANGE: add more parameters to PdfViewerParams.normalizeMatrix to make it easier to handle more complex situations (#239) +- BREAKING CHANGE: add more parameters to `PdfViewerParams.normalizeMatrix` to make it easier to handle more complex situations ([#239](https://github.com/espresso3389/pdfrx/issues/239)) # 1.0.86 -- Add PdfViewerParams.normalizeMatrix to customize the transform matrix restriction; customizing existing logic on _PdfViewerState._makeMatrixInSafeRange; for issues like #239 +- Add `PdfViewerParams.normalizeMatrix` to customize the transform matrix restriction; customizing existing logic on `_PdfViewerState._makeMatrixInSafeRange`; for issues like [#239](https://github.com/espresso3389/pdfrx/issues/239) # 1.0.85 -- Fixes single-page layout issue on viewer start (#247) -- Fixes blurry image issues (#245, #232) +- Fixes single-page layout issue on viewer start ([#247](https://github.com/espresso3389/pdfrx/issues/247)) +- Fixes blurry image issues ([#245](https://github.com/espresso3389/pdfrx/issues/245), [#232](https://github.com/espresso3389/pdfrx/issues/232)) # 1.0.84 -- Merge PR #230 to add try-catch on UTF-8 decoding of URI path +- Merge PR [#230](https://github.com/espresso3389/pdfrx/pull/230) to add try-catch on UTF-8 decoding of URI path # 1.0.83 - Web related improvements - - PDF.js 4.5.136 - - Remove dependency to dart:js_interop_unsafe + - Pdf.js 4.5.136 + - Remove dependency to `dart:js_interop_unsafe` - Remove unnecessary synchronized call -- Improve text selection stability (#4, #185) -- Add more mounted checks to improve PdfViewer stability and speed +- Improve text selection stability ([#4](https://github.com/espresso3389/pdfrx/issues/4), [#185](https://github.com/espresso3389/pdfrx/issues/185)) +- Add more mounted checks to improve `PdfViewer` stability and speed # 1.0.82 -- collection/rxdart dependency workaround (#211) +- collection/rxdart dependency workaround ([#211](https://github.com/espresso3389/pdfrx/issues/211)) # 1.0.81 -- Introduces PdfViewerController.useDocument to make it easy to use PdfDocument safely -- Introduces PdfViewerController.pageCount to get page count without explicitly access PdfViewerController.pages -- PdfViewerController.document/PdfViewerController.pages are now deprecated +- Introduces `PdfViewerController.useDocument` to make it easy to use `PdfDocument` safely +- Introduces `PdfViewerController.pageCount` to get page count without explicitly access `PdfViewerController.pages` +- `PdfViewerController.document`/`PdfViewerController.pages` are now deprecated # 1.0.80 -- BREAKING CHANGE: PdfViewerParams.viewerOverlayBuilder introduces third parameter named handleLinkTap, which is used with GestureDetector to handle link-tap events on user code (#175) -- Fix typos on README.md +- BREAKING CHANGE: `PdfViewerParams.viewerOverlayBuilder` introduces third parameter named `handleLinkTap`, which is used with `GestureDetector` to handle link-tap events on user code ([#175](https://github.com/espresso3389/pdfrx/issues/175)) +- Fix typos on `README.md` # 1.0.79 -- FIXED: RangeError on PdfViewer.uri when missing "Expires" header (#206) +- FIXED: `RangeError` on `PdfViewer.uri` when missing "Expires" header ([#206](https://github.com/espresso3389/pdfrx/issues/206)) # 1.0.78 -- Add packagingOptions pickFirst to workaround multiple libpdfium.so problem on Android build (#8) -- FIXED: \_relayoutPages may cause null access -- Update README.md to explain PdfViewerParam.linkHandlerParams for link handling +- Add `packagingOptions pickFirst` to workaround multiple `libpdfium.so` problem on Android build ([#8](https://github.com/espresso3389/pdfrx/issues/8)) +- FIXED: `_relayoutPages` may cause null access +- Update `README.md` to explain `PdfViewerParam.linkHandlerParams` for link handling # 1.0.77 -- #175: Woops, just missing synchronized to call loadLinks causes multiple load invocations... +- [#175](https://github.com/espresso3389/pdfrx/issues/175): Woops, just missing synchronized to call `loadLinks` causes multiple load invocations... # 1.0.76 -- Add several tweaks to reduce PdfLink's memory footprint (Related: #175) -- Introduces PdfViewerParam.linkHandlerParams and PdfLinkHandlerParams to show/handle PDF links without using Flutter Widgets (#175) +- Add several tweaks to reduce `PdfLink`'s memory footprint (Related: [#175](https://github.com/espresso3389/pdfrx/issues/175)) +- Introduces `PdfViewerParam.linkHandlerParams` and `PdfLinkHandlerParams` to show/handle PDF links without using Flutter Widgets ([#175](https://github.com/espresso3389/pdfrx/issues/175)) # 1.0.75 -- PDF.js 4.4.168 +- Pdf.js 4.4.168 # 1.0.74 -- Introduces PdfViewerController.getPdfPageHitTestResult -- Introduces PdfViewerController.layout to get page layout +- Introduces `PdfViewerController.getPdfPageHitTestResult` +- Introduces `PdfViewerController.layout` to get page layout # 1.0.73 -- Introduces PdfViewerParams.onViewSizeChanged, which is called on view size change - - The feature can be used to keep the screen center on device screen rotation (#194) +- Introduces `PdfViewerParams.onViewSizeChanged`, which is called on view size change + - The feature can be used to keep the screen center on device screen rotation ([#194](https://github.com/espresso3389/pdfrx/issues/194)) # 1.0.72 - FIXED: Example code is not compilable -- FIXED: Marker could not be placed correctly on the example code (#189) -- FIXED: Updated podspec file not to download the same archive again and again (#154) +- FIXED: Marker could not be placed correctly on the example code ([#189](https://github.com/espresso3389/pdfrx/issues/189)) +- FIXED: Updated podspec file not to download the same archive again and again ([#154](https://github.com/espresso3389/pdfrx/issues/154)) - Introduces chromium/6555 for all platforms - Darwin uses pdfium-apple-v9 (chromium/6555) - - ~~Improves memory consumption by pdfium's internal caching feature (#184)~~ + - ~~Improves memory consumption by pdfium's internal caching feature ([#184](https://github.com/espresso3389/pdfrx/issues/184))~~ # 1.0.71 -- Introduces withCredentials for Web to download PDF file using current session credentials (Cookie) (#182) -- FIXED: Re-download logic error that causes 416 on certain web site (#183) +- Introduces `withCredentials` for Web to download PDF file using current session credentials (Cookie) ([#182](https://github.com/espresso3389/pdfrx/issues/182)) +- FIXED: Re-download logic error that causes 416 on certain web site ([#183](https://github.com/espresso3389/pdfrx/issues/183)) # 1.0.70 -- PdfViewer calls re-layout logic on every zoom ratio changes (#131) -- Add PdfViewerParams.interactionEndFrictionCoefficient (#176) +- `PdfViewer` calls re-layout logic on every zoom ratio changes ([#131](https://github.com/espresso3389/pdfrx/issues/131)) +- Add `PdfViewerParams.interactionEndFrictionCoefficient` ([#176](https://github.com/espresso3389/pdfrx/issues/176)) - Minor fix for downloading cache -- rxdart gets back to 0.27.7 because 0.28.0 causes incompatibility with several other plugins... +- `rxdart` gets back to 0.27.7 because 0.28.0 causes incompatibility with several other plugins... # 1.0.69 -- FIXED: Small Page Size PDF Not Scaling to Fit Screen (#174) +- FIXED: Small Page Size PDF Not Scaling to Fit Screen ([#174](https://github.com/espresso3389/pdfrx/issues/174)) # 1.0.68 -- Introduces PdfViewerController.setCurrentPageNumber (#152) -- BREAKING CHANGE: Current page number behavior change (#152) -- BREAKING CHANGE: PdfPageAnchor behavior changes for existing PdfPageAnchor enumeration values. -- Introduces PdfPageAnchor.top/left/right/bottom -- Introduces PdfViewerController.calcMatrixToEnsureRectVisible +- Introduces `PdfViewerController.setCurrentPageNumber` ([#152](https://github.com/espresso3389/pdfrx/issues/152)) +- BREAKING CHANGE: Current page number behavior change ([#152](https://github.com/espresso3389/pdfrx/issues/152)) +- BREAKING CHANGE: `PdfPageAnchor` behavior changes for existing `PdfPageAnchor` enumeration values. +- Introduces `PdfPageAnchor.top`/`left`/`right`/`bottom` +- Introduces `PdfViewerController.calcMatrixToEnsureRectVisible` # 1.0.67 -- FIXED: LateInitializationError: Field '\_cacheBlockCount@1436474497' has not been initialized (#167) +- FIXED: `LateInitializationError`: Field `_cacheBlockCount@1436474497` has not been initialized ([#167](https://github.com/espresso3389/pdfrx/issues/167)) # 1.0.66 -- FIXED: PdfException: Failed to load PDF document (FPDF_GetLastError=3) (#166) +- FIXED: `PdfException`: Failed to load PDF document (`FPDF_GetLastError=3`) ([#166](https://github.com/espresso3389/pdfrx/issues/166)) - Add explicit HTTP error handling code (to show the error detail) - bblanchon/pdfium-binaries 127.0.6517.0 (chromium/6517) (iOS/macOS is still using 6406) # 1.0.65 -- Remove dependency to intl (#151) +- Remove dependency to `intl` ([#151](https://github.com/espresso3389/pdfrx/issues/151)) # 1.0.64 -- Android: minSdkVersion to 21 (related #158) +- Android: `minSdkVersion` to 21 (related [#158](https://github.com/espresso3389/pdfrx/issues/158)) # 1.0.63 -- Workaround for SelectionEventType.selectParagraph that is introduced in master (#156/PR #157) +- Workaround for `SelectionEventType.selectParagraph` that is introduced in master ([#156](https://github.com/espresso3389/pdfrx/issues/156)/PR [#157](https://github.com/espresso3389/pdfrx/pull/157)) - The code uses `default` to handle the case but we should update it with the "right" code when it is introduced to the stable # 1.0.62 @@ -354,48 +361,48 @@ # 1.0.61 -- Introduces PdfViewerParams.pageDropShadow -- Introduces PdfViewerParams.pageBackgroundPaintCallbacks +- Introduces `PdfViewerParams.pageDropShadow` +- Introduces `PdfViewerParams.pageBackgroundPaintCallbacks` # 1.0.60 - bblanchon/pdfium-binaries 125.0.6406.0 (chromium/6406) - - default_min_sdk_version=21 to support lower API level devices ([#145](https://github.com/espresso3389/pdfrx/issues/145)) + - `default_min_sdk_version=21` to support lower API level devices ([#145](https://github.com/espresso3389/pdfrx/issues/145)) # 1.0.59 -- Fixes concurrency issue on PdfDocument dispose (#143) -- FIXED: Null check operator used on \_guessCurrentPage ([#147](https://github.com/espresso3389/pdfrx/issues/147)) +- Fixes concurrency issue on `PdfDocument` dispose ([#143](https://github.com/espresso3389/pdfrx/issues/143)) +- FIXED: Null check operator used on `_guessCurrentPage` ([#147](https://github.com/espresso3389/pdfrx/issues/147)) # 1.0.58 - Any API calls that wraps PDFium are now completely synchronized. They are run in an app-wide single worker isolate - - This is because PDFium does not support any kind of concurrency and even different PdfDocument instances could not be called concurrently + - This is because PDFium does not support any kind of concurrency and even different `PdfDocument` instances could not be called concurrently # 1.0.57 -- FIXED: possible double-dispose on race condition (#136) -- Add mechanism to cancel partial real size rendering (#137) -- WIP: Custom HTTP header for downloading PDF files (#132) -- Text search match color customization (#142) +- FIXED: possible double-dispose on race condition ([#136](https://github.com/espresso3389/pdfrx/issues/136)) +- Add mechanism to cancel partial real size rendering ([#137](https://github.com/espresso3389/pdfrx/issues/137)) +- WIP: Custom HTTP header for downloading PDF files ([#132](https://github.com/espresso3389/pdfrx/issues/132)) +- Text search match color customization ([#142](https://github.com/espresso3389/pdfrx/issues/142)) # 1.0.56 - Reduce total number of Isolates used when opening PDF documents -- Add PdfViewerParams.calculateCurrentPageNumber -- FIXED: Could not handle certain destination coordinates correctly (#135) +- Add `PdfViewerParams.calculateCurrentPageNumber` +- FIXED: Could not handle certain destination coordinates correctly ([#135](https://github.com/espresso3389/pdfrx/issues/135)) # 1.0.55 -- Improve memory consumption by opening/closing page handle every time pdfrx need it (PR #125) +- Improve memory consumption by opening/closing page handle every time pdfrx need it (PR [#125](https://github.com/espresso3389/pdfrx/pull/125)) # 1.0.54 - Improves [End] button behavior to reach the actual end of document rather than the top of the last page - - PdfViewerParams.pageAnchorEnd for specifying anchor for the "virtual" page next to the last page -- PdfViewerParams.onePassRenderingScaleThreshold to specify maximum scale that is rendered in single rendering call + - `PdfViewerParams.pageAnchorEnd` for specifying anchor for the "virtual" page next to the last page +- `PdfViewerParams.onePassRenderingScaleThreshold` to specify maximum scale that is rendered in single rendering call - If a page is scaled over the threshold scale, the page is once rendered in the threshold scale and after a some delay, the real scaled image is rendered partially that fits in the view port -- PdfViewerParams.perPageSelectionAreaInjector is introduced to customize text selection behavior +- `PdfViewerParams.perPageSelectionAreaInjector` is introduced to customize text selection behavior # 1.0.53 @@ -404,17 +411,17 @@ # 1.0.52 -- Fixes memory consumption control issues (Related: #121) +- Fixes memory consumption control issues (Related: [#121](https://github.com/espresso3389/pdfrx/issues/121)) # 1.0.51 -- FIXED: memory leak on \_PdfPageViewState (#110) -- Remove dependency on dart:js_util (#109) -- FIXED: Crash on \_PdfViewerScrollThumbState (#86) +- FIXED: memory leak on `_PdfPageViewState` ([#110](https://github.com/espresso3389/pdfrx/issues/110)) +- Remove dependency on `dart:js_util` ([#109](https://github.com/espresso3389/pdfrx/issues/109)) +- FIXED: Crash on `_PdfViewerScrollThumbState` ([#86](https://github.com/espresso3389/pdfrx/issues/86)) # 1.0.50 -- Introduces PdfViewerParams.useAlternativeFitScaleAsMinScale but it's not recommended to set the value to false because it may degrade the viewer performance +- Introduces `PdfViewerParams.useAlternativeFitScaleAsMinScale` but it's not recommended to set the value to false because it may degrade the viewer performance # 1.0.49 @@ -422,7 +429,7 @@ # 1.0.11 -- intl 0.18.1 (#87) +- `intl` 0.18.1 ([#87](https://github.com/espresso3389/pdfrx/issues/87)) # 1.0.10+1 @@ -430,21 +437,21 @@ # 1.0.10 -- FIXED: calcZoomStopTable hangs app if zoom ratio is almost 0 (#79) +- FIXED: `calcZoomStopTable` hangs app if zoom ratio is almost 0 ([#79](https://github.com/espresso3389/pdfrx/issues/79)) # 1.0.9 -- PdfRect.toRect: scaledTo -> scaledPageSize -- FIXED: PdfJsConfiguration.cMapUrl/cMapPacked does not have correct default values +- `PdfRect.toRect`: `scaledTo` -> `scaledPageSize` +- FIXED: `PdfJsConfiguration.cMapUrl`/`cMapPacked` does not have correct default values # 1.0.8 -- Condition analysis warnings on auto-generated pdfium_bindings.dart +- Condition analysis warnings on auto-generated `pdfium_bindings.dart` # 1.0.7 - Requires Flutter 3.19/Dart 3.3 again (pub.dev is upgraded to the stable🎉) -- dart:js_interop based pdf.js interop implementation (remove dependency on package:js) +- `dart:js_interop` based Pdf.js interop implementation (remove dependency on `package:js`) # 1.0.6 @@ -453,7 +460,7 @@ - `flutter: '>=3.19.0-0.4.pre'` - `web: ^0.4.2` I'll update them as soon as [pub.dev upgrades their toolchains](https://github.com/dart-lang/pub-dev/issues/7484#issuecomment-1948206197) -- pdf.js interop refactoring +- Pdf.js interop refactoring # 1.0.5 @@ -464,7 +471,7 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 1.0.4 - Rollback version constraints to the older stable versions... - - I've created an issue for pub.dev: + - I've created an issue for pub.dev: [dart-lang/pub-dev#7484](https://github.com/dart-lang/pub-dev/issues/7484) # 1.0.3 @@ -476,34 +483,34 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 1.0.1 -- PdfViewerController.addListener/removeListener independently has listener list on it to make it work regardless of PdfViewer attached or not (#74) +- `PdfViewerController.addListener`/`removeListener` independently has listener list on it to make it work regardless of `PdfViewer` attached or not ([#74](https://github.com/espresso3389/pdfrx/issues/74)) # 1.0.0 - Requires Flutter 3.19/Dart 3.3 -- Update Web code to use package:web (removing dependency to dart:html) +- Update Web code to use `package:web` (removing dependency to `dart:html`) # 0.4.44 -- FIXED: PdfViewerParams.boundaryMargin does not work correctly. +- FIXED: `PdfViewerParams.boundaryMargin` does not work correctly. # 0.4.43 -- Add note for dark/night mode support on README.md; the trick is originally introduced by [pckimlong](https://github.com/pckimlong) on #46. -- FIXED: wrong PdfPageAnchor behavior with landscape pages +- Add note for dark/night mode support on `README.md`; the trick is originally introduced by [pckimlong](https://github.com/pckimlong) on [#46](https://github.com/espresso3389/pdfrx/issues/46). +- FIXED: wrong `PdfPageAnchor` behavior with landscape pages # 0.4.42 -- FIXED: PdfDocumentRefData's operator== is broken (#66) +- FIXED: `PdfDocumentRefData`'s `operator==` is broken ([#66](https://github.com/espresso3389/pdfrx/issues/66)) # 0.4.41 -- Marker example for PdfViewerParams.onTextSelectionChange (#65) -- Add more explanation for sourceName (#66) +- Marker example for `PdfViewerParams.onTextSelectionChange` ([#65](https://github.com/espresso3389/pdfrx/issues/65)) +- Add more explanation for `sourceName` ([#66](https://github.com/espresso3389/pdfrx/issues/66)) # 0.4.40 -- Introduces PdfViewerParams.onTextSelectionChange (#65) to know the last text selection +- Introduces `PdfViewerParams.onTextSelectionChange` ([#65](https://github.com/espresso3389/pdfrx/issues/65)) to know the last text selection # 0.4.39 @@ -512,19 +519,19 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.4.38 - Minor updates on text selection (still experimental...) -- Minor fix on PdfPageView +- Minor fix on `PdfPageView` # 0.4.37 -- CMake version "3.18.1+" for #48, #62 +- CMake version "3.18.1+" for [#48](https://github.com/espresso3389/pdfrx/issues/48), [#62](https://github.com/espresso3389/pdfrx/issues/62) # 0.4.36 -- Introduces PdfJsConfiguration to configure pdf.js download URLs +- Introduces `PdfJsConfiguration` to configure Pdf.js download URLs # 0.4.35 -- Download cache mechanism update (#57/#58) +- Download cache mechanism update ([#57](https://github.com/espresso3389/pdfrx/issues/57)/[#58](https://github.com/espresso3389/pdfrx/issues/58)) # 0.4.34 @@ -536,8 +543,8 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.4.32 -- Add PdfViewerParams.calculateInitialPageNumber to calculate the initial page number dynamically -- Add PdfViewerParams.onViewerReady to know when the viewer gets ready +- Add `PdfViewerParams.calculateInitialPageNumber` to calculate the initial page number dynamically +- Add `PdfViewerParams.onViewerReady` to know when the viewer gets ready # 0.4.31 @@ -547,25 +554,25 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa - FIXED: Link URI contains null-terminator - Add support text/links on rotated pages -- Stability updates for PdfTextSearcher -- README.md/example updates -- Revival of PdfViewer.data/PdfViewer.custom +- Stability updates for `PdfTextSearcher` +- `README.md`/example updates +- Revival of `PdfViewer.data`/`PdfViewer.custom` # 0.4.29 -- Minor fixes to PdfTextSearcher +- Minor fixes to `PdfTextSearcher` # 0.4.28 -- README.md/example updates +- `README.md`/example updates # 0.4.27 -- Minor updates and README.md updates +- Minor updates and `README.md` updates # 0.4.26 -- Introduces PdfTextSearcher that helps you to implement search UI feature (#47) +- Introduces `PdfTextSearcher` that helps you to implement search UI feature ([#47](https://github.com/espresso3389/pdfrx/issues/47)) - Example code is vastly changed to explain more about the widget functions # 0.4.25 @@ -574,40 +581,40 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.4.24 -- Huge refactoring on PdfViewerController; it's no longer TransformationController but just a `ValueListenable` - - This fixes an "Unhandled Exception: Null check operator used on a null value" on widget state disposal (#46) +- Huge refactoring on `PdfViewerController`; it's no longer `TransformationController` but just a `ValueListenable` + - This fixes an "Unhandled Exception: Null check operator used on a null value" on widget state disposal ([#46](https://github.com/espresso3389/pdfrx/issues/46)) # 0.4.23 -- Introduces PdfDocumentViewBuilder/PdfPageView widgets +- Introduces `PdfDocumentViewBuilder`/`PdfPageView` widgets - Example code is super updated with index and thumbnails. # 0.4.22 -- Web: Now pdf.js is loaded automatically and no modification to index.html is required! -- Default implementation for PdfViewerParams.errorBannerBuilder to show internally thrown errors -- PdfPasswordException is introduced to notify password error -- PdfDocumentRef now has stackTrace for error -- PdfFileCache now uses dedicated http.Client instance +- Web: Now Pdf.js is loaded automatically and no modification to `index.html` is required! +- Default implementation for `PdfViewerParams.errorBannerBuilder` to show internally thrown errors +- `PdfPasswordException` is introduced to notify password error +- `PdfDocumentRef` now has `stackTrace` for error +- `PdfFileCache` now uses dedicated `http.Client` instance # 0.4.21 -- Now PdfDocumentRef has const constructor and PdfViewer.documentRef is also const +- Now `PdfDocumentRef` has const constructor and `PdfViewer.documentRef` is also const # 0.4.20 -- Removes PdfDocumentProvider (Actually PdfDocumentRef does everything) +- Removes `PdfDocumentProvider` (Actually `PdfDocumentRef` does everything) - Fixes breakage introduced by 0.4.18 # 0.4.19 -- firstAttemptByEmptyPassword should be true by default +- `firstAttemptByEmptyPassword` should be true by default # 0.4.18 -- PdfDocumentProvider supercedes PdfDocumentStore (PR #42) +- `PdfDocumentProvider` supercedes `PdfDocumentStore` ([#42](https://github.com/espresso3389/pdfrx/pull/42)) - PDFium 6259 for Windows, Linux, and Android -- FIXED: Bug: Tests fail due to null operator check on PdfViewerController #44 +- FIXED: Bug: Tests fail due to null operator check on `PdfViewerController` ([#44](https://github.com/espresso3389/pdfrx/issues/44)) # 0.4.17 @@ -615,35 +622,35 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.4.16 -- Remove password parameters; use passwordProvider instead. -- Fixes several resource leak scenarios on PdfDocument open failures +- Remove password parameters; use `passwordProvider` instead. +- Fixes several resource leak scenarios on `PdfDocument` open failures - Restrict text selection if PDF permission does not allow copying -- Remove PdfViewer.documentRef; unnamed constructor is enough for the purpose +- Remove `PdfViewer.documentRef`; unnamed constructor is enough for the purpose # 0.4.15 -- Introduces PdfViewer.documentRef (#36) -- FIXED: PdfViewer.uri is broken on web for non relative paths #37 -- FIXED: Don't Animate to initialPage #39 +- Introduces `PdfViewer.documentRef` ([#36](https://github.com/espresso3389/pdfrx/issues/36)) +- FIXED: `PdfViewer.uri` is broken on web for non relative paths ([#37](https://github.com/espresso3389/pdfrx/issues/37)) +- FIXED: Don't Animate to `initialPage` ([#39](https://github.com/espresso3389/pdfrx/issues/39)) # 0.4.14 -- Introduces PdfViewerParams.onDocumentChanged event -- Introduces PdfDocument.loadOutline to load outline (a.k.a. bookmark) +- Introduces `PdfViewerParams.onDocumentChanged` event +- Introduces `PdfDocument.loadOutline` to load outline (a.k.a. bookmark) # 0.4.13 -- Improves document password handling by async PasswordProvider (#20) -- Introduces PdfViewerParams.errorBannerBuilder +- Improves document password handling by async `PasswordProvider` ([#20](https://github.com/espresso3389/pdfrx/issues/20)) +- Introduces `PdfViewerParams.errorBannerBuilder` # 0.4.12 -- Introduces PdfViewerParams.maxImageBytesCachedOnMemory, which restricts the maximum cache memory consumption - - Better than logic based on maxThumbCacheCount -- Remove the following parameters from PdfViewerParams: - - maxThumbCacheCount - - maxRealSizeImageCount - - enableRealSizeRendering +- Introduces `PdfViewerParams.maxImageBytesCachedOnMemory`, which restricts the maximum cache memory consumption + - Better than logic based on `maxThumbCacheCount` +- Remove the following parameters from `PdfViewerParams`: + - `maxThumbCacheCount` + - `maxRealSizeImageCount` + - `enableRealSizeRendering` # 0.4.11 @@ -651,91 +658,91 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.4.10 -- FIXED: isEncrypted property of document returns always true even the document is not encrypted (#29) +- FIXED: `isEncrypted` property of document returns always true even the document is not encrypted ([#29](https://github.com/espresso3389/pdfrx/issues/29)) # 0.4.9 -- FIXED: SelectionArea makes Web version almost unusable (#31) +- FIXED: `SelectionArea` makes Web version almost unusable ([#31](https://github.com/espresso3389/pdfrx/issues/31)) # 0.4.8 -- FIXED: Unhandled Exception: type 'Null' is not a subtype of type 'PdfPageRenderCancellationTokenPdfium' in type cast (#26) +- FIXED: Unhandled Exception: type 'Null' is not a subtype of type `PdfPageRenderCancellationTokenPdfium` in type cast ([#26](https://github.com/espresso3389/pdfrx/issues/26)) # 0.4.7 -- FIXED: Android build broken? Cannot find libpdfium.so error (#25) -- PdfViewerParams.loadingBannerBuilder to customize HTTP download progress -- PdfViewerParams.linkWidgetBuilder to support embedded links +- FIXED: Android build broken? Cannot find `libpdfium.so` error ([#25](https://github.com/espresso3389/pdfrx/issues/25)) +- `PdfViewerParams.loadingBannerBuilder` to customize HTTP download progress +- `PdfViewerParams.linkWidgetBuilder` to support embedded links - WIP: Updated text selection mechanism, which is faster and stable but still certain issues - Pan-to-scroll does not work on Desktop/Web - Selection does not work as expected on mobile devices -- Support Linux running on arm64 Raspberry PI (#23/#24) +- Support Linux running on arm64 Raspberry PI ([#23](https://github.com/espresso3389/pdfrx/issues/23), [#24](https://github.com/espresso3389/pdfrx/issues/24)) # 0.4.6 -- Introduces PdfPage.render cancellation mechanism - - PdfPageRenderCancellationToken to cancel the rendering process - - BREAKING CHANGE: PdfPage.render may return null if the rendering process is canceled -- PdfPageRender.render limits render resolution up to 300-dpi unless you use getPageRenderingScale - - Even with the restriction, image size may get large and you'd better implement getPageRenderingScale to restrict such large image rendering -- PdfViewerParams default changes: - - scrollByMouseWheel default is 0.2 - - maxRealSizeImageCount default is 3 -- PdfViewerParams.scrollByArrowKey to enable keyboard navigation +- Introduces `PdfPage.render` cancellation mechanism + - `PdfPageRenderCancellationToken` to cancel the rendering process + - BREAKING CHANGE: `PdfPage.render` may return null if the rendering process is canceled +- `PdfPageRender.render` limits render resolution up to 300-dpi unless you use `getPageRenderingScale` + - Even with the restriction, image size may get large and you'd better implement `getPageRenderingScale` to restrict such large image rendering +- `PdfViewerParams` default changes: + - `scrollByMouseWheel` default is 0.2 + - `maxRealSizeImageCount` default is 3 +- `PdfViewerParams.scrollByArrowKey` to enable keyboard navigation # 0.4.5 -- PdfViewerParams updates - - PdfViewerParams.onPageChanged replaces onPageChanged parameter on PdfViewer factories - - PdfViewerParams.pageAnchor replaces anchor parameter on PdfViewer factories -- pdfDocumentFromUri/PdfFileCache improves mechanism to cache downloaded PDF file +- `PdfViewerParams` updates + - `PdfViewerParams.onPageChanged` replaces `onPageChanged` parameter on `PdfViewer` factories + - `PdfViewerParams.pageAnchor` replaces `anchor` parameter on `PdfViewer` factories +- `pdfDocumentFromUri`/`PdfFileCache` improves mechanism to cache downloaded PDF file - ETag check to invalidate the existing cache - Better downloaded region handling # 0.4.4 -- PdfPage.render can render Annotations and FORMS -- PdfFileCache: More realistic file cache mechanism -- Introduces PasswordProvider to repeatedly test passwords (only API layer) +- `PdfPage.render` can render Annotations and FORMS +- `PdfFileCache`: More realistic file cache mechanism +- Introduces `PasswordProvider` to repeatedly test passwords (only API layer) # 0.4.3 -- FIXED: cache mechanism is apparently broken (#12) +- FIXED: cache mechanism is apparently broken ([#12](https://github.com/espresso3389/pdfrx/issues/12)) # 0.4.2 -- PdfViewerParams.pageOverlayBuilder to customize PDF page (#17) -- Updating README.md +- `PdfViewerParams.pageOverlayBuilder` to customize PDF page ([#17](https://github.com/espresso3389/pdfrx/issues/17)) +- Updating `README.md` # 0.4.1 -- Add PdfViewerParams.enableRenderAnnotations to enable annotations on rendering (#18,#19) +- Add `PdfViewerParams.enableRenderAnnotations` to enable annotations on rendering ([#18](https://github.com/espresso3389/pdfrx/issues/18), [#19](https://github.com/espresso3389/pdfrx/issues/19)) # 0.4.0 - Many breaking changes but they improve the code integrity: - - PdfDocument.pages supersedes PdfDocument.getPage - - PdfDocument.pageCount is removed - - PdfViewerParams.devicePixelRatioOverride is removed; use getPageRenderingScale instead -- Add PdfPageAnchor.all -- PdfViewerParams.viewerOverlayBuilder/PdfViewerScrollThumb to support scroll thumbs + - `PdfDocument.pages` supersedes `PdfDocument.getPage` + - `PdfDocument.pageCount` is removed + - `PdfViewerParams.devicePixelRatioOverride` is removed; use `getPageRenderingScale` instead +- Add `PdfPageAnchor.all` +- `PdfViewerParams.viewerOverlayBuilder`/`PdfViewerScrollThumb` to support scroll thumbs # 0.3.6 -- PageLayout -> PdfPageLayout +- `PageLayout` -> `PdfPageLayout` # 0.3.5 -- PageLayout class change to ease page layout customization +- `PageLayout` class change to ease page layout customization - Add example use case in API document # 0.3.4 - Rewriting page rendering code - Due to the internal structure change, page drawing customization parameters are once removed: - - pageDecoration - - pageOverlaysBuilder -- Example code does not enables enableTextSelection; it's still too experimental... + - `pageDecoration` + - `pageOverlaysBuilder` +- Example code does not enables `enableTextSelection`; it's still too experimental... # 0.3.3 @@ -749,8 +756,8 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa - Minor API changes - Internal integrity updates that controls the viewer behaviors -- FIX: example code does not have android.permission.INTERNET on AndroidManifest.xml -- PdfViewerParams.devicePixelRatioOverride is deprecated and introduces PdfViewerParams.getPageRenderingScale +- FIX: example code does not have `android.permission.INTERNET` on `AndroidManifest.xml` +- `PdfViewerParams.devicePixelRatioOverride` is deprecated and introduces `PdfViewerParams.getPageRenderingScale` # 0.3.0 @@ -758,34 +765,34 @@ _NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packa # 0.2.4 -- Now uses plugin_ffi. (Not containing any Flutter plugin stab) +- Now uses `plugin_ffi`. (Not containing any Flutter plugin stab) # 0.2.3 -- Fixed: #6 PdfPageWeb.render behavior is different from PdfPagePdfium.render +- FIXED: [#6](https://github.com/espresso3389/pdfrx/issues/6) `PdfPageWeb.render` behavior is different from `PdfPagePdfium.render` # 0.2.2 -- Explicitly specify Flutter 3.16/Dart 3.2 as NativeCallable.listener does not accept non-static function (#5) +- Explicitly specify Flutter 3.16/Dart 3.2 as `NativeCallable.listener` does not accept non-static function ([#5](https://github.com/espresso3389/pdfrx/issues/5)) # 0.2.1 - Stabilizing API surface - - Introducing PdfViewer.asset/file/uri/custom - - PdfViewer has documentLoader to accept function to load PdfDocument -- Fixes minor issues on PdfViewer + - Introducing `PdfViewer.asset`/`file`/`uri`/`custom` + - `PdfViewer` has `documentLoader` to accept function to load `PdfDocument` +- Fixes minor issues on `PdfViewer` # 0.2.0 -- Introducing PdfDocument.openUri/PdfFileCache\* classes -- Introducing PdfPermissions -- PdfPage.loadText/PdfPageText for text extraction +- Introducing `PdfDocument.openUri`/`PdfFileCache*` classes +- Introducing `PdfPermissions` +- `PdfPage.loadText`/`PdfPageText` for text extraction - Android NDK CMake to 3.18.1 # 0.1.1 - Document updates -- pdf.js 3.11.174 +- Pdf.js 3.11.174 # 0.1.0 From a95d26cc4aff8c6cc44669f10bd23285842dadd6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 9 Jun 2025 18:16:26 +0900 Subject: [PATCH 109/663] WIP: Experimetal workflow for testing all the builds Still Windows ARM64 build is not working correctly; it builds x64 binary... --- .github/workflows/build-test.yml | 256 +++++++++++++++++++++++++++++++ CHANGELOG.md | 1 + README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 5 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build-test.yml diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 00000000..d149a31f --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,256 @@ +name: Build Test + +on: + # push: + # branches: [ master, main ] + # pull_request: + # branches: [ master, main ] + workflow_dispatch: + inputs: + build_android: + description: 'Build Android' + required: false + type: boolean + default: true + build_ios: + description: 'Build iOS' + required: false + type: boolean + default: true + build_macos: + description: 'Build macOS' + required: false + type: boolean + default: true + build_linux: + description: 'Build Linux' + required: false + type: boolean + default: true + build_linux_arm64: + description: 'Build Linux ARM64' + required: false + type: boolean + default: true + build_windows: + description: 'Build Windows' + required: false + type: boolean + default: true + build_windows_arm64: + description: 'Build Windows ARM64' + required: false + type: boolean + default: true + build_web: + description: 'Build Web' + required: false + type: boolean + default: true + +jobs: + # Android build + android: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '18' + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build App Bundle + working-directory: example/viewer + run: flutter build appbundle --debug + + # iOS build + ios: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_ios }} + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build iOS (no signing) + working-directory: example/viewer + run: flutter build ios --debug --no-codesign + + # macOS build + macos: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_macos }} + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build macOS + working-directory: example/viewer + run: flutter build macos --debug + + # Linux build + linux: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Linux dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y ninja-build libgtk-3-dev + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build Linux + working-directory: example/viewer + run: flutter build linux --debug + + # Linux ARM64 build + linux-arm64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }} + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + + - name: Install Linux dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y ninja-build libgtk-3-dev + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build Linux + working-directory: example/viewer + run: flutter build linux --debug + + # Windows build + windows: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }} + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + shell: pwsh + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter + echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + C:\flutter\bin\flutter.bat config --no-enable-android + C:\flutter\bin\flutter.bat channel stable + C:\flutter\bin\flutter.bat doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: C:\flutter\bin\flutter.bat pub get + + - name: Build Windows + working-directory: example/viewer + run: C:\flutter\bin\flutter.bat build windows --debug + + # Windows ARM64 build (requires ARM64 runner or cross-compilation) + windows-arm64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} + runs-on: windows-11-arm + continue-on-error: true # ARM64 build might fail on x64 runners + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + shell: pwsh + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter + echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + C:\flutter\bin\flutter.bat config --no-enable-android + C:\flutter\bin\flutter.bat channel stable + C:\flutter\bin\flutter.bat doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: C:\flutter\bin\flutter.bat pub get + + - name: Build Windows ARM64 + working-directory: example/viewer + run: C:\flutter\bin\flutter.bat build windows --debug + + # Web build + web: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Install dependencies + working-directory: example/viewer + run: flutter pub get + + - name: Build Web + working-directory: example/viewer + run: flutter build web + + - name: Build Web (WASM) + working-directory: example/viewer + run: flutter build web --wasm diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc4fb58..beeb2c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Add `CLAUDE.md` for Claude Code integration - FIXED: preserve null `max-age` in cache control ([#387](https://github.com/espresso3389/pdfrx/pull/387)) - FIXED: `ArgumentError` parameter name in `PdfRect` ([#385](https://github.com/espresso3389/pdfrx/pull/385)) +- Windows ARM64 support ([#388](https://github.com/espresso3389/pdfrx/issues/388)) - Documentation updates and improvements # 1.1.33 diff --git a/README.md b/README.md index af29d9c2..4a0cc486 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.33 + pdfrx: ^1.1.34 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index a80bab87..6644e3de 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -315,7 +315,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.33" + version: "1.1.34" pdfrx_wasm: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e9ca18d4..7a3a2c9c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.33 +version: 1.1.34 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 90d69ce371a8466594d1aa0248e8bc37f19e39d2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 10 Jun 2025 00:02:47 +0900 Subject: [PATCH 110/663] WIP: try to make the build faster --- .github/workflows/build-test.yml | 7 ++++--- .github/workflows/github-pages.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d149a31f..347ee46c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -66,6 +66,7 @@ jobs: run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-linux-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v @@ -88,7 +89,7 @@ jobs: run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter echo "$HOME/flutter/bin" >> $GITHUB_PATH - ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter config --no-enable-android --no-enable-macos-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v @@ -111,7 +112,7 @@ jobs: run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter echo "$HOME/flutter/bin" >> $GITHUB_PATH - ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter config --no-enable-android --no-enable-ios ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v @@ -239,7 +240,7 @@ jobs: run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter echo "$HOME/flutter/bin" >> $GITHUB_PATH - ~/flutter/bin/flutter config --no-enable-android + ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 33c427d5..84c1818d 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -15,10 +15,12 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop + ~/flutter/bin/flutter channel stable - name: Check Flutter SDK version run: | flutter --version @@ -28,8 +30,6 @@ jobs: run: | PDFRX_VERSION=$(awk -F": " '/^version:/ {print $2}' pubspec.yaml) echo "PDFRX_VERSION=$PDFRX_VERSION" >> $GITHUB_ENV - - name: Enable Flutter Web - run: flutter config --enable-web - name: Install dependencies run: flutter pub get - name: Build Flutter Web App (WASM) From 7a0877a7904a50d90fa0095f9f78c5c133bc44d7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 10 Jun 2025 01:43:42 +0900 Subject: [PATCH 111/663] Restrict claude's users to vars.CLAUDE_ALLOWED_USERS --- .github/workflows/claude.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index bcd8ef59..de55216f 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -13,10 +13,10 @@ on: jobs: claude: if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.comment.user.login)) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.comment.user.login)) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.review.user.login)) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(vars.CLAUDE_ALLOWED_USERS, github.event.issue.user.login)) runs-on: ubuntu-latest permissions: contents: read From bb3e9648eff3ed52ec30c86b0c2fad3dca1d2102 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 15:27:09 +0900 Subject: [PATCH 112/663] Add rendering flags to PdfPage.render --- lib/src/pdf_api.dart | 34 +++++++++++++++++++++++++ lib/src/pdfium/pdfrx_pdfium.dart | 9 +++++-- lib/src/web/pdfrx_js.dart | 1 + lib/src/web/pdfrx_wasm.dart | 2 ++ wasm/pdfrx_wasm/assets/pdfium_worker.js | 10 ++++++-- 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index a1e688cd..acfc1b67 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -338,6 +338,7 @@ abstract class PdfPage { /// - If [fullWidth], [fullHeight] are not specified, [PdfPage.width] and [PdfPage.height] are used (it means rendered at 72-dpi). /// [backgroundColor] is used to fill the background of the page. If no color is specified, [Colors.white] is used. /// - [annotationRenderingMode] controls to render annotations or not. The default is [PdfAnnotationRenderingMode.annotationAndForms]. + /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderingFlags.none]. /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. /// /// The following figure illustrates what each parameter means: @@ -364,6 +365,7 @@ abstract class PdfPage { double? fullHeight, Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }); @@ -389,6 +391,38 @@ enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } /// - [annotationAndForms]: Render annotations and forms. enum PdfAnnotationRenderingMode { none, annotation, annotationAndForms } +/// Flags for [PdfPage.render]. +/// +/// Basically, they are Pdfium's `FPDF_RENDER_*` flags and not supported on Pdf.js. +abstract class PdfPageRenderFlags { + /// None. + static const none = 0; + + /// `FPDF_LCD_TEXT` flag. + static const lcdText = 0x0002; + + /// `FPDF_GRAYSCALE` flag. + static const grayscale = 0x0008; + + /// `FPDF_RENDER_LIMITEDIMAGECACHE` flag. + static const limitedImageCache = 0x0200; + + /// `FPDF_RENDER_FORCEHALFTONE` flag. + static const forceHalftone = 0x0400; + + /// `FPDF_PRINTING` flag. + static const printing = 0x0800; + + /// `FPDF_RENDER_NO_SMOOTHTEXT` flag. + static const noSmoothText = 0x1000; + + /// `FPDF_RENDER_NO_SMOOTHIMAGE` flag. + static const noSmoothImage = 0x2000; + + /// `FPDF_RENDER_NO_SMOOTHPATH` flag. + static const noSmoothPath = 0x4000; +} + /// Token to try to cancel the rendering process. abstract class PdfPageRenderCancellationToken { /// Cancel the rendering process. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index eaf0d3f1..474571e5 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -505,6 +505,7 @@ class PdfPagePdfium extends PdfPage { double? fullHeight, Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { if (cancellationToken != null && cancellationToken is! PdfPageRenderCancellationTokenPdfium) { @@ -551,6 +552,7 @@ class PdfPagePdfium extends PdfPage { throw PdfException('FPDF_LoadPage(${params.pageNumber}) failed.'); } pdfium.FPDFBitmap_FillRect(bmp, 0, 0, params.width, params.height, params.backgroundColor); + pdfium.FPDF_RenderPageBitmap( bmp, page, @@ -559,7 +561,10 @@ class PdfPagePdfium extends PdfPage { params.fullWidth, params.fullHeight, 0, - params.annotationRenderingMode != PdfAnnotationRenderingMode.none ? pdfium_bindings.FPDF_ANNOT : 0, + flags | + (params.annotationRenderingMode != PdfAnnotationRenderingMode.none + ? pdfium_bindings.FPDF_ANNOT + : 0), ); if (params.formHandle != 0 && @@ -573,7 +578,7 @@ class PdfPagePdfium extends PdfPage { params.fullWidth, params.fullHeight, 0, - 0, + flags, ); } return true; diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart index 0b60e63f..f5b28fbd 100644 --- a/lib/src/web/pdfrx_js.dart +++ b/lib/src/web/pdfrx_js.dart @@ -289,6 +289,7 @@ class PdfPageJs extends PdfPage { double? fullHeight, Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { if (cancellationToken == null) { diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 1b9a8818..d0815264 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -364,6 +364,7 @@ class PdfPageWasm extends PdfPage { double? fullHeight, Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { fullWidth ??= this.width; @@ -385,6 +386,7 @@ class PdfPageWasm extends PdfPage { 'fullHeight': fullHeight, 'backgroundColor': backgroundColor.toARGB32(), 'annotationRenderingMode': annotationRenderingMode.index, + 'flags': flags, 'formHandle': document.document['formHandle'], }, ); diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index 7f953d44..67f9167c 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -678,6 +678,7 @@ function closePage(params) { * fullHeight: number, * backgroundColor: number, * annotationRenderingMode: number, + * flags: number, * formHandle: number * }} params * @returns @@ -694,6 +695,7 @@ function renderPage(params) { fullHeight = height, backgroundColor, annotationRenderingMode = 0, + flags = 0, formHandle, } = params; @@ -727,8 +729,12 @@ function renderPage(params) { Pdfium.wasmExports.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, backgroundColor); const FPDF_ANNOT = 1; + const FPDF_RENDER_NO_SMOOTHTEXT = 0x1000; + const FPDF_RENDER_NO_SMOOTHIMAGE = 0x2000; + const FPDF_RENDER_NO_SMOOTHPATH = 0x4000; const PdfAnnotationRenderingMode_none = 0; const PdfAnnotationRenderingMode_annotationAndForms = 2; + Pdfium.wasmExports.FPDF_RenderPageBitmap( bitmap, pageHandle, @@ -737,11 +743,11 @@ function renderPage(params) { fullWidth, fullHeight, 0, - annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0, + flags | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0), ); if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { - Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, 0); + Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, flags); } let copiedBuffer = new ArrayBuffer(bufferSize); From 985471f1585aa5776ae7402314efac34aac25a62 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 17:02:28 +0900 Subject: [PATCH 113/663] Add limitRenderingCache parameter to PdfViewerParams and update PdfViewer rendering flags (#394) --- lib/src/widgets/pdf_viewer.dart | 2 ++ lib/src/widgets/pdf_viewer_params.dart | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index eae72897..12e28abb 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -1021,6 +1021,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix fullHeight: height, backgroundColor: Colors.white, annotationRenderingMode: widget.params.annotationRenderingMode, + flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, ); if (img == null) return; @@ -1082,6 +1083,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix fullHeight: pageRect.height * scale, backgroundColor: Colors.white, annotationRenderingMode: widget.params.annotationRenderingMode, + flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, ); if (img == null) return null; diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index 34a6c48f..844abf26 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -22,6 +22,7 @@ class PdfViewerParams { this.panAxis = PanAxis.free, this.boundaryMargin, this.annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + this.limitRenderingCache = true, this.pageAnchor = PdfPageAnchor.top, this.pageAnchorEnd = PdfPageAnchor.bottom, this.onePassRenderingScaleThreshold = 200 / 72, @@ -158,6 +159,12 @@ class PdfViewerParams { /// Annotation rendering mode. final PdfAnnotationRenderingMode annotationRenderingMode; + /// If true, the viewer limits the rendering cache to reduce memory consumption. + /// + /// For Pdfium, it internally enables `FPDF_RENDER_LIMITEDIMAGECACHE` flag on rendering + /// to reduce the memory consumption by image caching. + final bool limitRenderingCache; + /// Anchor to position the page. final PdfPageAnchor pageAnchor; @@ -527,6 +534,7 @@ class PdfViewerParams { other.panAxis != panAxis || other.boundaryMargin != boundaryMargin || other.annotationRenderingMode != annotationRenderingMode || + other.limitRenderingCache != limitRenderingCache || other.pageAnchor != pageAnchor || other.pageAnchorEnd != pageAnchorEnd || other.onePassRenderingScaleThreshold != onePassRenderingScaleThreshold || @@ -558,6 +566,7 @@ class PdfViewerParams { other.panAxis == panAxis && other.boundaryMargin == boundaryMargin && other.annotationRenderingMode == annotationRenderingMode && + other.limitRenderingCache == limitRenderingCache && other.pageAnchor == pageAnchor && other.pageAnchorEnd == pageAnchorEnd && other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && @@ -610,6 +619,7 @@ class PdfViewerParams { panAxis.hashCode ^ boundaryMargin.hashCode ^ annotationRenderingMode.hashCode ^ + limitRenderingCache.hashCode ^ pageAnchor.hashCode ^ pageAnchorEnd.hashCode ^ onePassRenderingScaleThreshold.hashCode ^ From 5551d3e1f7315201322820b37a78f3d7a3d36700 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 17:20:33 +0900 Subject: [PATCH 114/663] Release v1.1.35 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 5 +++++ CLAUDE.md | 15 +++++++++++++++ README.md | 2 +- pubspec.yaml | 2 +- wasm/pdfrx_wasm/CHANGELOG.md | 4 ++++ wasm/pdfrx_wasm/pubspec.yaml | 2 +- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beeb2c09..57b14bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.35 + +- Add `limitRenderingCache` parameter to `PdfViewerParams` to control rendering cache behavior ([#394](https://github.com/espresso3389/pdfrx/pull/394)) +- Add rendering flags support to `PdfPage.render` method + # 1.1.34 - Add `CLAUDE.md` for Claude Code integration diff --git a/CLAUDE.md b/CLAUDE.md index 412d717c..0c5bed73 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,3 +105,18 @@ flutter test test/pdf_document_test.dart # Run specific test file - 120 character line width - Relative imports within lib/ - Follow flutter_lints with custom rules in analysis_options.yaml + +## Release Process + +1. Update version in `pubspec.yaml` + - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) + - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) + - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) +2. Update CHANGELOG.md with changes +3. Update README.md with new version information (changes version in example fragments) +4. Do the same for wasm/pdfrx_wasm/ if applicable +5. Run `flutter pub get` +6. Run tests to ensure everything works +7. Commit changes with message "Release vX.Y.Z" +8. Tag the commit with `git tag vX.Y.Z` +9. Push changes and tags to remote diff --git a/README.md b/README.md index 4a0cc486..65a2730f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.34 + pdfrx: ^1.1.35 ``` ### Note for Windows diff --git a/pubspec.yaml b/pubspec.yaml index 7a3a2c9c..9c7ff415 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.34 +version: 1.1.35 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/wasm/pdfrx_wasm/CHANGELOG.md b/wasm/pdfrx_wasm/CHANGELOG.md index c3e940ae..0aaaf4e2 100644 --- a/wasm/pdfrx_wasm/CHANGELOG.md +++ b/wasm/pdfrx_wasm/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.11 + +* Add rendering flags support to PdfPage.render method + ## 1.1.10 * PDFium WASM 138.0.7162.0 diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml index 2b4cc103..ba1efc6d 100644 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ b/wasm/pdfrx_wasm/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_wasm description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.10 +version: 1.1.11 homepage: https://github.com/espresso3389/pdfrx environment: From 1704ae0920e08c004e0702bb809977042beca995 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 17:35:00 +0900 Subject: [PATCH 115/663] WIP --- CLAUDE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0c5bed73..1e60393f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,9 +109,9 @@ flutter test test/pdf_document_test.dart # Run specific test file ## Release Process 1. Update version in `pubspec.yaml` - - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) + - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) + - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) + - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) 2. Update CHANGELOG.md with changes 3. Update README.md with new version information (changes version in example fragments) 4. Do the same for wasm/pdfrx_wasm/ if applicable From 97d2318709e024dcf418a46e155e8c35e203e2fd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 17:40:28 +0900 Subject: [PATCH 116/663] WIP --- CLAUDE.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1e60393f..bfc1a002 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -112,11 +112,18 @@ flutter test test/pdf_document_test.dart # Run specific test file - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) -2. Update CHANGELOG.md with changes -3. Update README.md with new version information (changes version in example fragments) -4. Do the same for wasm/pdfrx_wasm/ if applicable -5. Run `flutter pub get` +2. Update `CHANGELOG.md` with changes +3. Update `README.md` with new version information + - Changes version in example fragments + - Consider to add notes for new features or breaking changes + - Notify the owner if you find any issues with the example app or documentation +4. Do the same for `wasm/pdfrx_wasm/` if applicable + - `wasm/pdfrx_wasm/assets/` may contain changes critical to the web version, so ensure to update the version in `wasm/pdfrx_wasm/pubspec.yaml` as well +5. Run `flutter pub get` on all affected directories + - This includes the main package, example app, and wasm package if applicable + - Ensure all dependencies are resolved and up-to-date 6. Run tests to ensure everything works 7. Commit changes with message "Release vX.Y.Z" 8. Tag the commit with `git tag vX.Y.Z` 9. Push changes and tags to remote +10. Do `flutter pub publish` to publish the package From b8fc8baa16fd54311ff36b73b511257bf3576599 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 11 Jun 2025 17:42:23 +0900 Subject: [PATCH 117/663] WIP --- CLAUDE.md | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bfc1a002..9ec8f788 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,9 +21,12 @@ flutter format . # Format code (120 char line width) # Example app cd example/viewer flutter run # Run on connected device/emulator -flutter build apk # Build Android APK +flutter build appbundle # Build Android App Bundle flutter build ios # Build iOS (requires macOS) -flutter build web # Build for web +flutter build web --wasm # Build for web +flutter build linux # Build for Linux +flutter build windows # Build for Windows +flutter build macos # Build for macOS ``` ### FFI Bindings Generation @@ -37,6 +40,28 @@ cd example/viewer && flutter build linux dart run ffigen # Regenerate PDFium FFI bindings ``` +## Release Process + +1. Update version in `pubspec.yaml` + - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) + - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) + - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) +2. Update `CHANGELOG.md` with changes +3. Update `README.md` with new version information + - Changes version in example fragments + - Consider to add notes for new features or breaking changes + - Notify the owner if you find any issues with the example app or documentation +4. Do the same for `wasm/pdfrx_wasm/` if applicable + - `wasm/pdfrx_wasm/assets/` may contain changes critical to the web version, so ensure to update the version in `wasm/pdfrx_wasm/pubspec.yaml` as well +5. Run `flutter pub get` on all affected directories + - This includes the main package, example app, and wasm package if applicable + - Ensure all dependencies are resolved and up-to-date +6. Run tests to ensure everything works +7. Commit changes with message "Release vX.Y.Z" +8. Tag the commit with `git tag vX.Y.Z` +9. Push changes and tags to remote +10. Do `flutter pub publish` to publish the package + ## Architecture Overview ### Platform Abstraction @@ -105,25 +130,3 @@ flutter test test/pdf_document_test.dart # Run specific test file - 120 character line width - Relative imports within lib/ - Follow flutter_lints with custom rules in analysis_options.yaml - -## Release Process - -1. Update version in `pubspec.yaml` - - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) -2. Update `CHANGELOG.md` with changes -3. Update `README.md` with new version information - - Changes version in example fragments - - Consider to add notes for new features or breaking changes - - Notify the owner if you find any issues with the example app or documentation -4. Do the same for `wasm/pdfrx_wasm/` if applicable - - `wasm/pdfrx_wasm/assets/` may contain changes critical to the web version, so ensure to update the version in `wasm/pdfrx_wasm/pubspec.yaml` as well -5. Run `flutter pub get` on all affected directories - - This includes the main package, example app, and wasm package if applicable - - Ensure all dependencies are resolved and up-to-date -6. Run tests to ensure everything works -7. Commit changes with message "Release vX.Y.Z" -8. Tag the commit with `git tag vX.Y.Z` -9. Push changes and tags to remote -10. Do `flutter pub publish` to publish the package From fc8eecf111621a03022d455b7e32f42499699ede Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 12 Jun 2025 02:31:21 +0900 Subject: [PATCH 118/663] WIP: loadPagesProgressively --- lib/src/pdf_api.dart | 12 +++ lib/src/pdfium/pdfrx_pdfium.dart | 148 +++++++++++++++++++++++++------ lib/src/web/pdfrx_js.dart | 18 +++- lib/src/web/pdfrx_wasm.dart | 26 ++++-- lib/src/widgets/pdf_viewer.dart | 8 ++ 5 files changed, 178 insertions(+), 34 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index acfc1b67..36773547 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -295,6 +295,12 @@ abstract class PdfDocument { withCredentials: withCredentials, ); + Future loadPagesProgressively( + FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { + T? context, + Duration loadUnitDuration = const Duration(seconds: 1), + }); + /// Pages. List get pages; @@ -329,6 +335,12 @@ abstract class PdfPage { /// PDF page rotation. PdfPageRotation get rotation; + /// Whether the page is really loaded or not. + /// + /// If the value is false, the page's [width], [height], [size], and [rotation] are just guessed values and + /// will be updated when the page is really loaded. + bool get isLoaded; + /// Render a sub-area or full image of specified PDF file. /// Returned image should be disposed after use. /// [x], [y], [width], [height] specify sub-area to render in pixels. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 474571e5..77265eeb 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; +import 'dart:math'; import 'dart:ui' as ui; import 'package:ffi/ffi.dart'; @@ -343,32 +344,15 @@ class PdfDocumentPdfium extends PdfDocument { Pointer formInfo = nullptr; pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; try { - final pageCount = pdfium.FPDF_GetPageCount(doc); final permissions = pdfium.FPDF_GetDocPermissions(doc); final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); formInfo = calloc.allocate(sizeOf()); formInfo.ref.version = 1; formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); - - final pages = <({double width, double height, int rotation})>[]; - for (int i = 0; i < pageCount; i++) { - final page = pdfium.FPDF_LoadPage(doc, i); - try { - pages.add(( - width: pdfium.FPDF_GetPageWidthF(page), - height: pdfium.FPDF_GetPageHeightF(page), - rotation: pdfium.FPDFPage_GetRotation(page), - )); - } finally { - pdfium.FPDF_ClosePage(page); - } - } - return ( permissions: permissions, securityHandlerRevision: securityHandlerRevision, - pages: pages, formHandle: formHandle.address, formInfo: formInfo.address, ); @@ -393,29 +377,133 @@ class PdfDocumentPdfium extends PdfDocument { disposeCallback: disposeCallback, ); - final pages = []; - for (int i = 0; i < result.pages.length; i++) { - final pageData = result.pages[i]; + final pages = await pdfDoc._loadPagesInTime(maxPageCountToLoadAdditionally: 1); + pdfDoc._pages = List.unmodifiable(pages.pages); + return pdfDoc; + } catch (e) { + pdfDoc?.dispose(); + rethrow; + } + } + + @override + Future loadPagesProgressively( + FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { + T? context, + Duration loadUnitDuration = const Duration(seconds: 1), + }) async { + for (;;) { + if (isDisposed) return; + + final loaded = await _loadPagesInTime( + pagesLoadedSoFar: _pages.takeWhile((p) => p.isLoaded).toList(), + timeout: loadUnitDuration, + ); + if (isDisposed) return; + _pages = List.unmodifiable(loaded.pages); + if (onPageLoaded != null) { + final result = await onPageLoaded(context, loaded.pageCountLoaded, loaded.pages.length); + if (result == false) { + // If the callback returns false, stop loading pages + return; + } + } + if (loaded.pageCountLoaded == loaded.pages.length || isDisposed) return; + } + } + + /// Loads pages in the document in a time-limited manner. + Future<({List pages, int pageCountLoaded})> _loadPagesInTime({ + List pagesLoadedSoFar = const [], + int? maxPageCountToLoadAdditionally, + Duration? timeout, + }) async { + try { + final results = await (await backgroundWorker).compute( + (params) { + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); + return using((arena) { + try { + final pageCount = pdfium.FPDF_GetPageCount(doc); + final end = + maxPageCountToLoadAdditionally == null + ? pageCount + : min(pageCount, params.pagesCountLoadesSoFar + params.maxPageCountToLoadAdditionally!); + final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; + final start = DateTime.now(); + final pages = <({double width, double height, int rotation})>[]; + for (int i = params.pagesCountLoadesSoFar; i < end; i++) { + final page = pdfium.FPDF_LoadPage(doc, i); + try { + pages.add(( + width: pdfium.FPDF_GetPageWidthF(page), + height: pdfium.FPDF_GetPageHeightF(page), + rotation: pdfium.FPDFPage_GetRotation(page), + )); + } finally { + pdfium.FPDF_ClosePage(page); + } + if (t != null && DateTime.now().isAfter(t)) { + break; + } + } + + return (pages: pages, totalPageCount: pageCount); + } catch (e) { + pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); + calloc.free(formInfo); + rethrow; + } + }); + }, + ( + docAddress: document.address, + pagesCountLoadesSoFar: pagesLoadedSoFar.length, + maxPageCountToLoadAdditionally: maxPageCountToLoadAdditionally, + timeoutUs: timeout?.inMicroseconds, + ), + ); + + final pages = [...pagesLoadedSoFar]; + for (int i = 0; i < results.pages.length; i++) { + final pageData = results.pages[i]; pages.add( PdfPagePdfium._( - document: pdfDoc, - pageNumber: i + 1, + document: this, + pageNumber: pages.length + 1, width: pageData.width, height: pageData.height, rotation: PdfPageRotation.values[pageData.rotation], + isLoaded: true, ), ); } - pdfDoc.pages = List.unmodifiable(pages); - return pdfDoc; + final pageCountLoaded = pages.length; + if (pageCountLoaded > 0) { + final last = pages.last; + for (int i = pages.length; i < results.totalPageCount; i++) { + pages.add( + PdfPagePdfium._( + document: this, + pageNumber: pages.length + 1, + width: last.width, + height: last.height, + rotation: last.rotation, + isLoaded: false, + ), + ); + } + } + return (pages: pages, pageCountLoaded: pageCountLoaded); } catch (e) { - pdfDoc?.dispose(); rethrow; } } @override - late final List pages; + List get pages => _pages; + + List _pages = []; @override bool isIdenticalDocumentHandle(Object? other) => @@ -487,12 +575,16 @@ class PdfPagePdfium extends PdfPage { @override final PdfPageRotation rotation; + @override + final bool isLoaded; + PdfPagePdfium._({ required this.document, required this.pageNumber, required this.width, required this.height, required this.rotation, + required this.isLoaded, }); @override @@ -669,7 +761,7 @@ class PdfPagePdfium extends PdfPage { ); return _rectFromLTRBBuffer(rectBuffer); }); - return PdfLink(rects, url: Uri.parse(_getLinkUrl(linkPage, index, arena))); + return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); }); }); } finally { @@ -762,7 +854,7 @@ class PdfPagePdfium extends PdfPage { pdfium.FPDFAction_GetURIPath(document, action, buffer.cast(), size); try { final String newBuffer = buffer.toDartString(); - return Uri.parse(newBuffer); + return Uri.tryParse(newBuffer); } catch (e) { return null; } diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart index f5b28fbd..5850278f 100644 --- a/lib/src/web/pdfrx_js.dart +++ b/lib/src/web/pdfrx_js.dart @@ -189,6 +189,19 @@ class PdfDocumentJs extends PdfDocument { width: vp1.width, height: vp1.height, rotation: PdfPageRotation.values[page.rotate ~/ 90], + isLoaded: true, + ); + } + + @override + Future loadPagesProgressively( + FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { + T? context, + Duration loadUnitDuration = const Duration(seconds: 1), + }) async { + throw UnimplementedError( + 'PdfDocumentJs.loadPagesAsync is not implemented. ' + 'Use PdfDocumentJs.fromDocument to load all pages at once.', ); } @@ -266,6 +279,7 @@ class PdfPageJs extends PdfPage { required this.width, required this.height, required this.rotation, + required this.isLoaded, }); @override final PdfDocumentJs document; @@ -278,6 +292,8 @@ class PdfPageJs extends PdfPage { final double height; @override final PdfPageRotation rotation; + @override + final bool isLoaded; @override Future render({ @@ -397,7 +413,7 @@ class PdfPageJs extends PdfPage { ), ]); if (annot.url != null) { - links.add(PdfLink(rects, url: Uri.parse(annot.url!))); + links.add(PdfLink(rects, url: Uri.tryParse(annot.url!))); continue; } final dest = await document._getDestination(annot.dest); diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index d0815264..06fbc8fc 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -235,6 +235,18 @@ class PdfDocumentWasm extends PdfDocument { ); } + @override + Future loadPagesProgressively( + FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { + T? context, + Duration loadUnitDuration = const Duration(seconds: 1), + }) async { + throw UnimplementedError( + 'PdfDocumentWasm.loadPagesAsync is not implemented. ' + 'Use PdfDocumentJs.fromDocument to load all pages at once.', + ); + } + @override late final List pages; @@ -258,6 +270,7 @@ class PdfDocumentWasm extends PdfDocument { page['width'], page['height'], (page['rotation'] as num).toInt(), + (page['isLoaded'] as bool?) ?? false, ), ) .toList(); @@ -279,7 +292,7 @@ class PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken } class PdfPageWasm extends PdfPage { - PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation) + PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) : pageNumber = pageIndex + 1, rotation = PdfPageRotation.values[rotation]; @@ -308,7 +321,7 @@ class PdfPageWasm extends PdfPage { }).toList(); final url = link['url']; if (url is String) { - return PdfLink(rects, url: Uri.parse(url)); + return PdfLink(rects, url: Uri.tryParse(url)); } final dest = link['dest']; if (dest is! Map) { @@ -345,15 +358,18 @@ class PdfPageWasm extends PdfPage { @override final int pageNumber; - @override - final PdfPageRotation rotation; - @override final double width; @override final double height; + @override + final PdfPageRotation rotation; + + @override + final bool isLoaded; + @override Future render({ int x = 0, diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 12e28abb..93c38579 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -318,6 +318,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } _notifyOnDocumentChanged(); + + _document?.loadPagesProgressively((document, pageNumber, totalPageCount) { + if (document == _document && mounted) { + _invalidate(); + return true; + } + return false; + }, context: _document); } void _notifyOnDocumentChanged() { From c625c38e8f6ee7db6a9ce4a7f05b3698155958c6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 12 Jun 2025 18:14:00 +0900 Subject: [PATCH 119/663] WIP: progressive loading for Pdfium WASM works now --- lib/src/pdf_api.dart | 47 +++++++++- lib/src/pdf_document_ref.dart | 7 ++ lib/src/pdfium/pdf_file_cache.dart | 3 + lib/src/pdfium/pdfrx_pdfium.dart | 114 ++++++++++++++---------- lib/src/web/pdfrx_js.dart | 29 ++++-- lib/src/web/pdfrx_wasm.dart | 72 +++++++++++---- lib/src/widgets/pdf_viewer.dart | 26 +++++- wasm/pdfrx_wasm/assets/pdfium_worker.js | 111 ++++++++++++++++------- 8 files changed, 296 insertions(+), 113 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 36773547..9fbbcee5 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -62,6 +62,7 @@ abstract class PdfDocumentFactory { String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }); /// See [PdfDocument.openData]. @@ -69,6 +70,7 @@ abstract class PdfDocumentFactory { Uint8List data, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, String? sourceName, bool allowDataOwnershipTransfer = false, void Function()? onDispose, @@ -79,6 +81,7 @@ abstract class PdfDocumentFactory { String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }); /// See [PdfDocument.openCustom]. @@ -88,6 +91,7 @@ abstract class PdfDocumentFactory { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, }); @@ -97,6 +101,7 @@ abstract class PdfDocumentFactory { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, @@ -178,14 +183,19 @@ abstract class PdfDocument { /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. static Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) => PdfDocumentFactory.instance.openFile( filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// Opening the specified asset. @@ -193,14 +203,19 @@ abstract class PdfDocument { /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. static Future openAsset( String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) => PdfDocumentFactory.instance.openAsset( name, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// Opening the PDF on memory. @@ -209,6 +224,9 @@ abstract class PdfDocument { /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. /// @@ -218,6 +236,7 @@ abstract class PdfDocument { Uint8List data, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, String? sourceName, bool allowDataOwnershipTransfer = false, void Function()? onDispose, @@ -225,6 +244,7 @@ abstract class PdfDocument { data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, sourceName: sourceName, allowDataOwnershipTransfer: allowDataOwnershipTransfer, onDispose: onDispose, @@ -240,6 +260,9 @@ abstract class PdfDocument { /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. static Future openCustom({ @@ -248,6 +271,7 @@ abstract class PdfDocument { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, }) => PdfDocumentFactory.instance.openCustom( @@ -256,6 +280,7 @@ abstract class PdfDocument { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, onDispose: onDispose, ); @@ -270,6 +295,9 @@ abstract class PdfDocument { /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// /// [progressCallback] is called when the download progress is updated (Not supported on Web). /// [reportCallback] is called when the download is completed (Not supported on Web). /// [preferRangeAccess] to prefer range access to download the PDF (Not supported on Web). @@ -279,6 +307,7 @@ abstract class PdfDocument { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, @@ -288,6 +317,7 @@ abstract class PdfDocument { uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, reportCallback: reportCallback, preferRangeAccess: preferRangeAccess, @@ -295,10 +325,19 @@ abstract class PdfDocument { withCredentials: withCredentials, ); + /// Load pages progressively. + /// + /// This function loads pages progressively if the pages are not loaded yet. + /// It calls [onPageLoadProgress] for each [loadUnitDuration] duration until all pages are loaded or the loading + /// is cancelled. + /// When [onPageLoadProgress] is called, it should return true to continue loading process or false to stop loading. + /// [data] is an optional data that can be used to pass additional information to the callback. + /// + /// It's always safe to call this function even if the pages are already loaded. Future loadPagesProgressively( - FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { - T? context, - Duration loadUnitDuration = const Duration(seconds: 1), + PdfPageLoadingCallback? onPageLoadProgress, { + T? data, + Duration loadUnitDuration = const Duration(milliseconds: 250), }); /// Pages. @@ -313,6 +352,8 @@ abstract class PdfDocument { bool isIdenticalDocumentHandle(Object? other); } +typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); + /// Handles a PDF page in [PdfDocument]. /// /// See [PdfDocument.pages]. diff --git a/lib/src/pdf_document_ref.dart b/lib/src/pdf_document_ref.dart index 6d575db5..4fc3af39 100644 --- a/lib/src/pdf_document_ref.dart +++ b/lib/src/pdf_document_ref.dart @@ -111,6 +111,7 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi name, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: true, ); @override @@ -159,6 +160,7 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: true, progressCallback: progressCallback, reportCallback: reportCallback, preferRangeAccess: preferRangeAccess, @@ -198,6 +200,7 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin file, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: true, ); @override @@ -240,6 +243,7 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: true, sourceName: sourceName, allowDataOwnershipTransfer: allowDataOwnershipTransfer, onDispose: onDispose, @@ -287,6 +291,7 @@ class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMix sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: true, maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, onDispose: onDispose, ); @@ -384,11 +389,13 @@ class PdfDocumentListenable extends Listenable { final PdfDocument document; PdfDownloadReport? report; try { + final stopwatch = Stopwatch()..start(); document = await ref.loadDocument( _progress, (downloaded, totalBytes, elapsed) => report = PdfDownloadReport(downloaded: downloaded, total: totalBytes, elapsedTime: elapsed), ); + debugPrint('PdfDocument initial load: ${ref.sourceName} (${stopwatch.elapsedMilliseconds} ms)'); } catch (err, stackTrace) { setError(err, stackTrace); return report; diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index 0ae463d3..504ffe21 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -277,6 +277,7 @@ Future pdfDocumentFromUri( Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? blockSize, PdfFileCache? cache, PdfDownloadProgressCallback? progressCallback, @@ -338,6 +339,7 @@ Future pdfDocumentFromUri( cache.filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); } } @@ -365,6 +367,7 @@ Future pdfDocumentFromUri( }, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, fileSize: cache.fileSize, sourceName: uri.toString(), onDispose: () { diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 77265eeb..114be938 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -91,6 +91,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) async { final data = await rootBundle.load(name); return await _openData( @@ -98,6 +99,9 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { 'asset:$name', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + maxSizeToCacheOnMemory: null, + onDispose: null, ); } @@ -108,12 +112,15 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { bool firstAttemptByEmptyPassword = true, String? sourceName, bool allowDataOwnershipTransfer = false, // just ignored + bool useProgressiveLoading = false, void Function()? onDispose, }) => _openData( data, sourceName ?? 'memory-${data.hashCode}', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + maxSizeToCacheOnMemory: null, onDispose: onDispose, ); @@ -122,6 +129,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) { _init(); return _openByFunc( @@ -132,16 +140,18 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { sourceName: filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); } Future _openData( Uint8List data, String sourceName, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - int? maxSizeToCacheOnMemory, - void Function()? onDispose, + required PdfPasswordProvider? passwordProvider, + required bool firstAttemptByEmptyPassword, + required bool useProgressiveLoading, + required int? maxSizeToCacheOnMemory, + required void Function()? onDispose, }) { return openCustom( read: (buffer, position, size) { @@ -158,6 +168,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, onDispose: onDispose, ); @@ -170,6 +181,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { @@ -195,6 +207,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, disposeCallback: () { try { onDispose?.call(); @@ -224,6 +237,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, disposeCallback: () { try { onDispose?.call(); @@ -243,6 +257,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, @@ -252,6 +267,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, reportCallback: reportCallback, useRangeAccess: preferRangeAccess, @@ -273,6 +289,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { required String sourceName, required PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, void Function()? disposeCallback, }) async { for (int i = 0; ; i++) { @@ -290,6 +307,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { return PdfDocumentPdfium.fromPdfDocument( pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), sourceName: sourceName, + useProgressiveLoading: useProgressiveLoading, disposeCallback: disposeCallback, ); } @@ -331,7 +349,8 @@ class PdfDocumentPdfium extends PdfDocument { static Future fromPdfDocument( pdfium_bindings.FPDF_DOCUMENT doc, { required String sourceName, - void Function()? disposeCallback, + required bool useProgressiveLoading, + required void Function()? disposeCallback, }) async { if (doc == nullptr) { throw const PdfException('Failed to load PDF document.'); @@ -376,8 +395,9 @@ class PdfDocumentPdfium extends PdfDocument { formInfo: Pointer.fromAddress(result.formInfo), disposeCallback: disposeCallback, ); - - final pages = await pdfDoc._loadPagesInTime(maxPageCountToLoadAdditionally: 1); + final pages = await pdfDoc._loadPagesInLimitedTime( + maxPageCountToLoadAdditionally: useProgressiveLoading ? 1 : null, + ); pdfDoc._pages = List.unmodifiable(pages.pages); return pdfDoc; } catch (e) { @@ -388,32 +408,38 @@ class PdfDocumentPdfium extends PdfDocument { @override Future loadPagesProgressively( - FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { - T? context, + PdfPageLoadingCallback? onPageLoadProgress, { + T? data, Duration loadUnitDuration = const Duration(seconds: 1), }) async { for (;;) { if (isDisposed) return; - final loaded = await _loadPagesInTime( - pagesLoadedSoFar: _pages.takeWhile((p) => p.isLoaded).toList(), + final firstUnloadedPageIndex = _pages.indexWhere((p) => !p.isLoaded); + if (firstUnloadedPageIndex == -1) { + // All pages are already loaded + return; + } + + final loaded = await _loadPagesInLimitedTime( + pagesLoadedSoFar: _pages.sublist(0, firstUnloadedPageIndex).toList(), timeout: loadUnitDuration, ); if (isDisposed) return; _pages = List.unmodifiable(loaded.pages); - if (onPageLoaded != null) { - final result = await onPageLoaded(context, loaded.pageCountLoaded, loaded.pages.length); + if (onPageLoadProgress != null) { + final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); if (result == false) { // If the callback returns false, stop loading pages return; } } - if (loaded.pageCountLoaded == loaded.pages.length || isDisposed) return; + if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) return; } } /// Loads pages in the document in a time-limited manner. - Future<({List pages, int pageCountLoaded})> _loadPagesInTime({ + Future<({List pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ List pagesLoadedSoFar = const [], int? maxPageCountToLoadAdditionally, Duration? timeout, @@ -423,42 +449,34 @@ class PdfDocumentPdfium extends PdfDocument { (params) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); return using((arena) { - try { - final pageCount = pdfium.FPDF_GetPageCount(doc); - final end = - maxPageCountToLoadAdditionally == null - ? pageCount - : min(pageCount, params.pagesCountLoadesSoFar + params.maxPageCountToLoadAdditionally!); - final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; - final start = DateTime.now(); - final pages = <({double width, double height, int rotation})>[]; - for (int i = params.pagesCountLoadesSoFar; i < end; i++) { - final page = pdfium.FPDF_LoadPage(doc, i); - try { - pages.add(( - width: pdfium.FPDF_GetPageWidthF(page), - height: pdfium.FPDF_GetPageHeightF(page), - rotation: pdfium.FPDFPage_GetRotation(page), - )); - } finally { - pdfium.FPDF_ClosePage(page); - } - if (t != null && DateTime.now().isAfter(t)) { - break; - } + final pageCount = pdfium.FPDF_GetPageCount(doc); + final end = + maxPageCountToLoadAdditionally == null + ? pageCount + : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); + final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; + final pages = <({double width, double height, int rotation})>[]; + for (int i = params.pagesCountLoadedSoFar; i < end; i++) { + final page = pdfium.FPDF_LoadPage(doc, i); + try { + pages.add(( + width: pdfium.FPDF_GetPageWidthF(page), + height: pdfium.FPDF_GetPageHeightF(page), + rotation: pdfium.FPDFPage_GetRotation(page), + )); + } finally { + pdfium.FPDF_ClosePage(page); + } + if (t != null && DateTime.now().isAfter(t)) { + break; } - - return (pages: pages, totalPageCount: pageCount); - } catch (e) { - pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); - calloc.free(formInfo); - rethrow; } + return (pages: pages, totalPageCount: pageCount); }); }, ( docAddress: document.address, - pagesCountLoadesSoFar: pagesLoadedSoFar.length, + pagesCountLoadedSoFar: pagesLoadedSoFar.length, maxPageCountToLoadAdditionally: maxPageCountToLoadAdditionally, timeoutUs: timeout?.inMicroseconds, ), @@ -478,8 +496,8 @@ class PdfDocumentPdfium extends PdfDocument { ), ); } - final pageCountLoaded = pages.length; - if (pageCountLoaded > 0) { + final pageCountLoadedTotal = pages.length; + if (pageCountLoadedTotal > 0) { final last = pages.last; for (int i = pages.length; i < results.totalPageCount; i++) { pages.add( @@ -494,7 +512,7 @@ class PdfDocumentPdfium extends PdfDocument { ); } } - return (pages: pages, pageCountLoaded: pageCountLoaded); + return (pages: pages, pageCountLoadedTotal: pageCountLoadedTotal); } catch (e) { rethrow; } diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart index 5850278f..4f204627 100644 --- a/lib/src/web/pdfrx_js.dart +++ b/lib/src/web/pdfrx_js.dart @@ -17,6 +17,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) => _openByFunc( (password) async { // NOTE: Moving the asset load outside the loop may cause: @@ -27,6 +28,8 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { sourceName: 'asset_js:$name', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + onDispose: null, ); @override @@ -36,6 +39,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { @@ -46,6 +50,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, onDispose: onDispose, ); } @@ -55,6 +60,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { Uint8List data, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, String? sourceName, bool allowDataOwnershipTransfer = false, void Function()? onDispose, @@ -68,6 +74,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { sourceName: sourceName ?? 'memory_js:${data.hashCode}', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, onDispose: onDispose, ); } @@ -77,11 +84,14 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) => _openByFunc( (password) => pdfjsGetDocument(filePath, password: password), sourceName: filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + onDispose: null, ); @override @@ -89,6 +99,7 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, @@ -100,14 +111,17 @@ class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { sourceName: 'uri_js:$uri', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + onDispose: null, ); Future _openByFunc( Future Function(String? password) openDocument, { required String sourceName, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - void Function()? onDispose, + required PdfPasswordProvider? passwordProvider, + required bool firstAttemptByEmptyPassword, + required bool useProgressiveLoading, + required void Function()? onDispose, }) async { for (int i = 0; ; i++) { final String? password; @@ -195,14 +209,11 @@ class PdfDocumentJs extends PdfDocument { @override Future loadPagesProgressively( - FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { - T? context, + FutureOr Function(int currentPageNumber, int totalPageCount, T? data)? onPageLoadProgress, { + T? data, Duration loadUnitDuration = const Duration(seconds: 1), }) async { - throw UnimplementedError( - 'PdfDocumentJs.loadPagesAsync is not implemented. ' - 'Use PdfDocumentJs.fromDocument to load all pages at once.', - ); + throw UnimplementedError('PdfDocumentJs.loadPagesProgressively is not implemented.'); } @override diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 06fbc8fc..a70be035 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -82,6 +82,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) async { final asset = await rootBundle.load(name); final data = asset.buffer.asUint8List(); @@ -89,6 +90,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, sourceName: name, allowDataOwnershipTransfer: true, ); @@ -101,10 +103,11 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { - throw UnimplementedError(); + throw UnimplementedError('PdfDocumentFactoryWasmImpl.openCustom is not implemented.'); } @override @@ -112,14 +115,19 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { Uint8List data, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, String? sourceName, bool allowDataOwnershipTransfer = false, void Function()? onDispose, }) => _openByFunc( - (password) => sendCommand('loadDocumentFromData', parameters: {'data': data, 'password': password}), + (password) => sendCommand( + 'loadDocumentFromData', + parameters: {'data': data, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + ), sourceName: sourceName ?? 'data', factory: this, passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: onDispose, ); @@ -128,11 +136,17 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, }) => _openByFunc( - (password) => sendCommand('loadDocumentFromUrl', parameters: {'url': filePath, 'password': password}), + (password) => sendCommand( + 'loadDocumentFromUrl', + parameters: {'url': filePath, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + ), sourceName: filePath, factory: this, passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + onDispose: null, ); @override @@ -140,25 +154,31 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, Map? headers, bool withCredentials = false, }) => _openByFunc( - (password) => sendCommand('loadDocumentFromUrl', parameters: {'url': uri.toString(), 'password': password}), + (password) => sendCommand( + 'loadDocumentFromUrl', + parameters: {'url': uri.toString(), 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + ), sourceName: uri.toString(), factory: this, passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + onDispose: null, ); Future _openByFunc( Future> Function(String? password) openDocument, { required String sourceName, required PdfDocumentFactoryWasmImpl factory, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - void Function()? onDispose, + required PdfPasswordProvider? passwordProvider, + required bool firstAttemptByEmptyPassword, + required void Function()? onDispose, }) async { for (int i = 0; ; i++) { final String? password; @@ -192,7 +212,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { class PdfDocumentWasm extends PdfDocument { PdfDocumentWasm._(this.document, {required super.sourceName, required this.factory, this.disposeCallback}) : permissions = parsePermissions(document) { - pages = parsePages(this, document); + pages = parsePages(this, document['pages'] as List); } final Map document; @@ -237,14 +257,35 @@ class PdfDocumentWasm extends PdfDocument { @override Future loadPagesProgressively( - FutureOr Function(T? context, int currentPageNumber, int totalPageCount)? onPageLoaded, { - T? context, + PdfPageLoadingCallback? onPageLoadProgress, { + T? data, Duration loadUnitDuration = const Duration(seconds: 1), }) async { - throw UnimplementedError( - 'PdfDocumentWasm.loadPagesAsync is not implemented. ' - 'Use PdfDocumentJs.fromDocument to load all pages at once.', - ); + if (isDisposed) return; + int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); + if (firstPageIndex < 0) return; // All pages are already loaded + + for (; firstPageIndex < pages.length;) { + final result = await factory.sendCommand( + 'loadPagesProgressively', + parameters: { + 'docHandle': document['docHandle'], + 'firstPageIndex': firstPageIndex, + 'loadUnitDuration': loadUnitDuration.inMilliseconds, + }, + ); + final pagesLoaded = parsePages(this, result['pages'] as List); + firstPageIndex += pagesLoaded.length; + for (final page in pagesLoaded) { + pages[page.pageNumber - 1] = page; // Update the existing page + } + if (onPageLoadProgress != null) { + if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { + // If the callback returns false, stop loading more pages + break; + } + } + } } @override @@ -260,8 +301,7 @@ class PdfDocumentWasm extends PdfDocument { } } - static List parsePages(PdfDocumentWasm doc, Map document) { - final pageList = document['pages'] as List; + static List parsePages(PdfDocumentWasm doc, List pageList) { return pageList .map( (page) => PdfPageWasm( diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 93c38579..dd0d6775 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -293,6 +293,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _releaseAllImages(); _canvasLinkPainter.resetAll(); _pageNumber = null; + _gotoTargetPageNumber = null; _initialized = false; _txController.removeListener(_onMatrixChanged); _controller?._attach(null); @@ -318,14 +319,31 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } _notifyOnDocumentChanged(); + _loadDelayed(); + } + + /// How long to wait before loading the trailing pages after the initial page load. + /// + /// This is to ensure that the initial page is displayed quickly, and the trailing pages are loaded in the background. + static const _trailingPageLoadingDelay = Duration(milliseconds: kIsWeb ? 200 : 100); + + /// How long to wait before loading the page image. + static const _pageImageCachingDelay = Duration(milliseconds: kIsWeb ? 100 : 20); + static const _partialImageLoadingDelay = Duration(milliseconds: kIsWeb ? 200 : 50); + + Future _loadDelayed() async { + // To make the page image loading more smooth, delay the loading of pages + await Future.delayed(_trailingPageLoadingDelay); - _document?.loadPagesProgressively((document, pageNumber, totalPageCount) { + final stopwatch = Stopwatch()..start(); + await _document?.loadPagesProgressively((pageNumber, totalPageCount, document) { if (document == _document && mounted) { + debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); _invalidate(); return true; } return false; - }, context: _document); + }, data: _document); } void _notifyOnDocumentChanged() { @@ -1011,7 +1029,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _pageImageRenderingTimers[page.pageNumber]?.cancel(); if (!mounted) return; _pageImageRenderingTimers[page.pageNumber] = Timer( - const Duration(milliseconds: 50), + _pageImageCachingDelay, () => _cachePageImage(page, width, height, scale), ); } @@ -1054,7 +1072,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); final cancellationToken = page.createCancellationToken(); _pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( - Timer(const Duration(milliseconds: 300), () async { + Timer(_partialImageLoadingDelay, () async { if (!mounted || cancellationToken.isCanceled) return; final newImage = await _createPartialImage(page, scale, cancellationToken); if (_pageImagesPartial[page.pageNumber] == newImage) return; diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/wasm/pdfrx_wasm/assets/pdfium_worker.js index 67f9167c..22d57ff1 100644 --- a/wasm/pdfrx_wasm/assets/pdfium_worker.js +++ b/wasm/pdfrx_wasm/assets/pdfium_worker.js @@ -471,26 +471,28 @@ async function registerFontFromUrl(params) { } /** - * @param {{url: string, password: string|undefined}} params + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean}} params */ async function loadDocumentFromUrl(params) { const url = params.url; const password = params.password || ""; - + const useProgressiveLoading = params.useProgressiveLoading || false; + const response = await fetch(url); if (!response.ok) { throw new Error("Failed to fetch PDF from URL: " + url); } - return loadDocumentFromData({data: await response.arrayBuffer(), url: url, password}); + return loadDocumentFromData({data: await response.arrayBuffer(), password, useProgressiveLoading}); } /** - * @param {{data: ArrayBuffer, password: string|undefined}} params + * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean}} params */ function loadDocumentFromData(params) { const data = params.data; const password = params.password || ""; + const useProgressiveLoading = params.useProgressiveLoading; const sizeThreshold = 1024 * 1024; // 1MB if (data.byteLength < sizeThreshold) { @@ -506,7 +508,7 @@ function loadDocumentFromData(params) { passwordPtr, ); StringUtils.freeUTF8(passwordPtr); - return _loadDocument(docHandle, () => Pdfium.wasmExports.free(buffer)); + return _loadDocument(docHandle, useProgressiveLoading, () => Pdfium.wasmExports.free(buffer)); } const tempFileName = params.url ?? '/tmp/temp.pdf'; @@ -517,7 +519,7 @@ function loadDocumentFromData(params) { const docHandle = Pdfium.wasmExports.FPDF_LoadDocument(fileNamePtr, passwordPtr); StringUtils.freeUTF8(passwordPtr); StringUtils.freeUTF8(fileNamePtr); - return _loadDocument(docHandle, () => fileSystem.unregisterFile(tempFileName)); + return _loadDocument(docHandle, useProgressiveLoading, () => fileSystem.unregisterFile(tempFileName)); } @@ -526,16 +528,19 @@ const disposers = {}; /** * @typedef {{docHandle: number,permissions: number, securityHandlerRevision: number, pages: PdfPage[], formHandle: number, formInfo: number}} PdfDocument - * @typedef {{pageIndex: number, width: number, height: number, rotation: number}} PdfPage + * @typedef {{pageIndex: number, width: number, height: number, rotation: number, isLoaded: boolean}} PdfPage * @typedef {{errorCode: number, errorCodeStr: string|undefined, message: string}} PdfError */ /** - * @param {number} docHandle + * @param {number} docHandle + * @param {boolean} useProgressiveLoading * @param {function():void} onDispose * @returns {PdfDocument|PdfError} */ -function _loadDocument(docHandle, onDispose) { +function _loadDocument(docHandle, useProgressiveLoading, onDispose) { + let formInfo = 0; + let formHandle = 0; try { if (!docHandle) { const error = Pdfium.wasmExports.FPDF_GetLastError(); @@ -548,31 +553,24 @@ function _loadDocument(docHandle, onDispose) { const securityHandlerRevision = Pdfium.wasmExports.FPDF_GetSecurityHandlerRevision(docHandle); const formInfoSize = 35 * 4; - let formInfo = Pdfium.wasmExports.malloc(formInfoSize); + formInfo = Pdfium.wasmExports.malloc(formInfoSize); const uint32 = new Uint32Array(Pdfium.memory.buffer, formInfo, formInfoSize >> 2); uint32[0] = 1; // version - const formHandle = Pdfium.wasmExports.FPDFDOC_InitFormFillEnvironment(docHandle, formInfo); - if (formHandle === 0) { - formInfo = 0; - } + formHandle = Pdfium.wasmExports.FPDFDOC_InitFormFillEnvironment(docHandle, formInfo); - const pages = []; - for (let i = 0; i < pageCount; i++) { - const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, i); - if (!pageHandle) { - const error = Pdfium.wasmExports.FPDF_GetLastError(); - throw new Error(`FPDF_LoadPage failed (${_getErrorMessage(error)})`); + const pages = _loadPagesInLimitedTime(docHandle, 0, useProgressiveLoading ? 1 : unknown); + if (useProgressiveLoading) { + const firstPage = pages[0]; + for (let i = 1; i < pageCount; i++) { + pages.push({ + pageIndex: i, + width: firstPage.width, + height: firstPage.height, + rotation: firstPage.rotation, + isLoaded: false, + }); } - - pages.push({ - pageIndex: i, - width: Pdfium.wasmExports.FPDF_GetPageWidth(pageHandle), - height: Pdfium.wasmExports.FPDF_GetPageHeight(pageHandle), - rotation: Pdfium.wasmExports.FPDFPage_GetRotation(pageHandle) - }); - Pdfium.wasmExports.FPDF_ClosePage(pageHandle); } - disposers[docHandle] = onDispose; return { docHandle: docHandle, @@ -583,6 +581,9 @@ function _loadDocument(docHandle, onDispose) { formInfo: formInfo, }; } catch (e) { + try { + if (formHandle !== 0) Pdfium.wasmExports.FPDFDOC_ExitFormFillEnvironment(formHandle); + } catch (e) {} Pdfium.wasmExports.free(formInfo); delete disposers[docHandle]; onDispose(); @@ -590,6 +591,52 @@ function _loadDocument(docHandle, onDispose) { } } +/** + * @param {number} docHandle + * @param {number} pagesLoadedCountSoFar + * @param {number|null|unknown} maxPageCountToLoadAdditionally + * @param {number} timeoutMs + * @returns {PdfPage[]} + */ +function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountToLoadAdditionally, timeoutMs) { + const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); + const end = maxPageCountToLoadAdditionally == null + ? pageCount : Math.min(pageCount, pagesLoadedCountSoFar + maxPageCountToLoadAdditionally); + const t = timeoutMs != null ? Date.now() + timeoutMs : null; + /** @type {PdfPage[]} */ + const pages = []; + for (let i = pagesLoadedCountSoFar; i < end; i++) { + const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, i); + if (!pageHandle) { + const error = Pdfium.wasmExports.FPDF_GetLastError(); + throw new Error(`FPDF_LoadPage failed (${_getErrorMessage(error)})`); + } + + pages.push({ + pageIndex: i, + width: Pdfium.wasmExports.FPDF_GetPageWidth(pageHandle), + height: Pdfium.wasmExports.FPDF_GetPageHeight(pageHandle), + rotation: Pdfium.wasmExports.FPDFPage_GetRotation(pageHandle), + isLoaded: true, + }); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + if (t != null && Date.now() > t) { + break; + } + } + return pages; +} + +/** + * @param {{docHandle: number, loadUnitDuration: number}} params + * @returns {{pages: PdfPage[]}} + */ +function loadPagesProgressively(params) { + const { docHandle, firstPageIndex, loadUnitDuration } = params; + const pages = _loadPagesInLimitedTime(docHandle, firstPageIndex, null, loadUnitDuration); + return {pages}; +} + /** * @param {{formHandle: number, formInfo: number, docHandle: number}} params */ @@ -729,9 +776,6 @@ function renderPage(params) { Pdfium.wasmExports.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, backgroundColor); const FPDF_ANNOT = 1; - const FPDF_RENDER_NO_SMOOTHTEXT = 0x1000; - const FPDF_RENDER_NO_SMOOTHIMAGE = 0x2000; - const FPDF_RENDER_NO_SMOOTHPATH = 0x4000; const PdfAnnotationRenderingMode_none = 0; const PdfAnnotationRenderingMode_annotationAndForms = 2; @@ -745,7 +789,7 @@ function renderPage(params) { 0, flags | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0), ); - + if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, flags); } @@ -1120,6 +1164,7 @@ const functions = { registerFontFromUrl, loadDocumentFromUrl, loadDocumentFromData, + loadPagesProgressively, closeDocument, loadOutline, loadPage, From ab90c4a038ebbfb0f280b52901f97cabd2676fc6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 13 Jun 2025 01:29:15 +0900 Subject: [PATCH 120/663] Remove pdf.js support and integrate WASM module to pdfrx plugin. pdfrx_wasm is no longer needed. --- .github/workflows/github-pages.yml | 1 - .vscode/launch.json | 2 +- CLAUDE.md | 29 +- README.md | 3 +- .../pdfrx_wasm/assets => assets}/pdfium.wasm | Bin .../assets => assets}/pdfium_client.js | 0 .../assets => assets}/pdfium_worker.js | 0 bin/remove_wasm_modules.dart | 40 ++ example/viewer/lib/main.dart | 8 - example/viewer/pubspec.lock | 69 ++- example/viewer/pubspec.yaml | 3 - example/viewer/web/index.html | 6 - lib/pdfrx.dart | 1 - lib/src/pdf_api.dart | 37 +- lib/src/pdfium/pdfrx_pdfium.dart | 10 +- lib/src/web/pdfjs.dart | 387 ------------- lib/src/web/pdfjs_configuration.dart | 45 -- lib/src/web/pdfrx_js.dart | 523 ------------------ lib/src/web/pdfrx_wasm.dart | 74 ++- lib/src/web/pdfrx_web.dart | 54 -- pubspec.yaml | 4 + wasm/pdfrx_wasm/.gitignore | 33 -- wasm/pdfrx_wasm/CHANGELOG.md | 20 - wasm/pdfrx_wasm/LICENSE | 11 - wasm/pdfrx_wasm/README.md | 7 - wasm/pdfrx_wasm/pubspec.yaml | 20 - 26 files changed, 199 insertions(+), 1188 deletions(-) rename {wasm/pdfrx_wasm/assets => assets}/pdfium.wasm (100%) rename {wasm/pdfrx_wasm/assets => assets}/pdfium_client.js (100%) rename {wasm/pdfrx_wasm/assets => assets}/pdfium_worker.js (100%) create mode 100644 bin/remove_wasm_modules.dart delete mode 100644 lib/src/web/pdfjs.dart delete mode 100644 lib/src/web/pdfjs_configuration.dart delete mode 100644 lib/src/web/pdfrx_js.dart delete mode 100644 lib/src/web/pdfrx_web.dart delete mode 100644 wasm/pdfrx_wasm/.gitignore delete mode 100644 wasm/pdfrx_wasm/CHANGELOG.md delete mode 100644 wasm/pdfrx_wasm/LICENSE delete mode 100644 wasm/pdfrx_wasm/README.md delete mode 100644 wasm/pdfrx_wasm/pubspec.yaml diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 84c1818d..a3971d4b 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -39,7 +39,6 @@ jobs: sed -i \ -e 's|||' \ -e "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" \ - -e "s/__PDFIUM_WASM_DISABLED__/enabled/g" \ build/web/index.html - name: Configure Git for deployment run: | diff --git a/.vscode/launch.json b/.vscode/launch.json index 3dd4e510..0ec78403 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "request": "launch", "type": "dart", "program": "example/viewer/lib/main.dart", - "args": ["-d", "chrome", "--wasm", "--dart-define=pdfrx.enablePdfiumWasm=true"] + "args": ["-d", "chrome", "--wasm"] } ] } diff --git a/CLAUDE.md b/CLAUDE.md index 9ec8f788..5c36c7d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ pdfrx is a cross-platform PDF viewer plugin for Flutter that supports iOS, Andro ## Development Commands ### Basic Flutter Commands + ```bash flutter pub get # Install dependencies flutter analyze # Run static analysis @@ -17,6 +18,7 @@ flutter format . # Format code (120 char line width) ``` ### Platform-Specific Builds + ```bash # Example app cd example/viewer @@ -51,23 +53,23 @@ dart run ffigen # Regenerate PDFium FFI bindings - Changes version in example fragments - Consider to add notes for new features or breaking changes - Notify the owner if you find any issues with the example app or documentation -4. Do the same for `wasm/pdfrx_wasm/` if applicable - - `wasm/pdfrx_wasm/assets/` may contain changes critical to the web version, so ensure to update the version in `wasm/pdfrx_wasm/pubspec.yaml` as well -5. Run `flutter pub get` on all affected directories +4. Run `flutter pub get` on all affected directories - This includes the main package, example app, and wasm package if applicable - Ensure all dependencies are resolved and up-to-date -6. Run tests to ensure everything works -7. Commit changes with message "Release vX.Y.Z" -8. Tag the commit with `git tag vX.Y.Z` -9. Push changes and tags to remote -10. Do `flutter pub publish` to publish the package +5. Run tests to ensure everything works +6. Commit changes with message "Release vX.Y.Z" +7. Tag the commit with `git tag vX.Y.Z` +8. Push changes and tags to remote +9. Do `flutter pub publish` to publish the package ## Architecture Overview ### Platform Abstraction + The plugin uses conditional imports to support different platforms: + - `lib/src/pdfium/` - Native platform implementation using PDFium via FFI -- `lib/src/web/` - Web implementation supporting PDF.js (default) and PDFium WASM +- `lib/src/web/` - Web implementation by PDFium WASM - Platform-specific code determined at import time based on `dart:library.io` availability ### Core Components @@ -99,6 +101,7 @@ The plugin uses conditional imports to support different platforms: ## Testing Tests download PDFium binaries automatically for supported platforms. Run tests with: + ```bash flutter test flutter test test/pdf_document_test.dart # Run specific test file @@ -107,20 +110,24 @@ flutter test test/pdf_document_test.dart # Run specific test file ## Platform-Specific Notes ### iOS/macOS + - Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) - CocoaPods integration via `darwin/pdfrx.podspec` - Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) ### Android + - Uses CMake for native build - Requires Android NDK - Downloads PDFium binaries during build ### Web -- Default: PDF.js for better compatibility -- Optional: PDFium WASM for better performance/compatibility + +- Prebuilt PDFium WASM included in the plugin +- Pdf.js backend is removed ### Windows/Linux + - CMake-based build - Downloads PDFium binaries during build diff --git a/README.md b/README.md index 65a2730f..72de06fa 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ A [demo site](https://espresso3389.github.io/pdfrx/) using Flutter Web - Windows - macOS - Linux (even on Raspberry PI) -- Web - - By default, pdfrx uses [PDF.js](https://mozilla.github.io/pdf.js/) but you can enable [Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support) +- Web (WASM) ## Example Code diff --git a/wasm/pdfrx_wasm/assets/pdfium.wasm b/assets/pdfium.wasm similarity index 100% rename from wasm/pdfrx_wasm/assets/pdfium.wasm rename to assets/pdfium.wasm diff --git a/wasm/pdfrx_wasm/assets/pdfium_client.js b/assets/pdfium_client.js similarity index 100% rename from wasm/pdfrx_wasm/assets/pdfium_client.js rename to assets/pdfium_client.js diff --git a/wasm/pdfrx_wasm/assets/pdfium_worker.js b/assets/pdfium_worker.js similarity index 100% rename from wasm/pdfrx_wasm/assets/pdfium_worker.js rename to assets/pdfium_worker.js diff --git a/bin/remove_wasm_modules.dart b/bin/remove_wasm_modules.dart new file mode 100644 index 00000000..d9ef547c --- /dev/null +++ b/bin/remove_wasm_modules.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +import 'package:dart_pubspec_licenses/dart_pubspec_licenses.dart' as oss; +import 'package:path/path.dart' as path; + +Future main(List args) async { + try { + final projectRoot = args.isEmpty ? '.' : args.first; + final pubspecLock = File(path.join(projectRoot, 'pubspec.lock')); + if (!pubspecLock.existsSync()) { + print('No pubspec.lock found in $projectRoot'); + return 2; + } + + final deps = await oss.listDependencies(pubspecLockPath: pubspecLock.path); + final pdfrxWasmPackage = deps.allDependencies.firstWhere((p) => p.name == 'pdfrx'); + print('Found: ${pdfrxWasmPackage.name} ${pdfrxWasmPackage.version}: ${pdfrxWasmPackage.pubspecYamlPath}'); + + final modulesDir = Directory(path.join(path.dirname(pdfrxWasmPackage.pubspecYamlPath!), 'assets')); + if (!modulesDir.existsSync()) { + print('Not found: $modulesDir'); + return 3; + } + + for (final file in modulesDir.listSync()) { + if (file is File) { + print('Deleting: ${file.path}'); + // try { + // file.deleteSync(); + // } catch (e) { + // print('Error deleting file: ${file.path}, error: $e'); + // } + } + } + return 0; + } catch (e, s) { + print('Error: $e\n$s'); + return 1; + } +} diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index a0c57425..b42157cb 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -12,15 +12,7 @@ import 'password_dialog.dart'; import 'search_view.dart'; import 'thumbnails_view.dart'; -const isWasmEnabled = bool.fromEnvironment('pdfrx.enablePdfiumWasm'); - void main() { - // NOTE: - // For Pdfium WASM support, see https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support - if (isWasmEnabled) { - Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; - } - runApp(const MyApp()); } diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 6644e3de..475fed0d 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_pubspec_licenses: + dependency: transitive + description: + name: dart_pubspec_licenses + sha256: "23ddb78ff9204d08e3109ced67cd3c6c6a066f581b0edf5ee092fc3e1127f4ea" + url: "https://pub.dev" + source: hosted + version: "3.0.4" fake_async: dependency: transitive description: @@ -77,10 +85,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file_selector: dependency: "direct main" description: @@ -93,10 +101,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" + sha256: "6bba3d590ee9462758879741abc132a19133600dd31832f55627442f1ebd7b54" url: "https://pub.dev" source: hosted - version: "0.5.1+12" + version: "0.5.1+14" file_selector_ios: dependency: transitive description: @@ -117,10 +125,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+3" file_selector_platform_interface: dependency: transitive description: @@ -141,10 +149,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.3+4" flutter: dependency: "direct main" description: flutter @@ -177,10 +185,10 @@ packages: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_parser: dependency: transitive description: @@ -197,6 +205,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -273,10 +289,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -315,14 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.34" - pdfrx_wasm: - dependency: "direct main" - description: - path: "../../wasm/pdfrx_wasm" - relative: true - source: path - version: "1.1.10" + version: "1.1.35" platform: dependency: transitive description: @@ -428,18 +437,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.14" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_linux: dependency: transitive description: @@ -468,10 +477,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -512,6 +521,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.29.0" diff --git a/example/viewer/pubspec.yaml b/example/viewer/pubspec.yaml index d507dd6e..fff04372 100644 --- a/example/viewer/pubspec.yaml +++ b/example/viewer/pubspec.yaml @@ -14,8 +14,6 @@ dependencies: pdfrx: path: ../../ - pdfrx_wasm: - path: ../../wasm/pdfrx_wasm/ cupertino_icons: ^1.0.8 rxdart: ^0.28.0 @@ -26,7 +24,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 flutter: diff --git a/example/viewer/web/index.html b/example/viewer/web/index.html index ff0eccf3..64196cf0 100644 --- a/example/viewer/web/index.html +++ b/example/viewer/web/index.html @@ -17,12 +17,6 @@ - - diff --git a/lib/pdfrx.dart b/lib/pdfrx.dart index b22ceea4..d7e03a0e 100644 --- a/lib/pdfrx.dart +++ b/lib/pdfrx.dart @@ -1,6 +1,5 @@ export 'src/pdf_api.dart'; export 'src/pdf_document_ref.dart'; -export 'src/web/pdfjs_configuration.dart'; export 'src/widgets/pdf_page_text_overlay.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 9fbbcee5..3364b1e5 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -10,7 +10,7 @@ import 'package:http/http.dart' as http; // The trick to support Flutter Web is to use conditional import // Both of the files define PdfDocumentFactoryImpl class but only one of them is imported. import '../pdfrx.dart'; -import 'web/pdfrx_web.dart' if (dart.library.io) 'pdfium/pdfrx_pdfium.dart'; +import 'web/pdfrx_wasm.dart' if (dart.library.io) 'pdfium/pdfrx_pdfium.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. @@ -32,27 +32,22 @@ class Pdfrx { /// It is not supported on Flutter Web. static http.Client Function()? createHttpClient; - /// Select the Web runtime type. - /// - /// To use PDFium (WASM) runtime, set this value to [PdfrxWebRuntimeType.pdfiumWasm] and you must add - /// [pdfrx_wasm](https://pub.dartlang.org/packages/pdfrx_wasm) to your `pubspec.yaml`'s `dependencies`. - /// - /// It is used only when on Flutter Web. - static PdfrxWebRuntimeType webRuntimeType = PdfrxWebRuntimeType.pdfjs; + /// pdfrx always uses PDFium (WASM) on Flutter Web and the runtime type is not used now. + @Deprecated('PdfrxWebRuntimeType is not used now. pdfrx always uses PDFium (WASM) on Flutter Web.') + static PdfrxWebRuntimeType webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. - /// - /// It is used only when on Flutter Web with [Pdfrx.webRuntimeType] is [PdfrxWebRuntimeType.pdfiumWasm]. static String? pdfiumWasmModulesUrl; } /// Web runtime type. +@Deprecated('PdfrxWebRuntimeType is not working now. pdfrx always uses PDFium (WASM) on Flutter Web.') enum PdfrxWebRuntimeType { - /// Use PDF.js. - pdfjs, - /// Use PDFium (WASM). pdfiumWasm, + + /// Pdf.js is no longer supported. + pdfjs, } /// For platform abstraction purpose; use [PdfDocument] instead. @@ -120,12 +115,22 @@ abstract class PdfDocumentFactory { /// For Flutter Web, it uses PDFium (WASM) implementation. static PdfDocumentFactory get pdfium => getPdfiumDocumentFactory(); - /// Get [PdfDocumentFactory] that uses PDF.js implementation. - /// - /// It is only supported on Flutter Web. + /// Pdf.js is no longer supported. + /// This function is deprecated and will throw an error if called. + @Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') static PdfDocumentFactory get pdfjs => getPdfjsDocumentFactory(); } +/// Pdf.js is no longer supported. +/// This function is deprecated and will throw an error if called. +@Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') +PdfDocumentFactory getPdfjsDocumentFactory() { + throw UnsupportedError('PdfDocumentFactory backed by PDF.js is no longer supported.'); +} + +/// Get the default [PdfDocumentFactory]. +PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); + /// Callback function to notify download progress. /// /// [downloadedBytes] is the number of bytes downloaded so far. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 114be938..aaf8c94d 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -24,14 +24,6 @@ PdfDocumentFactory? _pdfiumDocumentFactory; /// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= PdfDocumentFactoryImpl(); -/// Get [PdfDocumentFactory] backed by PDF.js. -/// -/// It throws [UnsupportedError] on non-Web platforms. -PdfDocumentFactory getPdfjsDocumentFactory() => throw UnsupportedError('Pdf.js is only supported on Web'); - -/// Get the default [PdfDocumentFactory]. -PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); - /// Get the module file name for pdfium. String _getModuleFileName() { if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; @@ -410,7 +402,7 @@ class PdfDocumentPdfium extends PdfDocument { Future loadPagesProgressively( PdfPageLoadingCallback? onPageLoadProgress, { T? data, - Duration loadUnitDuration = const Duration(seconds: 1), + Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { for (;;) { if (isDisposed) return; diff --git a/lib/src/web/pdfjs.dart b/lib/src/web/pdfjs.dart deleted file mode 100644 index 69cd52d0..00000000 --- a/lib/src/web/pdfjs.dart +++ /dev/null @@ -1,387 +0,0 @@ -// ignore_for_file: avoid_web_libraries_in_flutter - -@JS() -library; - -import 'dart:async'; -import 'dart:developer' as developer; -import 'dart:js_interop'; -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; -import 'package:synchronized/extension.dart'; -import 'package:web/web.dart' as web; - -import '../../pdfrx.dart'; -import 'js_utils.dart'; - -/// Default pdf.js version -const _pdfjsVersion = '4.10.38'; - -/// Default pdf.js URL -const _pdfjsUrl = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@$_pdfjsVersion/build/pdf.min.mjs'; - -/// Default pdf.worker.js URL -const _pdfjsWorkerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@$_pdfjsVersion/build/pdf.worker.min.mjs'; - -/// Default CMap URL -const _pdfjsCMapUrl = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@$_pdfjsVersion/cmaps/'; - -@JS('pdfjsLib') -external JSAny? get _pdfjsLib; - -bool get _isPdfjsLoaded => _pdfjsLib != null; - -@JS('pdfjsLib.getDocument') -external _PDFDocumentLoadingTask _pdfjsGetDocument(_PdfjsDocumentInitParameters data); - -extension type _PdfjsDocumentInitParameters._(JSObject _) implements JSObject { - external _PdfjsDocumentInitParameters({ - String? url, - JSArrayBuffer? data, - JSAny? httpHeaders, - bool? withCredentials, - String? password, - String? cMapUrl, - bool? cMapPacked, - bool? useSystemFonts, - String? standardFontDataUrl, - }); - - external String? get url; - external JSArrayBuffer? get data; - external JSAny? get httpHeaders; - external bool? get withCredentials; - external String? get password; - external String? get cMapUrl; - external bool? get cMapPacked; - external bool? get useSystemFonts; - external String? get standardFontDataUrl; -} - -@JS('pdfjsLib.GlobalWorkerOptions.workerSrc') -external set _pdfjsWorkerSrc(String src); - -extension type _PDFDocumentLoadingTask(JSObject _) implements JSObject { - external JSPromise get promise; -} - -Future pdfjsGetDocument( - String url, { - String? password, - Map? headers, - bool withCredentials = false, -}) => - _pdfjsGetDocument( - _PdfjsDocumentInitParameters( - url: url, - password: password, - httpHeaders: headers?.jsify(), - withCredentials: withCredentials, - cMapUrl: PdfJsConfiguration.configuration?.cMapUrl ?? _pdfjsCMapUrl, - cMapPacked: PdfJsConfiguration.configuration?.cMapPacked ?? true, - useSystemFonts: PdfJsConfiguration.configuration?.useSystemFonts, - standardFontDataUrl: PdfJsConfiguration.configuration?.standardFontDataUrl, - ), - ).promise.toDart; - -/// [allowDataOwnershipTransfer] is used to determine if the data buffer can be transferred to the worker thread. -Future pdfjsGetDocumentFromData( - ByteBuffer data, { - String? password, - bool allowDataOwnershipTransfer = false, -}) async { - if (!allowDataOwnershipTransfer) { - // We may need to duplicate the buffer if it is "technically transferrable". - if (data.isTechnicallyTransferrable) { - data = data.duplicate(); - } - } - - final result = - await _pdfjsGetDocument( - _PdfjsDocumentInitParameters( - data: data.toJS, - password: password, - cMapUrl: PdfJsConfiguration.configuration?.cMapUrl ?? _pdfjsCMapUrl, - cMapPacked: PdfJsConfiguration.configuration?.cMapPacked ?? true, - useSystemFonts: PdfJsConfiguration.configuration?.useSystemFonts, - standardFontDataUrl: PdfJsConfiguration.configuration?.standardFontDataUrl, - ), - ).promise.toDart; - return result; -} - -extension _ByteBufferExtensions on ByteBuffer { - bool get isTechnicallyTransferrable { - if (!kIsWeb) throw UnsupportedError('This method is only available on web'); - // if NOT running with Flutter WASM runtime, every ByteBuffer can be transferrable - if (!kIsWasm) return true; - // if running with Flutter WASM runtime, only JSArrayBufferImpl can be transferrable (I believe) - return runtimeType.toString() == 'JSArrayBufferImpl'; - } - - ByteBuffer duplicate() => Uint8List.fromList(asUint8List()).buffer; -} - -extension type PdfjsDocument._(JSObject _) implements JSObject { - external JSPromise getPage(int pageNumber); - external JSPromise?> getPermissions(); - external int get numPages; - external void destroy(); - - external JSPromise getPageIndex(PdfjsRef ref); - external JSPromise getDestination(String id); - external JSPromise?> getOutline(); -} - -extension type PdfjsPage._(JSObject _) implements JSObject { - external PdfjsViewport getViewport(PdfjsViewportParams params); - external PdfjsRender render(PdfjsRenderContext params); - external int get pageNumber; - external int get rotate; - external JSNumber get userUnit; - external JSArray get view; - - external JSPromise getTextContent(PdfjsGetTextContentParameters params); - external ReadableStream streamTextContent(PdfjsGetTextContentParameters params); - - external JSPromise> getAnnotations(PdfjsGetAnnotationsParameters params); -} - -extension type PdfjsAnnotation._(JSObject _) implements JSObject { - external String get subtype; - external int get annotationType; - external JSArray get rect; - external String? get url; - external String? get unsafeUrl; - external int get annotationFlags; - external JSAny? get dest; -} - -extension type PdfjsViewportParams._(JSObject _) implements JSObject { - external PdfjsViewportParams({ - double scale, - int rotation, // 0, 90, 180, 270 - double offsetX, - double offsetY, - bool dontFlip, - }); - - external double scale; - external int rotation; - external double offsetX; - external double offsetY; - external bool dontFlip; -} - -extension type PdfjsViewport(JSObject _) implements JSObject { - external JSArray viewBox; - - external double scale; - - /// 0, 90, 180, 270 - external int rotation; - external double offsetX; - external double offsetY; - external bool dontFlip; - - external double width; - external double height; - - external JSArray? transform; -} - -extension type PdfjsRenderContext._(JSObject _) implements JSObject { - external PdfjsRenderContext({ - required web.CanvasRenderingContext2D canvasContext, - required PdfjsViewport viewport, - String intent, - int annotationMode, - bool renderInteractiveForms, - JSArray? transform, - JSObject imageLayer, - JSObject canvasFactory, - JSObject background, - }); - - external web.CanvasRenderingContext2D canvasContext; - external PdfjsViewport viewport; - - /// `display` or `print` - external String intent; - - /// DISABLE=0, ENABLE=1, ENABLE_FORMS=2, ENABLE_STORAGE=3 - external int annotationMode; - external bool renderInteractiveForms; - external JSArray? transform; - external JSObject imageLayer; - external JSObject canvasFactory; - external JSObject background; -} - -extension type PdfjsRender._(JSObject _) implements JSObject { - external JSPromise get promise; -} - -extension type PdfjsGetTextContentParameters._(JSObject _) implements JSObject { - external PdfjsGetTextContentParameters({bool includeMarkedContent, bool disableNormalization}); - - external bool includeMarkedContent; - external bool disableNormalization; -} - -extension type PdfjsTextContent._(JSObject _) implements JSObject { - /// Either [PdfjsTextItem] or [PdfjsTextMarkedContent] - external JSArray get items; - external JSObject get styles; -} - -extension type PdfjsTextItem._(JSObject _) implements JSObject { - external String get str; - - /// Text direction: `ttb`, `ltr` or `rtl`. - external String get dir; - - /// Matrix for transformation, in the form `[a, b, c, d, e, f]`, equivalent to: - /// ``` - /// | a b 0 | - /// | c d 0 | - /// | e f 1 | - /// ``` - /// - /// Translation is performed with `[1, 0, 0, 1, tx, ty]`. - /// - /// Scaling is performed with `[sx, 0, 0, sy, 0, 0]`. - /// - /// See PDF Reference 1.7, 4.2.2 Common Transformations for more. - external JSArray get transform; - external num get width; - external num get height; - external String get fontName; - external bool get hasEOL; -} - -extension type PdfjsTextMarkedContent._(JSObject _) implements JSObject { - external String get type; - external String get id; -} - -extension type PdfjsTextStyle._(JSObject _) implements JSObject { - external num get ascent; - external num get descent; - external bool get vertical; - external String get fontFamily; -} - -extension type PdfjsBaseException._(JSObject _) implements JSObject { - external String get message; - external String get name; -} - -extension type PdfjsPasswordException._(JSObject _) implements JSObject { - external String get message; - external String get name; - external String get code; -} - -extension type PdfjsGetAnnotationsParameters._(JSObject _) implements JSObject { - external PdfjsGetAnnotationsParameters({String intent}); - - /// `display` or `print` or, `any` - external String get intent; -} - -extension type PdfjsRef._(JSObject _) implements JSObject { - external int get num; - external int get gen; -} - -extension type PdfjsAnnotationData._(JSObject _) implements JSObject { - external String get subtype; - external int get annotationType; - external JSArray get rect; - external String? get url; - external String? get unsafeUrl; - external int get annotationFlags; - external JSObject? get dest; -} - -extension type PdfjsOutlineNode._(JSObject _) implements JSObject { - external String get title; - external JSAny? get dest; - external JSArray get items; -} - -final _dummyJsSyncContext = {}; - -bool _pdfjsInitialized = false; - -Future ensurePdfjsInitialized() async { - if (_pdfjsInitialized) return; - await _dummyJsSyncContext.synchronized(() async { - if (_pdfjsInitialized) return; - if (_isPdfjsLoaded) { - _pdfjsInitialized = true; - return; - } - - developer.log( - 'pdfrx Web status:\n' - '- Running WASM: $kIsWasm\n' - '- SharedArrayBuffer: $isSharedArrayBufferSupported', - ); - if (kIsWasm && !isSharedArrayBufferSupported) { - developer.log( - 'WARNING: SharedArrayBuffer is not enabled and WASM is running in single thread mode. Enable SharedArrayBuffer by setting the following HTTP header on your server:\n' - ' Cross-Origin-Embedder-Policy: require-corp|credentialless\n' - ' Cross-Origin-Opener-Policy: same-origin\n', - ); - } - - final pdfJsSrc = PdfJsConfiguration.configuration?.pdfJsSrc ?? _pdfjsUrl; - - final script = - web.document.createElement('script') as web.HTMLScriptElement - ..type = 'text/javascript' - ..charset = 'utf-8' - ..async = true - ..type = 'module' - ..src = pdfJsSrc; - web.document.querySelector('head')!.appendChild(script); - final completer = Completer(); - final sub1 = script.onLoad.listen((_) => completer.complete()); - final sub2 = script.onError.listen((event) => completer.completeError(event)); - try { - await completer.future; - } catch (e) { - throw StateError('Failed to load pdf.js from $pdfJsSrc: $e'); - } finally { - await sub1.cancel(); - await sub2.cancel(); - } - - if (!_isPdfjsLoaded) { - throw StateError('Failed to load pdfjs'); - } - _pdfjsWorkerSrc = PdfJsConfiguration.configuration?.workerSrc ?? _pdfjsWorkerSrc; - - _pdfjsInitialized = true; - }); -} - -extension type ReadableStream._(JSObject _) implements JSObject { - external JSPromise cancel(); - external ReadableStreamDefaultReader getReader(JSObject options); -} - -extension type ReadableStreamDefaultReader._(JSObject _) implements JSObject { - external JSPromise cancel(JSObject reason); - external JSPromise read(); - external void releaseLock(); -} - -extension type ReadableStreamChunk._(JSObject _) implements JSObject { - external JSObject get value; - external bool get done; -} diff --git a/lib/src/web/pdfjs_configuration.dart b/lib/src/web/pdfjs_configuration.dart deleted file mode 100644 index 8dda4b4c..00000000 --- a/lib/src/web/pdfjs_configuration.dart +++ /dev/null @@ -1,45 +0,0 @@ -/// Configuration for the PDF.js library. -/// -/// Set [PdfJsConfiguration.configuration] before using any APIs. It can be typically set in the main function. -class PdfJsConfiguration { - const PdfJsConfiguration({ - required this.pdfJsSrc, - required this.workerSrc, - required this.cMapUrl, - required this.cMapPacked, - this.useSystemFonts = true, - this.standardFontDataUrl, - }); - - /// `psf.js` file URL such as https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.min.mjs - final String pdfJsSrc; - - /// `psf.worker.js` file URL such as https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.worker.min.mjs - final String workerSrc; - - /// `cmaps` directory URL such as https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/cmaps/ - final String cMapUrl; - - /// Whether to use the packed cmaps. The default is true. - final bool cMapPacked; - - /// When true, fonts that aren't embedded in the PDF document will fallback to a system font. - /// The default is true. - final bool useSystemFonts; - - /// The URL where the standard font files are located. Include the trailing slash. - final String? standardFontDataUrl; - - /// The current configuration. null to use the default. - /// - /// To customize the pdf.js download URLs, set this before using any APIs.: - /// - /// ```dart - /// PdfJsConfiguration.configuration = const PdfJsConfiguration( - /// pdfJsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.min.mjs', - /// workerSrc: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.worker.min.mjs', - /// cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/cmaps/', - /// ); - /// ``` - static PdfJsConfiguration? configuration; -} diff --git a/lib/src/web/pdfrx_js.dart b/lib/src/web/pdfrx_js.dart deleted file mode 100644 index 4f204627..00000000 --- a/lib/src/web/pdfrx_js.dart +++ /dev/null @@ -1,523 +0,0 @@ -import 'dart:async'; -import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; -import 'dart:ui'; - -import 'package:flutter/services.dart'; -import 'package:web/web.dart' as web; - -import '../../pdfrx.dart'; -import 'pdfjs.dart'; - -class PdfDocumentFactoryJsImpl extends PdfDocumentFactory { - PdfDocumentFactoryJsImpl(); - - @override - Future openAsset( - String name, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }) => _openByFunc( - (password) async { - // NOTE: Moving the asset load outside the loop may cause: - // Uncaught TypeError: Cannot perform Construct on a detached ArrayBuffer - final bytes = await rootBundle.load(name); - return await pdfjsGetDocumentFromData(bytes.buffer, password: password, allowDataOwnershipTransfer: true); - }, - sourceName: 'asset_js:$name', - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - onDispose: null, - ); - - @override - Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, - required int fileSize, - required String sourceName, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - int? maxSizeToCacheOnMemory, - void Function()? onDispose, - }) async { - final buffer = Uint8List(fileSize); - await read(buffer, 0, fileSize); - return _openByFunc( - (password) => pdfjsGetDocumentFromData(buffer.buffer, password: password), - sourceName: sourceName, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - onDispose: onDispose, - ); - } - - @override - Future openData( - Uint8List data, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - String? sourceName, - bool allowDataOwnershipTransfer = false, - void Function()? onDispose, - }) async { - return _openByFunc( - (password) => pdfjsGetDocumentFromData( - data.buffer, - password: password, - allowDataOwnershipTransfer: allowDataOwnershipTransfer, - ), - sourceName: sourceName ?? 'memory_js:${data.hashCode}', - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - onDispose: onDispose, - ); - } - - @override - Future openFile( - String filePath, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }) => _openByFunc( - (password) => pdfjsGetDocument(filePath, password: password), - sourceName: filePath, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - onDispose: null, - ); - - @override - Future openUri( - Uri uri, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, - bool preferRangeAccess = false, - Map? headers, - bool withCredentials = false, - }) => _openByFunc( - (password) => - pdfjsGetDocument(uri.toString(), password: password, headers: headers, withCredentials: withCredentials), - sourceName: 'uri_js:$uri', - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - onDispose: null, - ); - - Future _openByFunc( - Future Function(String? password) openDocument, { - required String sourceName, - required PdfPasswordProvider? passwordProvider, - required bool firstAttemptByEmptyPassword, - required bool useProgressiveLoading, - required void Function()? onDispose, - }) async { - for (int i = 0; ; i++) { - final String? password; - if (firstAttemptByEmptyPassword && i == 0) { - password = null; - } else { - password = await passwordProvider?.call(); - if (password == null) { - throw const PdfPasswordException('No password supplied by PasswordProvider.'); - } - } - try { - await ensurePdfjsInitialized(); - - return PdfDocumentJs.fromDocument(await openDocument(password), sourceName: sourceName, onDispose: onDispose); - } catch (e) { - if (!_isPasswordError(e)) { - rethrow; - } - } - } - } - - static bool _isPasswordError(dynamic e) => e.toString().startsWith('PasswordException:'); -} - -class PdfDocumentJs extends PdfDocument { - PdfDocumentJs._( - this._document, { - required super.sourceName, - required this.isEncrypted, - required this.permissions, - this.onDispose, - }); - - @override - final bool isEncrypted; - @override - final PdfPermissions? permissions; - - final PdfjsDocument _document; - final void Function()? onDispose; - - static Future fromDocument( - PdfjsDocument document, { - required String sourceName, - void Function()? onDispose, - }) async { - final perms = (await document.getPermissions().toDart)?.toDart.map((v) => v.toDartInt).toList(); - final doc = PdfDocumentJs._( - document, - sourceName: sourceName, - isEncrypted: perms != null, - permissions: perms != null ? PdfPermissions(perms.fold(0, (p, e) => p | e), 2) : null, - onDispose: onDispose, - ); - final pageCount = document.numPages; - final pages = []; - for (int i = 0; i < pageCount; i++) { - pages.add(await doc._getPage(document, i + 1)); - } - doc.pages = List.unmodifiable(pages); - return doc; - } - - @override - Future dispose() async { - _document.destroy(); - onDispose?.call(); - } - - Future _getPage(PdfjsDocument document, int pageNumber) async { - final page = await _document.getPage(pageNumber).toDart; - final vp1 = page.getViewport(PdfjsViewportParams(scale: 1)); - return PdfPageJs._( - document: this, - pageNumber: pageNumber, - page: page, - width: vp1.width, - height: vp1.height, - rotation: PdfPageRotation.values[page.rotate ~/ 90], - isLoaded: true, - ); - } - - @override - Future loadPagesProgressively( - FutureOr Function(int currentPageNumber, int totalPageCount, T? data)? onPageLoadProgress, { - T? data, - Duration loadUnitDuration = const Duration(seconds: 1), - }) async { - throw UnimplementedError('PdfDocumentJs.loadPagesProgressively is not implemented.'); - } - - @override - late final List pages; - - @override - bool isIdenticalDocumentHandle(Object? other) => other is PdfDocumentJs && _document == other._document; - - Future _getDestObject(JSAny? dest) async { - if (dest == null) return null; - if (dest.isA()) { - return await _document.getDestination((dest as JSString).toDart).toDart; - } else { - return dest as JSObject; - } - } - - @override - Future> loadOutline() async { - final outline = await _document.getOutline().toDart; - if (outline == null) return []; - final nodes = []; - for (final node in outline.toDart) { - nodes.add(await _pdfOutlineNodeFromOutline(node)); - } - return nodes; - } - - Future _pdfOutlineNodeFromOutline(PdfjsOutlineNode outline) async { - final children = []; - for (final item in outline.items.toDart) { - children.add(await _pdfOutlineNodeFromOutline(item)); - } - return PdfOutlineNode(title: outline.title, dest: await _getDestination(outline.dest), children: children); - } - - /// NOTE: The returned [PdfDest] is always compacted. - Future _getDestination(JSAny? dest) async { - final destObj = await _getDestObject(dest); - if (!destObj.isA()) return null; - final arr = (destObj as JSArray).toDart; - final ref = arr[0] as PdfjsRef; - final cmdStr = _getName(arr[1]); - final List? params; - if (arr.length < 3) { - params = null; - } else { - params = List.unmodifiable(arr.sublist(2).map((v) => (v as JSNumber?)?.toDartDouble)); - } - - return PdfDest((await _document.getPageIndex(ref).toDart).toDartInt + 1, PdfDestCommand.parse(cmdStr), params); - } - - static String _getName(JSAny? name) { - if (name == null) { - throw ArgumentError.notNull('name'); - } - if (name.isA()) { - return (name as JSString).toDart; - } - final nameValue = (name as JSObject).getProperty('name'.toJS)?.toString(); - if (nameValue == null) { - throw ArgumentError.value(name, 'name', 'Invalid name object.'); - } - return nameValue; - } -} - -class PdfPageJs extends PdfPage { - PdfPageJs._({ - required this.document, - required this.pageNumber, - required this.page, - required this.width, - required this.height, - required this.rotation, - required this.isLoaded, - }); - @override - final PdfDocumentJs document; - @override - final int pageNumber; - final PdfjsPage page; - @override - final double width; - @override - final double height; - @override - final PdfPageRotation rotation; - @override - final bool isLoaded; - - @override - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - Color? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }) async { - if (cancellationToken == null) { - cancellationToken = PdfPageRenderCancellationTokenWeb(); - } else if (cancellationToken is! PdfPageRenderCancellationTokenWeb) { - throw ArgumentError( - 'cancellationToken must be created by PdfPage.createCancellationToken().', - 'cancellationToken', - ); - } - fullWidth ??= this.width; - fullHeight ??= this.height; - width ??= fullWidth.toInt(); - height ??= fullHeight.toInt(); - - return PdfImageWeb( - width: width, - height: height, - pixels: await _renderRaw( - x, - y, - width, - height, - fullWidth, - fullHeight, - backgroundColor, - false, - annotationRenderingMode, - cancellationToken as PdfPageRenderCancellationTokenWeb, - ), - ); - } - - @override - PdfPageRenderCancellationTokenWeb createCancellationToken() => PdfPageRenderCancellationTokenWeb(); - - Future _renderRaw( - int x, - int y, - int width, - int height, - double fullWidth, - double fullHeight, - Color? backgroundColor, - bool dontFlip, - PdfAnnotationRenderingMode annotationRenderingMode, - PdfPageRenderCancellationTokenWeb cancellationToken, - ) async { - final vp1 = page.getViewport(PdfjsViewportParams(scale: 1)); - final pageWidth = vp1.width; - if (width <= 0 || height <= 0) { - throw PdfException('Invalid PDF page rendering rectangle ($width x $height)'); - } - - final vp = page.getViewport( - PdfjsViewportParams( - scale: fullWidth / pageWidth, - offsetX: -x.toDouble(), - offsetY: -y.toDouble(), - dontFlip: dontFlip, - ), - ); - - final canvas = web.document.createElement('canvas') as web.HTMLCanvasElement; - canvas.width = width; - canvas.height = height; - - if (backgroundColor != null) { - canvas.context2D.fillStyle = '#${backgroundColor.toARGB32().toRadixString(16).padLeft(8, '0')}'.toJS; - canvas.context2D.fillRect(0, 0, width, height); - } - - await page - .render( - PdfjsRenderContext( - canvasContext: canvas.context2D, - viewport: vp, - annotationMode: annotationRenderingMode.index, - ), - ) - .promise - .toDart; - - return canvas.context2D.getImageData(0, 0, width, height).data.toDart.buffer.asUint8List(); - } - - @override - Future loadText() => PdfPageTextJs._loadText(this); - - @override - Future> loadLinks({bool compact = false}) async { - final annots = (await page.getAnnotations(PdfjsGetAnnotationsParameters()).toDart).toDart; - final links = []; - for (final annot in annots) { - if (annot.subtype != 'Link') { - continue; - } - final rects = List.unmodifiable([ - PdfRect( - // be careful with the order of the rect values - annot.rect[0].toDartDouble, // L - annot.rect[3].toDartDouble, // T - annot.rect[2].toDartDouble, // R - annot.rect[1].toDartDouble, // B - ), - ]); - if (annot.url != null) { - links.add(PdfLink(rects, url: Uri.tryParse(annot.url!))); - continue; - } - final dest = await document._getDestination(annot.dest); - if (dest != null) { - links.add(PdfLink(rects, dest: dest)); - continue; - } - } - return compact ? List.unmodifiable(links) : links; - } -} - -class PdfPageRenderCancellationTokenWeb extends PdfPageRenderCancellationToken { - bool _canceled = false; - @override - void cancel() => _canceled = true; - - @override - bool get isCanceled => _canceled; -} - -class PdfImageWeb extends PdfImage { - PdfImageWeb({required this.width, required this.height, required this.pixels, this.format = PixelFormat.rgba8888}); - - @override - final int width; - @override - final int height; - @override - final Uint8List pixels; - @override - final PixelFormat format; - @override - void dispose() {} -} - -class PdfPageTextFragmentWeb implements PdfPageTextFragment { - PdfPageTextFragmentWeb(this.index, this.bounds, this.text); - - @override - final int index; - @override - int get length => text.length; - @override - int get end => index + length; - @override - final PdfRect bounds; - @override - List? get charRects => null; - @override - final String text; -} - -class PdfPageTextJs extends PdfPageText { - PdfPageTextJs({required this.pageNumber, required this.fullText, required this.fragments}); - - @override - final int pageNumber; - - @override - final String fullText; - @override - final List fragments; - - static Future _loadText(PdfPageJs page) async { - final content = - await page.page - .getTextContent(PdfjsGetTextContentParameters(includeMarkedContent: false, disableNormalization: false)) - .toDart; - final sb = StringBuffer(); - final fragments = []; - for (final item in content.items.toDart) { - final x = item.transform[4].toDartDouble; - final y = item.transform[5].toDartDouble; - final str = item.hasEOL ? '${item.str}\n' : item.str; - if (str == '\n' && fragments.isNotEmpty) { - final prev = fragments.last; - fragments.add( - PdfPageTextFragmentWeb( - sb.length, - PdfRect(prev.bounds.right, prev.bounds.top, prev.bounds.right + item.width.toDouble(), prev.bounds.bottom), - str, - ), - ); - } else { - fragments.add( - PdfPageTextFragmentWeb(sb.length, PdfRect(x, y + item.height.toDouble(), x + item.width.toDouble(), y), str), - ); - } - - sb.write(str); - } - - return PdfPageTextJs(pageNumber: page.pageNumber, fullText: sb.toString(), fragments: fragments); - } -} diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index a70be035..3734e641 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -8,7 +8,12 @@ import 'package:flutter/services.dart'; import 'package:web/web.dart' as web; import '../pdf_api.dart'; -import 'pdfrx_js.dart'; + +/// Get [PdfDocumentFactory] backed by Pdfium. +/// +/// For Flutter Web, you must set up Pdfium WASM module. +/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). +PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryWasmImpl.singleton; /// Calls PDFium WASM worker with the given command and parameters. @JS() @@ -22,15 +27,19 @@ external String pdfiumWasmWorkerUrl; /// [PdfDocumentFactory] for PDFium WASM implementation. class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { - PdfDocumentFactoryWasmImpl(); + PdfDocumentFactoryWasmImpl._(); + + static final singleton = PdfDocumentFactoryWasmImpl._(); /// Default path to the WASM modules /// /// Normally, the WASM modules are provided by pdfrx_wasm package and this is the path to its assets. - static const defaultWasmModulePath = 'assets/packages/pdfrx_wasm/assets/'; + static const defaultWasmModulePath = 'assets/packages/pdfrx/assets/'; Future _init() async { + _globalInit(); pdfiumWasmWorkerUrl = _getWorkerUrl(); + Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); final script = web.document.createElement('script') as web.HTMLScriptElement @@ -53,6 +62,19 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } } + static bool _globalInitialized = false; + + static void _globalInit() { + if (_globalInitialized) return; + Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); + _globalInitialized = true; + } + + static String? _pdfiumWasmModulesUrlFromMetaTag() { + final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; + return meta?.content; + } + /// Workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments String _getWorkerUrl() { final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); @@ -259,7 +281,7 @@ class PdfDocumentWasm extends PdfDocument { Future loadPagesProgressively( PdfPageLoadingCallback? onPageLoadProgress, { T? data, - Duration loadUnitDuration = const Duration(seconds: 1), + Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { if (isDisposed) return; int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); @@ -452,6 +474,21 @@ class PdfPageWasm extends PdfPage { } } +class PdfImageWeb extends PdfImage { + PdfImageWeb({required this.width, required this.height, required this.pixels, this.format = ui.PixelFormat.rgba8888}); + + @override + final int width; + @override + final int height; + @override + final Uint8List pixels; + @override + final ui.PixelFormat format; + @override + void dispose() {} +} + @immutable class PdfPageTextFragmentPdfium implements PdfPageTextFragment { const PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); @@ -481,3 +518,32 @@ PdfDest? _pdfDestFromMap(dynamic dest) { params.map((p) => p as double).toList(), ); } + +class PdfPageTextFragmentWeb implements PdfPageTextFragment { + PdfPageTextFragmentWeb(this.index, this.bounds, this.text); + + @override + final int index; + @override + int get length => text.length; + @override + int get end => index + length; + @override + final PdfRect bounds; + @override + List? get charRects => null; + @override + final String text; +} + +class PdfPageTextJs extends PdfPageText { + PdfPageTextJs({required this.pageNumber, required this.fullText, required this.fragments}); + + @override + final int pageNumber; + + @override + final String fullText; + @override + final List fragments; +} diff --git a/lib/src/web/pdfrx_web.dart b/lib/src/web/pdfrx_web.dart deleted file mode 100644 index 021e6103..00000000 --- a/lib/src/web/pdfrx_web.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:web/web.dart' as web; - -import '../../pdfrx.dart'; -import 'pdfrx_js.dart'; -import 'pdfrx_wasm.dart'; - -PdfDocumentFactory? _pdfiumDocumentFactory; -PdfDocumentFactory? _pdfjsDocumentFactory; - -/// Get [PdfDocumentFactory] backed by Pdfium. -/// -/// For Flutter Web, you must set up Pdfium WASM module. -/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). -PdfDocumentFactory getPdfiumDocumentFactory() { - _init(); - return _pdfiumDocumentFactory ??= PdfDocumentFactoryWasmImpl(); -} - -/// Get [PdfDocumentFactory] backed by PDF.js. -/// -/// It throws [UnsupportedError] on non-Web platforms. -PdfDocumentFactory getPdfjsDocumentFactory() { - _init(); - return _pdfjsDocumentFactory ??= PdfDocumentFactoryJsImpl(); -} - -/// Get the default [PdfDocumentFactory]. -PdfDocumentFactory getDocumentFactory() { - _init(); - if (Pdfrx.webRuntimeType == PdfrxWebRuntimeType.pdfiumWasm) { - return getPdfiumDocumentFactory(); - } else { - return getPdfjsDocumentFactory(); - } -} - -bool _initialized = false; - -void _init() { - if (_initialized) return; - if (_isWasmShouldBeEnabled()) Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; - Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); - _initialized = true; -} - -bool _isWasmShouldBeEnabled() { - final meta = web.document.querySelector('meta[name="pdfrx-pdfium-wasm"]') as web.HTMLMetaElement?; - return meta?.content == 'enabled'; -} - -String? _pdfiumWasmModulesUrlFromMetaTag() { - final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; - return meta?.content; -} diff --git a/pubspec.yaml b/pubspec.yaml index 9c7ff415..37581aec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: url_launcher: ^6.3.1 vector_math: ^2.1.4 web: ^1.1.1 + dart_pubspec_licenses: ^3.0.4 dev_dependencies: ffigen: ^18.0.0 @@ -51,6 +52,9 @@ flutter: ffiPlugin: true web: + assets: + - assets/ + # To generate the bindings, firstly you must build example/viewer on x64 linux and # then run the following command: # dart run ffigen diff --git a/wasm/pdfrx_wasm/.gitignore b/wasm/pdfrx_wasm/.gitignore deleted file mode 100644 index e7d347d9..00000000 --- a/wasm/pdfrx_wasm/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -build/ diff --git a/wasm/pdfrx_wasm/CHANGELOG.md b/wasm/pdfrx_wasm/CHANGELOG.md deleted file mode 100644 index 0aaaf4e2..00000000 --- a/wasm/pdfrx_wasm/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -## 1.1.11 - -* Add rendering flags support to PdfPage.render method - -## 1.1.10 - -* PDFium WASM 138.0.7162.0 - -## 1.1.8 - -* FIXED: loadOutline is not implemented on Pdfium WASM - -## 1.1.7 - -* FIXED: WASM: could not open PDF files smaller than 1MB (#326) -* FIXED: WASM memory allocation issue - -## 0.0.1 - -* Initial release diff --git a/wasm/pdfrx_wasm/LICENSE b/wasm/pdfrx_wasm/LICENSE deleted file mode 100644 index 575416cb..00000000 --- a/wasm/pdfrx_wasm/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ - -The MIT License (MIT) -=============== - -Copyright (c) 2018 @espresso3389 (Takashi Kawasaki) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wasm/pdfrx_wasm/README.md b/wasm/pdfrx_wasm/README.md deleted file mode 100644 index 1ed071a2..00000000 --- a/wasm/pdfrx_wasm/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# pdfrx_wasm - -This is a satellite plugin for [pdfrx](https://pub.dev/packages/pdfrx) that allows you to use WASM version of Pdfium. - -## Usage - -See [pdfrx](https://pub.dev/packages/pdfrx)'s documentation for usage. diff --git a/wasm/pdfrx_wasm/pubspec.yaml b/wasm/pdfrx_wasm/pubspec.yaml deleted file mode 100644 index ba1efc6d..00000000 --- a/wasm/pdfrx_wasm/pubspec.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: pdfrx_wasm -description: This is a satellite plugin for pdfrx that allows you to use WASM version of Pdfium. -version: 1.1.11 -homepage: https://github.com/espresso3389/pdfrx - -environment: - sdk: '>=3.7.0 <4.0.0' - flutter: ">=3.29.0" - -dependencies: - flutter: - sdk: flutter - -flutter: - plugin: - platforms: - web: - - assets: - - assets/ From 00a137ab4a6f809c0169cdede9c9d6dd01952cf3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 13 Jun 2025 01:45:05 +0900 Subject: [PATCH 121/663] WIP --- README.md | 15 +++++++++++++++ bin/remove_wasm_modules.dart | 10 +++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 72de06fa..5c01455e 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,21 @@ dependencies: The build process internally uses *symbolic links* and it requires Developer Mode to be enabled. Without this, you may encounter errors [like this](https://github.com/espresso3389/pdfrx/issues/34). +## Note for Building Release Builds + +*Please note that the section is not applicable to Web.* + +Because the plugin contains WASM binaries as it's assets and they increase the size of the app regardless of the platform. +This is normally OK for development or debugging but you may want to remove them when building release builds. + +To do this, do `dart run pdfrx:remove_wasm_modules` between `flutter pub get` and `flutter build ...` on your app project's root directory: + +```bash +flutter pub get +dart run pdfrx:remove_wasm_modules +flutter build ... +``` + ## Customizations/Features You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). diff --git a/bin/remove_wasm_modules.dart b/bin/remove_wasm_modules.dart index d9ef547c..f5c0ceda 100644 --- a/bin/remove_wasm_modules.dart +++ b/bin/remove_wasm_modules.dart @@ -25,11 +25,11 @@ Future main(List args) async { for (final file in modulesDir.listSync()) { if (file is File) { print('Deleting: ${file.path}'); - // try { - // file.deleteSync(); - // } catch (e) { - // print('Error deleting file: ${file.path}, error: $e'); - // } + try { + file.deleteSync(); + } catch (e) { + print('Error deleting file: ${file.path}, error: $e'); + } } } return 0; From 6964170ee4557b907e148acfba9fab7084c8e3ca Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 13 Jun 2025 02:04:49 +0900 Subject: [PATCH 122/663] Release v1.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 1 + .pubignore | 1 - CHANGELOG.md | 12 ++++++++++++ CLAUDE.md | 1 + README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7ead85d9..056b4880 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ build/ /test/.tmp +.claude/ diff --git a/.pubignore b/.pubignore index c270f665..907c75c0 100644 --- a/.pubignore +++ b/.pubignore @@ -1,2 +1 @@ -# wasm/ example/viewer/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 57b14bc3..ace95a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.2.0 + +**BREAKING CHANGES:** + +- Removed PDF.js support - PDFium WASM is now the only web implementation +- The separate `pdfrx_wasm` package is no longer needed; WASM assets are now included directly in the main `pdfrx` package + +**New Features:** + +- Implemented progressive/lazy page loading for PDFium WASM for better performance with large PDF files ([#319](https://github.com/espresso3389/pdfrx/issues/319)) +- Simplified web architecture by consolidating WASM assets into the main package + # 1.1.35 - Add `limitRenderingCache` parameter to `PdfViewerParams` to control rendering cache behavior ([#394](https://github.com/espresso3389/pdfrx/pull/394)) diff --git a/CLAUDE.md b/CLAUDE.md index 5c36c7d8..23798861 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -130,6 +130,7 @@ flutter test test/pdf_document_test.dart # Run specific test file - CMake-based build - Downloads PDFium binaries during build +- If PATH contains `/mnt/.../flutter/bin`, remove it before running `flutter` or `dart` commands to avoid conflicts with WSL paths ## Code Style diff --git a/README.md b/README.md index 5c01455e..95cc66de 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.1.35 + pdfrx: ^1.2.0 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 475fed0d..bff46e63 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.1.35" + version: "1.2.0" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 37581aec..4c66dfb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.1.35 +version: 1.2.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 70d3274c645b8b08e15d367d19b6cdf5ac368b16 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 14 Jun 2025 00:11:26 +0900 Subject: [PATCH 123/663] flutter stable still does not support Windows ARM64 #395/#388 --- windows/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 08fa46e8..9b8f037f 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -23,14 +23,14 @@ set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) file(MAKE_DIRECTORY ${PDFIUM_RELEASE_DIR}) # Determine target processor name for Windows -IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") +# IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") SET(CPU_NAME "x64") -ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") - SET(CPU_NAME "arm64") -ELSE() - MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only AMD64 and ARM64 are supported.") -ENDIF() -message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) +# ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") +# SET(CPU_NAME "arm64") +# ELSE() +# MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only AMD64 and ARM64 are supported.") +# ENDIF() +# message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) set(PDFIUM_PLATFORM "win") set(PDFIUM_LIB_FILENAME "pdfium.dll") From 51cea54450632b0b86eb523cfb2036d550838a03 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 14 Jun 2025 00:26:11 +0900 Subject: [PATCH 124/663] Release v1.2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 5 +++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace95a30..333df6ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.2.1 + +- Temporarily disable Windows ARM64 architecture detection to maintain compatibility with Flutter stable ([#395](https://github.com/espresso3389/pdfrx/issues/395), [#388](https://github.com/espresso3389/pdfrx/issues/388)) + - Flutter stable doesn't support Windows ARM64 yet, so the build always targets x64 + # 1.2.0 **BREAKING CHANGES:** diff --git a/README.md b/README.md index 95cc66de..aea4876d 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.0 + pdfrx: ^1.2.1 ``` ### Note for Windows diff --git a/pubspec.yaml b/pubspec.yaml index 4c66dfb7..403b850b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.0 +version: 1.2.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From a62f618c3853560b4dd47319b550479649b2e0e1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 14 Jun 2025 09:17:28 +0900 Subject: [PATCH 125/663] WIP: add --web-experimental-hot-reload --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0ec78403..a1dd1dcf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "request": "launch", "type": "dart", "program": "example/viewer/lib/main.dart", - "args": ["-d", "chrome", "--wasm"] + "args": ["-d", "chrome", "--wasm", "--web-experimental-hot-reload"] } ] } From 4ddbc21f34a80143896c7e04187256c691b6474c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 14 Jun 2025 23:39:40 +0900 Subject: [PATCH 126/663] Update example viewer to support PDF file path/URL as a parameter. --- example/viewer/lib/main.dart | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index b42157cb..cae0db61 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -12,24 +12,30 @@ import 'password_dialog.dart'; import 'search_view.dart'; import 'thumbnails_view.dart'; -void main() { - runApp(const MyApp()); +List _args = []; + +void main(List args) { + runApp(MyApp(fileOrUri: args.isNotEmpty ? args[0] : null)); } class MyApp extends StatelessWidget { - const MyApp({super.key}); + const MyApp({this.fileOrUri, super.key}); + + final String? fileOrUri; @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( title: 'Pdfrx example', - home: MainPage(), + home: MainPage(fileOrUri: fileOrUri), ); } } class MainPage extends StatefulWidget { - const MainPage({super.key}); + const MainPage({this.fileOrUri, super.key}); + + final String? fileOrUri; @override State createState() => _MainPageState(); @@ -54,7 +60,7 @@ class _MainPageState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - openDefaultAsset(); + openInitialFile(); } @override @@ -600,7 +606,20 @@ class _MainPageState extends State with WidgetsBindingObserver { return result ?? false; } - Future openDefaultAsset() async { + Future openInitialFile() async { + if (widget.fileOrUri != null) { + final fileOrUri = widget.fileOrUri!; + if (fileOrUri.startsWith('https://') || fileOrUri.startsWith('http://')) { + documentRef.value = PdfDocumentRefUri( + Uri.parse(fileOrUri), + passwordProvider: () => passwordDialog(context), + ); + return; + } else { + documentRef.value = PdfDocumentRefFile(fileOrUri, passwordProvider: () => passwordDialog(context)); + return; + } + } documentRef.value = PdfDocumentRefAsset('assets/hello.pdf'); } From 5acb058b0417fae252cbf915e665440fc0d26114 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 16 Jun 2025 01:50:38 +0900 Subject: [PATCH 127/663] WIP --- example/viewer/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index bff46e63..c08538bc 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.0" + version: "1.2.1" platform: dependency: transitive description: From 1fa8ce508b4dfaec8f5bfc61a0ba91ec770af71e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 16 Jun 2025 23:06:42 +0900 Subject: [PATCH 128/663] Refactor build-test.yml to remove Windows ARM64 build option and add verbose flag to build commands --- .github/workflows/build-test.yml | 28 +++++++++---------- example/viewer/ios/Podfile.lock | 26 +++++++++++++++++ .../ios/Runner.xcodeproj/project.pbxproj | 18 ++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 347ee46c..c3a8815c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -37,11 +37,11 @@ on: required: false type: boolean default: true - build_windows_arm64: - description: 'Build Windows ARM64' - required: false - type: boolean - default: true + # build_windows_arm64: + # description: 'Build Windows ARM64' + # required: false + # type: boolean + # default: true build_web: description: 'Build Web' required: false @@ -76,7 +76,7 @@ jobs: - name: Build App Bundle working-directory: example/viewer - run: flutter build appbundle --debug + run: flutter build appbundle --debug --verbose # iOS build ios: @@ -99,7 +99,7 @@ jobs: - name: Build iOS (no signing) working-directory: example/viewer - run: flutter build ios --debug --no-codesign + run: flutter build ios --debug --no-codesign --verbose # macOS build macos: @@ -122,7 +122,7 @@ jobs: - name: Build macOS working-directory: example/viewer - run: flutter build macos --debug + run: flutter build macos --debug --verbose # Linux build linux: @@ -150,7 +150,7 @@ jobs: - name: Build Linux working-directory: example/viewer - run: flutter build linux --debug + run: flutter build linux --debug --verbose # Linux ARM64 build linux-arm64: @@ -178,7 +178,7 @@ jobs: - name: Build Linux working-directory: example/viewer - run: flutter build linux --debug + run: flutter build linux --debug --verbose # Windows build windows: @@ -202,7 +202,7 @@ jobs: - name: Build Windows working-directory: example/viewer - run: C:\flutter\bin\flutter.bat build windows --debug + run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Windows ARM64 build (requires ARM64 runner or cross-compilation) windows-arm64: @@ -227,7 +227,7 @@ jobs: - name: Build Windows ARM64 working-directory: example/viewer - run: C:\flutter\bin\flutter.bat build windows --debug + run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Web build web: @@ -250,8 +250,8 @@ jobs: - name: Build Web working-directory: example/viewer - run: flutter build web + run: flutter build web --verbose - name: Build Web (WASM) working-directory: example/viewer - run: flutter build web --wasm + run: flutter build web --wasm --verbose diff --git a/example/viewer/ios/Podfile.lock b/example/viewer/ios/Podfile.lock index a16608d4..820ec283 100644 --- a/example/viewer/ios/Podfile.lock +++ b/example/viewer/ios/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_ios (0.0.1): + - Flutter - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfrx (0.0.6): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: + - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: + file_selector_ios: + :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + pdfrx: + :path: ".symlinks/plugins/pdfrx/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 39b093b9..f11e0be1 100644 --- a/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 41B2057A58D2AC93EC82BCC3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -292,6 +293,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 41B2057A58D2AC93EC82BCC3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 475BC99F98491F877D325517 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 4e201a1b0cdf588ba4f15726e21b305512b95b5f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 16 Jun 2025 23:23:19 +0900 Subject: [PATCH 129/663] Enable build-test workflow on master commits and pull requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Uncomment push and pull_request triggers for master/main branches - Add build status badge to README.md - Workflow now runs automatically on commits and PRs for CI/CD 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/build-test.yml | 8 ++++---- README.md | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c3a8815c..886b4552 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,10 +1,10 @@ name: Build Test on: - # push: - # branches: [ master, main ] - # pull_request: - # branches: [ master, main ] + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] workflow_dispatch: inputs: build_android: diff --git a/README.md b/README.md index aea4876d..5da555b4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # pdfrx +[![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) + [pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer implementation built on the top of [PDFium](https://pdfium.googlesource.com/pdfium/). The plugin supports Android, iOS, Windows, macOS, Linux, and Web. From af284437ec77426d903a6dac66778fdd041d4fe9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 17 Jun 2025 00:37:13 +0900 Subject: [PATCH 130/663] --wasm and --web-experimental-hot-reload seems incompatible :( --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a1dd1dcf..0ec78403 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "request": "launch", "type": "dart", "program": "example/viewer/lib/main.dart", - "args": ["-d", "chrome", "--wasm", "--web-experimental-hot-reload"] + "args": ["-d", "chrome", "--wasm"] } ] } From a99d106bf8237e3d76b12ad77f92bd6b16a8cbe4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 17 Jun 2025 01:04:40 +0900 Subject: [PATCH 131/663] :( --- .github/workflows/build-test.yml | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 886b4552..fca05716 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -205,29 +205,29 @@ jobs: run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Windows ARM64 build (requires ARM64 runner or cross-compilation) - windows-arm64: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} - runs-on: windows-11-arm - continue-on-error: true # ARM64 build might fail on x64 runners - steps: - - uses: actions/checkout@v4 + # windows-arm64: + # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} + # runs-on: windows-11-arm + # continue-on-error: true # ARM64 build might fail on x64 runners + # steps: + # - uses: actions/checkout@v4 - - name: Setup Flutter - shell: pwsh - run: | - git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter - echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - C:\flutter\bin\flutter.bat config --no-enable-android - C:\flutter\bin\flutter.bat channel stable - C:\flutter\bin\flutter.bat doctor -v + # - name: Setup Flutter + # shell: pwsh + # run: | + # git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter + # echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + # C:\flutter\bin\flutter.bat config --no-enable-android + # C:\flutter\bin\flutter.bat channel stable + # C:\flutter\bin\flutter.bat doctor -v - - name: Install dependencies - working-directory: example/viewer - run: C:\flutter\bin\flutter.bat pub get + # - name: Install dependencies + # working-directory: example/viewer + # run: C:\flutter\bin\flutter.bat pub get - - name: Build Windows ARM64 - working-directory: example/viewer - run: C:\flutter\bin\flutter.bat build windows --debug --verbose + # - name: Build Windows ARM64 + # working-directory: example/viewer + # run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Web build web: From 6994277285fc22b82abb4f5a464281eb44e9a819 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 03:34:20 +0900 Subject: [PATCH 132/663] FIXED: #354 _emscripten_throw_longjmp --- assets/pdfium_worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 22d57ff1..960db20a 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -380,7 +380,7 @@ const emEnv = { __syscall_unlinkat: function(dirfd, pathnamePtr, flags) { _notImplemented('__syscall_unlinkat'); }, __syscall_rmdir: function(pathnamePtr) { _notImplemented('__syscall_rmdir'); }, _abort_js: function(what) { throw new Error(what); }, - _emscripten_throw_longjmp: function() { _notImplemented('longjmp'); }, + _emscripten_throw_longjmp: function() { throw Infinity; }, _gmtime_js: function(time, tmPtr) { const date = new Date(time * 1000); const tm = new Int32Array(Pdfium.memory.buffer, tmPtr, 9); From ee55e530d7641a89fe7e7a9e174d2d4eb83cfbe6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 03:44:43 +0900 Subject: [PATCH 133/663] Release v1.2.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 333df6ca..d91935f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.2.2 + +- FIXED: `_emscripten_throw_longjmp` error in PDFium WASM ([#354](https://github.com/espresso3389/pdfrx/issues/354)) +- Enhanced example viewer to support PDF file path/URL as a parameter +- Enabled build-test workflow on master commits and pull requests for better CI/CD + # 1.2.1 - Temporarily disable Windows ARM64 architecture detection to maintain compatibility with Flutter stable ([#395](https://github.com/espresso3389/pdfrx/issues/395), [#388](https://github.com/espresso3389/pdfrx/issues/388)) diff --git a/README.md b/README.md index 5da555b4..2fc4dd5c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.1 + pdfrx: ^1.2.2 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index c08538bc..19172bcd 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.1" + version: "1.2.2" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 403b850b..35659f8f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.1 +version: 1.2.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 0f4dacd14e66b5aaa14e64f542361da8025e7701 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 08:38:05 +0900 Subject: [PATCH 134/663] apk build seems faster than appbundle one --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index fca05716..0e7666c4 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,7 @@ jobs: - name: Build App Bundle working-directory: example/viewer - run: flutter build appbundle --debug --verbose + run: flutter build apk --debug --verbose # iOS build ios: From 34559ba2cb222cfd8be266af1a4a8e222aa0aa80 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 15:28:12 +0900 Subject: [PATCH 135/663] WIP --- .vscode/settings.json | 1 + CLAUDE.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ef5e5c0d..fc0fc55f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "ANNOT", "Annots", "Antialiasing", + "appbundle", "APPEARANCEMODE", "ARGB", "armeabi", diff --git a/CLAUDE.md b/CLAUDE.md index 23798861..20aa87a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,14 +123,14 @@ flutter test test/pdf_document_test.dart # Run specific test file ### Web -- Prebuilt PDFium WASM included in the plugin -- Pdf.js backend is removed +- `assets/pdfium.wasm` is prebuilt PDFium WASM binary +- `assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim +- `assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by `lib/src/web/pdfrx_wasm.dart` ### Windows/Linux - CMake-based build - Downloads PDFium binaries during build -- If PATH contains `/mnt/.../flutter/bin`, remove it before running `flutter` or `dart` commands to avoid conflicts with WSL paths ## Code Style From 831b62c1e389172bb4ccba1455ca513c356b3d0b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 18:14:38 +0900 Subject: [PATCH 136/663] Fix for #397 and enhancement for progressive loading support in PDF document references and loading functions --- assets/pdfium_worker.js | 8 ++++---- example/viewer/lib/main.dart | 20 ++++++++++++++------ lib/src/pdf_document_ref.dart | 30 +++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 960db20a..180a78fa 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -471,7 +471,7 @@ async function registerFontFromUrl(params) { } /** - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean}} params + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ async function loadDocumentFromUrl(params) { const url = params.url; @@ -487,7 +487,7 @@ async function loadDocumentFromUrl(params) { } /** - * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean}} params + * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ function loadDocumentFromData(params) { const data = params.data; @@ -558,7 +558,7 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { uint32[0] = 1; // version formHandle = Pdfium.wasmExports.FPDFDOC_InitFormFillEnvironment(docHandle, formInfo); - const pages = _loadPagesInLimitedTime(docHandle, 0, useProgressiveLoading ? 1 : unknown); + const pages = _loadPagesInLimitedTime(docHandle, 0, useProgressiveLoading ? 1 : null); if (useProgressiveLoading) { const firstPage = pages[0]; for (let i = 1; i < pageCount; i++) { @@ -594,7 +594,7 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { /** * @param {number} docHandle * @param {number} pagesLoadedCountSoFar - * @param {number|null|unknown} maxPageCountToLoadAdditionally + * @param {number|null} maxPageCountToLoadAdditionally * @param {number} timeoutMs * @returns {PdfPage[]} */ diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index cae0db61..b9f5707c 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -606,24 +606,26 @@ class _MainPageState extends State with WidgetsBindingObserver { return result ?? false; } - Future openInitialFile() async { + Future openInitialFile({bool useProgressiveLoading = true}) async { if (widget.fileOrUri != null) { final fileOrUri = widget.fileOrUri!; if (fileOrUri.startsWith('https://') || fileOrUri.startsWith('http://')) { documentRef.value = PdfDocumentRefUri( Uri.parse(fileOrUri), passwordProvider: () => passwordDialog(context), + useProgressiveLoading: useProgressiveLoading, ); return; } else { - documentRef.value = PdfDocumentRefFile(fileOrUri, passwordProvider: () => passwordDialog(context)); + documentRef.value = PdfDocumentRefFile(fileOrUri, + passwordProvider: () => passwordDialog(context), useProgressiveLoading: useProgressiveLoading); return; } } - documentRef.value = PdfDocumentRefAsset('assets/hello.pdf'); + documentRef.value = PdfDocumentRefAsset('assets/hello.pdf', useProgressiveLoading: useProgressiveLoading); } - Future openFile() async { + Future openFile({bool useProgressiveLoading = true}) async { final file = await fs.openFile(acceptedTypeGroups: [ fs.XTypeGroup( label: 'PDF files', @@ -638,13 +640,18 @@ class _MainPageState extends State with WidgetsBindingObserver { bytes, sourceName: file.name, passwordProvider: () => passwordDialog(context), + useProgressiveLoading: useProgressiveLoading, ); } else { - documentRef.value = PdfDocumentRefFile(file.path, passwordProvider: () => passwordDialog(context)); + documentRef.value = PdfDocumentRefFile( + file.path, + passwordProvider: () => passwordDialog(context), + useProgressiveLoading: useProgressiveLoading, + ); } } - Future openUri() async { + Future openUri({bool useProgressiveLoading = true}) async { final result = await showDialog( context: context, builder: (context) { @@ -685,6 +692,7 @@ class _MainPageState extends State with WidgetsBindingObserver { documentRef.value = PdfDocumentRefUri( uri, passwordProvider: () => passwordDialog(context), + useProgressiveLoading: useProgressiveLoading, ); } diff --git a/lib/src/pdf_document_ref.dart b/lib/src/pdf_document_ref.dart index 4fc3af39..b6c7c648 100644 --- a/lib/src/pdf_document_ref.dart +++ b/lib/src/pdf_document_ref.dart @@ -93,6 +93,7 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, + this.useProgressiveLoading = true, }); final String name; @@ -103,6 +104,9 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi @override String get sourceName => name; + /// Whether to use progressive loading or not. + final bool useProgressiveLoading; + @override Future loadDocument( PdfDocumentLoaderProgressCallback progressCallback, @@ -111,7 +115,7 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi name, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: true, + useProgressiveLoading: useProgressiveLoading, ); @override @@ -131,6 +135,7 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin this.preferRangeAccess = false, this.headers, this.withCredentials = false, + this.useProgressiveLoading = true, }); /// The URI to load the document. @@ -149,6 +154,9 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin /// Whether to include credentials in the request (Only supported on Web). final bool withCredentials; + /// Whether to use progressive loading or not. + final bool useProgressiveLoading; + @override String get sourceName => uri.toString(); @@ -160,7 +168,7 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: true, + useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, reportCallback: reportCallback, preferRangeAccess: preferRangeAccess, @@ -182,6 +190,7 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, + this.useProgressiveLoading = true, }); final String file; @@ -192,6 +201,9 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin @override String get sourceName => file; + /// Whether to use progressive loading or not. + final bool useProgressiveLoading; + @override Future loadDocument( PdfDocumentLoaderProgressCallback progressCallback, @@ -200,7 +212,7 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin file, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: true, + useProgressiveLoading: useProgressiveLoading, ); @override @@ -222,6 +234,7 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin super.autoDispose = true, this.allowDataOwnershipTransfer = false, this.onDispose, + this.useProgressiveLoading = true, }); final Uint8List data; @@ -235,6 +248,9 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin @override final String sourceName; + /// Whether to use progressive loading or not. + final bool useProgressiveLoading; + @override Future loadDocument( PdfDocumentLoaderProgressCallback progressCallback, @@ -243,7 +259,7 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: true, + useProgressiveLoading: useProgressiveLoading, sourceName: sourceName, allowDataOwnershipTransfer: allowDataOwnershipTransfer, onDispose: onDispose, @@ -267,6 +283,7 @@ class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMix super.autoDispose = true, this.maxSizeToCacheOnMemory, this.onDispose, + this.useProgressiveLoading = true, }); final int fileSize; @@ -281,6 +298,9 @@ class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMix @override final String sourceName; + /// Whether to use progressive loading or not. + final bool useProgressiveLoading; + @override Future loadDocument( PdfDocumentLoaderProgressCallback progressCallback, @@ -291,7 +311,7 @@ class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMix sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: true, + useProgressiveLoading: useProgressiveLoading, maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, onDispose: onDispose, ); From 056590cec03d93ae93f58a85a454a412d9d30afb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 18:18:22 +0900 Subject: [PATCH 137/663] Release v1.2.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FIXED: Progressive loading support for PDF document references (#397) - Enhanced PDF document loading functions with better error handling - Improved PDFium WASM worker implementation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d91935f1..3da638ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.2.3 + +- FIXED: Progressive loading support for PDF document references ([#397](https://github.com/espresso3389/pdfrx/issues/397)) +- Enhanced PDF document loading functions with better error handling +- Improved PDFium WASM worker implementation + # 1.2.2 - FIXED: `_emscripten_throw_longjmp` error in PDFium WASM ([#354](https://github.com/espresso3389/pdfrx/issues/354)) diff --git a/README.md b/README.md index 2fc4dd5c..6b3f328a 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.2 + pdfrx: ^1.2.3 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 19172bcd..d088f0a5 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.2" + version: "1.2.3" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 35659f8f..d6df0b99 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.2 +version: 1.2.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7fee79415cea62254f0fd1a4132fe6a3a79d2944 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 21:12:21 +0900 Subject: [PATCH 138/663] Add Windows Developer Mode requirement check --- README.md | 10 +++++++--- windows/CMakeLists.txt | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6b3f328a..8f4e0f3c 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,14 @@ dependencies: ### Note for Windows -**Ensure your Windows installation enables [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode).** +**REQUIRED: You must enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode) to build pdfrx on Windows.** -The build process internally uses *symbolic links* and it requires Developer Mode to be enabled. -Without this, you may encounter errors [like this](https://github.com/espresso3389/pdfrx/issues/34). +The build process uses *symbolic links* which requires Developer Mode to be enabled. If Developer Mode is not enabled: +- The build will fail with an error message +- You will see a link to Microsoft's official instructions +- You must enable Developer Mode and restart your computer before building + +Please follow Microsoft's official guide to enable Developer Mode as the exact steps may vary depending on your Windows version. ## Note for Building Release Builds diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 9b8f037f..00d70a99 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -75,7 +75,39 @@ if (NOT EXISTS ${PDFIUM_LIBS_DIR}/include) endif() file(REMOVE ${PDFIUM_LATEST_DIR}) -file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) + +# Check if we can create symbolic links (requires Developer Mode on Windows) +execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR}_test + RESULT_VARIABLE SYMLINK_RESULT + ERROR_QUIET + OUTPUT_QUIET +) + +if(EXISTS ${PDFIUM_LATEST_DIR}_test) + file(REMOVE ${PDFIUM_LATEST_DIR}_test) +endif() + +if(SYMLINK_RESULT EQUAL 0) + # Developer Mode is enabled, use symbolic link + file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) +else() + # Developer Mode is not enabled, fail the build + message(FATAL_ERROR "\n" + "========================================================================\n" + " ERROR: Windows Developer Mode is not enabled!\n" + "------------------------------------------------------------------------\n" + " The pdfrx build process requires Developer Mode to create symbolic\n" + " links. The build cannot continue without Developer Mode enabled.\n" + "\n" + " Please enable Developer Mode by following the instructions at:\n" + " https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode\n" + "\n" + " After enabling Developer Mode, restart your computer and run the\n" + " build again.\n" + "========================================================================\n" + ) +endif() # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an From 926c9206de8d249b8d57bc1473eae74e9225cf33 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 18 Jun 2025 18:56:36 +0900 Subject: [PATCH 139/663] Update CLAUDE.md with GitHub notification step in release process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added step 10 to the release process to notify GitHub issues/PRs when they are addressed in a new release. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 20aa87a6..26ae30f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,9 @@ dart run ffigen # Regenerate PDFium FFI bindings 7. Tag the commit with `git tag vX.Y.Z` 8. Push changes and tags to remote 9. Do `flutter pub publish` to publish the package +10. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release + - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release + - Include the version number and a link to the pub.dev release page ## Architecture Overview From ecadec3a3fd4a5b264ca5b2def54c681121f3495 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 01:36:35 +0900 Subject: [PATCH 140/663] Revert the zoom ratio calculation change on 1.1.28 that affects #391 --- lib/src/widgets/pdf_viewer.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index dd0d6775..43e8cc7e 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -666,9 +666,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return; } _minScale = - !widget.params.useAlternativeFitScaleAsMinScale || _alternativeFitScale == null + !widget.params.useAlternativeFitScaleAsMinScale ? widget.params.minScale - : _alternativeFitScale!; + : _alternativeFitScale == null + ? _coverScale! + : min(_coverScale!, _alternativeFitScale!); } void _calcZoomStopTable() { From 1ec7fdfdcee7aa6d0dcfbeb5e59e7bd07a706443 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 01:42:16 +0900 Subject: [PATCH 141/663] Release v1.2.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Claude Code https://claude.ai/code Co-Authored-By: Claude --- CHANGELOG.md | 6 ++++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da638ce..b500cc90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.2.4 + +- FIXED: Reverted zoom ratio calculation change from 1.1.28 that was affecting pinch-to-zoom behavior ([#391](https://github.com/espresso3389/pdfrx/issues/391)) +- Added Windows Developer Mode requirement check in CMake build configuration +- Updated CLAUDE.md with GitHub notification step in release process + # 1.2.3 - FIXED: Progressive loading support for PDF document references ([#397](https://github.com/espresso3389/pdfrx/issues/397)) diff --git a/README.md b/README.md index 8f4e0f3c..8274a102 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.3 + pdfrx: ^1.2.4 ``` ### Note for Windows diff --git a/pubspec.yaml b/pubspec.yaml index d6df0b99..4266d024 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.3 +version: 1.2.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 4133c6a5cfa3e9562b5b3fc9e506e61022e2f2d4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 01:56:47 +0900 Subject: [PATCH 142/663] Note for Dependency Version Policy --- CLAUDE.md | 9 +++++++++ example/viewer/pubspec.lock | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 26ae30f5..48cd8883 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -141,3 +141,12 @@ flutter test test/pdf_document_test.dart # Run specific test file - 120 character line width - Relative imports within lib/ - Follow flutter_lints with custom rules in analysis_options.yaml + +## Dependency Version Policy + +This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: +- Flutter SDK to manage these dependencies based on the user's Flutter version +- Broader compatibility across different Flutter stable versions +- Avoiding version conflicts for users on older Flutter stable releases + +When running `flutter pub publish`, warnings about missing version constraints for these packages can be safely ignored. Only packages that are not managed by Flutter SDK should have explicit version constraints. diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index d088f0a5..c249be16 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.3" + version: "1.2.4" platform: dependency: transitive description: From 6d94db042b127dc641cbd240a1ae576f1725d7a9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 02:34:25 +0900 Subject: [PATCH 143/663] Fix flag handling in PdfPagePdfium for improved rendering (#398) --- lib/src/pdfium/pdfrx_pdfium.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index aaf8c94d..500f5cc5 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -663,7 +663,7 @@ class PdfPagePdfium extends PdfPage { params.fullWidth, params.fullHeight, 0, - flags | + params.flags | (params.annotationRenderingMode != PdfAnnotationRenderingMode.none ? pdfium_bindings.FPDF_ANNOT : 0), @@ -680,7 +680,7 @@ class PdfPagePdfium extends PdfPage { params.fullWidth, params.fullHeight, 0, - flags, + params.flags, ); } return true; @@ -701,6 +701,7 @@ class PdfPagePdfium extends PdfPage { fullHeight: fullHeight!.toInt(), backgroundColor: backgroundColor!.toARGB32(), annotationRenderingMode: annotationRenderingMode, + flags: flags, formHandle: document.formHandle.address, formInfo: document.formInfo.address, cancelFlag: cancelFlag.address, From db6be920344036ce13d16e284be26ba89c889834 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 02:43:12 +0900 Subject: [PATCH 144/663] Release v1.2.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Claude Code https://claude.ai/code Co-Authored-By: Claude --- CHANGELOG.md | 5 +++++ README.md | 2 +- example/viewer/lib/main.dart | 2 -- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b500cc90..2b59198e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.2.5 + +- FIXED: Flag handling in PdfPagePdfium for improved rendering ([#398](https://github.com/espresso3389/pdfrx/issues/398)) +- Updated CLAUDE.md with dependency version policy documentation + # 1.2.4 - FIXED: Reverted zoom ratio calculation change from 1.1.28 that was affecting pinch-to-zoom behavior ([#391](https://github.com/espresso3389/pdfrx/issues/391)) diff --git a/README.md b/README.md index 8274a102..3992e018 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.4 + pdfrx: ^1.2.5 ``` ### Note for Windows diff --git a/example/viewer/lib/main.dart b/example/viewer/lib/main.dart index b9f5707c..8a64813d 100644 --- a/example/viewer/lib/main.dart +++ b/example/viewer/lib/main.dart @@ -12,8 +12,6 @@ import 'password_dialog.dart'; import 'search_view.dart'; import 'thumbnails_view.dart'; -List _args = []; - void main(List args) { runApp(MyApp(fileOrUri: args.isNotEmpty ? args[0] : null)); } diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index c249be16..f39bc9f4 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.4" + version: "1.2.5" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4266d024..21a29dc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.4 +version: 1.2.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 80ca3fa1fad25dc9adf03afcb89bbaf623726712 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 02:51:32 +0900 Subject: [PATCH 145/663] WIP --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 48cd8883..0723f7a2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,8 @@ dart run ffigen # Regenerate PDFium FFI bindings 9. Do `flutter pub publish` to publish the package 10. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - - Include the version number and a link to the pub.dev release page + - Focus on the release notes and what was fixed/changed rather than upgrade instructions + - Include a link to the changelog for the specific version ## Architecture Overview From 118c15951dd69d2d1ecee4c3fd5e99b432416f63 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 03:21:28 +0900 Subject: [PATCH 146/663] CLAUDE.md updates --- .vscode/settings.json | 1 + CHANGELOG.md | 2 -- CLAUDE.md | 29 +++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fc0fc55f..94149f26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "credentialless", "Cupertino", "d_reclen", + "dartdoc", "dartify", "Dests", "DEVICECMYK", diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b59198e..eae9936f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,11 @@ # 1.2.5 - FIXED: Flag handling in PdfPagePdfium for improved rendering ([#398](https://github.com/espresso3389/pdfrx/issues/398)) -- Updated CLAUDE.md with dependency version policy documentation # 1.2.4 - FIXED: Reverted zoom ratio calculation change from 1.1.28 that was affecting pinch-to-zoom behavior ([#391](https://github.com/espresso3389/pdfrx/issues/391)) - Added Windows Developer Mode requirement check in CMake build configuration -- Updated CLAUDE.md with GitHub notification step in release process # 1.2.3 diff --git a/CLAUDE.md b/CLAUDE.md index 0723f7a2..14ace3bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,6 +49,7 @@ dart run ffigen # Regenerate PDFium FFI bindings - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) 2. Update `CHANGELOG.md` with changes + - Don't mention CI/CD changes and `CLAUDE.md` related changes (unless they are significant) 3. Update `README.md` with new version information - Changes version in example fragments - Consider to add notes for new features or breaking changes @@ -146,8 +147,36 @@ flutter test test/pdf_document_test.dart # Run specific test file ## Dependency Version Policy This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: + - Flutter SDK to manage these dependencies based on the user's Flutter version - Broader compatibility across different Flutter stable versions - Avoiding version conflicts for users on older Flutter stable releases When running `flutter pub publish`, warnings about missing version constraints for these packages can be safely ignored. Only packages that are not managed by Flutter SDK should have explicit version constraints. + +## Documentation Guidelines + +The following guidelines should be followed when writing documentation including comments, `README.md`, and other markdown files: + +- Use proper grammar and spelling +- Use clear and concise language +- Use consistent terminology +- Use proper headings for sections +- Use code blocks for code snippets +- Use bullet points for lists +- Use link to relevant issues/PRs when applicable +- Use backticks (`` ` ``) for code references and file/directory/path names in documentation + +### Commenting Guidelines + +- Use reference links for classes, enums, and functions in documentation +- Use `///` (dartdoc comments) for public API comments (and even for important private APIs) + +### Markdown Documentation Guidelines + +- Include links to issues/PRs when relevant +- Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible +- `README.md` should provide an overview of the project, how to use it, and any important notes +- `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles + - Use sections for different versions + - Use bullet points for changes From 906ce982a27aef68b1ebbf489c88451a5bbed69e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 03:40:45 +0900 Subject: [PATCH 147/663] Documentation consistency updates --- CHANGELOG.md | 17 ++++++----------- README.md | 10 +++++----- lib/src/pdf_api.dart | 8 ++++---- lib/src/pdfium/pdfrx_pdfium.dart | 5 ++--- lib/src/web/pdfrx_wasm.dart | 6 +++--- lib/src/widgets/pdf_viewer_params.dart | 2 +- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eae9936f..ac1e7921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,14 +26,9 @@ # 1.2.0 -**BREAKING CHANGES:** - -- Removed PDF.js support - PDFium WASM is now the only web implementation -- The separate `pdfrx_wasm` package is no longer needed; WASM assets are now included directly in the main `pdfrx` package - -**New Features:** - -- Implemented progressive/lazy page loading for PDFium WASM for better performance with large PDF files ([#319](https://github.com/espresso3389/pdfrx/issues/319)) +- BREAKING CHANGE: Removed PDF.js support - PDFium WASM is now the only web implementation +- BREAKING CHANGE: The separate `pdfrx_wasm` package is no longer needed; WASM assets are now included directly in the main `pdfrx` package +- NEW FEATURE: Implemented progressive/lazy page loading for PDFium WASM for better performance with large PDF files ([#319](https://github.com/espresso3389/pdfrx/issues/319)) - Simplified web architecture by consolidating WASM assets into the main package # 1.1.35 @@ -115,11 +110,11 @@ # 1.1.21 -- FIXED: `loadOutline` is not implemented on Pdfium WASM +- FIXED: `loadOutline` is not implemented on PDFium WASM # 1.1.20 -- Add HTML meta tags (`pdfrx-pdfium-wasm`/`pdfium-wasm-module-url`) to enable Pdfium WASM support +- Add HTML meta tags (`pdfrx-pdfium-wasm`/`pdfium-wasm-module-url`) to enable PDFium WASM support # 1.1.19 @@ -170,7 +165,7 @@ # 1.1.6 -- "Bleeding edge" Pdfium WASM support (disabled by default) +- "Bleeding edge" PDFium WASM support (disabled by default) # 1.1.5 diff --git a/README.md b/README.md index 3992e018..bea0d0e8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer implementation built on the top of [PDFium](https://pdfium.googlesource.com/pdfium/). +[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer implementation built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). The plugin supports Android, iOS, Windows, macOS, Linux, and Web. ## Interactive Demo @@ -17,7 +17,7 @@ A [demo site](https://espresso3389.github.io/pdfrx/) using Flutter Web - iOS - Windows - macOS -- Linux (even on Raspberry PI) +- Linux (even on Raspberry Pi) - Web (WASM) ## Example Code @@ -72,7 +72,7 @@ Please follow Microsoft's official guide to enable Developer Mode as the exact s *Please note that the section is not applicable to Web.* -Because the plugin contains WASM binaries as it's assets and they increase the size of the app regardless of the platform. +Because the plugin contains WASM binaries as its assets and they increase the size of the app regardless of the platform. This is normally OK for development or debugging but you may want to remove them when building release builds. To do this, do `dart run pdfrx:remove_wasm_modules` between `flutter pub get` and `flutter build ...` on your app project's root directory: @@ -181,7 +181,7 @@ PdfDocumentViewBuilder.asset( ## PdfDocument Management -[PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) can accept [PdfDocumentRef](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentRef-class.html) from [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) to safely share the same [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) instance. For more information, see [example/viewer/lib/thumbnails_view.dart](example/viewer/lib/thumbnails_view.dart). +[PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) can accept [PdfDocumentRef](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentRef-class.html) from [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) to safely share the same [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) instance. For more information, see [`example/viewer/lib/thumbnails_view.dart`](example/viewer/lib/thumbnails_view.dart). ## Low Level PDF API @@ -192,4 +192,4 @@ PdfDocumentViewBuilder.asset( - Easy to use PDF APIs - [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) - PDFium bindings - - Not encouraged but you can import [package:pdfrx/src/pdfium/pdfium_bindings.dart](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart) + - Not encouraged but you can import [`package:pdfrx/src/pdfium/pdfium_bindings.dart`](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 3364b1e5..a06925c2 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -46,7 +46,7 @@ enum PdfrxWebRuntimeType { /// Use PDFium (WASM). pdfiumWasm, - /// Pdf.js is no longer supported. + /// PDF.js is no longer supported. pdfjs, } @@ -115,13 +115,13 @@ abstract class PdfDocumentFactory { /// For Flutter Web, it uses PDFium (WASM) implementation. static PdfDocumentFactory get pdfium => getPdfiumDocumentFactory(); - /// Pdf.js is no longer supported. + /// PDF.js is no longer supported. /// This function is deprecated and will throw an error if called. @Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') static PdfDocumentFactory get pdfjs => getPdfjsDocumentFactory(); } -/// Pdf.js is no longer supported. +/// PDF.js is no longer supported. /// This function is deprecated and will throw an error if called. @Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') PdfDocumentFactory getPdfjsDocumentFactory() { @@ -451,7 +451,7 @@ enum PdfAnnotationRenderingMode { none, annotation, annotationAndForms } /// Flags for [PdfPage.render]. /// -/// Basically, they are Pdfium's `FPDF_RENDER_*` flags and not supported on Pdf.js. +/// Basically, they are PDFium's `FPDF_RENDER_*` flags and not supported on PDF.js. abstract class PdfPageRenderFlags { /// None. static const none = 0; diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 500f5cc5..c2987509 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -18,10 +18,9 @@ import 'worker.dart'; PdfDocumentFactory? _pdfiumDocumentFactory; -/// Get [PdfDocumentFactory] backed by Pdfium. +/// Get [PdfDocumentFactory] backed by PDFium. /// -/// For Flutter Web, you must set up Pdfium WASM module. -/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). +/// For Flutter Web, you must set up PDFium WASM module. PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= PdfDocumentFactoryImpl(); /// Get the module file name for pdfium. diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 3734e641..3f07852e 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -9,10 +9,10 @@ import 'package:web/web.dart' as web; import '../pdf_api.dart'; -/// Get [PdfDocumentFactory] backed by Pdfium. +/// Get [PdfDocumentFactory] backed by PDFium. /// -/// For Flutter Web, you must set up Pdfium WASM module. -/// For more information, see [Enable Pdfium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-Pdfium-WASM-support). +/// For Flutter Web, you must set up PDFium WASM module. +/// For more information, see [Enable PDFium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-PDFium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryWasmImpl.singleton; /// Calls PDFium WASM worker with the given command and parameters. diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index 844abf26..e7b936f1 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -161,7 +161,7 @@ class PdfViewerParams { /// If true, the viewer limits the rendering cache to reduce memory consumption. /// - /// For Pdfium, it internally enables `FPDF_RENDER_LIMITEDIMAGECACHE` flag on rendering + /// For PDFium, it internally enables `FPDF_RENDER_LIMITEDIMAGECACHE` flag on rendering /// to reduce the memory consumption by image caching. final bool limitRenderingCache; From 6a323cbbf735cd8a3811b950776481a274cb9ec3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 15:23:32 +0900 Subject: [PATCH 148/663] WIP --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1e7921..6711550d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 1.2.5 -- FIXED: Flag handling in PdfPagePdfium for improved rendering ([#398](https://github.com/espresso3389/pdfrx/issues/398)) +- FIXED: Flag handling in `PdfPagePdfium` for improved rendering ([#398](https://github.com/espresso3389/pdfrx/issues/398)) # 1.2.4 From 606f2672d72a295c29c501b72305e351e4f9d9b3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 19 Jun 2025 15:41:45 +0900 Subject: [PATCH 149/663] Format JS files --- .prettierrc | 8 + assets/pdfium_client.js | 49 ++-- assets/pdfium_worker.js | 496 +++++++++++++++++++++++----------------- 3 files changed, 323 insertions(+), 230 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..eb33b1aa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/assets/pdfium_client.js b/assets/pdfium_client.js index 7eec551e..c74aa3bb 100644 --- a/assets/pdfium_client.js +++ b/assets/pdfium_client.js @@ -1,36 +1,37 @@ -globalThis.pdfiumWasmSendCommand = function() { +globalThis.pdfiumWasmSendCommand = (function () { const worker = new Worker(globalThis.pdfiumWasmWorkerUrl); let requestId = 0; const callbacks = new Map(); - + worker.onmessage = (event) => { - const data = event.data; - if (data.type === "ready") { - console.log("PDFium WASM worker is ready"); - return; - } - // For command responses, match using the request id. - if (data.id) { - const callback = callbacks.get(data.id); - if (callback) { - if (data.status === "success") { - callback.resolve(data.result); - } else { - callback.reject(new Error(data.error, data.cause != null ? { cause: data.cause } : undefined)); - } - callbacks.delete(data.id); + const data = event.data; + if (data.type === 'ready') { + console.log('PDFium WASM worker is ready'); + return; + } + // For command responses, match using the request id. + if (data.id) { + const callback = callbacks.get(data.id); + if (callback) { + if (data.status === 'success') { + callback.resolve(data.result); + } else { + callback.reject(new Error(data.error, data.cause != null ? { cause: data.cause } : undefined)); } + callbacks.delete(data.id); } - }; - + } + }; + worker.onerror = (err) => { - console.error("Worker error:", err); - }; + console.error('Worker error:', err); + }; - return function(command, parameters = {}, transfer = []) { + return function (command, parameters = {}, transfer = []) { return new Promise((resolve, reject) => { const id = ++requestId; callbacks.set(id, { resolve, reject }); worker.postMessage({ id, command, parameters }, transfer); - });}; -}(); + }); + }; +})(); diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 180a78fa..1f9d27b2 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -7,15 +7,15 @@ */ const Pdfium = { /** - * @param {WebAssembly.Exports} wasmExports + * @param {WebAssembly.Exports} wasmExports */ - initWith: function(wasmExports) { + initWith: function (wasmExports) { Pdfium.wasmExports = wasmExports; Pdfium.memory = Pdfium.wasmExports.memory; - Pdfium.wasmTable = Pdfium.wasmExports["__indirect_function_table"]; - Pdfium.stackSave = Pdfium.wasmExports["emscripten_stack_get_current"]; - Pdfium.stackRestore = Pdfium.wasmExports["_emscripten_stack_restore"]; - Pdfium.setThrew = Pdfium.wasmExports["setThrew"]; + Pdfium.wasmTable = Pdfium.wasmExports['__indirect_function_table']; + Pdfium.stackSave = Pdfium.wasmExports['emscripten_stack_get_current']; + Pdfium.stackRestore = Pdfium.wasmExports['_emscripten_stack_restore']; + Pdfium.setThrew = Pdfium.wasmExports['setThrew']; }, /** @@ -49,7 +49,7 @@ const Pdfium = { * @param {function(function())} func Function to call * @returns {*} Result of the function */ - invokeFunc: function(index, func) { + invokeFunc: function (index, func) { const sp = Pdfium.stackSave(); try { return func(Pdfium.wasmTable.get(index)); @@ -58,8 +58,8 @@ const Pdfium = { if (e !== e + 0) throw e; Pdfium.setThrew(1, 0); } - } -} + }, +}; /** * @typedef {Object} FileContext Defines I/O functions for a file @@ -139,7 +139,7 @@ class FileSystemEmulator { data = data.buffer != null ? data.buffer : data; this.registerFile(fn, { size: data.byteLength, - read: function(context, buffer) { + read: function (context, buffer) { try { const size = Math.min(buffer.byteLength, data.byteLength - context.position); const array = new Uint8Array(data, context.position, size); @@ -233,10 +233,10 @@ class FileSystemEmulator { /** * fd__write - * @param {num} fd - * @param {num} iovs - * @param {num} iovs_len - * @param {num} ret_ptr + * @param {num} fd + * @param {num} iovs + * @param {num} iovs_len + * @param {num} ret_ptr */ write(fd, iovs, iovs_len, ret_ptr) { const context = this.fd2context[fd]; @@ -255,12 +255,12 @@ class FileSystemEmulator { /** * fd_read - * @param {num} fd - * @param {num} iovs - * @param {num} iovs_len - * @param {num} ret_ptr + * @param {num} fd + * @param {num} iovs + * @param {num} iovs_len + * @param {num} ret_ptr */ - read(fd,iovs, iovs_len, ret_ptr) { + read(fd, iovs, iovs_len, ret_ptr) { /** @type {FileDescriptorContext} */ const context = this.fd2context[fd]; let total = 0; @@ -283,22 +283,22 @@ class FileSystemEmulator { /** * __syscall_fstat64 - * @param {num} fd - * @param {num} statbuf + * @param {num} fd + * @param {num} statbuf * @returns {num} */ fstat(fd, statbuf) { const context = this.fd2context[fd]; const buffer = new Int32Array(Pdfium.memory.buffer, statbuf, 92); buffer[6] = context.size; // st_size - buffer[7] = 0; + buffer[7] = 0; return 0; } /** * __syscall_stat64 - * @param {num} pathnamePtr - * @param {num} statbuf + * @param {num} pathnamePtr + * @param {num} statbuf * @returns {num} */ stat64(pathnamePtr, statbuf) { @@ -307,7 +307,7 @@ class FileSystemEmulator { if (funcs) { const buffer = new Int32Array(Pdfium.memory.buffer, statbuf, 92); buffer[6] = funcs.size; // st_size - buffer[7] = 0; + buffer[7] = 0; return 0; } return -1; @@ -326,7 +326,8 @@ class FileSystemEmulator { const entries = context.entries; if (entries == null) return 0; let written = 0; - const DT_REG = 8, DT_DIR = 4; + const DT_REG = 8, + DT_DIR = 4; _memset(dirp, 0, count); for (let i = context.position; i < entries.length; i++) { let d_type, d_name; @@ -357,31 +358,57 @@ class FileSystemEmulator { } return written; } -}; +} -function _error(e) { return e.stack ? e.stack.toString() : e.toString(); } +function _error(e) { + return e.stack ? e.stack.toString() : e.toString(); +} -function _notImplemented(name) { throw new Error(`${name} is not implemented`); } +function _notImplemented(name) { + throw new Error(`${name} is not implemented`); +} const fileSystem = new FileSystemEmulator(); const emEnv = { - __assert_fail: function(condition, filename, line, func) { throw new Error(`Assertion failed: ${condition} at ${filename}:${line} (${func})`); }, - _emscripten_memcpy_js: function(dest, src, num) { new Uint8Array(Pdfium.memory.buffer).copyWithin(dest, src, src + num); }, + __assert_fail: function (condition, filename, line, func) { + throw new Error(`Assertion failed: ${condition} at ${filename}:${line} (${func})`); + }, + _emscripten_memcpy_js: function (dest, src, num) { + new Uint8Array(Pdfium.memory.buffer).copyWithin(dest, src, src + num); + }, __syscall_openat: fileSystem.openFile.bind(fileSystem), __syscall_fstat64: fileSystem.fstat.bind(fileSystem), - __syscall_ftruncate64: function(fd, zero, zero2, zero3) { _notImplemented('__syscall_ftruncate64'); }, + __syscall_ftruncate64: function (fd, zero, zero2, zero3) { + _notImplemented('__syscall_ftruncate64'); + }, __syscall_stat64: fileSystem.stat64.bind(fileSystem), - __syscall_newfstatat: function(dirfd, pathnamePtr, statbuf, flags) { _notImplemented('__syscall_newfstatat'); }, - __syscall_lstat64: function(pathnamePtr, statbuf) { _notImplemented('__syscall_lstat64'); }, - __syscall_fcntl64: function(fd, cmd, arg) { _notImplemented('__syscall_fcntl64'); }, - __syscall_ioctl: function(fd, request, arg) { _notImplemented('__syscall_ioctl'); }, + __syscall_newfstatat: function (dirfd, pathnamePtr, statbuf, flags) { + _notImplemented('__syscall_newfstatat'); + }, + __syscall_lstat64: function (pathnamePtr, statbuf) { + _notImplemented('__syscall_lstat64'); + }, + __syscall_fcntl64: function (fd, cmd, arg) { + _notImplemented('__syscall_fcntl64'); + }, + __syscall_ioctl: function (fd, request, arg) { + _notImplemented('__syscall_ioctl'); + }, __syscall_getdents64: fileSystem.getdents64.bind(fileSystem), - __syscall_unlinkat: function(dirfd, pathnamePtr, flags) { _notImplemented('__syscall_unlinkat'); }, - __syscall_rmdir: function(pathnamePtr) { _notImplemented('__syscall_rmdir'); }, - _abort_js: function(what) { throw new Error(what); }, - _emscripten_throw_longjmp: function() { throw Infinity; }, - _gmtime_js: function(time, tmPtr) { + __syscall_unlinkat: function (dirfd, pathnamePtr, flags) { + _notImplemented('__syscall_unlinkat'); + }, + __syscall_rmdir: function (pathnamePtr) { + _notImplemented('__syscall_rmdir'); + }, + _abort_js: function (what) { + throw new Error(what); + }, + _emscripten_throw_longjmp: function () { + throw Infinity; + }, + _gmtime_js: function (time, tmPtr) { const date = new Date(time * 1000); const tm = new Int32Array(Pdfium.memory.buffer, tmPtr, 9); tm[0] = date.getUTCSeconds(); @@ -393,21 +420,29 @@ const emEnv = { tm[6] = date.getUTCDay(); tm[7] = 0; // dst tm[8] = 0; // gmtoff - }, - _localtime_js: function(time, tmPtr) { _notImplemented('_localtime_js'); }, - _tzset_js: function() { }, - emscripten_date_now: function() { return Date.now(); }, - emscripten_errn: function() { _notImplemented('emscripten_errn'); }, - emscripten_resize_heap: function(requestedSizeInBytes) { + }, + _localtime_js: function (time, tmPtr) { + _notImplemented('_localtime_js'); + }, + _tzset_js: function () {}, + emscripten_date_now: function () { + return Date.now(); + }, + emscripten_errn: function () { + _notImplemented('emscripten_errn'); + }, + emscripten_resize_heap: function (requestedSizeInBytes) { const maxHeapSizeInBytes = 2 * 1024 * 1024 * 1024; // 2GB if (requestedSizeInBytes > maxHeapSizeInBytes) { - console.error(`emscripten_resize_heap: Cannot enlarge memory, asked for ${requestedPageCount} bytes but limit is ${maxHeapSizeInBytes}`); + console.error( + `emscripten_resize_heap: Cannot enlarge memory, asked for ${requestedPageCount} bytes but limit is ${maxHeapSizeInBytes}` + ); return false; } const pageSize = 65536; - const oldPageCount = ((Pdfium.memory.buffer.byteLength + pageSize - 1) / pageSize)|0; - const requestedPageCount = ((requestedSizeInBytes + pageSize - 1) / pageSize)|0; + const oldPageCount = ((Pdfium.memory.buffer.byteLength + pageSize - 1) / pageSize) | 0; + const requestedPageCount = ((requestedSizeInBytes + pageSize - 1) / pageSize) | 0; const newPageCount = Math.max(oldPageCount * 1.5, requestedPageCount) | 0; try { Pdfium.memory.grow(newPageCount - oldPageCount); @@ -418,22 +453,62 @@ const emEnv = { return false; } }, - exit: function(status) { _notImplemented('exit'); }, - invoke_ii: function(index, a) { return Pdfium.invokeFunc(index, function(func) { return func(a); });}, - invoke_iii: function(index, a, b) { return Pdfium.invokeFunc(index, function(func) { return func(a, b); });}, - invoke_iiii: function(index, a, b, c) { return Pdfium.invokeFunc(index, function(func) { return func(a, b, c); });}, - invoke_iiiii: function(index, a, b, c, d) { return Pdfium.invokeFunc(index, function(func) { return func(a, b, c, d); });}, - invoke_v: function(index) { return Pdfium.invokeFunc(index, function(func) { func(); });}, - invoke_viii: function(index, a, b, c) { Pdfium.invokeFunc(index, function(func) { func(a, b, c); });}, - invoke_viiii: function(index, a, b, c, d) { Pdfium.invokeFunc(index, function(func) { func(a, b, c, d); });}, - print: function(text) { console.log(text); }, - printErr: function(text) { console.error(text); }, + exit: function (status) { + _notImplemented('exit'); + }, + invoke_ii: function (index, a) { + return Pdfium.invokeFunc(index, function (func) { + return func(a); + }); + }, + invoke_iii: function (index, a, b) { + return Pdfium.invokeFunc(index, function (func) { + return func(a, b); + }); + }, + invoke_iiii: function (index, a, b, c) { + return Pdfium.invokeFunc(index, function (func) { + return func(a, b, c); + }); + }, + invoke_iiiii: function (index, a, b, c, d) { + return Pdfium.invokeFunc(index, function (func) { + return func(a, b, c, d); + }); + }, + invoke_v: function (index) { + return Pdfium.invokeFunc(index, function (func) { + func(); + }); + }, + invoke_viii: function (index, a, b, c) { + Pdfium.invokeFunc(index, function (func) { + func(a, b, c); + }); + }, + invoke_viiii: function (index, a, b, c, d) { + Pdfium.invokeFunc(index, function (func) { + func(a, b, c, d); + }); + }, + print: function (text) { + console.log(text); + }, + printErr: function (text) { + console.error(text); + }, }; const wasi = { - proc_exit: function(code) { _notImplemented('proc_exit'); }, - environ_sizes_get: function(environCount, environBufSize) { _notImplemented('environ_sizes_get'); }, - environ_get: function(environ, environBuf) { _notImplemented('environ_get'); }, + proc_exit: function (code) { + _notImplemented('proc_exit'); + }, + environ_sizes_get: function (environCount, environBufSize) { + _notImplemented('environ_sizes_get'); + }, + environ_get: function (environ, environBuf) { + _notImplemented('environ_get'); + }, fd_close: fileSystem.closeFile.bind(fileSystem), fd_seek: fileSystem.seek.bind(fileSystem), fd_write: fileSystem.write.bind(fileSystem), @@ -441,15 +516,14 @@ const wasi = { fd_sync: fileSystem.sync.bind(fileSystem), }; - /** @type {string[]} */ const fontNames = []; /** - * @param {{data: ArrayBuffer, name: string}} params + * @param {{data: ArrayBuffer, name: string}} params */ function registerFont(params) { - const {name, data} = params; + const { name, data } = params; const fileDir = '/usr/share/fonts'; fontNames.push(fileDir + name); fileSystem.registerDirectoryWithEntries(fileDir, name); @@ -458,59 +532,59 @@ function registerFont(params) { } /** - * @param {{url: string, name: string}} params + * @param {{url: string, name: string}} params */ async function registerFontFromUrl(params) { - const {name, url} = params; + const { name, url } = params; const response = await fetch(url); if (!response.ok) { - throw new Error("Failed to fetch font from URL: " + fontUrl); + throw new Error('Failed to fetch font from URL: ' + fontUrl); } const data = await response.arrayBuffer(); - registerFont({name, data}); + registerFont({ name, data }); } /** - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined}} params + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ async function loadDocumentFromUrl(params) { const url = params.url; - const password = params.password || ""; + const password = params.password || ''; const useProgressiveLoading = params.useProgressiveLoading || false; const response = await fetch(url); if (!response.ok) { - throw new Error("Failed to fetch PDF from URL: " + url); + throw new Error('Failed to fetch PDF from URL: ' + url); } - return loadDocumentFromData({data: await response.arrayBuffer(), password, useProgressiveLoading}); + return loadDocumentFromData({ + data: await response.arrayBuffer(), + password, + useProgressiveLoading, + }); } /** - * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean|undefined}} params + * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ function loadDocumentFromData(params) { const data = params.data; - const password = params.password || ""; + const password = params.password || ''; const useProgressiveLoading = params.useProgressiveLoading; const sizeThreshold = 1024 * 1024; // 1MB if (data.byteLength < sizeThreshold) { const buffer = Pdfium.wasmExports.malloc(data.byteLength); if (buffer === 0) { - throw new Error("Failed to allocate memory for PDF data (${data.byteLength} bytes)"); + throw new Error('Failed to allocate memory for PDF data (${data.byteLength} bytes)'); } new Uint8Array(Pdfium.memory.buffer, buffer, data.byteLength).set(new Uint8Array(data)); const passwordPtr = StringUtils.allocateUTF8(password); - const docHandle = Pdfium.wasmExports.FPDF_LoadMemDocument( - buffer, - data.byteLength, - passwordPtr, - ); + const docHandle = Pdfium.wasmExports.FPDF_LoadMemDocument(buffer, data.byteLength, passwordPtr); StringUtils.freeUTF8(passwordPtr); return _loadDocument(docHandle, useProgressiveLoading, () => Pdfium.wasmExports.free(buffer)); } - + const tempFileName = params.url ?? '/tmp/temp.pdf'; fileSystem.registerFileWithData(tempFileName, data); @@ -520,7 +594,6 @@ function loadDocumentFromData(params) { StringUtils.freeUTF8(passwordPtr); StringUtils.freeUTF8(fileNamePtr); return _loadDocument(docHandle, useProgressiveLoading, () => fileSystem.unregisterFile(tempFileName)); - } /** @type {Object} */ @@ -545,19 +618,23 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { if (!docHandle) { const error = Pdfium.wasmExports.FPDF_GetLastError(); const errorStr = _errorMappings[error]; - return { errorCode: error, errorCodeStr: _errorMappings[error], message: `Failed to load document` }; + return { + errorCode: error, + errorCodeStr: _errorMappings[error], + message: `Failed to load document`, + }; } - + const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); const permissions = Pdfium.wasmExports.FPDF_GetDocPermissions(docHandle); const securityHandlerRevision = Pdfium.wasmExports.FPDF_GetSecurityHandlerRevision(docHandle); - + const formInfoSize = 35 * 4; formInfo = Pdfium.wasmExports.malloc(formInfoSize); const uint32 = new Uint32Array(Pdfium.memory.buffer, formInfo, formInfoSize >> 2); uint32[0] = 1; // version formHandle = Pdfium.wasmExports.FPDFDOC_InitFormFillEnvironment(docHandle, formInfo); - + const pages = _loadPagesInLimitedTime(docHandle, 0, useProgressiveLoading ? 1 : null); if (useProgressiveLoading) { const firstPage = pages[0]; @@ -600,8 +677,10 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { */ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountToLoadAdditionally, timeoutMs) { const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); - const end = maxPageCountToLoadAdditionally == null - ? pageCount : Math.min(pageCount, pagesLoadedCountSoFar + maxPageCountToLoadAdditionally); + const end = + maxPageCountToLoadAdditionally == null + ? pageCount + : Math.min(pageCount, pagesLoadedCountSoFar + maxPageCountToLoadAdditionally); const t = timeoutMs != null ? Date.now() + timeoutMs : null; /** @type {PdfPage[]} */ const pages = []; @@ -634,11 +713,11 @@ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountT function loadPagesProgressively(params) { const { docHandle, firstPageIndex, loadUnitDuration } = params; const pages = _loadPagesInLimitedTime(docHandle, firstPageIndex, null, loadUnitDuration); - return {pages}; + return { pages }; } /** - * @param {{formHandle: number, formInfo: number, docHandle: number}} params + * @param {{formHandle: number, formInfo: number, docHandle: number}} params */ function closeDocument(params) { if (params.formHandle) { @@ -650,7 +729,7 @@ function closeDocument(params) { Pdfium.wasmExports.FPDF_CloseDocument(params.docHandle); disposers[params.docHandle](); delete disposers[params.docHandle]; - return { message: "Document closed" }; + return { message: 'Document closed' }; } /** @@ -659,18 +738,21 @@ function closeDocument(params) { */ /** - * @param {{docHandle: number}} params + * @param {{docHandle: number}} params * @return {OutlineNode[]} */ function loadOutline(params) { return { - outline: _getOutlineNodeSiblings(Pdfium.wasmExports.FPDFBookmark_GetFirstChild(params.docHandle, null), params.docHandle), + outline: _getOutlineNodeSiblings( + Pdfium.wasmExports.FPDFBookmark_GetFirstChild(params.docHandle, null), + params.docHandle + ), }; } /** - * @param {number} bookmark - * @param {number} docHandle + * @param {number} bookmark + * @param {number} docHandle * @return {OutlineNode[]} */ function _getOutlineNodeSiblings(bookmark, docHandle) { @@ -706,14 +788,14 @@ function loadPage(params) { /** * @param {{pageHandle: number}} params -*/ + */ function closePage(params) { Pdfium.wasmExports.FPDF_ClosePage(params.pageHandle); - return { message: "Page closed" }; + return { message: 'Page closed' }; } /** - * + * * @param {{ * docHandle: number, * pageIndex: number, @@ -727,8 +809,8 @@ function closePage(params) { * annotationRenderingMode: number, * flags: number, * formHandle: number - * }} params - * @returns + * }} params + * @returns */ function renderPage(params) { const { @@ -755,26 +837,20 @@ function renderPage(params) { if (!pageHandle) { throw new Error(`Failed to load page ${pageIndex} from document ${docHandle}`); } - + const bufferSize = width * height * 4; bufferPtr = Pdfium.wasmExports.malloc(bufferSize); if (!bufferPtr) { - throw new Error("Failed to allocate memory for rendering"); + throw new Error('Failed to allocate memory for rendering'); } const FPDFBitmap_BGRA = 4; - bitmap = Pdfium.wasmExports.FPDFBitmap_CreateEx( - width, - height, - FPDFBitmap_BGRA, - bufferPtr, - width * 4 - ); + bitmap = Pdfium.wasmExports.FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGRA, bufferPtr, width * 4); if (!bitmap) { - throw new Error("Failed to create bitmap for rendering"); + throw new Error('Failed to create bitmap for rendering'); } - + Pdfium.wasmExports.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, backgroundColor); - + const FPDF_ANNOT = 1; const PdfAnnotationRenderingMode_none = 0; const PdfAnnotationRenderingMode_annotationAndForms = 2; @@ -787,7 +863,7 @@ function renderPage(params) { fullWidth, fullHeight, 0, - flags | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0), + flags | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0) ); if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { @@ -802,7 +878,7 @@ function renderPage(params) { result: { imageData: copiedBuffer, width: width, - height: height + height: height, }, transfer: [copiedBuffer], }; @@ -820,18 +896,20 @@ function _memset(ptr, value, num) { } } -const CR = 0x0D, LF = 0x0A, SPC = 0x20; +const CR = 0x0d, + LF = 0x0a, + SPC = 0x20; /** - * - * @param {{pageIndex: number, docHandle: number}} params + * + * @param {{pageIndex: number, docHandle: number}} params * @returns {{fullText: string, charRects: number[][], fragments: number[]}} */ function loadText(params) { const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); - if (textPage == null) return {fullText: '', charRects: [], fragments: []}; + if (textPage == null) return { fullText: '', charRects: [], fragments: [] }; const charCount = Pdfium.wasmExports.FPDFText_CountChars(textPage); /** @type {number[][]} */ const charRects = []; @@ -840,7 +918,7 @@ function loadText(params) { const fullText = _loadTextInternal(textPage, 0, charCount, charRects, fragments); Pdfium.wasmExports.FPDFText_ClosePage(textPage); Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - return {fullText, charRects, fragments}; + return { fullText, charRects, fragments }; } /** @@ -849,17 +927,22 @@ function loadText(params) { * @param {number} length * @param {number[][]} charRects * @param {number[]} fragments - * @returns + * @returns */ function _loadTextInternal(textPage, from, length, charRects, fragments) { const fullText = _getText(textPage, from, length); const rectBuffer = Pdfium.wasmExports.malloc(8 * 4); // double[4] const sb = { str: '', - push(text) { this.str += text; }, - get length() { return this.str.length; }, + push(text) { + this.str += text; + }, + get length() { + return this.str.length; + }, }; - let lineStart = 0, wordStart = 0; + let lineStart = 0, + wordStart = 0; let lastChar; for (let i = 0; i < length; i++) { const char = fullText.charCodeAt(i); @@ -881,11 +964,14 @@ function _loadTextInternal(textPage, from, length, charRects, fragments) { continue; } - Pdfium.wasmExports.FPDFText_GetCharBox(textPage, from + i, + Pdfium.wasmExports.FPDFText_GetCharBox( + textPage, + from + i, rectBuffer, // L rectBuffer + 8 * 2, // R rectBuffer + 8 * 3, // B - rectBuffer + 8); // T + rectBuffer + 8 + ); // T const rect = Array.from(new Float64Array(Pdfium.memory.buffer, rectBuffer, 4)); if (char === SPC) { if (lastChar == SPC) continue; @@ -956,7 +1042,10 @@ function _makeLineFlat(rects, start, end, sb) { } function _boundingRect(rects, start, end) { - let l = Number.MAX_VALUE, t = 0, r = 0, b = Number.MAX_VALUE; + let l = Number.MAX_VALUE, + t = 0, + r = 0, + b = Number.MAX_VALUE; for (let i = start; i < end; i++) { const rect = rects[i]; l = Math.min(l, rect[0]); @@ -982,7 +1071,6 @@ function _getText(textPage, from, length) { // return text; } - /** * @typedef {{rects: number[][], dest: url: string}} PdfUrlLink * @typedef {{rects: number[][], dest: PdfDest}} PdfDestLink @@ -1035,7 +1123,7 @@ function _loadLinks(params) { /** * @param {number} linkPage - * @param {number} linkIndex + * @param {number} linkIndex * @returns {string} */ function _getLinkUrl(linkPage, linkIndex) { @@ -1061,19 +1149,14 @@ function _loadAnnotLinks(params) { const annot = Pdfium.wasmExports.FPDFPage_GetAnnot(pageHandle, i); Pdfium.wasmExports.FPDFAnnot_GetRect(annot, rectF); const [l, t, r, b] = new Float32Array(Pdfium.memory.buffer, rectF, 4); - const rect = [ - l, - t > b ? t : b, - r, - t > b ? b : t, - ]; + const rect = [l, t > b ? t : b, r, t > b ? b : t]; const dest = _processAnnotDest(annot, docHandle); if (dest) { - links.push({rects: [rect], dest: _pdfDestFromDest(dest, docHandle)}); + links.push({ rects: [rect], dest: _pdfDestFromDest(dest, docHandle) }); } else { const url = _processAnnotLink(annot, docHandle); if (url) { - links.push({rects: [rect], url: url}); + links.push({ rects: [rect], url: url }); } } Pdfium.wasmExports.FPDFPage_CloseAnnot(annot); @@ -1084,9 +1167,9 @@ function _loadAnnotLinks(params) { } /** - * - * @param {number} annot - * @param {number} docHandle + * + * @param {number} annot + * @param {number} docHandle * @returns {number|null} Dest */ function _processAnnotDest(annot, docHandle) { @@ -1108,8 +1191,8 @@ function _processAnnotDest(annot, docHandle) { } /** - * @param {number} annot - * @param {number} docHandle + * @param {number} annot + * @param {number} docHandle * @returns {string|null} URI */ function _processAnnotLink(annot, docHandle) { @@ -1134,8 +1217,8 @@ function _processAnnotLink(annot, docHandle) { const pdfDestCommands = ['unknown', 'xyz', 'fit', 'fitH', 'fitV', 'fitR', 'fitB', 'fitBH', 'fitBV']; /** - * @param {number} dest - * @param {number} docHandle + * @param {number} dest + * @param {number} docHandle * @returns {PdfDest|null} */ function _pdfDestFromDest(dest, docHandle) { @@ -1176,37 +1259,37 @@ const functions = { function handleRequest(data) { const { id, command, parameters = {} } = data; - + try { const result = functions[command](parameters); if (result instanceof Promise) { result - .then(finalResult => { + .then((finalResult) => { if (finalResult.result != null && finalResult.transfer != null) { - postMessage({ id, status: "success", result: finalResult.result }, finalResult.transfer); + postMessage({ id, status: 'success', result: finalResult.result }, finalResult.transfer); } else { - postMessage({ id, status: "success", result: finalResult }); + postMessage({ id, status: 'success', result: finalResult }); } }) - .catch(err => { + .catch((err) => { postMessage({ id, - status: "error", - error: _error(err) + status: 'error', + error: _error(err), }); }); } else { if (result.result != null && result.transfer != null) { - postMessage({ id, status: "success", result: result.result }, result.transfer); + postMessage({ id, status: 'success', result: result.result }, result.transfer); } else { - postMessage({ id, status: "success", result: result }); + postMessage({ id, status: 'success', result: result }); } } } catch (err) { postMessage({ id, - status: "error", - error: _error(err) + status: 'error', + error: _error(err), }); } } @@ -1215,28 +1298,29 @@ let messagesBeforeInitialized = []; console.log(`PDFium worker initialized: ${self.location.href}`); - /** * Entrypoint */ console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); WebAssembly.instantiateStreaming(fetch(pdfiumWasmUrl), { env: emEnv, - wasi_snapshot_preview1: wasi -}).then(result => { - Pdfium.initWith(result.instance.exports); - - Pdfium.wasmExports.FPDF_InitLibrary(); - postMessage({ type: "ready" }); - - messagesBeforeInitialized.forEach(event => handleRequest(event.data)); - messagesBeforeInitialized = null; -}).catch(err => { - console.error('Failed to load WASM module:', err); - postMessage({ type: "error", error: _error(err) }); -}); - -onmessage = function(e) { + wasi_snapshot_preview1: wasi, +}) + .then((result) => { + Pdfium.initWith(result.instance.exports); + + Pdfium.wasmExports.FPDF_InitLibrary(); + postMessage({ type: 'ready' }); + + messagesBeforeInitialized.forEach((event) => handleRequest(event.data)); + messagesBeforeInitialized = null; + }) + .catch((err) => { + console.error('Failed to load WASM module:', err); + postMessage({ type: 'error', error: _error(err) }); + }); + +onmessage = function (e) { const data = e.data; if (data && data.id && data.command) { if (messagesBeforeInitialized) { @@ -1245,20 +1329,20 @@ onmessage = function(e) { } handleRequest(data); } else { - console.error("Received improperly formatted message:", data); + console.error('Received improperly formatted message:', data); } }; const _errorMappings = { - 0: "FPDF_ERR_SUCCESS", - 1: "FPDF_ERR_UNKNOWN", - 2: "FPDF_ERR_FILE", - 3: "FPDF_ERR_FORMAT", - 4: "FPDF_ERR_PASSWORD", - 5: "FPDF_ERR_SECURITY", - 6: "FPDF_ERR_PAGE", - 7: "FPDF_ERR_XFALOAD", - 8: "FPDF_ERR_XFALAYOUT", + 0: 'FPDF_ERR_SUCCESS', + 1: 'FPDF_ERR_UNKNOWN', + 2: 'FPDF_ERR_FILE', + 3: 'FPDF_ERR_FORMAT', + 4: 'FPDF_ERR_PASSWORD', + 5: 'FPDF_ERR_SECURITY', + 6: 'FPDF_ERR_PAGE', + 7: 'FPDF_ERR_XFALOAD', + 8: 'FPDF_ERR_XFALAYOUT', }; function _getErrorMessage(errorCode) { @@ -1272,7 +1356,7 @@ function _getErrorMessage(errorCode) { class StringUtils { /** * UTF-16 string to bytes - * @param {number[]} buffer + * @param {number[]} buffer * @returns {string} Converted string */ static utf16BytesToString(buffer) { @@ -1283,13 +1367,13 @@ class StringUtils { } /** * UTF-8 bytes to string - * @param {number[]} buffer + * @param {number[]} buffer * @returns {string} Converted string */ static utf8BytesToString(buffer) { let endPtr = 0; while (buffer[endPtr] && !(endPtr >= buffer.length)) ++endPtr; - + let str = ''; let idx = 0; while (idx < endPtr) { @@ -1299,12 +1383,12 @@ class StringUtils { continue; } const u1 = buffer[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { + if ((u0 & 0xe0) == 0xc0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } const u2 = buffer[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { + if ((u0 & 0xf0) == 0xe0) { u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; } else { u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (buffer[idx++] & 63); @@ -1313,36 +1397,36 @@ class StringUtils { str += String.fromCharCode(u0); } else { const ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + str += String.fromCharCode(0xd800 | (ch >> 10), 0xdc00 | (ch & 0x3ff)); } } return str; } /** * String to UTF-8 bytes - * @param {string} str + * @param {string} str * @param {number[]} buffer * @returns {number} Number of bytes written to the buffer */ static stringToUtf8Bytes(str, buffer) { let idx = 0; - for(let i = 0; i < str.length; ++i) { + for (let i = 0; i < str.length; ++i) { let u = str.charCodeAt(i); - if(u >= 0xD800 && u <= 0xDFFF) { + if (u >= 0xd800 && u <= 0xdfff) { const u1 = str.charCodeAt(++i); - u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF); + u = (0x10000 + ((u & 0x3ff) << 10)) | (u1 & 0x3ff); } - if(u <= 0x7F) { + if (u <= 0x7f) { buffer[idx++] = u; - } else if(u <= 0x7FF) { - buffer[idx++] = 0xC0 | (u >> 6); + } else if (u <= 0x7ff) { + buffer[idx++] = 0xc0 | (u >> 6); buffer[idx++] = 0x80 | (u & 63); - } else if(u <= 0xFFFF) { - buffer[idx++] = 0xE0 | (u >> 12); + } else if (u <= 0xffff) { + buffer[idx++] = 0xe0 | (u >> 12); buffer[idx++] = 0x80 | ((u >> 6) & 63); buffer[idx++] = 0x80 | (u & 63); } else { - buffer[idx++] = 0xF0 | (u >> 18); + buffer[idx++] = 0xf0 | (u >> 18); buffer[idx++] = 0x80 | ((u >> 12) & 63); buffer[idx++] = 0x80 | ((u >> 6) & 63); buffer[idx++] = 0x80 | (u & 63); @@ -1358,21 +1442,21 @@ class StringUtils { */ static lengthBytesUTF8(str) { let len = 0; - for(let i = 0; i < str.length; ++i) { + for (let i = 0; i < str.length; ++i) { let u = str.charCodeAt(i); - if(u >= 0xD800 && u <= 0xDFFF) { - u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF); + if (u >= 0xd800 && u <= 0xdfff) { + u = (0x10000 + ((u & 0x3ff) << 10)) | (str.charCodeAt(++i) & 0x3ff); } - if(u <= 0x7F) len += 1; - else if(u <= 0x7FF) len += 2; - else if(u <= 0xFFFF) len += 3; + if (u <= 0x7f) len += 1; + else if (u <= 0x7ff) len += 2; + else if (u <= 0xffff) len += 3; else len += 4; } return len; } /** * Allocate memory for UTF-8 string - * @param {string} str + * @param {string} str * @returns {number} Pointer to allocated buffer that contains UTF-8 string. The buffer should be released by calling [freeUTF8]. */ static allocateUTF8(str) { @@ -1389,4 +1473,4 @@ class StringUtils { static freeUTF8(ptr) { Pdfium.wasmExports.free(ptr); } -}; +} From d94717aef3a3a647c14899b0459a744293208c9f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 01:00:17 +0900 Subject: [PATCH 150/663] #399 header/withCredentials support for WASM --- assets/pdfium_worker.js | 43 +++++++++---------------------------- lib/src/web/pdfrx_wasm.dart | 11 +++++++++- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 1f9d27b2..6479f32f 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -516,43 +516,22 @@ const wasi = { fd_sync: fileSystem.sync.bind(fileSystem), }; -/** @type {string[]} */ -const fontNames = []; - -/** - * @param {{data: ArrayBuffer, name: string}} params - */ -function registerFont(params) { - const { name, data } = params; - const fileDir = '/usr/share/fonts'; - fontNames.push(fileDir + name); - fileSystem.registerDirectoryWithEntries(fileDir, name); - - fileSystem.registerFileWithData(name, data); -} - -/** - * @param {{url: string, name: string}} params - */ -async function registerFontFromUrl(params) { - const { name, url } = params; - const response = await fetch(url); - if (!response.ok) { - throw new Error('Failed to fetch font from URL: ' + fontUrl); - } - const data = await response.arrayBuffer(); - registerFont({ name, data }); -} - /** - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined}} params + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined}} params */ async function loadDocumentFromUrl(params) { const url = params.url; const password = params.password || ''; const useProgressiveLoading = params.useProgressiveLoading || false; - - const response = await fetch(url); + const headers = params.headers || {}; + const withCredentials = params.withCredentials || false; + + const response = await fetch(url, { + headers: headers, + mode: 'cors', + credentials: withCredentials ? 'include' : 'same-origin', + redirect: "follow", + }); if (!response.ok) { throw new Error('Failed to fetch PDF from URL: ' + url); } @@ -1243,8 +1222,6 @@ function _pdfDestFromDest(dest, docHandle) { * Functions that can be called from the main thread */ const functions = { - registerFont, - registerFontFromUrl, loadDocumentFromUrl, loadDocumentFromData, loadPagesProgressively, diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 3f07852e..da563161 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -185,7 +185,16 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => sendCommand( 'loadDocumentFromUrl', - parameters: {'url': uri.toString(), 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + parameters: { + 'url': uri.toString(), + 'password': password, + 'useProgressiveLoading': useProgressiveLoading, + // if (progressCallback != null) 'progressCallback': progressCallback, + // if (reportCallback != null) 'reportCallback': reportCallback, + // 'preferRangeAccess': preferRangeAccess, + if (headers != null) 'headers': headers, + 'withCredentials': withCredentials, + }, ), sourceName: uri.toString(), factory: this, From 754d1720b1982fa56070c94ea18acee010db4e9d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 01:40:11 +0900 Subject: [PATCH 151/663] PdfDownloadReportCallback on WASM --- assets/pdfium_client.js | 48 +++++++++++---- assets/pdfium_worker.js | 68 ++++++++++++++++++--- lib/src/pdf_api.dart | 10 ---- lib/src/pdf_document_ref.dart | 49 +++------------- lib/src/pdfium/pdf_file_cache.dart | 10 ---- lib/src/pdfium/pdfrx_pdfium.dart | 2 - lib/src/web/pdfrx_wasm.dart | 94 ++++++++++++++++++++++-------- 7 files changed, 174 insertions(+), 107 deletions(-) diff --git a/assets/pdfium_client.js b/assets/pdfium_client.js index c74aa3bb..81fd2ce8 100644 --- a/assets/pdfium_client.js +++ b/assets/pdfium_client.js @@ -1,7 +1,9 @@ -globalThis.pdfiumWasmSendCommand = (function () { +globalThis.PdfiumWasmCommunicator = (function () { const worker = new Worker(globalThis.pdfiumWasmWorkerUrl); let requestId = 0; - const callbacks = new Map(); + let callbackId = 0; + const requestCallbacks = new Map(); + const registeredCallbacks = new Map(); worker.onmessage = (event) => { const data = event.data; @@ -9,16 +11,30 @@ globalThis.pdfiumWasmSendCommand = (function () { console.log('PDFium WASM worker is ready'); return; } + + // Handle callback invocations from the worker + if (data.type === 'callback') { + const callback = registeredCallbacks.get(data.callbackId); + if (callback) { + try { + callback(...data.args); + } catch (e) { + console.error('Error in callback:', e); + } + } + return; + } + // For command responses, match using the request id. if (data.id) { - const callback = callbacks.get(data.id); + const callback = requestCallbacks.get(data.id); if (callback) { if (data.status === 'success') { callback.resolve(data.result); } else { callback.reject(new Error(data.error, data.cause != null ? { cause: data.cause } : undefined)); } - callbacks.delete(data.id); + requestCallbacks.delete(data.id); } } }; @@ -27,11 +43,23 @@ globalThis.pdfiumWasmSendCommand = (function () { console.error('Worker error:', err); }; - return function (command, parameters = {}, transfer = []) { - return new Promise((resolve, reject) => { - const id = ++requestId; - callbacks.set(id, { resolve, reject }); - worker.postMessage({ id, command, parameters }, transfer); - }); + return { + sendCommand: function (command, parameters = {}, transfer = []) { + return new Promise((resolve, reject) => { + const id = ++requestId; + requestCallbacks.set(id, { resolve, reject }); + worker.postMessage({ id, command, parameters }, transfer); + }); + }, + + registerCallback: function (callback) { + const id = ++callbackId; + registeredCallbacks.set(id, callback); + return id; + }, + + unregisterCallback: function (id) { + registeredCallbacks.delete(id); + } }; })(); diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 6479f32f..2147195b 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -517,7 +517,7 @@ const wasi = { }; /** - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined}} params + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined}} params */ async function loadDocumentFromUrl(params) { const url = params.url; @@ -525,6 +525,7 @@ async function loadDocumentFromUrl(params) { const useProgressiveLoading = params.useProgressiveLoading || false; const headers = params.headers || {}; const withCredentials = params.withCredentials || false; + const progressCallbackId = params.progressCallbackId; const response = await fetch(url, { headers: headers, @@ -532,15 +533,49 @@ async function loadDocumentFromUrl(params) { credentials: withCredentials ? 'include' : 'same-origin', redirect: "follow", }); - if (!response.ok) { - throw new Error('Failed to fetch PDF from URL: ' + url); - } - return loadDocumentFromData({ - data: await response.arrayBuffer(), - password, - useProgressiveLoading, - }); + // Get the content length for progress reporting + const contentLength = parseInt(response.headers.get('content-length') || '0', 10); + let receivedLength = 0; + + // If we have progress callback and a valid content length, use streaming + if (progressCallbackId && contentLength > 0 && response.body) { + const reader = response.body.getReader(); + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + + if (done) break; + + chunks.push(value); + receivedLength += value.length; + + // Send progress callback + invokeCallback(progressCallbackId, receivedLength, contentLength); + } + + // Combine chunks into single ArrayBuffer + const data = new Uint8Array(receivedLength); + let position = 0; + for (const chunk of chunks) { + data.set(chunk, position); + position += chunk.length; + } + + return loadDocumentFromData({ + data: data.buffer, + password, + useProgressiveLoading, + }); + } else { + // No progress callback or content-length, just get the data directly + return loadDocumentFromData({ + data: await response.arrayBuffer(), + password, + useProgressiveLoading, + }); + } } /** @@ -1234,6 +1269,21 @@ const functions = { loadLinks, }; +/** + * Send a callback invocation message back to the client + * @param {number} callbackId The callback ID to invoke + * @param {*} args Arguments to pass to the callback + */ +function invokeCallback(callbackId, ...args) { + if (callbackId) { + postMessage({ + type: 'callback', + callbackId: callbackId, + args: args + }); + } +} + function handleRequest(data) { const { id, command, parameters = {} } = data; diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index a06925c2..82d1b581 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -98,7 +98,6 @@ abstract class PdfDocumentFactory { bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, Map? headers, bool withCredentials = false, @@ -137,13 +136,6 @@ PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); /// [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]); -/// Callback function to report download status on completion. -/// -/// [downloaded] is the number of bytes downloaded. -/// [total] is the total number of bytes downloaded. -/// [elapsedTime] is the time taken to download the file. -typedef PdfDownloadReportCallback = void Function(int downloaded, int total, Duration elapsedTime); - /// Function to provide password for encrypted PDF. /// /// The function is called when PDF requires password. @@ -314,7 +306,6 @@ abstract class PdfDocument { bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, Map? headers, bool withCredentials = false, @@ -324,7 +315,6 @@ abstract class PdfDocument { firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, - reportCallback: reportCallback, preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, diff --git a/lib/src/pdf_document_ref.dart b/lib/src/pdf_document_ref.dart index b6c7c648..3ec671d2 100644 --- a/lib/src/pdf_document_ref.dart +++ b/lib/src/pdf_document_ref.dart @@ -11,13 +11,6 @@ import '../pdfrx.dart'; /// [totalBytes] is the total number of bytes to download. It may be null if the total size is unknown. typedef PdfDocumentLoaderProgressCallback = void Function(int downloadedBytes, [int? totalBytes]); -/// Callback function to report download status on completion. -/// -/// [downloaded] is the number of bytes downloaded. -/// [total] is the total number of bytes downloaded. -/// [elapsedTime] is the time taken to download the file. -typedef PdfDocumentLoaderReportCallback = void Function(int downloaded, int total, Duration elapsedTime); - /// PdfDocumentRef controls loading of a [PdfDocument] and it also provide you with a way to use [PdfDocument] /// safely in your long running async operations. /// @@ -62,10 +55,7 @@ abstract class PdfDocumentRef { /// Classes that extends [PdfDocumentRef] should override this function to load the document. /// /// [progressCallback] should be called when the document is loaded from remote source to notify the progress. - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback); /// Classes that extends [PdfDocumentRef] should override this function to compare the equality by [sourceName] /// or such. @@ -108,10 +98,7 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi final bool useProgressiveLoading; @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => PdfDocument.openAsset( + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openAsset( name, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -161,16 +148,12 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin String get sourceName => uri.toString(); @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => PdfDocument.openUri( + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openUri( uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, - reportCallback: reportCallback, preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, @@ -205,10 +188,7 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin final bool useProgressiveLoading; @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => PdfDocument.openFile( + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openFile( file, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -252,10 +232,7 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin final bool useProgressiveLoading; @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => PdfDocument.openData( + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openData( data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -302,10 +279,7 @@ class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMix final bool useProgressiveLoading; @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => PdfDocument.openCustom( + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openCustom( read: read, fileSize: fileSize, sourceName: sourceName, @@ -333,10 +307,7 @@ class PdfDocumentRefDirect extends PdfDocumentRef { String get sourceName => document.sourceName; @override - Future loadDocument( - PdfDocumentLoaderProgressCallback progressCallback, - PdfDocumentLoaderReportCallback reportCallback, - ) => Future.value(document); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => Future.value(document); @override bool operator ==(Object other) => other is PdfDocumentRefDirect && sourceName == other.sourceName; @@ -410,11 +381,7 @@ class PdfDocumentListenable extends Listenable { PdfDownloadReport? report; try { final stopwatch = Stopwatch()..start(); - document = await ref.loadDocument( - _progress, - (downloaded, totalBytes, elapsed) => - report = PdfDownloadReport(downloaded: downloaded, total: totalBytes, elapsedTime: elapsed), - ); + document = await ref.loadDocument(_progress); debugPrint('PdfDocument initial load: ${ref.sourceName} (${stopwatch.elapsedMilliseconds} ms)'); } catch (err, stackTrace) { setError(err, stackTrace); diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index 504ffe21..eb967b18 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -281,17 +281,9 @@ Future pdfDocumentFromUri( int? blockSize, PdfFileCache? cache, PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, bool useRangeAccess = true, Map? headers, }) async { - final startTime = reportCallback != null ? DateTime.now() : null; - void report() { - if (reportCallback != null && cache?.isInitialized == true) { - reportCallback(cache?.cachedBytes ?? 0, cache?.fileSize ?? 0, DateTime.now().difference(startTime!)); - } - } - progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); final httpClientWrapper = _HttpClientWrapper(Pdfrx.createHttpClient ?? () => http.Client()); @@ -379,8 +371,6 @@ Future pdfDocumentFromUri( cache.close(); httpClientWrapper.reset(); rethrow; - } finally { - report(); } } diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index c2987509..7f92b452 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -250,7 +250,6 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, Map? headers, bool withCredentials = false, @@ -260,7 +259,6 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, progressCallback: progressCallback, - reportCallback: reportCallback, useRangeAccess: preferRangeAccess, headers: headers, ); diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index da563161..131922d4 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -15,9 +15,39 @@ import '../pdf_api.dart'; /// For more information, see [Enable PDFium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-PDFium-WASM-support). PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryWasmImpl.singleton; -/// Calls PDFium WASM worker with the given command and parameters. -@JS() -external JSPromise pdfiumWasmSendCommand([String command, JSAny? parameters, JSArray? transfer]); +/// The PDFium WASM communicator object +@JS('PdfiumWasmCommunicator') +extension type PdfiumWasmCommunicator(JSObject _) implements JSObject { + /// Sends a command to the worker and returns a promise + @JS('sendCommand') + external JSPromise sendCommand([String command, JSAny? parameters, JSArray? transfer]); + + /// Registers a callback function and returns its ID + @JS('registerCallback') + external int _registerCallback(JSFunction callback); + + /// Unregisters a callback by its ID + @JS('unregisterCallback') + external void _unregisterCallback(int callbackId); +} + +/// Get the global PdfiumWasmCommunicator instance +@JS('PdfiumWasmCommunicator') +external PdfiumWasmCommunicator get pdfiumWasmCommunicator; + +/// A handle to a registered callback that can be unregistered +class PdfiumWasmCallback { + PdfiumWasmCallback.register(JSFunction callback) + : id = pdfiumWasmCommunicator._registerCallback(callback), + _communicator = pdfiumWasmCommunicator; + + final int id; + final PdfiumWasmCommunicator _communicator; + + void unregister() { + _communicator._unregisterCallback(id); + } +} /// The URL of the PDFium WASM worker script; pdfium_client.js tries to load worker script from this URL.' /// @@ -95,7 +125,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } Future> sendCommand(String command, {Map? parameters}) async { - final result = await pdfiumWasmSendCommand(command, parameters?.jsify()).toDart; + final result = await pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; return (result.dartify()) as Map; } @@ -178,30 +208,44 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, PdfDownloadProgressCallback? progressCallback, - PdfDownloadReportCallback? reportCallback, bool preferRangeAccess = false, Map? headers, bool withCredentials = false, - }) => _openByFunc( - (password) => sendCommand( - 'loadDocumentFromUrl', - parameters: { - 'url': uri.toString(), - 'password': password, - 'useProgressiveLoading': useProgressiveLoading, - // if (progressCallback != null) 'progressCallback': progressCallback, - // if (reportCallback != null) 'reportCallback': reportCallback, - // 'preferRangeAccess': preferRangeAccess, - if (headers != null) 'headers': headers, - 'withCredentials': withCredentials, - }, - ), - sourceName: uri.toString(), - factory: this, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - onDispose: null, - ); + }) { + PdfiumWasmCallback? progressCallbackReg; + void cleanupCallbacks() => progressCallbackReg?.unregister(); + + try { + if (progressCallback != null) { + progressCallbackReg = PdfiumWasmCallback.register( + ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, + ); + } + + return _openByFunc( + (password) => sendCommand( + 'loadDocumentFromUrl', + parameters: { + 'url': uri.toString(), + 'password': password, + 'useProgressiveLoading': useProgressiveLoading, + if (progressCallbackReg != null) 'progressCallbackId': progressCallbackReg.id, + // 'preferRangeAccess': preferRangeAccess, + if (headers != null) 'headers': headers, + 'withCredentials': withCredentials, + }, + ), + sourceName: uri.toString(), + factory: this, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + onDispose: cleanupCallbacks, + ); + } catch (e) { + cleanupCallbacks(); + rethrow; + } + } Future _openByFunc( Future> Function(String? password) openDocument, { From ec59a5493897d0c2c961fb11654e53f6a2598bb5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 02:10:09 +0900 Subject: [PATCH 152/663] Release v1.2.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added headers and withCredentials support for PDFium WASM implementation - Implemented progress callback support for PDFium WASM - Improved PDFium WASM worker-client communication architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 6 ++++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6711550d..223f9af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.2.6 + +- NEW FEATURE: Added `headers` and `withCredentials` support for PDFium WASM implementation ([#399](https://github.com/espresso3389/pdfrx/issues/399)) +- NEW FEATURE: Implemented progress callback support for PDFium WASM using a general callback mechanism +- Improved PDFium WASM worker-client communication architecture with `PdfiumWasmCommunicator` + # 1.2.5 - FIXED: Flag handling in `PdfPagePdfium` for improved rendering ([#398](https://github.com/espresso3389/pdfrx/issues/398)) diff --git a/README.md b/README.md index bea0d0e8..e2cf81a0 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.5 + pdfrx: ^1.2.6 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index f39bc9f4..d8a234fa 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.5" + version: "1.2.6" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 21a29dc2..6808d841 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.5 +version: 1.2.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 4d97e76cec2b2795df237a47bb904ccc8b1bc7e2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 08:56:05 +0900 Subject: [PATCH 153/663] Enhance PDFium initialization with optional authentication parameters for WASM --- assets/pdfium_worker.js | 60 +++++++++++++++++++++++++------- lib/src/pdf_api.dart | 10 ++++++ lib/src/web/pdfrx_wasm.dart | 68 +++++++++++++++++++++---------------- 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 2147195b..d86ee8a2 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -1322,35 +1322,69 @@ function handleRequest(data) { } let messagesBeforeInitialized = []; +let pdfiumInitialized = false; console.log(`PDFium worker initialized: ${self.location.href}`); /** - * Entrypoint + * Initialize PDFium with optional authentication parameters + * @param {Object} params - Initialization parameters + * @param {boolean} params.withCredentials - Whether to include credentials in the fetch + * @param {Object} params.headers - Additional headers for the fetch request */ -console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); -WebAssembly.instantiateStreaming(fetch(pdfiumWasmUrl), { - env: emEnv, - wasi_snapshot_preview1: wasi, -}) - .then((result) => { +async function initializePdfium(params = {}) { + try { + console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); + + const fetchOptions = { + credentials: params.withCredentials ? 'include' : 'same-origin', + }; + + if (params.headers) { + fetchOptions.headers = params.headers; + } + + const result = await WebAssembly.instantiateStreaming( + fetch(pdfiumWasmUrl, fetchOptions), + { + env: emEnv, + wasi_snapshot_preview1: wasi, + } + ); + Pdfium.initWith(result.instance.exports); - Pdfium.wasmExports.FPDF_InitLibrary(); + pdfiumInitialized = true; + postMessage({ type: 'ready' }); - + + // Process queued messages messagesBeforeInitialized.forEach((event) => handleRequest(event.data)); messagesBeforeInitialized = null; - }) - .catch((err) => { + } catch (err) { console.error('Failed to load WASM module:', err); postMessage({ type: 'error', error: _error(err) }); - }); + throw err; + } +} onmessage = function (e) { const data = e.data; + + // Handle init command + if (data && data.command === 'init') { + initializePdfium(data.parameters || {}) + .then(() => { + postMessage({ id: data.id, status: 'success', result: {} }); + }) + .catch((err) => { + postMessage({ id: data.id, status: 'error', error: _error(err) }); + }); + return; + } + if (data && data.id && data.command) { - if (messagesBeforeInitialized) { + if (!pdfiumInitialized && messagesBeforeInitialized) { messagesBeforeInitialized.push(e); return; } diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 82d1b581..df070d88 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -38,6 +38,16 @@ class Pdfrx { /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. static String? pdfiumWasmModulesUrl; + + /// HTTP headers to use when fetching the PDFium WASM module. + /// This is useful for authentication on protected servers. + /// Only supported on Flutter Web. + static Map? pdfiumWasmHeaders; + + /// Whether to include credentials (cookies) when fetching the PDFium WASM module. + /// This is useful for authentication on protected servers. + /// Only supported on Flutter Web. + static bool pdfiumWasmWithCredentials = false; } /// Web runtime type. diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 131922d4..976e54bc 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart' show Colors, immutable; import 'package:flutter/services.dart'; +import 'package:synchronized/extension.dart'; import 'package:web/web.dart' as web; import '../pdf_api.dart'; @@ -66,38 +67,45 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { /// Normally, the WASM modules are provided by pdfrx_wasm package and this is the path to its assets. static const defaultWasmModulePath = 'assets/packages/pdfrx/assets/'; - Future _init() async { - _globalInit(); - pdfiumWasmWorkerUrl = _getWorkerUrl(); - Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); - final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); - final script = - web.document.createElement('script') as web.HTMLScriptElement - ..type = 'text/javascript' - ..charset = 'utf-8' - ..async = true - ..type = 'module' - ..src = _resolveUrl('pdfium_client.js', baseUrl: moduleUrl); - web.document.querySelector('head')!.appendChild(script); - final completer = Completer(); - final sub1 = script.onLoad.listen((_) => completer.complete()); - final sub2 = script.onError.listen((event) => completer.completeError(event)); - try { - await completer.future; - } catch (e) { - throw StateError('Failed to load pdfium_client.js from $moduleUrl: $e'); - } finally { - await sub1.cancel(); - await sub2.cancel(); - } - } + bool _initialized = false; - static bool _globalInitialized = false; + Future _init() async { + if (_initialized) return; + await synchronized(() async { + if (_initialized) return; + Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); + pdfiumWasmWorkerUrl = _getWorkerUrl(); + final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); + final script = + web.document.createElement('script') as web.HTMLScriptElement + ..type = 'text/javascript' + ..charset = 'utf-8' + ..async = true + ..type = 'module' + ..src = _resolveUrl('pdfium_client.js', baseUrl: moduleUrl); + web.document.querySelector('head')!.appendChild(script); + final completer = Completer(); + final sub1 = script.onLoad.listen((_) => completer.complete()); + final sub2 = script.onError.listen((event) => completer.completeError(event)); + try { + await completer.future; + } catch (e) { + throw StateError('Failed to load pdfium_client.js from $moduleUrl: $e'); + } finally { + await sub1.cancel(); + await sub2.cancel(); + } - static void _globalInit() { - if (_globalInitialized) return; - Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); - _globalInitialized = true; + // Send init command to worker with authentication options + await sendCommand( + 'init', + parameters: { + if (Pdfrx.pdfiumWasmHeaders != null) 'headers': Pdfrx.pdfiumWasmHeaders, + 'withCredentials': Pdfrx.pdfiumWasmWithCredentials, + }, + ); + _initialized = true; + }); } static String? _pdfiumWasmModulesUrlFromMetaTag() { From aed51113d967eeac26868076f5e00e1f9dc352c4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 08:56:17 +0900 Subject: [PATCH 154/663] WIP --- .pubignore | 1 + CLAUDE.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.pubignore b/.pubignore index 907c75c0..b51bf653 100644 --- a/.pubignore +++ b/.pubignore @@ -1 +1,2 @@ example/viewer/ +build/ diff --git a/CLAUDE.md b/CLAUDE.md index 14ace3bb..a7d2dc53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -178,5 +178,11 @@ The following guidelines should be followed when writing documentation including - Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible - `README.md` should provide an overview of the project, how to use it, and any important notes - `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles + - Be careful not to include implementation details in the changelog + - Focus on user-facing changes, new features, bug fixes, and breaking changes - Use sections for different versions - Use bullet points for changes + +## Special Notes + +- `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version From b86f12c90d963bcb134267f88b4edade32bc7cba Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 09:01:26 +0900 Subject: [PATCH 155/663] Release v1.2.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 223f9af6..888af9de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.7 + +- Enhanced PDFium initialization with optional authentication parameters for WASM + # 1.2.6 - NEW FEATURE: Added `headers` and `withCredentials` support for PDFium WASM implementation ([#399](https://github.com/espresso3389/pdfrx/issues/399)) diff --git a/README.md b/README.md index e2cf81a0..46ee1684 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.6 + pdfrx: ^1.2.7 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index d8a234fa..aa500123 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.6" + version: "1.2.7" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6808d841..d3225abf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.6 +version: 1.2.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From d7ebf9c645242e22523c53dcbd9506a00c9f3e35 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 20 Jun 2025 18:10:15 +0900 Subject: [PATCH 156/663] WIP: range-access on WASM --- assets/pdfium_worker.js | 332 +++++++++++++++++++++++++++++++--- lib/src/pdf_api.dart | 2 +- lib/src/pdf_document_ref.dart | 2 +- lib/src/web/pdfrx_wasm.dart | 2 +- 4 files changed, 314 insertions(+), 24 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index d86ee8a2..02c6f8f0 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -517,7 +517,12 @@ const wasi = { }; /** - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined}} params + * Check if SharedArrayBuffer is available + */ +const canUseSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined'; + +/** + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined, preferRangeAccess: boolean|undefined}} params */ async function loadDocumentFromUrl(params) { const url = params.url; @@ -526,28 +531,86 @@ async function loadDocumentFromUrl(params) { const headers = params.headers || {}; const withCredentials = params.withCredentials || false; const progressCallbackId = params.progressCallbackId; + const preferRangeAccess = params.preferRangeAccess || false; + + if (canUseSharedArrayBuffer && preferRangeAccess) { + try { + // Send initial range request to check if server supports it + const testRangeSize = 16 * 1024; // 16KB + const rangeResponse = await fetch(url, { + headers: { + ...headers, + Range: `bytes=0-${testRangeSize - 1}`, + }, + mode: 'cors', + credentials: withCredentials ? 'include' : 'same-origin', + redirect: 'follow', + }); + + // Check if we got a partial content response + if (rangeResponse.status === 206) { + // Parse content-range header to get total file size + const contentRange = rangeResponse.headers.get('Content-Range'); + if (contentRange) { + const match = contentRange.match(/bytes \d+-\d+\/(\d+)/); + if (match) { + const contentLength = parseInt(match[1], 10); + + // Server supports range requests! + return loadDocumentFromUrlWithRangeAccess({ + url, + password, + useProgressiveLoading, + headers, + withCredentials, + progressCallbackId, + contentLength, + initialData: new Uint8Array(await rangeResponse.arrayBuffer()), + }); + } + } + } else if (rangeResponse.status === 200) { + // Server doesn't support range requests, returned full file + // Use the common download handler + return _handleFullDownload(rangeResponse, password, useProgressiveLoading, progressCallbackId); + } + } catch (e) { + // If range request fails, fall back to regular loading + console.warn('Range request failed, falling back to regular loading:', e); + } + } + // Regular loading without range access const response = await fetch(url, { headers: headers, mode: 'cors', credentials: withCredentials ? 'include' : 'same-origin', - redirect: "follow", + redirect: 'follow', }); + return _handleFullDownload(response, password, useProgressiveLoading, progressCallbackId); +} + +/** + * Common handler for full file downloads (both regular and when range is not supported) + * @param {Response} response - The fetch response + * @param {string} password - Password for the PDF + * @param {boolean} useProgressiveLoading - Whether to use progressive loading + * @param {number|undefined} progressCallbackId - Progress callback ID + */ +async function _handleFullDownload(response, password, useProgressiveLoading, progressCallbackId) { // Get the content length for progress reporting const contentLength = parseInt(response.headers.get('content-length') || '0', 10); - let receivedLength = 0; // If we have progress callback and a valid content length, use streaming if (progressCallbackId && contentLength > 0 && response.body) { const reader = response.body.getReader(); const chunks = []; + let receivedLength = 0; while (true) { const { done, value } = await reader.read(); - if (done) break; - chunks.push(value); receivedLength += value.length; @@ -578,6 +641,236 @@ async function loadDocumentFromUrl(params) { } } +/** + * A global shared buffer for read synchronization + * @type {Int32Array|null} + */ +const _readWait32 = canUseSharedArrayBuffer ? new Int32Array(new SharedArrayBuffer(4)) : null; + +/** + * Load document using range requests + * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined, contentLength: number, initialData: Uint8Array}} params + */ +async function loadDocumentFromUrlWithRangeAccess(params) { + if (_readWait32 == null) { + throw new Error('SharedArrayBuffer is required for range-based loading'); + } + const url = params.url; + const password = params.password || ''; + const useProgressiveLoading = params.useProgressiveLoading; + const headers = params.headers || {}; + const withCredentials = params.withCredentials || false; + const progressCallbackId = params.progressCallbackId; + const contentLength = params.contentLength; + const initialData = params.initialData; + + try { + // We already have the initial data from the range test request + const headerData = initialData; + const headerSize = headerData.length; + + // For PDFs, we need to fetch critical parts: + // 1. Header (already have it) + // 2. Cross-reference table (usually at the end) + // 3. Linearization dictionary (if linearized, near the beginning) + + // Fetch the end of the file where xref table usually is + const trailerSize = Math.min(64 * 1024, contentLength - headerSize); // Last 64KB + const trailerStart = Math.max(headerSize, contentLength - trailerSize); + + let trailerData = null; + if (trailerStart > headerSize) { + try { + const trailerResponse = await fetch(url, { + headers: { + ...headers, + Range: `bytes=${trailerStart}-${contentLength - 1}`, + }, + mode: 'cors', + credentials: withCredentials ? 'include' : 'same-origin', + redirect: 'follow', + }); + + if (trailerResponse.status === 206) { + trailerData = new Uint8Array(await trailerResponse.arrayBuffer()); + } + } catch (e) { + console.warn('Failed to fetch PDF trailer:', e); + } + } + + const sharedBuffer = new SharedArrayBuffer(contentLength); + let pdfData = new Uint8Array(sharedBuffer); + + // Create a single wait buffer for synchronization + const blockSize = 64 * 1024; + const blockCount = Math.ceil(contentLength / blockSize); + // Track which blocks are fetched in a regular array + let blockStatus = new Int8Array(blockCount); // 0=not fetched, 1=fetched, -1=failed + + async function readAsync(context, buffer) { + const position = context.position; + const length = buffer.length; + const endPosition = Math.min(position + length, contentLength); + const blockSize = 64 * 1024; + + // Check which blocks we need + const startBlock = Math.floor(position / blockSize); + const endBlock = Math.floor((endPosition - 1) / blockSize); + + // Find ranges of sequential blocks that need to be fetched + let rangeStart = -1; + for (let blockIndex = startBlock; blockIndex <= endBlock; blockIndex++) { + if (blockStatus[blockIndex] === -1) { + // Block already failed + console.error(`Block ${blockIndex} was already marked as failed`); + return 0; // Read failed + } + + if (blockStatus[blockIndex] === 0) { + if (rangeStart === -1) { + rangeStart = blockIndex; + } + } else if (rangeStart !== -1) { + // End of unfetched range, fetch it + const rangeEnd = blockIndex - 1; + const success = await fetchBlockRange(rangeStart, rangeEnd); + if (!success) { + return 0; // Read failed + } + rangeStart = -1; + } + } + + // Fetch any remaining range + if (rangeStart !== -1) { + const success = await fetchBlockRange(rangeStart, endBlock); + if (!success) { + return 0; // Read failed + } + } + + // Helper function to fetch a range of blocks + async function fetchBlockRange(firstBlock, lastBlock) { + const start = firstBlock * blockSize; + const end = Math.min((lastBlock + 1) * blockSize, contentLength); + + console.log(`Fetching blocks ${firstBlock}-${lastBlock} (bytes ${start}-${end - 1})`); + + try { + const response = await fetch(url, { + headers: { + ...headers, + Range: `bytes=${start}-${end - 1}`, + }, + mode: 'cors', + credentials: withCredentials ? 'include' : 'same-origin', + redirect: 'follow', + }); + + if (response.status === 206) { + const data = new Uint8Array(await response.arrayBuffer()); + pdfData.set(data, start); + + // Update progress + totalFetched += data.length; + if (progressCallbackId) { + invokeCallback(progressCallbackId, Math.min(totalFetched, contentLength), contentLength); + } + + // Mark all blocks in range as fetched + for (let i = firstBlock; i <= lastBlock; i++) { + blockStatus[i] = 1; + } + return true; + } else { + throw new Error(`Unexpected status: ${response.status}`); + } + } catch (e) { + console.error(`Failed to fetch blocks ${firstBlock}-${lastBlock}:`, e); + // Mark all blocks in range as failed + for (let i = firstBlock; i <= lastBlock; i++) { + blockStatus[i] = -1; + } + return false; + } + } + + // All blocks are ready, copy from our data array + const bytesToRead = endPosition - position; + buffer.set(pdfData.subarray(position, endPosition)); + context.position += bytesToRead; + return bytesToRead; + } + + // Copy initial data + pdfData.set(headerData, 0); + if (trailerData) { + pdfData.set(trailerData, trailerStart); + } + + // Mark header blocks as fetched + for (let i = 0; i < Math.ceil(headerSize / blockSize); i++) { + blockStatus[i] = 1; + } + // Mark trailer blocks as fetched + if (trailerData) { + for (let i = Math.floor(trailerStart / blockSize); i < blockCount; i++) { + blockStatus[i] = 1; + } + } + + let totalFetched = headerSize + (trailerData ? trailerData.length : 0); + if (progressCallbackId) { + invokeCallback(progressCallbackId, totalFetched, contentLength); + } + + // Register the file with custom read handler + const tempFileName = url.replace(/[^a-zA-Z0-9]/g, '_') + '_' + Date.now() + '.pdf'; + const fullPath = '/tmp/' + tempFileName; + + fileSystem.registerFile(fullPath, { + size: contentLength, + read: function (context, buffer) { + let result = -1; + let resultReady = false; + readAsync(context, buffer).then((bytesRead) => { + result = bytesRead; + resultReady = true; + // Wake up the waiting sync function + Atomics.store(_readWait32, 0, 1); + Atomics.notify(_readWait32, 0); + }); + // Wait for the async read to complete + while (!resultReady) { + Atomics.wait(_readWait32, 0, 0, 1000); // Wait up to 1 second + Atomics.store(_readWait32, 0, 0); // Reset for next wait + } + return result; + }, + close: function () { + // Clean up - nothing to do + }, + }); + + // Load the document using the virtual file + const fileNamePtr = StringUtils.allocateUTF8(fullPath); + const passwordPtr = StringUtils.allocateUTF8(password); + const docHandle = Pdfium.wasmExports.FPDF_LoadDocument(fileNamePtr, passwordPtr); + StringUtils.freeUTF8(passwordPtr); + StringUtils.freeUTF8(fileNamePtr); + + const doc = _loadDocument(docHandle, useProgressiveLoading, () => { + fileSystem.unregisterFile(fullPath); + }); + + return doc; + } catch (e) { + console.error('Range-based loading failed:', e); + throw e; + } +} + /** * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ @@ -1279,7 +1572,7 @@ function invokeCallback(callbackId, ...args) { postMessage({ type: 'callback', callbackId: callbackId, - args: args + args: args, }); } } @@ -1335,29 +1628,26 @@ console.log(`PDFium worker initialized: ${self.location.href}`); async function initializePdfium(params = {}) { try { console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); - + const fetchOptions = { credentials: params.withCredentials ? 'include' : 'same-origin', }; - + if (params.headers) { fetchOptions.headers = params.headers; } - - const result = await WebAssembly.instantiateStreaming( - fetch(pdfiumWasmUrl, fetchOptions), - { - env: emEnv, - wasi_snapshot_preview1: wasi, - } - ); - + + const result = await WebAssembly.instantiateStreaming(fetch(pdfiumWasmUrl, fetchOptions), { + env: emEnv, + wasi_snapshot_preview1: wasi, + }); + Pdfium.initWith(result.instance.exports); Pdfium.wasmExports.FPDF_InitLibrary(); pdfiumInitialized = true; - + postMessage({ type: 'ready' }); - + // Process queued messages messagesBeforeInitialized.forEach((event) => handleRequest(event.data)); messagesBeforeInitialized = null; @@ -1370,7 +1660,7 @@ async function initializePdfium(params = {}) { onmessage = function (e) { const data = e.data; - + // Handle init command if (data && data.command === 'init') { initializePdfium(data.parameters || {}) @@ -1382,7 +1672,7 @@ onmessage = function (e) { }); return; } - + if (data && data.id && data.command) { if (!pdfiumInitialized && messagesBeforeInitialized) { messagesBeforeInitialized.push(e); diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index df070d88..1e321460 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -307,7 +307,7 @@ abstract class PdfDocument { /// /// [progressCallback] is called when the download progress is updated (Not supported on Web). /// [reportCallback] is called when the download is completed (Not supported on Web). - /// [preferRangeAccess] to prefer range access to download the PDF (Not supported on Web). + /// [preferRangeAccess] to prefer range access to download the PDF. /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). static Future openUri( diff --git a/lib/src/pdf_document_ref.dart b/lib/src/pdf_document_ref.dart index 3ec671d2..cec05536 100644 --- a/lib/src/pdf_document_ref.dart +++ b/lib/src/pdf_document_ref.dart @@ -132,7 +132,7 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin @override final bool firstAttemptByEmptyPassword; - /// Whether to prefer range access or not (Not supported on Web). + /// Whether to prefer range access or not. final bool preferRangeAccess; /// Additional HTTP headers especially for authentication/authorization. diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 976e54bc..1fdc2cf4 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -238,7 +238,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { 'password': password, 'useProgressiveLoading': useProgressiveLoading, if (progressCallbackReg != null) 'progressCallbackId': progressCallbackReg.id, - // 'preferRangeAccess': preferRangeAccess, + 'preferRangeAccess': preferRangeAccess, if (headers != null) 'headers': headers, 'withCredentials': withCredentials, }, From 3a015a0634c52cc08e484a51deb4a4fe8952b90e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 22 Jun 2025 00:37:16 +0900 Subject: [PATCH 157/663] WIP --- assets/pdfium_worker.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 02c6f8f0..704d393d 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -678,17 +678,21 @@ async function loadDocumentFromUrlWithRangeAccess(params) { const trailerSize = Math.min(64 * 1024, contentLength - headerSize); // Last 64KB const trailerStart = Math.max(headerSize, contentLength - trailerSize); + const corsParams = { + mode: 'cors', + credentials: withCredentials ? 'include' : 'same-origin', + redirect: 'follow', + }; + let trailerData = null; if (trailerStart > headerSize) { try { const trailerResponse = await fetch(url, { + ...corsParams, headers: { ...headers, Range: `bytes=${trailerStart}-${contentLength - 1}`, }, - mode: 'cors', - credentials: withCredentials ? 'include' : 'same-origin', - redirect: 'follow', }); if (trailerResponse.status === 206) { @@ -706,7 +710,7 @@ async function loadDocumentFromUrlWithRangeAccess(params) { const blockSize = 64 * 1024; const blockCount = Math.ceil(contentLength / blockSize); // Track which blocks are fetched in a regular array - let blockStatus = new Int8Array(blockCount); // 0=not fetched, 1=fetched, -1=failed + const blockStatus = new Int8Array(blockCount); // 0=not fetched, 1=fetched, -1=failed async function readAsync(context, buffer) { const position = context.position; @@ -759,13 +763,11 @@ async function loadDocumentFromUrlWithRangeAccess(params) { try { const response = await fetch(url, { + ...corsParams, headers: { ...headers, Range: `bytes=${start}-${end - 1}`, }, - mode: 'cors', - credentials: withCredentials ? 'include' : 'same-origin', - redirect: 'follow', }); if (response.status === 206) { From efb47bea6fa1e0b936e67ba431fa5cd785c300bb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 22 Jun 2025 00:37:59 +0900 Subject: [PATCH 158/663] Add code to call _init before registering PdfiumWasmCallback (#399) --- lib/src/web/pdfrx_wasm.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 1fdc2cf4..2e3804cc 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -219,12 +219,13 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, - }) { + }) async { PdfiumWasmCallback? progressCallbackReg; void cleanupCallbacks() => progressCallbackReg?.unregister(); try { if (progressCallback != null) { + await _init(); progressCallbackReg = PdfiumWasmCallback.register( ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, ); @@ -263,6 +264,8 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { required bool firstAttemptByEmptyPassword, required void Function()? onDispose, }) async { + await _init(); + for (int i = 0; ; i++) { final String? password; if (firstAttemptByEmptyPassword && i == 0) { @@ -274,8 +277,6 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } } - await _init(); - final result = await openDocument(password); const fpdfErrPassword = 4; From 0e679633c2b30584d192269ed14dd0c5ebd3b1a8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 22 Jun 2025 00:49:09 +0900 Subject: [PATCH 159/663] Release v1.2.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 888af9de..4967a25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.8 + +- FIXED: Ensure PDFium WASM is initialized before registering callbacks ([#399](https://github.com/espresso3389/pdfrx/issues/399)) + # 1.2.7 - Enhanced PDFium initialization with optional authentication parameters for WASM diff --git a/README.md b/README.md index 46ee1684..d5956beb 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.7 + pdfrx: ^1.2.8 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index aa500123..204e214a 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.7" + version: "1.2.8" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d3225abf..9dcd71ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.7 +version: 1.2.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From e8094150b554dcc8bbb376b57fbb1b05d8c6ad7c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 24 Jun 2025 01:37:26 +0900 Subject: [PATCH 160/663] WIP: remove functions depend on SharedArrayBuffer SharedArrayBuffer is too much restricted and they would not be used on production easily... The efforts are useless I think. --- assets/pdfium_worker.js | 299 -------------------------------- lib/src/pdf_api.dart | 5 +- lib/src/widgets/pdf_viewer.dart | 2 +- 3 files changed, 3 insertions(+), 303 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 704d393d..3483d6cc 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -516,11 +516,6 @@ const wasi = { fd_sync: fileSystem.sync.bind(fileSystem), }; -/** - * Check if SharedArrayBuffer is available - */ -const canUseSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined'; - /** * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined, preferRangeAccess: boolean|undefined}} params */ @@ -531,75 +526,13 @@ async function loadDocumentFromUrl(params) { const headers = params.headers || {}; const withCredentials = params.withCredentials || false; const progressCallbackId = params.progressCallbackId; - const preferRangeAccess = params.preferRangeAccess || false; - - if (canUseSharedArrayBuffer && preferRangeAccess) { - try { - // Send initial range request to check if server supports it - const testRangeSize = 16 * 1024; // 16KB - const rangeResponse = await fetch(url, { - headers: { - ...headers, - Range: `bytes=0-${testRangeSize - 1}`, - }, - mode: 'cors', - credentials: withCredentials ? 'include' : 'same-origin', - redirect: 'follow', - }); - // Check if we got a partial content response - if (rangeResponse.status === 206) { - // Parse content-range header to get total file size - const contentRange = rangeResponse.headers.get('Content-Range'); - if (contentRange) { - const match = contentRange.match(/bytes \d+-\d+\/(\d+)/); - if (match) { - const contentLength = parseInt(match[1], 10); - - // Server supports range requests! - return loadDocumentFromUrlWithRangeAccess({ - url, - password, - useProgressiveLoading, - headers, - withCredentials, - progressCallbackId, - contentLength, - initialData: new Uint8Array(await rangeResponse.arrayBuffer()), - }); - } - } - } else if (rangeResponse.status === 200) { - // Server doesn't support range requests, returned full file - // Use the common download handler - return _handleFullDownload(rangeResponse, password, useProgressiveLoading, progressCallbackId); - } - } catch (e) { - // If range request fails, fall back to regular loading - console.warn('Range request failed, falling back to regular loading:', e); - } - } - - // Regular loading without range access const response = await fetch(url, { headers: headers, mode: 'cors', credentials: withCredentials ? 'include' : 'same-origin', redirect: 'follow', }); - - return _handleFullDownload(response, password, useProgressiveLoading, progressCallbackId); -} - -/** - * Common handler for full file downloads (both regular and when range is not supported) - * @param {Response} response - The fetch response - * @param {string} password - Password for the PDF - * @param {boolean} useProgressiveLoading - Whether to use progressive loading - * @param {number|undefined} progressCallbackId - Progress callback ID - */ -async function _handleFullDownload(response, password, useProgressiveLoading, progressCallbackId) { - // Get the content length for progress reporting const contentLength = parseInt(response.headers.get('content-length') || '0', 10); // If we have progress callback and a valid content length, use streaming @@ -641,238 +574,6 @@ async function _handleFullDownload(response, password, useProgressiveLoading, pr } } -/** - * A global shared buffer for read synchronization - * @type {Int32Array|null} - */ -const _readWait32 = canUseSharedArrayBuffer ? new Int32Array(new SharedArrayBuffer(4)) : null; - -/** - * Load document using range requests - * @param {{url: string, password: string|undefined, useProgressiveLoading: boolean|undefined, headers: Object.|undefined, withCredentials: boolean|undefined, progressCallbackId: number|undefined, contentLength: number, initialData: Uint8Array}} params - */ -async function loadDocumentFromUrlWithRangeAccess(params) { - if (_readWait32 == null) { - throw new Error('SharedArrayBuffer is required for range-based loading'); - } - const url = params.url; - const password = params.password || ''; - const useProgressiveLoading = params.useProgressiveLoading; - const headers = params.headers || {}; - const withCredentials = params.withCredentials || false; - const progressCallbackId = params.progressCallbackId; - const contentLength = params.contentLength; - const initialData = params.initialData; - - try { - // We already have the initial data from the range test request - const headerData = initialData; - const headerSize = headerData.length; - - // For PDFs, we need to fetch critical parts: - // 1. Header (already have it) - // 2. Cross-reference table (usually at the end) - // 3. Linearization dictionary (if linearized, near the beginning) - - // Fetch the end of the file where xref table usually is - const trailerSize = Math.min(64 * 1024, contentLength - headerSize); // Last 64KB - const trailerStart = Math.max(headerSize, contentLength - trailerSize); - - const corsParams = { - mode: 'cors', - credentials: withCredentials ? 'include' : 'same-origin', - redirect: 'follow', - }; - - let trailerData = null; - if (trailerStart > headerSize) { - try { - const trailerResponse = await fetch(url, { - ...corsParams, - headers: { - ...headers, - Range: `bytes=${trailerStart}-${contentLength - 1}`, - }, - }); - - if (trailerResponse.status === 206) { - trailerData = new Uint8Array(await trailerResponse.arrayBuffer()); - } - } catch (e) { - console.warn('Failed to fetch PDF trailer:', e); - } - } - - const sharedBuffer = new SharedArrayBuffer(contentLength); - let pdfData = new Uint8Array(sharedBuffer); - - // Create a single wait buffer for synchronization - const blockSize = 64 * 1024; - const blockCount = Math.ceil(contentLength / blockSize); - // Track which blocks are fetched in a regular array - const blockStatus = new Int8Array(blockCount); // 0=not fetched, 1=fetched, -1=failed - - async function readAsync(context, buffer) { - const position = context.position; - const length = buffer.length; - const endPosition = Math.min(position + length, contentLength); - const blockSize = 64 * 1024; - - // Check which blocks we need - const startBlock = Math.floor(position / blockSize); - const endBlock = Math.floor((endPosition - 1) / blockSize); - - // Find ranges of sequential blocks that need to be fetched - let rangeStart = -1; - for (let blockIndex = startBlock; blockIndex <= endBlock; blockIndex++) { - if (blockStatus[blockIndex] === -1) { - // Block already failed - console.error(`Block ${blockIndex} was already marked as failed`); - return 0; // Read failed - } - - if (blockStatus[blockIndex] === 0) { - if (rangeStart === -1) { - rangeStart = blockIndex; - } - } else if (rangeStart !== -1) { - // End of unfetched range, fetch it - const rangeEnd = blockIndex - 1; - const success = await fetchBlockRange(rangeStart, rangeEnd); - if (!success) { - return 0; // Read failed - } - rangeStart = -1; - } - } - - // Fetch any remaining range - if (rangeStart !== -1) { - const success = await fetchBlockRange(rangeStart, endBlock); - if (!success) { - return 0; // Read failed - } - } - - // Helper function to fetch a range of blocks - async function fetchBlockRange(firstBlock, lastBlock) { - const start = firstBlock * blockSize; - const end = Math.min((lastBlock + 1) * blockSize, contentLength); - - console.log(`Fetching blocks ${firstBlock}-${lastBlock} (bytes ${start}-${end - 1})`); - - try { - const response = await fetch(url, { - ...corsParams, - headers: { - ...headers, - Range: `bytes=${start}-${end - 1}`, - }, - }); - - if (response.status === 206) { - const data = new Uint8Array(await response.arrayBuffer()); - pdfData.set(data, start); - - // Update progress - totalFetched += data.length; - if (progressCallbackId) { - invokeCallback(progressCallbackId, Math.min(totalFetched, contentLength), contentLength); - } - - // Mark all blocks in range as fetched - for (let i = firstBlock; i <= lastBlock; i++) { - blockStatus[i] = 1; - } - return true; - } else { - throw new Error(`Unexpected status: ${response.status}`); - } - } catch (e) { - console.error(`Failed to fetch blocks ${firstBlock}-${lastBlock}:`, e); - // Mark all blocks in range as failed - for (let i = firstBlock; i <= lastBlock; i++) { - blockStatus[i] = -1; - } - return false; - } - } - - // All blocks are ready, copy from our data array - const bytesToRead = endPosition - position; - buffer.set(pdfData.subarray(position, endPosition)); - context.position += bytesToRead; - return bytesToRead; - } - - // Copy initial data - pdfData.set(headerData, 0); - if (trailerData) { - pdfData.set(trailerData, trailerStart); - } - - // Mark header blocks as fetched - for (let i = 0; i < Math.ceil(headerSize / blockSize); i++) { - blockStatus[i] = 1; - } - // Mark trailer blocks as fetched - if (trailerData) { - for (let i = Math.floor(trailerStart / blockSize); i < blockCount; i++) { - blockStatus[i] = 1; - } - } - - let totalFetched = headerSize + (trailerData ? trailerData.length : 0); - if (progressCallbackId) { - invokeCallback(progressCallbackId, totalFetched, contentLength); - } - - // Register the file with custom read handler - const tempFileName = url.replace(/[^a-zA-Z0-9]/g, '_') + '_' + Date.now() + '.pdf'; - const fullPath = '/tmp/' + tempFileName; - - fileSystem.registerFile(fullPath, { - size: contentLength, - read: function (context, buffer) { - let result = -1; - let resultReady = false; - readAsync(context, buffer).then((bytesRead) => { - result = bytesRead; - resultReady = true; - // Wake up the waiting sync function - Atomics.store(_readWait32, 0, 1); - Atomics.notify(_readWait32, 0); - }); - // Wait for the async read to complete - while (!resultReady) { - Atomics.wait(_readWait32, 0, 0, 1000); // Wait up to 1 second - Atomics.store(_readWait32, 0, 0); // Reset for next wait - } - return result; - }, - close: function () { - // Clean up - nothing to do - }, - }); - - // Load the document using the virtual file - const fileNamePtr = StringUtils.allocateUTF8(fullPath); - const passwordPtr = StringUtils.allocateUTF8(password); - const docHandle = Pdfium.wasmExports.FPDF_LoadDocument(fileNamePtr, passwordPtr); - StringUtils.freeUTF8(passwordPtr); - StringUtils.freeUTF8(fileNamePtr); - - const doc = _loadDocument(docHandle, useProgressiveLoading, () => { - fileSystem.unregisterFile(fullPath); - }); - - return doc; - } catch (e) { - console.error('Range-based loading failed:', e); - throw e; - } -} - /** * @param {{data: ArrayBuffer, password: string|undefined, useProgressiveLoading: boolean|undefined}} params */ diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 1e321460..ed708864 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -305,9 +305,8 @@ abstract class PdfDocument { /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. /// - /// [progressCallback] is called when the download progress is updated (Not supported on Web). - /// [reportCallback] is called when the download is completed (Not supported on Web). - /// [preferRangeAccess] to prefer range access to download the PDF. + /// [progressCallback] is called when the download progress is updated. + /// [preferRangeAccess] to prefer range access to download the PDF. The default is false (Not supported on Web). /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). static Future openUri( diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 43e8cc7e..49f75ad4 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -98,7 +98,7 @@ class PdfViewer extends StatefulWidget { /// [controller] is the controller to control the viewer. /// [params] is the parameters to customize the viewer. /// [initialPageNumber] is the page number to show initially. - /// [preferRangeAccess] to prefer range access to download the PDF. The default is false. + /// [preferRangeAccess] to prefer range access to download the PDF. The default is false. (Not supported on Web). /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). PdfViewer.uri( From 0c687b1f74d96ca94ed2147cdaffe46a11aa9e02 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 25 Jun 2025 14:00:41 +0900 Subject: [PATCH 161/663] Pdfium WASM: hot-restarting may call initializePdfium multiple times --- assets/pdfium_worker.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 3483d6cc..48ad395f 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -1330,6 +1330,11 @@ console.log(`PDFium worker initialized: ${self.location.href}`); */ async function initializePdfium(params = {}) { try { + if (pdfiumInitialized) { + // Hot-restart or such may call this multiple times, so we can skip re-initialization + return; + } + console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); const fetchOptions = { From cd8512b8007f4534c5e0fe5599ecf01ab2121d79 Mon Sep 17 00:00:00 2001 From: Francesco Berardi Date: Tue, 24 Jun 2025 16:30:49 +0200 Subject: [PATCH 162/663] fix: Use document base href for resolving URLs This change updates the URL resolution logic to consider the `` tag in the HTML document. The order of precedence for the base URL is now: 1. Explicitly provided `baseUrl` parameter. 2. `` tag of the current HTML document. 3. Current browser window's URL. --- lib/src/web/pdfrx_wasm.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 2e3804cc..2ec1843f 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart' show Colors, immutable; import 'package:flutter/services.dart'; @@ -126,10 +127,15 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { return web.URL.createObjectURL(blob); } - /// Resolves the given [relativeUrl] against the [baseUrl]. - /// If [baseUrl] is null, it uses the current page URL. + /// Resolves the given [relativeUrl] against a base URL to produce an absolute URL. + /// + /// The base URL is determined in the following order of preference: + /// 1. The explicitly provided [baseUrl] parameter. + /// 2. The `` tag of the current HTML document (obtained via `ui_web.BrowserPlatformLocation().getBaseHref()`). + /// 3. The current browser window's URL (`web.window.location.href`). static String _resolveUrl(String relativeUrl, {String? baseUrl}) { - return Uri.parse(baseUrl ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); + final baseHref = ui_web.BrowserPlatformLocation().getBaseHref(); + return Uri.parse(baseUrl ?? baseHref ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); } Future> sendCommand(String command, {Map? parameters}) async { From dae96415763804c30be110a647fb43930409b152 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 25 Jun 2025 21:12:30 +0900 Subject: [PATCH 163/663] Release v1.2.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 5 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- wasm/pdfrx_wasm/pubspec.lock | 56 ++++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 wasm/pdfrx_wasm/pubspec.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 4967a25a..bc608c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.2.9 + +- FIXED: Use document base href for resolving URLs in PDFium WASM ([#402](https://github.com/espresso3389/pdfrx/pull/402)) +- FIXED: PDFium WASM hot-restarting may call initializePdfium multiple times + # 1.2.8 - FIXED: Ensure PDFium WASM is initialized before registering callbacks ([#399](https://github.com/espresso3389/pdfrx/issues/399)) diff --git a/README.md b/README.md index d5956beb..7c619fb3 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.8 + pdfrx: ^1.2.9 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 204e214a..65055e88 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.8" + version: "1.2.9" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9dcd71ad..78596706 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.8 +version: 1.2.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/wasm/pdfrx_wasm/pubspec.lock b/wasm/pdfrx_wasm/pubspec.lock new file mode 100644 index 00000000..8a87a4f4 --- /dev/null +++ b/wasm/pdfrx_wasm/pubspec.lock @@ -0,0 +1,56 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" From d917881af58240aff02705a0ac0bf2fa97132ba6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Jul 2025 15:58:18 +0900 Subject: [PATCH 164/663] WIP --- CLAUDE.md | 12 ++++++++++++ bin/remove_wasm_modules.dart | 2 ++ 2 files changed, 14 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index a7d2dc53..faae396a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,17 @@ dart run ffigen # Regenerate PDFium FFI bindings 9. Do `flutter pub publish` to publish the package 10. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release + - If the PR references issues, please also comment on the issues + - Follow the template below for comments (but modify it as needed): + + ``` + The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). + + ...Fix/update summary... + + Written by 🤖[Claude Code](https://claude.ai/code) + ``` + - Focus on the release notes and what was fixed/changed rather than upgrade instructions - Include a link to the changelog for the specific version @@ -186,3 +197,4 @@ The following guidelines should be followed when writing documentation including ## Special Notes - `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version +- For web search, if `gemini` command is available, use `gemini -p ""`. diff --git a/bin/remove_wasm_modules.dart b/bin/remove_wasm_modules.dart index f5c0ceda..b2d5a2c8 100644 --- a/bin/remove_wasm_modules.dart +++ b/bin/remove_wasm_modules.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_print + import 'dart:io'; import 'package:dart_pubspec_licenses/dart_pubspec_licenses.dart' as oss; From c2b692290a4ab3395a95ba2488601a6690c3405e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 2 Jul 2025 16:01:08 +0900 Subject: [PATCH 165/663] WIP: pdfrx API changes Some changes are compatibility breaking. --- lib/src/pdf_api.dart | 199 ++++++++++++++++----- lib/src/pdfium/pdfrx_pdfium.dart | 67 ++++--- lib/src/web/pdfrx_wasm.dart | 83 ++++----- lib/src/widgets/pdf_page_text_overlay.dart | 32 +--- 4 files changed, 230 insertions(+), 151 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index ed708864..88a9b4ba 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -1,5 +1,6 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; +import 'dart:math'; import 'dart:ui' as ui; import 'package:collection/collection.dart'; @@ -557,8 +558,13 @@ abstract class PdfPageText { /// Find text fragment index for the specified text index. /// - /// If the specified text index is out of range, it returns -1. + /// If the specified text index is out of range, it returns -1; + /// only the exception is [textIndex] is equal to [fullText.length], + /// which means the end of the text and it returns [fragments.length]. int getFragmentIndexForTextIndex(int textIndex) { + if (textIndex == fullText.length) { + return fragments.length; // the end of the text + } final index = fragments.lowerBound(_PdfPageTextFragmentForSearch(textIndex), (a, b) => a.index - b.index); if (index > fragments.length) { return -1; // range error @@ -618,12 +624,39 @@ abstract class PdfPageTextFragment { /// Bounds of the text fragment in PDF page coordinates. PdfRect get bounds; - /// Fragment's child character bounding boxes in PDF page coordinates if available. - List? get charRects; + /// Fragment's child character bounding boxes in PDF page coordinates. + List get charRects; /// Text for the fragment. String get text; + /// Get the bounds of the subrange of the text fragment. + /// + /// If the specified range is out of bounds, it returns null. + PdfRect? getBoundsForRange({int? start, int? end, double? widthForEmpty}) { + start ??= 0; + end ??= length; + if (start < 0 || start >= length) { + throw RangeError.range(start, 0, length, 'start', 'Invalid start index'); + } + if (end < start || end > length) { + throw RangeError.range(end, start, length, 'end', 'Invalid end index'); + } + if (start == end) { + if (widthForEmpty == null) return null; // empty range + final cur = charRects[start]; + final next = charRects[start + 1]; + final w = next.left - cur.right; + final h = next.bottom - cur.top; // the Y-coord is bottom-up + if (w > h) { + return PdfRect(cur.left, cur.top, cur.left + widthForEmpty, cur.bottom); + } else { + return PdfRect(cur.left, cur.top, cur.right, cur.top - widthForEmpty); + } + } + return charRects.skip(start).take(end - start).boundingRect(); + } + @override bool operator ==(covariant PdfPageTextFragment other) { if (identical(this, other)) return true; @@ -638,17 +671,12 @@ abstract class PdfPageTextFragment { int get hashCode => index.hashCode ^ bounds.hashCode ^ text.hashCode; /// Create a [PdfPageTextFragment]. - static PdfPageTextFragment fromParams( - int index, - int length, - PdfRect bounds, - String text, { - List? charRects, - }) => _PdfPageTextFragment(index, length, bounds, text, charRects: charRects); + static PdfPageTextFragment fromParams(int index, int length, PdfRect bounds, String text, List charRects) => + _PdfPageTextFragment(index, length, bounds, text, charRects); } class _PdfPageTextFragment extends PdfPageTextFragment { - _PdfPageTextFragment(this.index, this.length, this.bounds, this.text, {this.charRects}); + _PdfPageTextFragment(this.index, this.length, this.bounds, this.text, this.charRects); @override final int index; @@ -657,7 +685,7 @@ class _PdfPageTextFragment extends PdfPageTextFragment { @override final PdfRect bounds; @override - final List? charRects; + final List charRects; @override final String text; } @@ -674,7 +702,7 @@ class _PdfPageTextFragmentForSearch extends PdfPageTextFragment { @override String get text => throw UnimplementedError(); @override - List? get charRects => null; + List get charRects => throw UnimplementedError(); } /// Simple text range in a PDF page. @@ -693,7 +721,7 @@ class PdfTextRange { PdfTextRange copyWith({int? start, int? end}) => PdfTextRange(start: start ?? this.start, end: end ?? this.end); @override - int get hashCode => start ^ end; + int get hashCode => start ^ end.hashCode; @override bool operator ==(Object other) { @@ -710,6 +738,22 @@ class PdfTextRange { PdfTextRangeWithFragments.fromTextRange(pageText, start, end); } +extension PdfTextRangeListExt on List { + void appendRange(PdfTextRange range) { + if (isNotEmpty && range.start >= last.start && range.start <= last.end) { + last = PdfTextRange(start: last.start, end: range.end); + } else { + add(range); + } + } + + void appendAllRanges(Iterable ranges) { + for (final r in ranges) { + appendRange(r); + } + } +} + /// Text ranges in a PDF page typically used to describe text selection. class PdfTextRanges { /// Create a [PdfTextRanges]. @@ -753,15 +797,57 @@ class PdfTextRangeWithFragments { /// Fragments that contains the text. final List fragments; - /// In-fragment text start index on the first fragment. + /// In-fragment text start index on the first fragment ([fragments.first]). final int start; - /// In-fragment text end index on the last fragment. + /// In-fragment text end index on the last fragment ([fragments.last]). final int end; /// Bounding rectangle of the text. final PdfRect bounds; + /// The first character's bounding rectangle. + /// + /// If the first fragment does not have character level bounding rectangles, + /// it returns the bounds of the first fragment. + /// + /// The function is useful when you implement text selection algorithm or such. + PdfRect get startCharRect { + final firstFragment = fragments.first; + if (firstFragment.charRects.isEmpty) { + return firstFragment.bounds; + } + return firstFragment.charRects[start]; + } + + /// The last character's bounding rectangle. + /// + /// If the last fragment does not have character level bounding rectangles, + /// it returns the bounds of the last fragment. + /// + /// The function is useful when you implement text selection algorithm or such. + PdfRect get endCharRect { + final lastFragment = fragments.last; + if (lastFragment.charRects.isEmpty) { + return lastFragment.bounds; + } + return lastFragment.charRects[end - 1]; + } + + /// Enumerate all the character bounding rectangles for the text range. + /// + /// The function is useful when you implement text selection algorithm or such. + Iterable enumerateRectsForRange({int? start, int? end, double? widthForEmpty}) sync* { + start ??= fragments.first.index + this.start; + end ??= fragments.last.index + this.end; + for (final f in fragments) { + if (f.end <= start || end <= f.index) continue; + final s = max(start - f.index, 0); + final e = min(end - f.index, f.length); + yield f.getBoundsForRange(start: s, end: e, widthForEmpty: widthForEmpty)!; + } + } + /// Create [PdfTextRangeWithFragments] from text range in [PdfPageText]. /// /// When you implement search-to-highlight feature, the most easiest way is to use [PdfTextSearcher] but you can @@ -778,7 +864,8 @@ class PdfTextRangeWithFragments { /// ``` /// /// To paint text highlights on PDF pages, see [PdfViewerParams.pagePaintCallbacks] and [PdfViewerPagePaintCallback]. - static PdfTextRangeWithFragments? fromTextRange(PdfPageText pageText, int start, int end) { + static PdfTextRangeWithFragments? fromTextRange(PdfPageText pageText, int start, [int? end]) { + end ??= pageText.fullText.length; if (start >= end) { return null; } @@ -790,43 +877,46 @@ class PdfTextRangeWithFragments { [pageText.fragments[s]], start - sf.index, end - sf.index, - sf.bounds, + sf.charRects.skip(start - sf.index).take(end - start).boundingRect(), ); } - final l = pageText.getFragmentIndexForTextIndex(end - 1); + final l = pageText.getFragmentIndexForTextIndex(end); if (s == l) { - if (sf.charRects == null) { - return PdfTextRangeWithFragments( - pageText.pageNumber, - [pageText.fragments[s]], - start - sf.index, - end - sf.index, - sf.bounds, - ); - } else { - return PdfTextRangeWithFragments( - pageText.pageNumber, - [pageText.fragments[s]], - start - sf.index, - end - sf.index, - sf.charRects!.skip(start - sf.index).take(end - start).boundingRect(), - ); - } + return PdfTextRangeWithFragments( + pageText.pageNumber, + [pageText.fragments[s]], + start - sf.index, + end - sf.index, + sf.charRects.skip(start - sf.index).take(end - start).boundingRect(), + ); } - var bounds = sf.charRects != null ? sf.charRects!.skip(start - sf.index).boundingRect() : sf.bounds; + var bounds = sf.charRects.skip(start - sf.index).boundingRect(); for (int i = s + 1; i < l; i++) { bounds = bounds.merge(pageText.fragments[i].bounds); } + if (l == pageText.fragments.length) { + return PdfTextRangeWithFragments( + pageText.pageNumber, + pageText.fragments.sublist(s), + start - sf.index, + pageText.fragments.last.length, + bounds, + ); + } + final lf = pageText.fragments[l]; - bounds = bounds.merge(lf.charRects != null ? lf.charRects!.take(end - lf.index).boundingRect() : lf.bounds); + final containLastFragment = end > lf.index; + if (containLastFragment) { + bounds = bounds.merge(lf.charRects.take(end - lf.index).boundingRect()); + } return PdfTextRangeWithFragments( pageText.pageNumber, - pageText.fragments.sublist(s, l + 1), + pageText.fragments.sublist(s, containLastFragment ? l + 1 : l), start - sf.index, - end - lf.index, + containLastFragment ? end - lf.index : end - pageText.fragments[l - 1].index, bounds, ); } @@ -879,6 +969,21 @@ class PdfRect { /// Height of the rectangle. double get height => top - bottom; + /// Top-left point of the rectangle. + PdfPoint get topLeft => PdfPoint(left, top); + + /// Top-right point of the rectangle. + PdfPoint get topRight => PdfPoint(right, top); + + /// Bottom-left point of the rectangle. + PdfPoint get bottomLeft => PdfPoint(left, bottom); + + /// Bottom-right point of the rectangle. + PdfPoint get bottomRight => PdfPoint(right, bottom); + + /// Center point of the rectangle. + PdfPoint get center => PdfPoint((left + right) / 2, (top + bottom) / 2); + /// Merge two rectangles. PdfRect merge(PdfRect other) { return PdfRect( @@ -896,6 +1001,14 @@ class PdfRect { /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); + /// Determine whether the rectangle overlaps the specified rectangle (in the PDF page coordinates). + bool overlaps(PdfRect other) { + return left < other.right && + right > other.left && + top > other.bottom && + bottom < other.top; // PDF page coordinates: top is bigger than bottom + } + /// Empty rectangle. static const empty = PdfRect(0, 0, 0, 0); @@ -1155,6 +1268,12 @@ class PdfPoint { @override int get hashCode => x.hashCode ^ y.hashCode; + double distanceSquaredTo(PdfPoint other) { + final dx = x - other.x; + final dy = y - other.y; + return dx * dx + dy * dy; + } + /// Convert to [Offset] in Flutter coordinate. /// [page] is the page to convert the rectangle. /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 7f92b452..83ba6c75 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -21,7 +21,7 @@ PdfDocumentFactory? _pdfiumDocumentFactory; /// Get [PdfDocumentFactory] backed by PDFium. /// /// For Flutter Web, you must set up PDFium WASM module. -PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= PdfDocumentFactoryImpl(); +PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= _PdfDocumentFactoryImpl(); /// Get the module file name for pdfium. String _getModuleFileName() { @@ -76,7 +76,7 @@ void _init() { final backgroundWorker = BackgroundWorker.create(); -class PdfDocumentFactoryImpl extends PdfDocumentFactory { +class _PdfDocumentFactoryImpl extends PdfDocumentFactory { @override Future openAsset( String name, { @@ -293,7 +293,7 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { } final doc = await openPdfDocument(password); if (doc != 0) { - return PdfDocumentPdfium.fromPdfDocument( + return _PdfDocumentPdfium.fromPdfDocument( pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), sourceName: sourceName, useProgressiveLoading: useProgressiveLoading, @@ -308,11 +308,11 @@ class PdfDocumentFactoryImpl extends PdfDocumentFactory { } } -extension FpdfUtf8StringExt on String { +extension _FpdfUtf8StringExt on String { Pointer toUtf8(Arena arena) => Pointer.fromAddress(toNativeUtf8(allocator: arena).address); } -class PdfDocumentPdfium extends PdfDocument { +class _PdfDocumentPdfium extends PdfDocument { final pdfium_bindings.FPDF_DOCUMENT document; final void Function()? disposeCallback; final int securityHandlerRevision; @@ -325,7 +325,7 @@ class PdfDocumentPdfium extends PdfDocument { @override final PdfPermissions? permissions; - PdfDocumentPdfium._( + _PdfDocumentPdfium._( this.document, { required super.sourceName, required this.securityHandlerRevision, @@ -344,7 +344,7 @@ class PdfDocumentPdfium extends PdfDocument { if (doc == nullptr) { throw const PdfException('Failed to load PDF document.'); } - PdfDocumentPdfium? pdfDoc; + _PdfDocumentPdfium? pdfDoc; try { final result = await (await backgroundWorker).compute((docAddress) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(docAddress); @@ -372,7 +372,7 @@ class PdfDocumentPdfium extends PdfDocument { }); }, doc.address); - pdfDoc = PdfDocumentPdfium._( + pdfDoc = _PdfDocumentPdfium._( doc, sourceName: sourceName, securityHandlerRevision: result.securityHandlerRevision, @@ -428,8 +428,8 @@ class PdfDocumentPdfium extends PdfDocument { } /// Loads pages in the document in a time-limited manner. - Future<({List pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ - List pagesLoadedSoFar = const [], + Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ + List<_PdfPagePdfium> pagesLoadedSoFar = const [], int? maxPageCountToLoadAdditionally, Duration? timeout, }) async { @@ -475,7 +475,7 @@ class PdfDocumentPdfium extends PdfDocument { for (int i = 0; i < results.pages.length; i++) { final pageData = results.pages[i]; pages.add( - PdfPagePdfium._( + _PdfPagePdfium._( document: this, pageNumber: pages.length + 1, width: pageData.width, @@ -490,7 +490,7 @@ class PdfDocumentPdfium extends PdfDocument { final last = pages.last; for (int i = pages.length; i < results.totalPageCount; i++) { pages.add( - PdfPagePdfium._( + _PdfPagePdfium._( document: this, pageNumber: pages.length + 1, width: last.width, @@ -508,13 +508,13 @@ class PdfDocumentPdfium extends PdfDocument { } @override - List get pages => _pages; + List<_PdfPagePdfium> get pages => _pages; - List _pages = []; + List<_PdfPagePdfium> _pages = []; @override bool isIdenticalDocumentHandle(Object? other) => - other is PdfDocumentPdfium && document.address == other.document.address; + other is _PdfDocumentPdfium && document.address == other.document.address; @override Future dispose() async { @@ -569,9 +569,9 @@ class PdfDocumentPdfium extends PdfDocument { } } -class PdfPagePdfium extends PdfPage { +class _PdfPagePdfium extends PdfPage { @override - final PdfDocumentPdfium document; + final _PdfDocumentPdfium document; @override final int pageNumber; @override @@ -585,7 +585,7 @@ class PdfPagePdfium extends PdfPage { @override final bool isLoaded; - PdfPagePdfium._({ + _PdfPagePdfium._({ required this.document, required this.pageNumber, required this.width, @@ -712,7 +712,7 @@ class PdfPagePdfium extends PdfPage { final resultBuffer = buffer; buffer = nullptr; - return PdfImagePdfium._(width: width, height: height, buffer: resultBuffer); + return _PdfImagePdfium._(width: width, height: height, buffer: resultBuffer); } catch (e) { return null; } finally { @@ -725,7 +725,7 @@ class PdfPagePdfium extends PdfPage { PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this); @override - Future loadText() => PdfPageTextPdfium._loadText(this); + Future loadText() => _PdfPageTextPdfium._loadText(this); @override Future> loadLinks({bool compact = false}) async { @@ -899,7 +899,7 @@ class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToke } } -class PdfImagePdfium extends PdfImage { +class _PdfImagePdfium extends PdfImage { @override final int width; @override @@ -911,7 +911,7 @@ class PdfImagePdfium extends PdfImage { final Pointer _buffer; - PdfImagePdfium._({required this.width, required this.height, required Pointer buffer}) : _buffer = buffer; + _PdfImagePdfium._({required this.width, required this.height, required Pointer buffer}) : _buffer = buffer; @override void dispose() { @@ -920,8 +920,8 @@ class PdfImagePdfium extends PdfImage { } @immutable -class PdfPageTextFragmentPdfium implements PdfPageTextFragment { - const PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); +class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { + _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); final PdfPageText pageText; @@ -934,13 +934,13 @@ class PdfPageTextFragmentPdfium implements PdfPageTextFragment { @override final PdfRect bounds; @override - final List? charRects; + final List charRects; @override String get text => pageText.fullText.substring(index, index + length); } -class PdfPageTextPdfium extends PdfPageText { - PdfPageTextPdfium({required this.pageNumber, required this.fullText, required this.fragments}); +class _PdfPageTextPdfium extends PdfPageText { + _PdfPageTextPdfium({required this.pageNumber, required this.fullText, required this.fragments}); @override final int pageNumber; @@ -950,19 +950,19 @@ class PdfPageTextPdfium extends PdfPageText { @override final List fragments; - static Future _loadText(PdfPagePdfium page) async { + static Future<_PdfPageTextPdfium> _loadText(_PdfPagePdfium page) async { final result = await _load(page); - final pageText = PdfPageTextPdfium(pageNumber: page.pageNumber, fullText: result.fullText, fragments: []); + final pageText = _PdfPageTextPdfium(pageNumber: page.pageNumber, fullText: result.fullText, fragments: []); int pos = 0; for (final fragment in result.fragments) { final charRects = result.charRects.sublist(pos, pos + fragment); - pageText.fragments.add(PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects)); + pageText.fragments.add(_PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects)); pos += fragment; } return pageText; } - static Future<({String fullText, List charRects, List fragments})> _load(PdfPagePdfium page) async { + static Future<({String fullText, List charRects, List fragments})> _load(_PdfPagePdfium page) async { if (page.document.isDisposed) { return (fullText: '', charRects: [], fragments: []); } @@ -1093,15 +1093,14 @@ class PdfPageTextPdfium extends PdfPageText { } static String _getText(pdfium_bindings.FPDF_TEXTPAGE textPage, int from, int length, Arena arena) { + // Since FPDFText_GetText could not handle '\0' in the middle of the text, + // we'd better use FPDFText_GetUnicode to obtain the text here. final count = pdfium.FPDFText_CountChars(textPage); final sb = StringBuffer(); for (int i = 0; i < count; i++) { sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); } return sb.toString(); - // final buffer = arena.allocate((length + 1) * sizeOf()); - // pdfium.FPDFText_GetText(textPage, from, length, buffer.cast()); - // return String.fromCharCodes(buffer.asTypedList(length)); } } diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 2ec1843f..7dd76461 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -15,11 +15,11 @@ import '../pdf_api.dart'; /// /// For Flutter Web, you must set up PDFium WASM module. /// For more information, see [Enable PDFium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-PDFium-WASM-support). -PdfDocumentFactory getPdfiumDocumentFactory() => PdfDocumentFactoryWasmImpl.singleton; +PdfDocumentFactory getPdfiumDocumentFactory() => _PdfDocumentFactoryWasmImpl.singleton; /// The PDFium WASM communicator object @JS('PdfiumWasmCommunicator') -extension type PdfiumWasmCommunicator(JSObject _) implements JSObject { +extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { /// Sends a command to the worker and returns a promise @JS('sendCommand') external JSPromise sendCommand([String command, JSAny? parameters, JSArray? transfer]); @@ -35,16 +35,16 @@ extension type PdfiumWasmCommunicator(JSObject _) implements JSObject { /// Get the global PdfiumWasmCommunicator instance @JS('PdfiumWasmCommunicator') -external PdfiumWasmCommunicator get pdfiumWasmCommunicator; +external _PdfiumWasmCommunicator get _pdfiumWasmCommunicator; /// A handle to a registered callback that can be unregistered class PdfiumWasmCallback { PdfiumWasmCallback.register(JSFunction callback) - : id = pdfiumWasmCommunicator._registerCallback(callback), - _communicator = pdfiumWasmCommunicator; + : id = _pdfiumWasmCommunicator._registerCallback(callback), + _communicator = _pdfiumWasmCommunicator; final int id; - final PdfiumWasmCommunicator _communicator; + final _PdfiumWasmCommunicator _communicator; void unregister() { _communicator._unregisterCallback(id); @@ -53,15 +53,15 @@ class PdfiumWasmCallback { /// The URL of the PDFium WASM worker script; pdfium_client.js tries to load worker script from this URL.' /// -/// [PdfDocumentFactoryWasmImpl._init] will initializes its value. +/// [_PdfDocumentFactoryWasmImpl._init] will initializes its value. @JS() external String pdfiumWasmWorkerUrl; /// [PdfDocumentFactory] for PDFium WASM implementation. -class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { - PdfDocumentFactoryWasmImpl._(); +class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { + _PdfDocumentFactoryWasmImpl._(); - static final singleton = PdfDocumentFactoryWasmImpl._(); + static final singleton = _PdfDocumentFactoryWasmImpl._(); /// Default path to the WASM modules /// @@ -139,7 +139,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } Future> sendCommand(String command, {Map? parameters}) async { - final result = await pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; + final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; return (result.dartify()) as Map; } @@ -265,7 +265,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { Future _openByFunc( Future> Function(String? password) openDocument, { required String sourceName, - required PdfDocumentFactoryWasmImpl factory, + required _PdfDocumentFactoryWasmImpl factory, required PdfPasswordProvider? passwordProvider, required bool firstAttemptByEmptyPassword, required void Function()? onDispose, @@ -294,19 +294,19 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); } - return PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose, factory: factory); + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose, factory: factory); } } } -class PdfDocumentWasm extends PdfDocument { - PdfDocumentWasm._(this.document, {required super.sourceName, required this.factory, this.disposeCallback}) +class _PdfDocumentWasm extends PdfDocument { + _PdfDocumentWasm._(this.document, {required super.sourceName, required this.factory, this.disposeCallback}) : permissions = parsePermissions(document) { pages = parsePages(this, document['pages'] as List); } final Map document; - final PdfDocumentFactoryWasmImpl factory; + final _PdfDocumentFactoryWasmImpl factory; final void Function()? disposeCallback; bool isDisposed = false; @@ -327,7 +327,7 @@ class PdfDocumentWasm extends PdfDocument { @override bool isIdenticalDocumentHandle(Object? other) { - return other is PdfDocumentWasm && other.document['docHandle'] == document['docHandle']; + return other is _PdfDocumentWasm && other.document['docHandle'] == document['docHandle']; } @override @@ -391,10 +391,10 @@ class PdfDocumentWasm extends PdfDocument { } } - static List parsePages(PdfDocumentWasm doc, List pageList) { + static List parsePages(_PdfDocumentWasm doc, List pageList) { return pageList .map( - (page) => PdfPageWasm( + (page) => _PdfPageWasm( doc, (page['pageIndex'] as num).toInt(), page['width'], @@ -407,8 +407,8 @@ class PdfDocumentWasm extends PdfDocument { } } -class PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { - PdfPageRenderCancellationTokenWasm(); +class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { + _PdfPageRenderCancellationTokenWasm(); bool _isCanceled = false; @@ -421,18 +421,18 @@ class PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken bool get isCanceled => _isCanceled; } -class PdfPageWasm extends PdfPage { - PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) +class _PdfPageWasm extends PdfPage { + _PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) : pageNumber = pageIndex + 1, rotation = PdfPageRotation.values[rotation]; @override PdfPageRenderCancellationToken createCancellationToken() { - return PdfPageRenderCancellationTokenWasm(); + return _PdfPageRenderCancellationTokenWasm(); } @override - final PdfDocumentWasm document; + final _PdfDocumentWasm document; @override Future> loadLinks({bool compact = false}) async { @@ -467,7 +467,7 @@ class PdfPageWasm extends PdfPage { 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); - final pageText = PdfPageTextJs(pageNumber: pageNumber, fullText: result['fullText'], fragments: []); + final pageText = _PdfPageTextJs(pageNumber: pageNumber, fullText: result['fullText'], fragments: []); final fragmentOffsets = result['fragments']; final charRectsAll = result['charRects'] as List; if (fragmentOffsets is List) { @@ -478,7 +478,9 @@ class PdfPageWasm extends PdfPage { final r = rect as List; return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); }).toList(); - pageText.fragments.add(PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects)); + pageText.fragments.add( + _PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects), + ); pos += fragment; } } @@ -558,8 +560,8 @@ class PdfImageWeb extends PdfImage { } @immutable -class PdfPageTextFragmentPdfium implements PdfPageTextFragment { - const PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); +class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { + _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); final PdfPageText pageText; @@ -572,7 +574,7 @@ class PdfPageTextFragmentPdfium implements PdfPageTextFragment { @override final PdfRect bounds; @override - final List? charRects; + final List charRects; @override String get text => pageText.fullText.substring(index, index + length); } @@ -587,25 +589,8 @@ PdfDest? _pdfDestFromMap(dynamic dest) { ); } -class PdfPageTextFragmentWeb implements PdfPageTextFragment { - PdfPageTextFragmentWeb(this.index, this.bounds, this.text); - - @override - final int index; - @override - int get length => text.length; - @override - int get end => index + length; - @override - final PdfRect bounds; - @override - List? get charRects => null; - @override - final String text; -} - -class PdfPageTextJs extends PdfPageText { - PdfPageTextJs({required this.pageNumber, required this.fullText, required this.fragments}); +class _PdfPageTextJs extends PdfPageText { + _PdfPageTextJs({required this.pageNumber, required this.fullText, required this.fragments}); @override final int pageNumber; diff --git a/lib/src/widgets/pdf_page_text_overlay.dart b/lib/src/widgets/pdf_page_text_overlay.dart index 4a946cbf..c782651e 100644 --- a/lib/src/widgets/pdf_page_text_overlay.dart +++ b/lib/src/widgets/pdf_page_text_overlay.dart @@ -293,20 +293,12 @@ class _PdfTextRenderBox extends RenderBox with PdfPageTextSelectable, Selectable Iterable<({Rect rect, String text, PdfTextRange range})> enumerateCharRects(int start, int end) sync* { for (int i = start; i < end; i++) { final fragment = _fragments[i]; - if (fragment.charRects == null) { + for (int j = 0; j < fragment.charRects.length; j++) { yield ( - rect: fragment.bounds.toRect(page: _page, scaledPageSize: size), - text: fragment.text, - range: PdfTextRange(start: fragment.index, end: fragment.end), + rect: fragment.charRects[j].toRect(page: _page, scaledPageSize: size), + text: fragment.text.substring(j, j + 1), + range: PdfTextRange(start: fragment.index + j, end: fragment.index + j + 1), ); - } else { - for (int j = 0; j < fragment.charRects!.length; j++) { - yield ( - rect: fragment.charRects![j].toRect(page: _page, scaledPageSize: size), - text: fragment.text.substring(j, j + 1), - range: PdfTextRange(start: fragment.index + j, end: fragment.index + j + 1), - ); - } } } } @@ -654,19 +646,3 @@ class _PdfTextRenderBox extends RenderBox with PdfPageTextSelectable, Selectable // } } } - -extension _PdfTextRangeListExt on List { - void appendRange(PdfTextRange range) { - if (isNotEmpty && range.start >= last.start && range.start <= last.end) { - last = last.copyWith(end: range.end); - } else { - add(range); - } - } - - void appendAllRanges(Iterable ranges) { - for (final r in ranges) { - appendRange(r); - } - } -} From 9429feec318c51144f29bd1989426b8ea1dd41f1 Mon Sep 17 00:00:00 2001 From: "Toshiyuki.Tanaka" Date: Wed, 2 Jul 2025 17:58:49 +0900 Subject: [PATCH 166/663] feat: Add an option to disable web link detection in loadLinks --- assets/pdfium_worker.js | 11 +++++++---- lib/src/pdf_api.dart | 4 +++- lib/src/pdfium/pdfrx_pdfium.dart | 9 ++++++--- lib/src/web/pdfrx_wasm.dart | 8 ++++++-- lib/src/widgets/pdf_viewer.dart | 3 ++- lib/src/widgets/pdf_viewer_params.dart | 20 +++++++++++++++++--- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 48ad395f..149ad066 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -1087,21 +1087,24 @@ function _getText(textPage, from, length) { */ /** - * @param {{docHandle: number, pageIndex: number}} params + * @param {{docHandle: number, pageIndex: number, loadWebLinks: boolean}} params * @returns {{links: Array}} */ function loadLinks(params) { - const links = [..._loadAnnotLinks(params), ..._loadLinks(params)]; + const links = [ + ..._loadAnnotLinks(params), + ...(params.loadWebLinks ? _loadWebLinks(params) : []), + ]; return { links: links, }; } /** - * @param {{docHandle: number, pageIndex: number}} params + * @param {{docHandle: number, pageIndex: number, loadWebLinks: boolean}} params * @returns {Array} */ -function _loadLinks(params) { +function _loadWebLinks(params) { const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index ed708864..e26497c4 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -436,7 +436,9 @@ abstract class PdfPage { /// /// if [compact] is true, it tries to reduce memory usage by compacting the link data. /// See [PdfLink.compact] for more info. - Future> loadLinks({bool compact = false}); + /// if [loadWebLinks] is false, it does not load web-like links from text content. + /// The default is true. + Future> loadLinks({bool compact = false, bool loadWebLinks = true}); } /// Page rotation. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 7f92b452..dd7542e1 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -728,8 +728,11 @@ class PdfPagePdfium extends PdfPage { Future loadText() => PdfPageTextPdfium._loadText(this); @override - Future> loadLinks({bool compact = false}) async { - final links = await _loadAnnotLinks() + await _loadLinks(); + Future> loadLinks({bool compact = false, bool loadWebLinks = true}) async { + final links = await _loadAnnotLinks(); + if (loadWebLinks) { + links.addAll(await _loadWebLinks()); + } if (compact) { for (int i = 0; i < links.length; i++) { links[i] = links[i].compact(); @@ -738,7 +741,7 @@ class PdfPagePdfium extends PdfPage { return List.unmodifiable(links); } - Future> _loadLinks() async => + Future> _loadWebLinks() async => document.isDisposed ? [] : await (await backgroundWorker).compute((params) { diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 2ec1843f..8dbc60b3 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -435,10 +435,14 @@ class PdfPageWasm extends PdfPage { final PdfDocumentWasm document; @override - Future> loadLinks({bool compact = false}) async { + Future> loadLinks({bool compact = false, bool loadWebLinks = true}) async { final result = await document.factory.sendCommand( 'loadLinks', - parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, + parameters: { + 'docHandle': document.document['docHandle'], + 'pageIndex': pageNumber - 1, + 'loadWebLinks': loadWebLinks + }, ); return (result['links'] as List).map((link) { if (link is! Map) { diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 49f75ad4..b7585fe9 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -2078,7 +2078,8 @@ class _CanvasLinkPainter { synchronized(() async { final links = _links[page.pageNumber]; if (links != null) return links; - _links[page.pageNumber] = await page.loadLinks(compact: true); + final loadWebLinks = _state.widget.params.linkHandlerParams?.loadWebLinks ?? true; + _links[page.pageNumber] = await page.loadLinks(compact: true, loadWebLinks: loadWebLinks); if (onLoaded != null) { onLoaded(); } else { diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index e7b936f1..e2df49e6 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -817,7 +817,12 @@ enum PdfPageAnchor { /// Parameters to customize link handling/appearance. class PdfLinkHandlerParams { - const PdfLinkHandlerParams({required this.onLinkTap, this.linkColor, this.customPainter}); + const PdfLinkHandlerParams({ + required this.onLinkTap, + this.linkColor, + this.customPainter, + this.loadWebLinks = true, + }); /// Function to be called when the link is tapped. /// @@ -847,16 +852,25 @@ class PdfLinkHandlerParams { /// ``` final PdfLinkCustomPagePainter? customPainter; + /// Whether to load web-like links from text content. + final bool loadWebLinks; + @override bool operator ==(covariant PdfLinkHandlerParams other) { if (identical(this, other)) return true; - return other.onLinkTap == onLinkTap && other.linkColor == linkColor && other.customPainter == customPainter; + return other.onLinkTap == onLinkTap && + other.linkColor == linkColor && + other.customPainter == customPainter && + other.loadWebLinks == loadWebLinks; } @override int get hashCode { - return onLinkTap.hashCode ^ linkColor.hashCode ^ customPainter.hashCode; + return onLinkTap.hashCode ^ + linkColor.hashCode ^ + customPainter.hashCode ^ + loadWebLinks.hashCode; } } From cc25d7f0d5ca3e238daa9187f6feaad54cc72dd0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 3 Jul 2025 00:59:02 +0900 Subject: [PATCH 167/663] Remove unused file --- wasm/pdfrx_wasm/pubspec.lock | 56 ------------------------------------ 1 file changed, 56 deletions(-) delete mode 100644 wasm/pdfrx_wasm/pubspec.lock diff --git a/wasm/pdfrx_wasm/pubspec.lock b/wasm/pdfrx_wasm/pubspec.lock deleted file mode 100644 index 8a87a4f4..00000000 --- a/wasm/pdfrx_wasm/pubspec.lock +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" -sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.0" From 2e7373f4b967508ac62675074fc55fa3fe4587ae Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 3 Jul 2025 13:28:38 +0900 Subject: [PATCH 168/663] WIP: parameter renaming: loadWebLinks -> enableAutoLinkDetection --- assets/pdfium_worker.js | 6 +++--- lib/src/pdf_api.dart | 8 +++++--- lib/src/pdfium/pdfrx_pdfium.dart | 4 ++-- lib/src/web/pdfrx_wasm.dart | 4 ++-- lib/src/widgets/pdf_viewer.dart | 4 ++-- lib/src/widgets/pdf_viewer_params.dart | 15 +++++++-------- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/assets/pdfium_worker.js b/assets/pdfium_worker.js index 149ad066..d7fefcfd 100644 --- a/assets/pdfium_worker.js +++ b/assets/pdfium_worker.js @@ -1087,13 +1087,13 @@ function _getText(textPage, from, length) { */ /** - * @param {{docHandle: number, pageIndex: number, loadWebLinks: boolean}} params + * @param {{docHandle: number, pageIndex: number, enableAutoLinkDetection: boolean}} params * @returns {{links: Array}} */ function loadLinks(params) { const links = [ ..._loadAnnotLinks(params), - ...(params.loadWebLinks ? _loadWebLinks(params) : []), + ...(params.enableAutoLinkDetection ? _loadWebLinks(params) : []), ]; return { links: links, @@ -1101,7 +1101,7 @@ function loadLinks(params) { } /** - * @param {{docHandle: number, pageIndex: number, loadWebLinks: boolean}} params + * @param {{docHandle: number, pageIndex: number, enableAutoLinkDetection: boolean}} params * @returns {Array} */ function _loadWebLinks(params) { diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 350fbae7..4d387f98 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -435,11 +435,13 @@ abstract class PdfPage { /// Load links. /// - /// if [compact] is true, it tries to reduce memory usage by compacting the link data. + /// If [compact] is true, it tries to reduce memory usage by compacting the link data. /// See [PdfLink.compact] for more info. - /// if [loadWebLinks] is false, it does not load web-like links from text content. + /// + /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. + /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. /// The default is true. - Future> loadLinks({bool compact = false, bool loadWebLinks = true}); + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); } /// Page rotation. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 53b8d538..ba9fc2ea 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -728,9 +728,9 @@ class _PdfPagePdfium extends PdfPage { Future loadText() => _PdfPageTextPdfium._loadText(this); @override - Future> loadLinks({bool compact = false, bool loadWebLinks = true}) async { + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { final links = await _loadAnnotLinks(); - if (loadWebLinks) { + if (enableAutoLinkDetection) { links.addAll(await _loadWebLinks()); } if (compact) { diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index fffe5581..d8bedf74 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -435,13 +435,13 @@ class _PdfPageWasm extends PdfPage { final _PdfDocumentWasm document; @override - Future> loadLinks({bool compact = false, bool loadWebLinks = true}) async { + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { final result = await document.factory.sendCommand( 'loadLinks', parameters: { 'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1, - 'loadWebLinks': loadWebLinks + 'enableAutoLinkDetection': enableAutoLinkDetection, }, ); return (result['links'] as List).map((link) { diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index b7585fe9..9838fea5 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -2078,8 +2078,8 @@ class _CanvasLinkPainter { synchronized(() async { final links = _links[page.pageNumber]; if (links != null) return links; - final loadWebLinks = _state.widget.params.linkHandlerParams?.loadWebLinks ?? true; - _links[page.pageNumber] = await page.loadLinks(compact: true, loadWebLinks: loadWebLinks); + final enableAutoLinkDetection = _state.widget.params.linkHandlerParams?.enableAutoLinkDetection ?? true; + _links[page.pageNumber] = await page.loadLinks(compact: true, enableAutoLinkDetection: enableAutoLinkDetection); if (onLoaded != null) { onLoaded(); } else { diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index e2df49e6..fd05ee7a 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -821,7 +821,7 @@ class PdfLinkHandlerParams { required this.onLinkTap, this.linkColor, this.customPainter, - this.loadWebLinks = true, + this.enableAutoLinkDetection = true, }); /// Function to be called when the link is tapped. @@ -852,8 +852,10 @@ class PdfLinkHandlerParams { /// ``` final PdfLinkCustomPagePainter? customPainter; - /// Whether to load web-like links from text content. - final bool loadWebLinks; + /// Whether to try to detect Web links automatically or not. + /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. + /// The default is true. + final bool enableAutoLinkDetection; @override bool operator ==(covariant PdfLinkHandlerParams other) { @@ -862,15 +864,12 @@ class PdfLinkHandlerParams { return other.onLinkTap == onLinkTap && other.linkColor == linkColor && other.customPainter == customPainter && - other.loadWebLinks == loadWebLinks; + other.enableAutoLinkDetection == enableAutoLinkDetection; } @override int get hashCode { - return onLinkTap.hashCode ^ - linkColor.hashCode ^ - customPainter.hashCode ^ - loadWebLinks.hashCode; + return onLinkTap.hashCode ^ linkColor.hashCode ^ customPainter.hashCode ^ enableAutoLinkDetection.hashCode; } } From e4fce00884e140285d280644752cf442ba513a13 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 3 Jul 2025 13:39:16 +0900 Subject: [PATCH 169/663] Release v1.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc608c8c..57f7f89b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.3.0 + +- NEW FEATURE: Added support for disabling automatic web link detection from text content ([#403](https://github.com/espresso3389/pdfrx/pull/403)) + # 1.2.9 - FIXED: Use document base href for resolving URLs in PDFium WASM ([#402](https://github.com/espresso3389/pdfrx/pull/402)) diff --git a/README.md b/README.md index 7c619fb3..95f7f063 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.2.9 + pdfrx: ^1.3.0 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 65055e88..88d13494 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.2.9" + version: "1.3.0" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 78596706..91bea858 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.2.9 +version: 1.3.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7d18592a8944c637b71c71cd67f206ffe2bc5dfc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 3 Jul 2025 16:22:09 +0900 Subject: [PATCH 170/663] Remove deprecated PdfrxWebRuntimeType --- lib/src/pdf_api.dart | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 4d387f98..72888796 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -33,10 +33,6 @@ class Pdfrx { /// It is not supported on Flutter Web. static http.Client Function()? createHttpClient; - /// pdfrx always uses PDFium (WASM) on Flutter Web and the runtime type is not used now. - @Deprecated('PdfrxWebRuntimeType is not used now. pdfrx always uses PDFium (WASM) on Flutter Web.') - static PdfrxWebRuntimeType webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm; - /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. static String? pdfiumWasmModulesUrl; @@ -51,16 +47,6 @@ class Pdfrx { static bool pdfiumWasmWithCredentials = false; } -/// Web runtime type. -@Deprecated('PdfrxWebRuntimeType is not working now. pdfrx always uses PDFium (WASM) on Flutter Web.') -enum PdfrxWebRuntimeType { - /// Use PDFium (WASM). - pdfiumWasm, - - /// PDF.js is no longer supported. - pdfjs, -} - /// For platform abstraction purpose; use [PdfDocument] instead. abstract class PdfDocumentFactory { /// See [PdfDocument.openAsset]. From 82870d6d9b7eced2767cfa7d37916a7093ee11ba Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 5 Jul 2025 03:34:44 +0900 Subject: [PATCH 171/663] Add PdfViewerParams.calculateInitialZoom (#406) --- lib/src/widgets/pdf_viewer.dart | 38 ++++++++++++++------------ lib/src/widgets/pdf_viewer_params.dart | 15 ++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 9838fea5..4fd2de1b 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -429,10 +429,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix size: _layout!.documentSize, ), ), - ..._buildPageOverlayWidgets(context), - if (_canvasLinkPainter.isEnabled) + if (_initialized) ..._buildPageOverlayWidgets(context), + if (_initialized && _canvasLinkPainter.isEnabled) SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(_viewSize!)), - if (widget.params.viewerOverlayBuilder != null) + if (_initialized && widget.params.viewerOverlayBuilder != null) ...widget.params.viewerOverlayBuilder!(context, _viewSize!, _canvasLinkPainter._handleLinkTap) .map((e) => SelectionContainer.disabled(child: e)), ], @@ -465,23 +465,27 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - if (!_initialized && _layout != null) { + if (!_initialized && _layout != null && _coverScale != null && _alternativeFitScale != null) { _initialized = true; Future.microtask(() async { - if (mounted) { - await _goToPage(pageNumber: _calcInitialPageNumber(), duration: Duration.zero); - - if (mounted && _document != null && _controller != null) { - widget.params.onViewerReady?.call(_document!, _controller!); - } - - callOnViewerSizeChanged(); + // forcibly calculate fit scale for the initial page + _pageNumber = _gotoTargetPageNumber = _calcInitialPageNumber(); + _calcCoverFitScale(); + _calcZoomStopTable(); + final zoom = + widget.params.calculateInitialZoom?.call(_document!, _controller!, _alternativeFitScale!, _coverScale!) ?? + _coverScale!; + await _setZoom(Offset.zero, zoom, duration: Duration.zero); + await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); + if (mounted && _document != null && _controller != null) { + widget.params.onViewerReady?.call(_document!, _controller!); } + callOnViewerSizeChanged(); }); } else if (isLayoutChanged || isViewSizeChanged) { Future.microtask(() async { if (mounted) { - await _goToPage(pageNumber: currentPageNumber ?? _calcInitialPageNumber(), duration: Duration.zero); + await _goToPage(pageNumber: currentPageNumber ?? _calcInitialPageNumber()); callOnViewerSizeChanged(); } }); @@ -902,6 +906,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// [_CustomPainter] calls the function to paint PDF pages. void _customPaint(ui.Canvas canvas, ui.Size size) { + if (!_initialized) return; final targetRect = _getCacheExtentRect(); final scale = MediaQuery.of(context).devicePixelRatio * _currentZoom; @@ -1192,7 +1197,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Matrix4 _calcMatrixFor(Offset position, {required double zoom, required Size viewSize}) { final hw = viewSize.width / 2; final hh = viewSize.height / 2; - return Matrix4.compose( vec.Vector3(-position.dx * zoom + hw, -position.dy * zoom + hh, 0), vec.Quaternion.identity(), @@ -1217,9 +1221,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix margin: margin, ); - // The function calculate the rectangle which should be shown in the view. - // - // If the rect is smaller than the view size, it will + /// The function calculate the rectangle which should be shown in the view. + /// + /// If the rect is smaller than the view size, it will Rect _calcRectForArea({required Rect rect, required PdfPageAnchor anchor}) { final viewSize = _visibleRect.size; final w = min(rect.width, viewSize.width); diff --git a/lib/src/widgets/pdf_viewer_params.dart b/lib/src/widgets/pdf_viewer_params.dart index fd05ee7a..9585deab 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/lib/src/widgets/pdf_viewer_params.dart @@ -38,6 +38,7 @@ class PdfViewerParams { this.interactionEndFrictionCoefficient = _kDrag, this.onDocumentChanged, this.calculateInitialPageNumber, + this.calculateInitialZoom, this.calculateCurrentPageNumber, this.onViewerReady, this.onViewSizeChanged, @@ -285,6 +286,9 @@ class PdfViewerParams { /// It is useful when you want to determine the initial page number based on the document content. final PdfViewerCalculateInitialPageNumberFunction? calculateInitialPageNumber; + /// Function to calculate the initial zoom level. + final PdfViewerCalculateZoomFunction? calculateInitialZoom; + /// Function to guess the current page number based on the visible rectangle and page layouts. /// /// The function is used to override the default behavior to calculate the current page number. @@ -582,6 +586,7 @@ class PdfViewerParams { other.interactionEndFrictionCoefficient == interactionEndFrictionCoefficient && other.onDocumentChanged == onDocumentChanged && other.calculateInitialPageNumber == calculateInitialPageNumber && + other.calculateInitialZoom == calculateInitialZoom && other.calculateCurrentPageNumber == calculateCurrentPageNumber && other.onViewerReady == onViewerReady && other.onViewSizeChanged == onViewSizeChanged && @@ -635,6 +640,7 @@ class PdfViewerParams { interactionEndFrictionCoefficient.hashCode ^ onDocumentChanged.hashCode ^ calculateInitialPageNumber.hashCode ^ + calculateInitialZoom.hashCode ^ calculateCurrentPageNumber.hashCode ^ onViewerReady.hashCode ^ onViewSizeChanged.hashCode ^ @@ -672,6 +678,15 @@ typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); typedef PdfViewerCalculateInitialPageNumberFunction = int? Function(PdfDocument document, PdfViewerController controller); +/// Function to calculate the initial zoom level. +/// +/// If the function returns null, the viewer will use the default zoom level. +/// You can use the following parameters to calculate the zoom level: +/// - [fitZoom] is the zoom level to fit the "initial" page into the viewer. +/// - [coverZoom] is the zoom level to cover the entire viewer with the "initial" page. +typedef PdfViewerCalculateZoomFunction = + double? Function(PdfDocument document, PdfViewerController controller, double fitZoom, double coverZoom); + /// Function to guess the current page number based on the visible rectangle and page layouts. typedef PdfViewerCalculateCurrentPageNumberFunction = int? Function(Rect visibleRect, List pageRects, PdfViewerController controller); From c1bd6ac4e62aa188393b557c187dfe7d2c9fd90e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 5 Jul 2025 03:55:04 +0900 Subject: [PATCH 172/663] Release v1.3.1 --- CHANGELOG.md | 5 +++++ CLAUDE.md | 13 ++++++++----- README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f7f89b..47ebc81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.3.1 + +- NEW FEATURE: Added `PdfViewerParams.calculateInitialZoom` to customize initial zoom calculation ([#406](https://github.com/espresso3389/pdfrx/pull/406)) +- Removed deprecated `PdfrxWebRuntimeType` API + # 1.3.0 - NEW FEATURE: Added support for disabling automatic web link detection from text content ([#403](https://github.com/espresso3389/pdfrx/pull/403)) diff --git a/CLAUDE.md b/CLAUDE.md index faae396a..0dd80f90 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,11 +58,14 @@ dart run ffigen # Regenerate PDFium FFI bindings - This includes the main package, example app, and wasm package if applicable - Ensure all dependencies are resolved and up-to-date 5. Run tests to ensure everything works -6. Commit changes with message "Release vX.Y.Z" -7. Tag the commit with `git tag vX.Y.Z` -8. Push changes and tags to remote -9. Do `flutter pub publish` to publish the package -10. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release + - Run `flutter test` to execute all tests on root directory (not in `example/viewer`) +6. Ensure the example app builds correctly + - Run `flutter build web --wasm` in `example/viewer` to test the example app +7. Commit changes with message "Release vX.Y.Z" +8. Tag the commit with `git tag vX.Y.Z` +9. Push changes and tags to remote +10. Do `flutter pub publish` to publish the package +11. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - If the PR references issues, please also comment on the issues - Follow the template below for comments (but modify it as needed): diff --git a/README.md b/README.md index 95f7f063..8b6af6f6 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.3.0 + pdfrx: ^1.3.1 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 88d13494..72dac6ed 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.3.0" + version: "1.3.1" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 91bea858..56001a71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.0 +version: 1.3.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 6bd3d1a8e0c214394d81b68fc5bea94015a8965a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 5 Jul 2025 16:09:41 +0900 Subject: [PATCH 173/663] Introduces PdfDocument.changes to notify page status changes (It also used to notify page loading progress). --- lib/src/pdf_api.dart | 29 ++++++++++++++++++++++++ lib/src/pdfium/pdfrx_pdfium.dart | 9 ++++++++ lib/src/web/pdfrx_wasm.dart | 9 ++++++++ lib/src/widgets/pdf_viewer.dart | 38 +++++++++++++++++++++++++++----- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index 72888796..b8cb42b1 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -171,6 +171,9 @@ abstract class PdfDocument { /// PdfDocument must have [dispose] function. Future dispose(); + /// Stream to notify change events in the document. + Stream get events; + /// Opening the specified file. /// For Web, [filePath] can be relative path from `index.html` or any arbitrary URL but it may be restricted by CORS. /// @@ -345,6 +348,32 @@ abstract class PdfDocument { typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); +/// PDF document event types. +enum PdfDocumentEventType { pageStatusChanged } + +/// Base class for PDF document events. +abstract class PdfDocumentEvent { + /// Event type. + PdfDocumentEventType get type; + + /// Document that this event is related to. + PdfDocument get document; +} + +/// Event that is triggered when the status of PDF document pages has changed. +class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { + PdfDocumentPageStatusChangedEvent(this.document, this.pages); + + @override + PdfDocumentEventType get type => PdfDocumentEventType.pageStatusChanged; + + @override + final PdfDocument document; + + /// The pages that have changed. + final List pages; +} + /// Handles a PDF page in [PdfDocument]. /// /// See [PdfDocument.pages]. diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index ba9fc2ea..4f1350c2 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -9,6 +9,7 @@ import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:rxdart/rxdart.dart'; import '../pdf_api.dart'; import 'pdf_file_cache.dart'; @@ -319,12 +320,16 @@ class _PdfDocumentPdfium extends PdfDocument { final pdfium_bindings.FPDF_FORMHANDLE formHandle; final Pointer formInfo; bool isDisposed = false; + final subject = BehaviorSubject(); @override bool get isEncrypted => securityHandlerRevision != -1; @override final PdfPermissions? permissions; + @override + Stream get events => subject.stream; + _PdfDocumentPdfium._( this.document, { required super.sourceName, @@ -416,6 +421,9 @@ class _PdfDocumentPdfium extends PdfDocument { ); if (isDisposed) return; _pages = List.unmodifiable(loaded.pages); + + subject.add(PdfDocumentPageStatusChangedEvent(this, _pages.sublist(firstUnloadedPageIndex))); + if (onPageLoadProgress != null) { final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); if (result == false) { @@ -520,6 +528,7 @@ class _PdfDocumentPdfium extends PdfDocument { Future dispose() async { if (!isDisposed) { isDisposed = true; + subject.close(); await (await backgroundWorker).compute((params) { final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle); final formInfo = Pointer.fromAddress(params.formInfo); diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index d8bedf74..94a49df0 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -6,6 +6,7 @@ import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart' show Colors, immutable; import 'package:flutter/services.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:web/web.dart' as web; @@ -309,6 +310,7 @@ class _PdfDocumentWasm extends PdfDocument { final _PdfDocumentFactoryWasmImpl factory; final void Function()? disposeCallback; bool isDisposed = false; + final subject = BehaviorSubject(); @override final PdfPermissions? permissions; @@ -316,10 +318,14 @@ class _PdfDocumentWasm extends PdfDocument { @override bool get isEncrypted => permissions != null; + @override + Stream get events => subject.stream; + @override Future dispose() async { if (!isDisposed) { isDisposed = true; + subject.close(); await factory.sendCommand('closeDocument', parameters: document); disposeCallback?.call(); } @@ -369,6 +375,9 @@ class _PdfDocumentWasm extends PdfDocument { for (final page in pagesLoaded) { pages[page.pageNumber - 1] = page; // Update the existing page } + + subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); + if (onPageLoadProgress != null) { if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { // If the callback returns false, stop loading more pages diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 4fd2de1b..14282816 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -196,7 +196,7 @@ class PdfViewer extends StatefulWidget { class _PdfViewerState extends State with SingleTickerProviderStateMixin { PdfViewerController? _controller; - late final TransformationController _txController = _PdfViewerTransformationController(this); + late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; Animation? _animGoTo; int _animationResettingGuard = 0; @@ -210,6 +210,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix double _minScale = _defaultMinScale; int? _pageNumber; bool _initialized = false; + StreamSubscription? _documentSubscription; final List _zoomStops = [1.0]; @@ -288,6 +289,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onDocumentChanged() async { _layout = null; + _documentSubscription?.cancel(); + _documentSubscription = null; _selectionChangedThrottleTimer?.cancel(); _stopInteraction(); _releaseAllImages(); @@ -313,6 +316,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _controller ??= widget.controller ?? PdfViewerController(); _controller!._attach(this); _txController.addListener(_onMatrixChanged); + _documentSubscription = document.events.listen(_onDocumentEvent); if (mounted) { setState(() {}); @@ -339,7 +343,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix await _document?.loadPagesProgressively((pageNumber, totalPageCount, document) { if (document == _document && mounted) { debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); - _invalidate(); return true; } return false; @@ -354,6 +357,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override void dispose() { + _documentSubscription?.cancel(); _selectionChangedThrottleTimer?.cancel(); _interactionEndedTimer?.cancel(); _cancelAllPendingRenderings(); @@ -367,8 +371,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix super.dispose(); } - void _onMatrixChanged() { - _updateStream.add(_txController.value); + void _onMatrixChanged() => _invalidate(); + + void _onDocumentEvent(PdfDocumentEvent event) { + if (event is PdfDocumentPageStatusChangedEvent) { + for (final page in event.pages) { + _removeCacheImagesForPage(page.pageNumber); + } + _invalidate(); + } } @override @@ -999,7 +1010,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final currentPageNumber = _pageNumber; if (currentPageNumber != null && currentPageNumber > 0) { final currentPage = _document!.pages[currentPageNumber - 1]; - _removeImagesIfCacheBytesExceedsLimit(unusedPageList, widget.params.maxImageBytesCachedOnMemory, currentPage); + _removeCacheImagesIfCacheBytesExceedsLimit( + unusedPageList, + widget.params.maxImageBytesCachedOnMemory, + currentPage, + ); } } } @@ -1129,7 +1144,18 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return result; } - void _removeImagesIfCacheBytesExceedsLimit(List pageNumbers, int acceptableBytes, PdfPage currentPage) { + void _removeCacheImagesForPage(int pageNumber) { + final removed = _pageImages.remove(pageNumber); + if (removed != null) { + removed.image.dispose(); + } + final removedPartial = _pageImagesPartial.remove(pageNumber); + if (removedPartial != null) { + removedPartial.image.dispose(); + } + } + + void _removeCacheImagesIfCacheBytesExceedsLimit(List pageNumbers, int acceptableBytes, PdfPage currentPage) { double dist(int pageNumber) { return (_layout!.pageLayouts[pageNumber - 1].center - _layout!.pageLayouts[currentPage.pageNumber - 1].center) .distanceSquared; From 837b69d0df943dd7560a29c1c5766ce2017166b6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 9 Jul 2025 22:02:02 +0900 Subject: [PATCH 174/663] Add useProgressiveLoading for PdfViewer.* constructors --- lib/src/widgets/pdf_viewer.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 14282816..96dcdeea 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -56,6 +56,7 @@ class PdfViewer extends StatefulWidget { String assetName, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = true, super.key, this.controller, this.params = const PdfViewerParams(), @@ -64,6 +65,7 @@ class PdfViewer extends StatefulWidget { assetName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// Create [PdfViewer] from a file. @@ -79,6 +81,7 @@ class PdfViewer extends StatefulWidget { String path, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = true, super.key, this.controller, this.params = const PdfViewerParams(), @@ -87,6 +90,7 @@ class PdfViewer extends StatefulWidget { path, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// Create [PdfViewer] from a URI. @@ -105,6 +109,7 @@ class PdfViewer extends StatefulWidget { Uri uri, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = true, super.key, this.controller, this.params = const PdfViewerParams(), @@ -116,6 +121,7 @@ class PdfViewer extends StatefulWidget { uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, @@ -137,6 +143,7 @@ class PdfViewer extends StatefulWidget { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = true, super.key, this.controller, this.params = const PdfViewerParams(), @@ -146,6 +153,7 @@ class PdfViewer extends StatefulWidget { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// Create [PdfViewer] from a custom source. @@ -166,6 +174,7 @@ class PdfViewer extends StatefulWidget { required String sourceName, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = true, super.key, this.controller, this.params = const PdfViewerParams(), @@ -176,6 +185,7 @@ class PdfViewer extends StatefulWidget { sourceName: sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); /// [PdfDocumentRef] that represents the PDF document. From 32faf209cb79bc80f16f00a59a7f023806874a96 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 11 Jul 2025 08:44:44 +0900 Subject: [PATCH 175/663] Release v1.3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Claude Code Co-Authored-By: Claude --- CHANGELOG.md | 5 +++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ebc81b..f74cce2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.3.2 + +- NEW FEATURE: Added `useProgressiveLoading` parameter for all `PdfViewer` constructors to enable progressive page loading +- NEW FEATURE: Added `PdfDocument.changes` stream to notify page status changes and loading progress + # 1.3.1 - NEW FEATURE: Added `PdfViewerParams.calculateInitialZoom` to customize initial zoom calculation ([#406](https://github.com/espresso3389/pdfrx/pull/406)) diff --git a/README.md b/README.md index 8b6af6f6..a7ac4e47 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.3.1 + pdfrx: ^1.3.2 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 72dac6ed..19035c51 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.3.1" + version: "1.3.2" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 56001a71..05f3ff81 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.1 +version: 1.3.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 16a6fafc6e1f924491ab3df4f12b5afbaebf35ec Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 11 Jul 2025 17:25:05 +0900 Subject: [PATCH 176/663] Update example/viewer to use build.gradle.kts as following latest flutter create --- .../app/{build.gradle => build.gradle.kts} | 25 +++++++++---------- example/viewer/android/build.gradle | 18 ------------- example/viewer/android/build.gradle.kts | 21 ++++++++++++++++ example/viewer/android/settings.gradle | 25 ------------------- example/viewer/android/settings.gradle.kts | 25 +++++++++++++++++++ 5 files changed, 58 insertions(+), 56 deletions(-) rename example/viewer/android/app/{build.gradle => build.gradle.kts} (62%) delete mode 100644 example/viewer/android/build.gradle create mode 100644 example/viewer/android/build.gradle.kts delete mode 100644 example/viewer/android/settings.gradle create mode 100644 example/viewer/android/settings.gradle.kts diff --git a/example/viewer/android/app/build.gradle b/example/viewer/android/app/build.gradle.kts similarity index 62% rename from example/viewer/android/app/build.gradle rename to example/viewer/android/app/build.gradle.kts index e0edd28d..0d97bbdc 100644 --- a/example/viewer/android/app/build.gradle +++ b/example/viewer/android/app/build.gradle.kts @@ -1,28 +1,27 @@ plugins { - id "com.android.application" - id "kotlin-android" + id("com.android.application") + id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id "dev.flutter.flutter-gradle-plugin" + id("dev.flutter.flutter-gradle-plugin") } android { - namespace "jp.espresso3389.pdfrx_example" - compileSdkVersion flutter.compileSdkVersion - - ndkVersion = android.ndkVersion + namespace = "jp.espresso3389.pdfrx_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_18 - targetCompatibility JavaVersion.VERSION_18 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = 18 + jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "jp.espresso3389.pdfrx_example" + applicationId = "jp.espresso3389.pdfrx_example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion @@ -35,11 +34,11 @@ android { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.getByName("debug") } } } flutter { - source '../..' + source = "../.." } diff --git a/example/viewer/android/build.gradle b/example/viewer/android/build.gradle deleted file mode 100644 index bc157bd1..00000000 --- a/example/viewer/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/viewer/android/build.gradle.kts b/example/viewer/android/build.gradle.kts new file mode 100644 index 00000000..89176ef4 --- /dev/null +++ b/example/viewer/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/viewer/android/settings.gradle b/example/viewer/android/settings.gradle deleted file mode 100644 index dbf9ff3f..00000000 --- a/example/viewer/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.0" apply false - id "org.jetbrains.kotlin.android" version "2.0.20" apply false -} - -include ":app" diff --git a/example/viewer/android/settings.gradle.kts b/example/viewer/android/settings.gradle.kts new file mode 100644 index 00000000..ab39a10a --- /dev/null +++ b/example/viewer/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") From 954bff3b81864a1beb0edbb02aca6882e1d72b4f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 11 Jul 2025 18:09:24 +0900 Subject: [PATCH 177/663] Recondition android/build.gradle according to Flutter's default plugin skelton (Related to #414) --- android/build.gradle | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 2af45c04..53b1f8b0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,9 +26,12 @@ android { namespace = "jp.espresso3389.pdfrx" } - compileSdk = 34 + compileSdk = 35 - ndkVersion = android.ndkVersion + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } // Invoke the shared CMake build with the Android Gradle Plugin. externalNativeBuild { @@ -42,11 +45,6 @@ android { main.jniLibs.srcDirs += ".lib/latest" } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - defaultConfig { minSdk = 21 } From 9c735670b1828c0fad6dc6676bfac786fd476bc1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 11 Jul 2025 21:24:44 +0900 Subject: [PATCH 178/663] Add code to accept all android-licenses --- .github/workflows/build-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0e7666c4..8f2888d0 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -69,6 +69,7 @@ jobs: ~/flutter/bin/flutter config --no-enable-linux-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + yes | ~/flutter/bin/flutter doctor --android-licenses - name: Install dependencies working-directory: example/viewer From 73a9b9752869cc64d2a33274a5d283711c9b1f96 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 12 Jul 2025 01:45:07 +0900 Subject: [PATCH 179/663] Now, PdfDocument does not depend on Flutter. --- lib/pdfrx.dart | 1 + lib/src/pdf_api.dart | 123 ++++++++------------------ lib/src/pdfium/pdf_file_cache.dart | 17 ++-- lib/src/pdfium/pdfium_interop.dart | 3 +- lib/src/pdfium/pdfrx_pdfium.dart | 34 +++---- lib/src/pdfium/worker.dart | 7 +- lib/src/pdfrx_flutter_initialize.dart | 25 ++++++ lib/src/web/pdfrx_wasm.dart | 40 +++------ lib/src/widgets/pdf_viewer.dart | 1 + lib/src/widgets/pdf_widgets.dart | 7 ++ test/setup.dart | 2 + 11 files changed, 116 insertions(+), 144 deletions(-) create mode 100644 lib/src/pdfrx_flutter_initialize.dart diff --git a/lib/pdfrx.dart b/lib/pdfrx.dart index d7e03a0e..dfaf56ba 100644 --- a/lib/pdfrx.dart +++ b/lib/pdfrx.dart @@ -1,5 +1,6 @@ export 'src/pdf_api.dart'; export 'src/pdf_document_ref.dart'; +export 'src/pdfrx_flutter_initialize.dart'; export 'src/widgets/pdf_page_text_overlay.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index b8cb42b1..ae31f98c 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -1,11 +1,11 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; import 'dart:math'; +import 'dart:typed_data'; import 'dart:ui' as ui; +import 'dart:ui'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; // The trick to support Flutter Web is to use conditional import @@ -15,6 +15,7 @@ import 'web/pdfrx_wasm.dart' if (dart.library.io) 'pdfium/pdfrx_pdfium.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. +/// class Pdfrx { Pdfrx._(); @@ -45,88 +46,25 @@ class Pdfrx { /// This is useful for authentication on protected servers. /// Only supported on Flutter Web. static bool pdfiumWasmWithCredentials = false; -} - -/// For platform abstraction purpose; use [PdfDocument] instead. -abstract class PdfDocumentFactory { - /// See [PdfDocument.openAsset]. - Future openAsset( - String name, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }); - /// See [PdfDocument.openData]. - Future openData( - Uint8List data, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - String? sourceName, - bool allowDataOwnershipTransfer = false, - void Function()? onDispose, - }); - - /// See [PdfDocument.openFile]. - Future openFile( - String filePath, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }); - - /// See [PdfDocument.openCustom]. - Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, - required int fileSize, - required String sourceName, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - int? maxSizeToCacheOnMemory, - void Function()? onDispose, - }); - - /// See [PdfDocument.openUri]. - Future openUri( - Uri uri, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - PdfDownloadProgressCallback? progressCallback, - bool preferRangeAccess = false, - Map? headers, - bool withCredentials = false, - }); - - /// Singleton [PdfDocumentFactory] instance. + /// Function to load asset data. /// - /// It is used to switch PDFium/web implementation based on the running platform and of course, you can - /// override it to use your own implementation. - static PdfDocumentFactory instance = getDocumentFactory(); - - /// Get [PdfDocumentFactory] that uses PDFium implementation. + /// This function is used to load PDF files from assets. + /// It is used to isolate pdfrx API implementation from Flutter framework. /// - /// For Flutter Web, it uses PDFium (WASM) implementation. - static PdfDocumentFactory get pdfium => getPdfiumDocumentFactory(); - - /// PDF.js is no longer supported. - /// This function is deprecated and will throw an error if called. - @Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') - static PdfDocumentFactory get pdfjs => getPdfjsDocumentFactory(); -} + /// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. + /// For Dart only, you can set this function to load assets from your own asset management system. + static Future Function(String name)? loadAsset; -/// PDF.js is no longer supported. -/// This function is deprecated and will throw an error if called. -@Deprecated('PdfDocumentFactory backed by PDF.js is no longer supported.') -PdfDocumentFactory getPdfjsDocumentFactory() { - throw UnsupportedError('PdfDocumentFactory backed by PDF.js is no longer supported.'); + /// Function to determine the cache directory. + /// + /// You can override the default cache directory by setting this variable. + /// + /// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. + /// For Dart only, you can set this function to obtain the cache directory from your own file system. + static Future Function()? getCacheDirectory; } -/// Get the default [PdfDocumentFactory]. -PdfDocumentFactory getDocumentFactory() => getPdfiumDocumentFactory(); - /// Callback function to notify download progress. /// /// [downloadedBytes] is the number of bytes downloaded so far. @@ -682,7 +620,7 @@ abstract class PdfPageTextFragment { return other.index == index && other.bounds == bounds && - listEquals(other.charRects, charRects) && + _listEquals(other.charRects, charRects) && other.text == text; } @@ -950,7 +888,7 @@ class PdfTextRangeWithFragments { other.start == start && other.end == end && other.bounds == bounds && - listEquals(other.fragments, fragments); + _listEquals(other.fragments, fragments); } } @@ -960,7 +898,6 @@ class PdfTextRangeWithFragments { /// PDF page coordinates's origin is at the bottom-left corner and Y-axis is pointing upward; /// [bottom] is generally smaller than [top]. /// The unit is normally in points (1/72 inch). -@immutable class PdfRect { const PdfRect(this.left, this.top, this.right, this.bottom); @@ -1150,7 +1087,6 @@ extension PdfRectsExt on Iterable { } /// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. -@immutable class PdfDest { const PdfDest(this.pageNumber, this.command, this.params); @@ -1203,7 +1139,6 @@ enum PdfDestCommand { /// /// Either one of [url] or [dest] is valid (not null). /// See [PdfPage.loadLinks]. -@immutable class PdfLink { const PdfLink(this.rects, {this.url, this.dest}); @@ -1236,7 +1171,6 @@ class PdfLink { /// Outline (a.k.a. Bookmark) node in PDF document. /// /// See [PdfDocument.loadOutline]. -@immutable class PdfOutlineNode { const PdfOutlineNode({required this.title, required this.dest, required this.children}); @@ -1349,3 +1283,24 @@ extension OffsetPdfPointExt on Offset { return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); } } + +/// Compares two lists for element-by-element equality. +/// +/// **NOTE: This function is copiedd from flutter's `foundation` library to remove dependency to Flutter** +bool _listEquals(List? a, List? b) { + if (a == null) { + return b == null; + } + if (b == null || a.length != b.length) { + return false; + } + if (identical(a, b)) { + return true; + } + for (int index = 0; index < a.length; index += 1) { + if (a[index] != b[index]) { + return false; + } + } + return true; +} diff --git a/lib/src/pdfium/pdf_file_cache.dart b/lib/src/pdfium/pdf_file_cache.dart index eb967b18..edfb4611 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/lib/src/pdfium/pdf_file_cache.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; import 'package:synchronized/extension.dart'; import '../../pdfrx.dart'; @@ -27,6 +26,10 @@ final _rafFinalizer = Finalizer((raf) { /// PDF file cache backed by a file. /// +/// The cache directory used by this class is obtained using [Pdfrx.getCacheDirectory]. +/// +/// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. +/// For Dart only, you can set this function to load assets from your own asset management system. class PdfFileCache { PdfFileCache(this.file); @@ -236,13 +239,16 @@ class PdfFileCache { } static Future getCacheFilePathForUri(Uri uri) async { - final cacheDir = await getCacheDirectory(); + if (Pdfrx.getCacheDirectory == null) { + throw StateError('Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.'); + } + final cacheDir = await Pdfrx.getCacheDirectory!(); final fnHash = sha1.convert(utf8.encode(uri.toString())).bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); final dir1 = fnHash.substring(0, 2); final dir2 = fnHash.substring(2, 4); final body = fnHash.substring(4); - final dir = Directory(path.join(cacheDir.path, 'pdfrx.cache', dir1, dir2)); + final dir = Directory(path.join(cacheDir, 'pdfrx.cache', dir1, dir2)); await dir.create(recursive: true); return File(path.join(dir.path, '$body.pdf')); } @@ -250,11 +256,6 @@ class PdfFileCache { static Future fromUri(Uri uri) async { return await fromFile(await getCacheFilePathForUri(uri)); } - - /// Function to determine the cache directory. - /// - /// You can override the default cache directory by setting this variable. - static Future Function() getCacheDirectory = getApplicationCacheDirectory; } class _HttpClientWrapper { diff --git a/lib/src/pdfium/pdfium_interop.dart b/lib/src/pdfium/pdfium_interop.dart index a91a5771..4252fe91 100644 --- a/lib/src/pdfium/pdfium_interop.dart +++ b/lib/src/pdfium/pdfium_interop.dart @@ -3,8 +3,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; - -import 'package:flutter/foundation.dart'; +import 'dart:typed_data'; import 'pdfium_bindings.dart'; diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/lib/src/pdfium/pdfrx_pdfium.dart index 4f1350c2..6560382b 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/lib/src/pdfium/pdfrx_pdfium.dart @@ -3,12 +3,10 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:rxdart/rxdart.dart'; import '../pdf_api.dart'; @@ -17,13 +15,6 @@ import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; import 'worker.dart'; -PdfDocumentFactory? _pdfiumDocumentFactory; - -/// Get [PdfDocumentFactory] backed by PDFium. -/// -/// For Flutter Web, you must set up PDFium WASM module. -PdfDocumentFactory getPdfiumDocumentFactory() => _pdfiumDocumentFactory ??= _PdfDocumentFactoryImpl(); - /// Get the module file name for pdfium. String _getModuleFileName() { if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; @@ -77,17 +68,23 @@ void _init() { final backgroundWorker = BackgroundWorker.create(); -class _PdfDocumentFactoryImpl extends PdfDocumentFactory { - @override +class PdfDocumentFactory { + PdfDocumentFactory._(); + + static final instance = PdfDocumentFactory._(); + Future openAsset( String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, }) async { - final data = await rootBundle.load(name); + if (Pdfrx.loadAsset == null) { + throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); + } + final asset = await Pdfrx.loadAsset!(name); return await _openData( - data.buffer.asUint8List(), + asset.buffer.asUint8List(), 'asset:$name', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -97,7 +94,6 @@ class _PdfDocumentFactoryImpl extends PdfDocumentFactory { ); } - @override Future openData( Uint8List data, { PdfPasswordProvider? passwordProvider, @@ -116,7 +112,6 @@ class _PdfDocumentFactoryImpl extends PdfDocumentFactory { onDispose: onDispose, ); - @override Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, @@ -166,7 +161,6 @@ class _PdfDocumentFactoryImpl extends PdfDocumentFactory { ); } - @override Future openCustom({ required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, @@ -244,7 +238,6 @@ class _PdfDocumentFactoryImpl extends PdfDocumentFactory { } } - @override Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -611,7 +604,7 @@ class _PdfPagePdfium extends PdfPage { int? height, double? fullWidth, double? fullHeight, - Color? backgroundColor, + ui.Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -628,7 +621,7 @@ class _PdfPagePdfium extends PdfPage { fullHeight ??= this.height; width ??= fullWidth.toInt(); height ??= fullHeight.toInt(); - backgroundColor ??= Colors.white; + backgroundColor ??= const ui.Color(0xffffffff); // white background const rgbaSize = 4; Pointer buffer = nullptr; try { @@ -931,7 +924,6 @@ class _PdfImagePdfium extends PdfImage { } } -@immutable class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); diff --git a/lib/src/pdfium/worker.dart b/lib/src/pdfium/worker.dart index e4233858..da782b33 100644 --- a/lib/src/pdfium/worker.dart +++ b/lib/src/pdfium/worker.dart @@ -3,10 +3,11 @@ import 'dart:developer' as developer; import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; import '../../pdfrx.dart'; +typedef PdfrxComputeCallback = FutureOr Function(M message); + /// Background worker based on Dart [Isolate]. class BackgroundWorker { BackgroundWorker._(this._receivePort, this._sendPort); @@ -42,7 +43,7 @@ class BackgroundWorker { }); } - Future compute(ComputeCallback callback, M message) async { + Future compute(PdfrxComputeCallback callback, M message) async { if (_isDisposed) { throw StateError('Worker is already disposed'); } @@ -69,7 +70,7 @@ class BackgroundWorker { class _ComputeParams { _ComputeParams(this.sendPort, this.callback, this.message); final SendPort sendPort; - final ComputeCallback callback; + final PdfrxComputeCallback callback; final M message; void execute() => sendPort.send(callback(message)); diff --git a/lib/src/pdfrx_flutter_initialize.dart b/lib/src/pdfrx_flutter_initialize.dart new file mode 100644 index 00000000..d6f61168 --- /dev/null +++ b/lib/src/pdfrx_flutter_initialize.dart @@ -0,0 +1,25 @@ +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'pdf_api.dart'; + +bool _isInitialized = false; + +/// Explicitly initializes the Pdfrx library for Flutter. +/// +/// This function actually sets up the following functions: +/// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. +/// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. +void pdfrxFlutterInitialize() { + if (_isInitialized) return; + Pdfrx.loadAsset ??= (name) async { + final asset = await rootBundle.load(name); + return asset.buffer.asUint8List(); + }; + Pdfrx.getCacheDirectory ??= () async { + final dir = await getTemporaryDirectory(); + return dir.path; + }; + + _isInitialized = true; +} diff --git a/lib/src/web/pdfrx_wasm.dart b/lib/src/web/pdfrx_wasm.dart index 94a49df0..84798de3 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/lib/src/web/pdfrx_wasm.dart @@ -4,20 +4,12 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'dart:ui_web' as ui_web; -import 'package:flutter/material.dart' show Colors, immutable; -import 'package:flutter/services.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:web/web.dart' as web; import '../pdf_api.dart'; -/// Get [PdfDocumentFactory] backed by PDFium. -/// -/// For Flutter Web, you must set up PDFium WASM module. -/// For more information, see [Enable PDFium WASM support](https://github.com/espresso3389/pdfrx/wiki/Enable-PDFium-WASM-support). -PdfDocumentFactory getPdfiumDocumentFactory() => _PdfDocumentFactoryWasmImpl.singleton; - /// The PDFium WASM communicator object @JS('PdfiumWasmCommunicator') extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { @@ -54,15 +46,15 @@ class PdfiumWasmCallback { /// The URL of the PDFium WASM worker script; pdfium_client.js tries to load worker script from this URL.' /// -/// [_PdfDocumentFactoryWasmImpl._init] will initializes its value. +/// [PdfDocumentFactory._init] will initializes its value. @JS() external String pdfiumWasmWorkerUrl; /// [PdfDocumentFactory] for PDFium WASM implementation. -class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { - _PdfDocumentFactoryWasmImpl._(); +class PdfDocumentFactory { + PdfDocumentFactory._(); - static final singleton = _PdfDocumentFactoryWasmImpl._(); + static final instance = PdfDocumentFactory._(); /// Default path to the WASM modules /// @@ -144,26 +136,26 @@ class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { return (result.dartify()) as Map; } - @override Future openAsset( String name, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, }) async { - final asset = await rootBundle.load(name); - final data = asset.buffer.asUint8List(); + if (Pdfrx.loadAsset == null) { + throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); + } + final asset = await Pdfrx.loadAsset!(name); return await openData( - data, + asset.buffer.asUint8List(), passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, - sourceName: name, + sourceName: 'asset:$name', allowDataOwnershipTransfer: true, ); } - @override Future openCustom({ required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, @@ -177,7 +169,6 @@ class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { throw UnimplementedError('PdfDocumentFactoryWasmImpl.openCustom is not implemented.'); } - @override Future openData( Uint8List data, { PdfPasswordProvider? passwordProvider, @@ -198,7 +189,6 @@ class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { onDispose: onDispose, ); - @override Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, @@ -216,7 +206,6 @@ class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { onDispose: null, ); - @override Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -266,7 +255,7 @@ class _PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { Future _openByFunc( Future> Function(String? password) openDocument, { required String sourceName, - required _PdfDocumentFactoryWasmImpl factory, + required PdfDocumentFactory factory, required PdfPasswordProvider? passwordProvider, required bool firstAttemptByEmptyPassword, required void Function()? onDispose, @@ -307,7 +296,7 @@ class _PdfDocumentWasm extends PdfDocument { } final Map document; - final _PdfDocumentFactoryWasmImpl factory; + final PdfDocumentFactory factory; final void Function()? disposeCallback; bool isDisposed = false; final subject = BehaviorSubject(); @@ -523,7 +512,7 @@ class _PdfPageWasm extends PdfPage { int? height, double? fullWidth, double? fullHeight, - Color? backgroundColor, + ui.Color? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -532,7 +521,7 @@ class _PdfPageWasm extends PdfPage { fullHeight ??= this.height; width ??= fullWidth.toInt(); height ??= fullHeight.toInt(); - backgroundColor ??= Colors.white; + backgroundColor ??= const ui.Color(0xffffffff); // white background final result = await document.factory.sendCommand( 'renderPage', @@ -572,7 +561,6 @@ class PdfImageWeb extends PdfImage { void dispose() {} } -@immutable class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); diff --git a/lib/src/widgets/pdf_viewer.dart b/lib/src/widgets/pdf_viewer.dart index 96dcdeea..5bda46d4 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/lib/src/widgets/pdf_viewer.dart @@ -244,6 +244,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override void initState() { super.initState(); + pdfrxFlutterInitialize(); _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); _widgetUpdated(null); } diff --git a/lib/src/widgets/pdf_widgets.dart b/lib/src/widgets/pdf_widgets.dart index 3872e0e2..0db691d5 100644 --- a/lib/src/widgets/pdf_widgets.dart +++ b/lib/src/widgets/pdf_widgets.dart @@ -106,6 +106,7 @@ class _PdfDocumentViewBuilderState extends State { @override void initState() { super.initState(); + pdfrxFlutterInitialize(); widget.documentRef.resolveListenable() ..addListener(_onDocumentChanged) ..load(); @@ -209,6 +210,12 @@ class _PdfPageViewState extends State { Size? _pageSize; PdfPageRenderCancellationToken? _cancellationToken; + @override + void initState() { + super.initState(); + pdfrxFlutterInitialize(); + } + @override void dispose() { _image?.dispose(); diff --git a/test/setup.dart b/test/setup.dart index 0db128c5..9384c62c 100644 --- a/test/setup.dart +++ b/test/setup.dart @@ -14,6 +14,8 @@ final cacheRoot = Directory('${tmpRoot.path}/cache'); /// Sets up the test environment. Future setup() async { + pdfrxFlutterInitialize(); + final envPath = Platform.environment['PDFIUM_PATH']; if (envPath != null && await File(envPath).exists()) { Pdfrx.pdfiumModulePath = envPath; From 64aed8b6607e3a8307f1f3983591a38f4beff1f3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 12 Jul 2025 04:43:47 +0900 Subject: [PATCH 180/663] WIP: Document updates --- lib/src/pdf_api.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/src/pdf_api.dart b/lib/src/pdf_api.dart index ae31f98c..b6b8978a 100644 --- a/lib/src/pdf_api.dart +++ b/lib/src/pdf_api.dart @@ -116,6 +116,7 @@ abstract class PdfDocument { /// For Web, [filePath] can be relative path from `index.html` or any arbitrary URL but it may be restricted by CORS. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// @@ -136,6 +137,7 @@ abstract class PdfDocument { /// Opening the specified asset. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// @@ -156,6 +158,7 @@ abstract class PdfDocument { /// Opening the PDF on memory. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. /// @@ -187,11 +190,15 @@ abstract class PdfDocument { /// Opening the PDF from custom source. /// + /// On Flutter Web, this function is not supported and throws an exception. + /// It is also not supported if pdfrx is running without libpdfrx (**typically on Dart only**). + /// /// [maxSizeToCacheOnMemory] is the maximum size of the PDF to cache on memory in bytes; the custom loading process /// may be heavy because of FFI overhead and it may be better to cache the PDF on memory if it's not too large. /// The default size is 1MB. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// @@ -227,6 +234,7 @@ abstract class PdfDocument { /// For other platforms, it uses [pdfDocumentFromUri] that uses HTTP's range request to download the file. /// /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty /// password or not. For more info, see [PdfPasswordProvider]. /// @@ -234,8 +242,12 @@ abstract class PdfDocument { /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. /// /// [progressCallback] is called when the download progress is updated. + /// /// [preferRangeAccess] to prefer range access to download the PDF. The default is false (Not supported on Web). + /// It is not supported if pdfrx is running without libpdfrx (**typically on Dart only**). + /// /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. + /// /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). static Future openUri( Uri uri, { From 2cb154935e0ffc4673d6821364165ab857135741 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 03:18:18 +0900 Subject: [PATCH 181/663] WIP --- .github/workflows/build-test.yml | 34 +- .github/workflows/github-pages.yml | 6 +- .github/workflows/pdfium-apple-release.yml | 12 +- .gitignore | 4 - .vscode/launch.json | 4 +- .vscode/settings.json | 1 + analysis_options.yaml | 47 -- darwin/pdfium/patches/ios/libjpeg_turbo.patch | 13 - darwin/pdfium/patches/ios/pdfium.patch | 39 -- .../pdfium/patches/macos/build-config.patch | 13 - darwin/pdfium/patches/macos/pdfium.patch | 13 - lib/src/pdfrx_flutter_initialize.dart | 25 - melos.yaml | 8 + packages/pdfrx/.gitignore | 42 ++ .metadata => packages/pdfrx/.metadata | 0 .prettierrc => packages/pdfrx/.prettierrc | 0 .pubignore => packages/pdfrx/.pubignore | 0 CHANGELOG.md => packages/pdfrx/CHANGELOG.md | 0 .../pdfrx/CODE_OF_CONDUCT.md | 0 LICENSE => packages/pdfrx/LICENSE | 0 README.md => packages/pdfrx/README.md | 5 +- .../pdfrx/android}/.gitignore | 0 .../pdfrx/android}/CMakeLists.txt | 0 .../pdfrx/android}/build.gradle | 0 .../pdfrx/android}/settings.gradle | 0 .../android}/src/main/AndroidManifest.xml | 0 {assets => packages/pdfrx/assets}/pdfium.wasm | Bin .../pdfrx/assets}/pdfium_client.js | 0 .../pdfrx/assets}/pdfium_worker.js | 0 .../pdfrx/bin}/remove_wasm_modules.dart | 0 .../pdfrx/darwin}/pdfium/.gitignore | 0 .../pdfrx/darwin}/pdfium/build | 0 .../pdfrx/darwin}/pdfium/build-config.sh | 0 .../pdfrx/darwin}/pdfrx.podspec | 0 .../pdfrx/darwin}/pdfrx/Package.swift | 0 .../pdfrx/Sources/pdfrx/include/.gitkeep | 0 .../darwin}/pdfrx/Sources/pdfrx/pdfrx.cpp | 0 {example => packages/pdfrx/example}/README.md | 0 .../pdfrx/example}/viewer/.gitignore | 3 +- .../pdfrx/example}/viewer/README.md | 0 .../example}/viewer/analysis_options.yaml | 0 .../pdfrx/example}/viewer/android/.gitignore | 0 .../pdfrx_example/MainActivity.kt | 0 .../viewer/android/app/build.gradle.kts | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../pdfrx_example/MainActivity.kt | 0 .../res/drawable-v21/launch_background.xml | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../example}/viewer/android/build.gradle.kts | 0 .../example}/viewer/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../viewer/android/settings.gradle.kts | 0 .../pdfrx/example}/viewer/assets/hello.pdf | Bin .../pdfrx/example}/viewer/ios/.gitignore | 0 .../viewer/ios/Flutter/AppFrameworkInfo.plist | 0 .../viewer/ios/Flutter/Debug.xcconfig | 0 .../viewer/ios/Flutter/Release.xcconfig | 0 .../pdfrx/example}/viewer/ios/Podfile | 0 .../pdfrx/example}/viewer/ios/Podfile.lock | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../viewer/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example}/viewer/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../viewer/ios/RunnerTests/RunnerTests.swift | 0 .../pdfrx/example}/viewer/lib/main.dart | 1 + .../example}/viewer/lib/markers_view.dart | 2 +- .../example}/viewer/lib/outline_view.dart | 1 + .../example}/viewer/lib/password_dialog.dart | 0 .../example}/viewer/lib/search_view.dart | 1 + .../example}/viewer/lib/thumbnails_view.dart | 0 .../pdfrx/example}/viewer/linux/.gitignore | 0 .../example}/viewer/linux/CMakeLists.txt | 0 .../viewer/linux/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../linux/flutter/generated_plugins.cmake | 0 .../pdfrx/example}/viewer/linux/main.cc | 0 .../example}/viewer/linux/my_application.cc | 0 .../example}/viewer/linux/my_application.h | 0 .../pdfrx/example}/viewer/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../Flutter/GeneratedPluginRegistrant.swift | 0 .../pdfrx/example}/viewer/macos/Podfile | 0 .../pdfrx/example}/viewer/macos/Podfile.lock | 0 .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../viewer/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example}/viewer/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../viewer/macos/Runner/Release.entitlements | 0 .../macos/RunnerTests/RunnerTests.swift | 0 .../pdfrx/example}/viewer/pubspec.lock | 25 +- .../pdfrx/example}/viewer/pubspec.yaml | 2 + .../example/viewer/pubspec_overrides.yaml | 6 + .../pdfrx/example}/viewer/web/favicon.png | Bin .../example}/viewer/web/icons/Icon-192.png | Bin .../example}/viewer/web/icons/Icon-512.png | Bin .../viewer/web/icons/Icon-maskable-192.png | Bin .../viewer/web/icons/Icon-maskable-512.png | Bin .../pdfrx/example}/viewer/web/index.html | 0 .../pdfrx/example}/viewer/web/manifest.json | 0 .../pdfrx/example}/viewer/windows/.gitignore | 0 .../example}/viewer/windows/CMakeLists.txt | 0 .../viewer/windows/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../windows/flutter/generated_plugins.cmake | 0 .../viewer/windows/runner/CMakeLists.txt | 0 .../example}/viewer/windows/runner/Runner.rc | 0 .../viewer/windows/runner/flutter_window.cpp | 0 .../viewer/windows/runner/flutter_window.h | 0 .../example}/viewer/windows/runner/main.cpp | 0 .../example}/viewer/windows/runner/resource.h | 0 .../windows/runner/resources/app_icon.ico | Bin .../viewer/windows/runner/runner.exe.manifest | 0 .../example}/viewer/windows/runner/utils.cpp | 0 .../example}/viewer/windows/runner/utils.h | 0 .../viewer/windows/runner/win32_window.cpp | 0 .../viewer/windows/runner/win32_window.h | 0 {lib => packages/pdfrx/lib}/pdfrx.dart | 3 +- .../pdfrx/lib}/src/pdf_document_ref.dart | 3 +- packages/pdfrx/lib/src/pdfrx_flutter.dart | 102 ++++ .../lib}/src/utils/double_extensions.dart | 0 .../pdfrx/lib}/src/utils/native/native.dart | 4 + .../pdfrx/lib}/src/utils/platform.dart | 0 .../pdfrx/lib}/src/utils/web/web.dart | 6 + .../pdfrx/lib/src/wasm}/js_utils.dart | 0 .../pdfrx/lib/src/wasm}/pdfrx_wasm.dart | 74 ++- .../lib}/src/widgets/interactive_viewer.dart | 0 .../lib}/src/widgets/pdf_error_widget.dart | 2 +- .../src/widgets/pdf_page_links_overlay.dart | 1 + .../src/widgets/pdf_page_text_overlay.dart | 1 + .../lib}/src/widgets/pdf_text_searcher.dart | 1 + .../pdfrx/lib}/src/widgets/pdf_viewer.dart | 5 +- .../lib}/src/widgets/pdf_viewer_params.dart | 1 + .../src/widgets/pdf_viewer_scroll_thumb.dart | 0 .../pdfrx/lib}/src/widgets/pdf_widgets.dart | 1 + .../pdfrx/linux}/CMakeLists.txt | 0 packages/pdfrx/pubspec.yaml | 57 ++ packages/pdfrx/pubspec_overrides.yaml | 4 + .../pdfrx/screenshot.jpg | Bin {src => packages/pdfrx/src}/CMakeLists.txt | 0 .../pdfrx/src}/pdfium_interop.cpp | 0 .../pdfrx/test}/pdf_viewer_test.dart | 3 +- .../pdfrx/windows}/.gitignore | 0 .../pdfrx/windows}/CMakeLists.txt | 0 packages/pdfrx_engine/.gitignore | 11 + packages/pdfrx_engine/CHANGELOG.md | 3 + packages/pdfrx_engine/CODE_OF_CONDUCT.md | 76 +++ packages/pdfrx_engine/LICENSE | 11 + packages/pdfrx_engine/README.md | 41 ++ packages/pdfrx_engine/analysis_options.yaml | 30 + packages/pdfrx_engine/lib/pdfrx_engine.dart | 3 + .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 70 +++ .../lib/src/native}/http_cache_control.dart | 0 .../lib/src/native}/pdf_file_cache.dart | 89 ++- .../lib/src/native}/pdfium_bindings.dart | 0 .../lib/src/native}/pdfium_interop.dart | 0 .../lib/src/native}/pdfrx_pdfium.dart | 536 ++++++++++++------ .../pdfrx_engine/lib/src/native}/worker.dart | 23 +- .../pdfrx_engine/lib}/src/pdf_api.dart | 276 +++++---- .../lib/src/pdfrx_engine_dart.dart | 117 ++++ packages/pdfrx_engine/pubspec.yaml | 52 ++ .../pdfrx_engine/test/pdf_document_test.dart | 34 ++ .../pdfrx_engine/test}/utils.dart | 13 +- pubspec.yaml | 80 +-- test/pdf_document_test.dart | 26 - test/setup.dart | 82 --- 225 files changed, 1418 insertions(+), 730 deletions(-) delete mode 100644 analysis_options.yaml delete mode 100644 darwin/pdfium/patches/ios/libjpeg_turbo.patch delete mode 100644 darwin/pdfium/patches/ios/pdfium.patch delete mode 100644 darwin/pdfium/patches/macos/build-config.patch delete mode 100644 darwin/pdfium/patches/macos/pdfium.patch delete mode 100644 lib/src/pdfrx_flutter_initialize.dart create mode 100644 melos.yaml create mode 100644 packages/pdfrx/.gitignore rename .metadata => packages/pdfrx/.metadata (100%) rename .prettierrc => packages/pdfrx/.prettierrc (100%) rename .pubignore => packages/pdfrx/.pubignore (100%) rename CHANGELOG.md => packages/pdfrx/CHANGELOG.md (100%) rename CODE_OF_CONDUCT.md => packages/pdfrx/CODE_OF_CONDUCT.md (100%) rename LICENSE => packages/pdfrx/LICENSE (100%) rename README.md => packages/pdfrx/README.md (96%) rename {android => packages/pdfrx/android}/.gitignore (100%) rename {android => packages/pdfrx/android}/CMakeLists.txt (100%) rename {android => packages/pdfrx/android}/build.gradle (100%) rename {android => packages/pdfrx/android}/settings.gradle (100%) rename {android => packages/pdfrx/android}/src/main/AndroidManifest.xml (100%) rename {assets => packages/pdfrx/assets}/pdfium.wasm (100%) rename {assets => packages/pdfrx/assets}/pdfium_client.js (100%) rename {assets => packages/pdfrx/assets}/pdfium_worker.js (100%) rename {bin => packages/pdfrx/bin}/remove_wasm_modules.dart (100%) rename {darwin => packages/pdfrx/darwin}/pdfium/.gitignore (100%) rename {darwin => packages/pdfrx/darwin}/pdfium/build (100%) mode change 100755 => 100644 rename {darwin => packages/pdfrx/darwin}/pdfium/build-config.sh (100%) mode change 100755 => 100644 rename {darwin => packages/pdfrx/darwin}/pdfrx.podspec (100%) rename {darwin => packages/pdfrx/darwin}/pdfrx/Package.swift (100%) rename {darwin => packages/pdfrx/darwin}/pdfrx/Sources/pdfrx/include/.gitkeep (100%) rename {darwin => packages/pdfrx/darwin}/pdfrx/Sources/pdfrx/pdfrx.cpp (100%) rename {example => packages/pdfrx/example}/README.md (100%) rename {example => packages/pdfrx/example}/viewer/.gitignore (96%) rename {example => packages/pdfrx/example}/viewer/README.md (100%) rename {example => packages/pdfrx/example}/viewer/analysis_options.yaml (100%) rename {example => packages/pdfrx/example}/viewer/android/.gitignore (100%) rename {example => packages/pdfrx/example}/viewer/android/app/bin/main/jp/espresso3389/pdfrx_example/MainActivity.kt (100%) rename {example => packages/pdfrx/example}/viewer/android/app/build.gradle.kts (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/debug/AndroidManifest.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/AndroidManifest.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/kotlin/jp/espresso3389/pdfrx_example/MainActivity.kt (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/drawable/launch_background.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/values-night/styles.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/main/res/values/styles.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/app/src/profile/AndroidManifest.xml (100%) rename {example => packages/pdfrx/example}/viewer/android/build.gradle.kts (100%) rename {example => packages/pdfrx/example}/viewer/android/gradle.properties (100%) rename {example => packages/pdfrx/example}/viewer/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {example => packages/pdfrx/example}/viewer/android/settings.gradle.kts (100%) rename {example => packages/pdfrx/example}/viewer/assets/hello.pdf (100%) rename {example => packages/pdfrx/example}/viewer/ios/.gitignore (100%) rename {example => packages/pdfrx/example}/viewer/ios/Flutter/AppFrameworkInfo.plist (100%) rename {example => packages/pdfrx/example}/viewer/ios/Flutter/Debug.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/ios/Flutter/Release.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/ios/Podfile (100%) rename {example => packages/pdfrx/example}/viewer/ios/Podfile.lock (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcodeproj/project.pbxproj (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/AppDelegate.swift (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Base.lproj/Main.storyboard (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Info.plist (100%) rename {example => packages/pdfrx/example}/viewer/ios/Runner/Runner-Bridging-Header.h (100%) rename {example => packages/pdfrx/example}/viewer/ios/RunnerTests/RunnerTests.swift (100%) rename {example => packages/pdfrx/example}/viewer/lib/main.dart (99%) rename {example => packages/pdfrx/example}/viewer/lib/markers_view.dart (97%) rename {example => packages/pdfrx/example}/viewer/lib/outline_view.dart (96%) rename {example => packages/pdfrx/example}/viewer/lib/password_dialog.dart (100%) rename {example => packages/pdfrx/example}/viewer/lib/search_view.dart (99%) rename {example => packages/pdfrx/example}/viewer/lib/thumbnails_view.dart (100%) rename {example => packages/pdfrx/example}/viewer/linux/.gitignore (100%) rename {example => packages/pdfrx/example}/viewer/linux/CMakeLists.txt (100%) rename {example => packages/pdfrx/example}/viewer/linux/flutter/CMakeLists.txt (100%) rename {example => packages/pdfrx/example}/viewer/linux/flutter/generated_plugin_registrant.cc (100%) rename {example => packages/pdfrx/example}/viewer/linux/flutter/generated_plugin_registrant.h (100%) rename {example => packages/pdfrx/example}/viewer/linux/flutter/generated_plugins.cmake (100%) rename {example => packages/pdfrx/example}/viewer/linux/main.cc (100%) rename {example => packages/pdfrx/example}/viewer/linux/my_application.cc (100%) rename {example => packages/pdfrx/example}/viewer/linux/my_application.h (100%) rename {example => packages/pdfrx/example}/viewer/macos/.gitignore (100%) rename {example => packages/pdfrx/example}/viewer/macos/Flutter/Flutter-Debug.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Flutter/Flutter-Release.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Flutter/GeneratedPluginRegistrant.swift (100%) rename {example => packages/pdfrx/example}/viewer/macos/Podfile (100%) rename {example => packages/pdfrx/example}/viewer/macos/Podfile.lock (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner.xcodeproj/project.pbxproj (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/AppDelegate.swift (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Base.lproj/MainMenu.xib (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Configs/AppInfo.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Configs/Debug.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Configs/Release.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Configs/Warnings.xcconfig (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/DebugProfile.entitlements (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Info.plist (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/MainFlutterWindow.swift (100%) rename {example => packages/pdfrx/example}/viewer/macos/Runner/Release.entitlements (100%) rename {example => packages/pdfrx/example}/viewer/macos/RunnerTests/RunnerTests.swift (100%) rename {example => packages/pdfrx/example}/viewer/pubspec.lock (96%) rename {example => packages/pdfrx/example}/viewer/pubspec.yaml (91%) create mode 100644 packages/pdfrx/example/viewer/pubspec_overrides.yaml rename {example => packages/pdfrx/example}/viewer/web/favicon.png (100%) rename {example => packages/pdfrx/example}/viewer/web/icons/Icon-192.png (100%) rename {example => packages/pdfrx/example}/viewer/web/icons/Icon-512.png (100%) rename {example => packages/pdfrx/example}/viewer/web/icons/Icon-maskable-192.png (100%) rename {example => packages/pdfrx/example}/viewer/web/icons/Icon-maskable-512.png (100%) rename {example => packages/pdfrx/example}/viewer/web/index.html (100%) rename {example => packages/pdfrx/example}/viewer/web/manifest.json (100%) rename {example => packages/pdfrx/example}/viewer/windows/.gitignore (100%) rename {example => packages/pdfrx/example}/viewer/windows/CMakeLists.txt (100%) rename {example => packages/pdfrx/example}/viewer/windows/flutter/CMakeLists.txt (100%) rename {example => packages/pdfrx/example}/viewer/windows/flutter/generated_plugin_registrant.cc (100%) rename {example => packages/pdfrx/example}/viewer/windows/flutter/generated_plugin_registrant.h (100%) rename {example => packages/pdfrx/example}/viewer/windows/flutter/generated_plugins.cmake (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/CMakeLists.txt (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/Runner.rc (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/flutter_window.cpp (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/flutter_window.h (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/main.cpp (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/resource.h (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/resources/app_icon.ico (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/runner.exe.manifest (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/utils.cpp (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/utils.h (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/win32_window.cpp (100%) rename {example => packages/pdfrx/example}/viewer/windows/runner/win32_window.h (100%) rename {lib => packages/pdfrx/lib}/pdfrx.dart (81%) rename {lib => packages/pdfrx/lib}/src/pdf_document_ref.dart (99%) create mode 100644 packages/pdfrx/lib/src/pdfrx_flutter.dart rename {lib => packages/pdfrx/lib}/src/utils/double_extensions.dart (100%) rename {lib => packages/pdfrx/lib}/src/utils/native/native.dart (65%) rename {lib => packages/pdfrx/lib}/src/utils/platform.dart (100%) rename {lib => packages/pdfrx/lib}/src/utils/web/web.dart (51%) rename {lib/src/web => packages/pdfrx/lib/src/wasm}/js_utils.dart (100%) rename {lib/src/web => packages/pdfrx/lib/src/wasm}/pdfrx_wasm.dart (91%) rename {lib => packages/pdfrx/lib}/src/widgets/interactive_viewer.dart (100%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_error_widget.dart (98%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_page_links_overlay.dart (97%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_page_text_overlay.dart (99%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_text_searcher.dart (99%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_viewer.dart (99%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_viewer_params.dart (99%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_viewer_scroll_thumb.dart (100%) rename {lib => packages/pdfrx/lib}/src/widgets/pdf_widgets.dart (99%) rename {linux => packages/pdfrx/linux}/CMakeLists.txt (100%) create mode 100644 packages/pdfrx/pubspec.yaml create mode 100644 packages/pdfrx/pubspec_overrides.yaml rename screenshot.jpg => packages/pdfrx/screenshot.jpg (100%) rename {src => packages/pdfrx/src}/CMakeLists.txt (100%) rename {src => packages/pdfrx/src}/pdfium_interop.cpp (100%) rename {test => packages/pdfrx/test}/pdf_viewer_test.dart (91%) rename {windows => packages/pdfrx/windows}/.gitignore (100%) rename {windows => packages/pdfrx/windows}/CMakeLists.txt (100%) create mode 100644 packages/pdfrx_engine/.gitignore create mode 100644 packages/pdfrx_engine/CHANGELOG.md create mode 100644 packages/pdfrx_engine/CODE_OF_CONDUCT.md create mode 100644 packages/pdfrx_engine/LICENSE create mode 100644 packages/pdfrx_engine/README.md create mode 100644 packages/pdfrx_engine/analysis_options.yaml create mode 100644 packages/pdfrx_engine/lib/pdfrx_engine.dart create mode 100644 packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/http_cache_control.dart (100%) rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/pdf_file_cache.dart (86%) rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/pdfium_bindings.dart (100%) rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/pdfium_interop.dart (100%) rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/pdfrx_pdfium.dart (70%) rename {lib/src/pdfium => packages/pdfrx_engine/lib/src/native}/worker.dart (81%) rename {lib => packages/pdfrx_engine/lib}/src/pdf_api.dart (87%) create mode 100644 packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart create mode 100644 packages/pdfrx_engine/pubspec.yaml create mode 100644 packages/pdfrx_engine/test/pdf_document_test.dart rename {test => packages/pdfrx_engine/test}/utils.dart (72%) delete mode 100644 test/pdf_document_test.dart delete mode 100644 test/setup.dart diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8f2888d0..690ff57f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -72,11 +72,11 @@ jobs: yes | ~/flutter/bin/flutter doctor --android-licenses - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build App Bundle - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build apk --debug --verbose # iOS build @@ -95,11 +95,11 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build iOS (no signing) - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build ios --debug --no-codesign --verbose # macOS build @@ -118,11 +118,11 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build macOS - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build macos --debug --verbose # Linux build @@ -146,11 +146,11 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build Linux - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build linux --debug --verbose # Linux ARM64 build @@ -174,11 +174,11 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build Linux - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build linux --debug --verbose # Windows build @@ -198,11 +198,11 @@ jobs: C:\flutter\bin\flutter.bat doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat pub get - name: Build Windows - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Windows ARM64 build (requires ARM64 runner or cross-compilation) @@ -223,11 +223,11 @@ jobs: # C:\flutter\bin\flutter.bat doctor -v # - name: Install dependencies - # working-directory: example/viewer + # working-directory: packages/pdfrx/example/viewer # run: C:\flutter\bin\flutter.bat pub get # - name: Build Windows ARM64 - # working-directory: example/viewer + # working-directory: packages/pdfrx/example/viewer # run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Web build @@ -246,13 +246,13 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Install dependencies - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter pub get - name: Build Web - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build web --verbose - name: Build Web (WASM) - working-directory: example/viewer + working-directory: packages/pdfrx/example/viewer run: flutter build web --wasm --verbose diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index a3971d4b..93d29d1f 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -34,7 +34,7 @@ jobs: run: flutter pub get - name: Build Flutter Web App (WASM) run: | - cd example/viewer/ + cd packages/pdfrx/example/viewer/ flutter build web --wasm --release --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION sed -i \ -e 's|||' \ @@ -47,7 +47,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.TOKEN_FOR_GHPAGE_DEPLOYMENT }}@github.com/${{ github.repository }}.git - name: Deploy to GitHub Pages using subtree push run: | - git add -f example/viewer/build/web + git add -f packages/pdfrx/example/viewer/build/web git commit -m "$PDFRX_VERSION $GITHUB_SHA" - git subtree split --prefix example/viewer/build/web -b tmp + git subtree split --prefix packages/pdfrx/example/viewer/build/web -b tmp git push -f origin tmp:gh-pages diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index a9db0fbe..44cd91d2 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -13,12 +13,12 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Build PDFium ${{ matrix.target }} - run: ./darwin/pdfium/build ${{ matrix.target }} + run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact uses: actions/upload-artifact@v4 with: name: pdfium-${{ matrix.target }} - path: ./darwin/pdfium/pdfium-${{ matrix.target }}.zip + path: ./packages/pdfrx/darwin/pdfium/pdfium-${{ matrix.target }}.zip release: needs: build @@ -28,12 +28,12 @@ jobs: uses: actions/download-artifact@v4 with: name: pdfium-ios - path: ./darwin/pdfium/ + path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact uses: actions/download-artifact@v4 with: name: pdfium-macos - path: ./darwin/pdfium/ + path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium uses: softprops/action-gh-release@v1 with: @@ -43,5 +43,5 @@ jobs: prerelease: false body: iOS/macOS PDFium prebuilt binary distribution for pdfrx (${{ github.ref_name }}). files: | - ./darwin/pdfium/pdfium-ios.zip - ./darwin/pdfium/pdfium-macos.zip + ./packages/pdfrx/darwin/pdfium/pdfium-ios.zip + ./packages/pdfrx/darwin/pdfium/pdfium-macos.zip diff --git a/.gitignore b/.gitignore index 056b4880..db39b04a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,10 +31,6 @@ migrate_working_dir/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock -**/doc/api/ .dart_tool/ -build/ - -/test/.tmp .claude/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 0ec78403..db264ac1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,13 +8,13 @@ "name": "viewer example", "request": "launch", "type": "dart", - "program": "example/viewer/lib/main.dart" + "program": "packages/pdfrx/example/viewer/lib/main.dart" }, { "name": "viewer example (WASM)", "request": "launch", "type": "dart", - "program": "example/viewer/lib/main.dart", + "program": "packages/pdfrx/example/viewer/lib/main.dart", "args": ["-d", "chrome", "--wasm"] } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 94149f26..d300fb58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "AACTION", + "AARRGGBB", "ACRO", "alloc", "allprojects", diff --git a/analysis_options.yaml b/analysis_options.yaml deleted file mode 100644 index d85fd475..00000000 --- a/analysis_options.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - prefer_single_quotes: true - # comment_references: true - prefer_relative_imports: true - use_key_in_widget_constructors: true - avoid_return_types_on_setters: true - avoid_types_on_closure_parameters: true - eol_at_end_of_file: true - sort_child_properties_last: true - sort_unnamed_constructors_first: true - sort_constructors_first: true - always_put_required_named_parameters_first: true - invalid_runtime_check_with_js_interop_types: true - - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - -analyzer: - exclude: - - lib/src/pdfium/pdfium_bindings.dart - -formatter: - page_width: 120 diff --git a/darwin/pdfium/patches/ios/libjpeg_turbo.patch b/darwin/pdfium/patches/ios/libjpeg_turbo.patch deleted file mode 100644 index 5f633124..00000000 --- a/darwin/pdfium/patches/ios/libjpeg_turbo.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/BUILD.gn b/BUILD.gn -index b39d278..596ea7a 100644 ---- a/BUILD.gn -+++ b/BUILD.gn -@@ -12,7 +12,7 @@ if (current_cpu == "arm" || current_cpu == "arm64") { - } - - assert( -- use_blink, -+ true, - "This is not used if blink is not enabled, don't drag it in unintentionally") - - source_set("libjpeg_headers") { diff --git a/darwin/pdfium/patches/ios/pdfium.patch b/darwin/pdfium/patches/ios/pdfium.patch deleted file mode 100644 index 673d3f5a..00000000 --- a/darwin/pdfium/patches/ios/pdfium.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn -index a07bcd089..3d1d197d2 100644 ---- a/core/fxge/BUILD.gn -+++ b/core/fxge/BUILD.gn -@@ -164,7 +164,7 @@ source_set("fxge") { - sources += [ "linux/fx_linux_impl.cpp" ] - } - -- if (is_mac) { -+ if (is_mac || is_ios) { - sources += [ - "apple/fx_apple_impl.cpp", - "apple/fx_apple_platform.cpp", -diff --git a/public/fpdfview.h b/public/fpdfview.h -index d96556b47..79b6f3ebb 100644 ---- a/public/fpdfview.h -+++ b/public/fpdfview.h -@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; - #endif // defined(FPDF_IMPLEMENTATION) - #endif // defined(WIN32) - #else --#define FPDF_EXPORT -+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #endif // defined(COMPONENT_BUILD) - - #if defined(WIN32) && defined(FPDFSDK_EXPORTS) -diff --git a/testing/test.gni b/testing/test.gni -index 6ad2c2d4a..bae5117d8 100644 ---- a/testing/test.gni -+++ b/testing/test.gni -@@ -207,7 +207,7 @@ template("test") { - } - - _bundle_id_suffix = target_name -- if (ios_automatically_manage_certs) { -+ if (true) { - # Use the same bundle identifier for all unit tests when managing - # certificates automatically as the number of free certs is limited. - _bundle_id_suffix = "generic-unit-test" diff --git a/darwin/pdfium/patches/macos/build-config.patch b/darwin/pdfium/patches/macos/build-config.patch deleted file mode 100644 index 7cd856db..00000000 --- a/darwin/pdfium/patches/macos/build-config.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/config/mac/BUILD.gn b/config/mac/BUILD.gn -index 85a668d33..54691a2e0 100644 ---- a/config/mac/BUILD.gn -+++ b/config/mac/BUILD.gn -@@ -52,6 +52,8 @@ config("compiler") { - if (export_libcxxabi_from_executables) { - ldflags += [ "-Wl,-undefined,dynamic_lookup" ] - } -+ -+ cflags += [ "-Wno-unknown-warning-option" ] - } - - # This is included by reference in the //build/config/compiler:runtime_library diff --git a/darwin/pdfium/patches/macos/pdfium.patch b/darwin/pdfium/patches/macos/pdfium.patch deleted file mode 100644 index 53ac1c23..00000000 --- a/darwin/pdfium/patches/macos/pdfium.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/public/fpdfview.h b/public/fpdfview.h -index d96556b47..79b6f3ebb 100644 ---- a/public/fpdfview.h -+++ b/public/fpdfview.h -@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; - #endif // defined(FPDF_IMPLEMENTATION) - #endif // defined(WIN32) - #else --#define FPDF_EXPORT -+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #endif // defined(COMPONENT_BUILD) - - #if defined(WIN32) && defined(FPDFSDK_EXPORTS) diff --git a/lib/src/pdfrx_flutter_initialize.dart b/lib/src/pdfrx_flutter_initialize.dart deleted file mode 100644 index d6f61168..00000000 --- a/lib/src/pdfrx_flutter_initialize.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:path_provider/path_provider.dart'; - -import 'pdf_api.dart'; - -bool _isInitialized = false; - -/// Explicitly initializes the Pdfrx library for Flutter. -/// -/// This function actually sets up the following functions: -/// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. -/// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. -void pdfrxFlutterInitialize() { - if (_isInitialized) return; - Pdfrx.loadAsset ??= (name) async { - final asset = await rootBundle.load(name); - return asset.buffer.asUint8List(); - }; - Pdfrx.getCacheDirectory ??= () async { - final dir = await getTemporaryDirectory(); - return dir.path; - }; - - _isInitialized = true; -} diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 00000000..271e401e --- /dev/null +++ b/melos.yaml @@ -0,0 +1,8 @@ +name: pdfrx + +packages: + - packages/** + +scripts: + analyze: + exec: dart analyze . diff --git a/packages/pdfrx/.gitignore b/packages/pdfrx/.gitignore new file mode 100644 index 00000000..94e202b0 --- /dev/null +++ b/packages/pdfrx/.gitignore @@ -0,0 +1,42 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# C++ +.cxx + +# For pdfium builds on macOS +.build/ +.swiftpm/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +/pubspec_overrides.yaml + +**/doc/api/ +.dart_tool/ +build/ + +/test/.tmp + +.claude/ diff --git a/.metadata b/packages/pdfrx/.metadata similarity index 100% rename from .metadata rename to packages/pdfrx/.metadata diff --git a/.prettierrc b/packages/pdfrx/.prettierrc similarity index 100% rename from .prettierrc rename to packages/pdfrx/.prettierrc diff --git a/.pubignore b/packages/pdfrx/.pubignore similarity index 100% rename from .pubignore rename to packages/pdfrx/.pubignore diff --git a/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/pdfrx/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/packages/pdfrx/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to packages/pdfrx/CODE_OF_CONDUCT.md diff --git a/LICENSE b/packages/pdfrx/LICENSE similarity index 100% rename from LICENSE rename to packages/pdfrx/LICENSE diff --git a/README.md b/packages/pdfrx/README.md similarity index 96% rename from README.md rename to packages/pdfrx/README.md index a7ac4e47..e73c008b 100644 --- a/README.md +++ b/packages/pdfrx/README.md @@ -62,6 +62,7 @@ dependencies: **REQUIRED: You must enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode) to build pdfrx on Windows.** The build process uses *symbolic links* which requires Developer Mode to be enabled. If Developer Mode is not enabled: + - The build will fail with an error message - You will see a link to Microsoft's official instructions - You must enable Developer Mode and restart your computer before building @@ -190,6 +191,4 @@ PdfDocumentViewBuilder.asset( - [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) - [PdfPageView](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageView-class.html) - Easy to use PDF APIs - - [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) -- PDFium bindings - - Not encouraged but you can import [`package:pdfrx/src/pdfium/pdfium_bindings.dart`](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart) + - [pdfrx_engine API reference](https://pub.dev/documentation/pdfrx_engine/latest/) diff --git a/android/.gitignore b/packages/pdfrx/android/.gitignore similarity index 100% rename from android/.gitignore rename to packages/pdfrx/android/.gitignore diff --git a/android/CMakeLists.txt b/packages/pdfrx/android/CMakeLists.txt similarity index 100% rename from android/CMakeLists.txt rename to packages/pdfrx/android/CMakeLists.txt diff --git a/android/build.gradle b/packages/pdfrx/android/build.gradle similarity index 100% rename from android/build.gradle rename to packages/pdfrx/android/build.gradle diff --git a/android/settings.gradle b/packages/pdfrx/android/settings.gradle similarity index 100% rename from android/settings.gradle rename to packages/pdfrx/android/settings.gradle diff --git a/android/src/main/AndroidManifest.xml b/packages/pdfrx/android/src/main/AndroidManifest.xml similarity index 100% rename from android/src/main/AndroidManifest.xml rename to packages/pdfrx/android/src/main/AndroidManifest.xml diff --git a/assets/pdfium.wasm b/packages/pdfrx/assets/pdfium.wasm similarity index 100% rename from assets/pdfium.wasm rename to packages/pdfrx/assets/pdfium.wasm diff --git a/assets/pdfium_client.js b/packages/pdfrx/assets/pdfium_client.js similarity index 100% rename from assets/pdfium_client.js rename to packages/pdfrx/assets/pdfium_client.js diff --git a/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js similarity index 100% rename from assets/pdfium_worker.js rename to packages/pdfrx/assets/pdfium_worker.js diff --git a/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart similarity index 100% rename from bin/remove_wasm_modules.dart rename to packages/pdfrx/bin/remove_wasm_modules.dart diff --git a/darwin/pdfium/.gitignore b/packages/pdfrx/darwin/pdfium/.gitignore similarity index 100% rename from darwin/pdfium/.gitignore rename to packages/pdfrx/darwin/pdfium/.gitignore diff --git a/darwin/pdfium/build b/packages/pdfrx/darwin/pdfium/build old mode 100755 new mode 100644 similarity index 100% rename from darwin/pdfium/build rename to packages/pdfrx/darwin/pdfium/build diff --git a/darwin/pdfium/build-config.sh b/packages/pdfrx/darwin/pdfium/build-config.sh old mode 100755 new mode 100644 similarity index 100% rename from darwin/pdfium/build-config.sh rename to packages/pdfrx/darwin/pdfium/build-config.sh diff --git a/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec similarity index 100% rename from darwin/pdfrx.podspec rename to packages/pdfrx/darwin/pdfrx.podspec diff --git a/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift similarity index 100% rename from darwin/pdfrx/Package.swift rename to packages/pdfrx/darwin/pdfrx/Package.swift diff --git a/darwin/pdfrx/Sources/pdfrx/include/.gitkeep b/packages/pdfrx/darwin/pdfrx/Sources/pdfrx/include/.gitkeep similarity index 100% rename from darwin/pdfrx/Sources/pdfrx/include/.gitkeep rename to packages/pdfrx/darwin/pdfrx/Sources/pdfrx/include/.gitkeep diff --git a/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp similarity index 100% rename from darwin/pdfrx/Sources/pdfrx/pdfrx.cpp rename to packages/pdfrx/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp diff --git a/example/README.md b/packages/pdfrx/example/README.md similarity index 100% rename from example/README.md rename to packages/pdfrx/example/README.md diff --git a/example/viewer/.gitignore b/packages/pdfrx/example/viewer/.gitignore similarity index 96% rename from example/viewer/.gitignore rename to packages/pdfrx/example/viewer/.gitignore index 6ed65412..1735b245 100644 --- a/example/viewer/.gitignore +++ b/packages/pdfrx/example/viewer/.gitignore @@ -45,4 +45,5 @@ app.*.map.json /android/app/release # never ever commit the local test example file... -assets/*.pdf + +pubspec_overrides.yaml diff --git a/example/viewer/README.md b/packages/pdfrx/example/viewer/README.md similarity index 100% rename from example/viewer/README.md rename to packages/pdfrx/example/viewer/README.md diff --git a/example/viewer/analysis_options.yaml b/packages/pdfrx/example/viewer/analysis_options.yaml similarity index 100% rename from example/viewer/analysis_options.yaml rename to packages/pdfrx/example/viewer/analysis_options.yaml diff --git a/example/viewer/android/.gitignore b/packages/pdfrx/example/viewer/android/.gitignore similarity index 100% rename from example/viewer/android/.gitignore rename to packages/pdfrx/example/viewer/android/.gitignore diff --git a/example/viewer/android/app/bin/main/jp/espresso3389/pdfrx_example/MainActivity.kt b/packages/pdfrx/example/viewer/android/app/bin/main/jp/espresso3389/pdfrx_example/MainActivity.kt similarity index 100% rename from example/viewer/android/app/bin/main/jp/espresso3389/pdfrx_example/MainActivity.kt rename to packages/pdfrx/example/viewer/android/app/bin/main/jp/espresso3389/pdfrx_example/MainActivity.kt diff --git a/example/viewer/android/app/build.gradle.kts b/packages/pdfrx/example/viewer/android/app/build.gradle.kts similarity index 100% rename from example/viewer/android/app/build.gradle.kts rename to packages/pdfrx/example/viewer/android/app/build.gradle.kts diff --git a/example/viewer/android/app/src/debug/AndroidManifest.xml b/packages/pdfrx/example/viewer/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from example/viewer/android/app/src/debug/AndroidManifest.xml rename to packages/pdfrx/example/viewer/android/app/src/debug/AndroidManifest.xml diff --git a/example/viewer/android/app/src/main/AndroidManifest.xml b/packages/pdfrx/example/viewer/android/app/src/main/AndroidManifest.xml similarity index 100% rename from example/viewer/android/app/src/main/AndroidManifest.xml rename to packages/pdfrx/example/viewer/android/app/src/main/AndroidManifest.xml diff --git a/example/viewer/android/app/src/main/kotlin/jp/espresso3389/pdfrx_example/MainActivity.kt b/packages/pdfrx/example/viewer/android/app/src/main/kotlin/jp/espresso3389/pdfrx_example/MainActivity.kt similarity index 100% rename from example/viewer/android/app/src/main/kotlin/jp/espresso3389/pdfrx_example/MainActivity.kt rename to packages/pdfrx/example/viewer/android/app/src/main/kotlin/jp/espresso3389/pdfrx_example/MainActivity.kt diff --git a/example/viewer/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/pdfrx/example/viewer/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from example/viewer/android/app/src/main/res/drawable-v21/launch_background.xml rename to packages/pdfrx/example/viewer/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/example/viewer/android/app/src/main/res/drawable/launch_background.xml b/packages/pdfrx/example/viewer/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from example/viewer/android/app/src/main/res/drawable/launch_background.xml rename to packages/pdfrx/example/viewer/android/app/src/main/res/drawable/launch_background.xml diff --git a/example/viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from example/viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/example/viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from example/viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/example/viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from example/viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/example/viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from example/viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/example/viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from example/viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/pdfrx/example/viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/example/viewer/android/app/src/main/res/values-night/styles.xml b/packages/pdfrx/example/viewer/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from example/viewer/android/app/src/main/res/values-night/styles.xml rename to packages/pdfrx/example/viewer/android/app/src/main/res/values-night/styles.xml diff --git a/example/viewer/android/app/src/main/res/values/styles.xml b/packages/pdfrx/example/viewer/android/app/src/main/res/values/styles.xml similarity index 100% rename from example/viewer/android/app/src/main/res/values/styles.xml rename to packages/pdfrx/example/viewer/android/app/src/main/res/values/styles.xml diff --git a/example/viewer/android/app/src/profile/AndroidManifest.xml b/packages/pdfrx/example/viewer/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from example/viewer/android/app/src/profile/AndroidManifest.xml rename to packages/pdfrx/example/viewer/android/app/src/profile/AndroidManifest.xml diff --git a/example/viewer/android/build.gradle.kts b/packages/pdfrx/example/viewer/android/build.gradle.kts similarity index 100% rename from example/viewer/android/build.gradle.kts rename to packages/pdfrx/example/viewer/android/build.gradle.kts diff --git a/example/viewer/android/gradle.properties b/packages/pdfrx/example/viewer/android/gradle.properties similarity index 100% rename from example/viewer/android/gradle.properties rename to packages/pdfrx/example/viewer/android/gradle.properties diff --git a/example/viewer/android/gradle/wrapper/gradle-wrapper.properties b/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from example/viewer/android/gradle/wrapper/gradle-wrapper.properties rename to packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties diff --git a/example/viewer/android/settings.gradle.kts b/packages/pdfrx/example/viewer/android/settings.gradle.kts similarity index 100% rename from example/viewer/android/settings.gradle.kts rename to packages/pdfrx/example/viewer/android/settings.gradle.kts diff --git a/example/viewer/assets/hello.pdf b/packages/pdfrx/example/viewer/assets/hello.pdf similarity index 100% rename from example/viewer/assets/hello.pdf rename to packages/pdfrx/example/viewer/assets/hello.pdf diff --git a/example/viewer/ios/.gitignore b/packages/pdfrx/example/viewer/ios/.gitignore similarity index 100% rename from example/viewer/ios/.gitignore rename to packages/pdfrx/example/viewer/ios/.gitignore diff --git a/example/viewer/ios/Flutter/AppFrameworkInfo.plist b/packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from example/viewer/ios/Flutter/AppFrameworkInfo.plist rename to packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist diff --git a/example/viewer/ios/Flutter/Debug.xcconfig b/packages/pdfrx/example/viewer/ios/Flutter/Debug.xcconfig similarity index 100% rename from example/viewer/ios/Flutter/Debug.xcconfig rename to packages/pdfrx/example/viewer/ios/Flutter/Debug.xcconfig diff --git a/example/viewer/ios/Flutter/Release.xcconfig b/packages/pdfrx/example/viewer/ios/Flutter/Release.xcconfig similarity index 100% rename from example/viewer/ios/Flutter/Release.xcconfig rename to packages/pdfrx/example/viewer/ios/Flutter/Release.xcconfig diff --git a/example/viewer/ios/Podfile b/packages/pdfrx/example/viewer/ios/Podfile similarity index 100% rename from example/viewer/ios/Podfile rename to packages/pdfrx/example/viewer/ios/Podfile diff --git a/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock similarity index 100% rename from example/viewer/ios/Podfile.lock rename to packages/pdfrx/example/viewer/ios/Podfile.lock diff --git a/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from example/viewer/ios/Runner.xcodeproj/project.pbxproj rename to packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj diff --git a/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/example/viewer/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/viewer/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/viewer/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/pdfrx/example/viewer/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/example/viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/pdfrx/example/viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/pdfrx/example/viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/pdfrx/example/viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/example/viewer/ios/Runner/AppDelegate.swift b/packages/pdfrx/example/viewer/ios/Runner/AppDelegate.swift similarity index 100% rename from example/viewer/ios/Runner/AppDelegate.swift rename to packages/pdfrx/example/viewer/ios/Runner/AppDelegate.swift diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/pdfrx/example/viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/example/viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/pdfrx/example/viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from example/viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/pdfrx/example/viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/example/viewer/ios/Runner/Base.lproj/Main.storyboard b/packages/pdfrx/example/viewer/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from example/viewer/ios/Runner/Base.lproj/Main.storyboard rename to packages/pdfrx/example/viewer/ios/Runner/Base.lproj/Main.storyboard diff --git a/example/viewer/ios/Runner/Info.plist b/packages/pdfrx/example/viewer/ios/Runner/Info.plist similarity index 100% rename from example/viewer/ios/Runner/Info.plist rename to packages/pdfrx/example/viewer/ios/Runner/Info.plist diff --git a/example/viewer/ios/Runner/Runner-Bridging-Header.h b/packages/pdfrx/example/viewer/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from example/viewer/ios/Runner/Runner-Bridging-Header.h rename to packages/pdfrx/example/viewer/ios/Runner/Runner-Bridging-Header.h diff --git a/example/viewer/ios/RunnerTests/RunnerTests.swift b/packages/pdfrx/example/viewer/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from example/viewer/ios/RunnerTests/RunnerTests.swift rename to packages/pdfrx/example/viewer/ios/RunnerTests/RunnerTests.swift diff --git a/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart similarity index 99% rename from example/viewer/lib/main.dart rename to packages/pdfrx/example/viewer/lib/main.dart index 8a64813d..fa5de4db 100644 --- a/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -4,6 +4,7 @@ import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:url_launcher/url_launcher.dart'; import 'markers_view.dart'; diff --git a/example/viewer/lib/markers_view.dart b/packages/pdfrx/example/viewer/lib/markers_view.dart similarity index 97% rename from example/viewer/lib/markers_view.dart rename to packages/pdfrx/example/viewer/lib/markers_view.dart index 51b8cd0c..e0f102b0 100644 --- a/example/viewer/lib/markers_view.dart +++ b/packages/pdfrx/example/viewer/lib/markers_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; class Marker { Marker(this.color, this.ranges); diff --git a/example/viewer/lib/outline_view.dart b/packages/pdfrx/example/viewer/lib/outline_view.dart similarity index 96% rename from example/viewer/lib/outline_view.dart rename to packages/pdfrx/example/viewer/lib/outline_view.dart index f7dbee1a..dabcd32e 100644 --- a/example/viewer/lib/outline_view.dart +++ b/packages/pdfrx/example/viewer/lib/outline_view.dart @@ -3,6 +3,7 @@ // import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; class OutlineView extends StatelessWidget { const OutlineView({ diff --git a/example/viewer/lib/password_dialog.dart b/packages/pdfrx/example/viewer/lib/password_dialog.dart similarity index 100% rename from example/viewer/lib/password_dialog.dart rename to packages/pdfrx/example/viewer/lib/password_dialog.dart diff --git a/example/viewer/lib/search_view.dart b/packages/pdfrx/example/viewer/lib/search_view.dart similarity index 99% rename from example/viewer/lib/search_view.dart rename to packages/pdfrx/example/viewer/lib/search_view.dart index a5658ad9..4bd31f3c 100644 --- a/example/viewer/lib/search_view.dart +++ b/packages/pdfrx/example/viewer/lib/search_view.dart @@ -1,6 +1,7 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:synchronized/extension.dart'; // diff --git a/example/viewer/lib/thumbnails_view.dart b/packages/pdfrx/example/viewer/lib/thumbnails_view.dart similarity index 100% rename from example/viewer/lib/thumbnails_view.dart rename to packages/pdfrx/example/viewer/lib/thumbnails_view.dart diff --git a/example/viewer/linux/.gitignore b/packages/pdfrx/example/viewer/linux/.gitignore similarity index 100% rename from example/viewer/linux/.gitignore rename to packages/pdfrx/example/viewer/linux/.gitignore diff --git a/example/viewer/linux/CMakeLists.txt b/packages/pdfrx/example/viewer/linux/CMakeLists.txt similarity index 100% rename from example/viewer/linux/CMakeLists.txt rename to packages/pdfrx/example/viewer/linux/CMakeLists.txt diff --git a/example/viewer/linux/flutter/CMakeLists.txt b/packages/pdfrx/example/viewer/linux/flutter/CMakeLists.txt similarity index 100% rename from example/viewer/linux/flutter/CMakeLists.txt rename to packages/pdfrx/example/viewer/linux/flutter/CMakeLists.txt diff --git a/example/viewer/linux/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/viewer/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from example/viewer/linux/flutter/generated_plugin_registrant.cc rename to packages/pdfrx/example/viewer/linux/flutter/generated_plugin_registrant.cc diff --git a/example/viewer/linux/flutter/generated_plugin_registrant.h b/packages/pdfrx/example/viewer/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from example/viewer/linux/flutter/generated_plugin_registrant.h rename to packages/pdfrx/example/viewer/linux/flutter/generated_plugin_registrant.h diff --git a/example/viewer/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake similarity index 100% rename from example/viewer/linux/flutter/generated_plugins.cmake rename to packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake diff --git a/example/viewer/linux/main.cc b/packages/pdfrx/example/viewer/linux/main.cc similarity index 100% rename from example/viewer/linux/main.cc rename to packages/pdfrx/example/viewer/linux/main.cc diff --git a/example/viewer/linux/my_application.cc b/packages/pdfrx/example/viewer/linux/my_application.cc similarity index 100% rename from example/viewer/linux/my_application.cc rename to packages/pdfrx/example/viewer/linux/my_application.cc diff --git a/example/viewer/linux/my_application.h b/packages/pdfrx/example/viewer/linux/my_application.h similarity index 100% rename from example/viewer/linux/my_application.h rename to packages/pdfrx/example/viewer/linux/my_application.h diff --git a/example/viewer/macos/.gitignore b/packages/pdfrx/example/viewer/macos/.gitignore similarity index 100% rename from example/viewer/macos/.gitignore rename to packages/pdfrx/example/viewer/macos/.gitignore diff --git a/example/viewer/macos/Flutter/Flutter-Debug.xcconfig b/packages/pdfrx/example/viewer/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from example/viewer/macos/Flutter/Flutter-Debug.xcconfig rename to packages/pdfrx/example/viewer/macos/Flutter/Flutter-Debug.xcconfig diff --git a/example/viewer/macos/Flutter/Flutter-Release.xcconfig b/packages/pdfrx/example/viewer/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from example/viewer/macos/Flutter/Flutter-Release.xcconfig rename to packages/pdfrx/example/viewer/macos/Flutter/Flutter-Release.xcconfig diff --git a/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift rename to packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/example/viewer/macos/Podfile b/packages/pdfrx/example/viewer/macos/Podfile similarity index 100% rename from example/viewer/macos/Podfile rename to packages/pdfrx/example/viewer/macos/Podfile diff --git a/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock similarity index 100% rename from example/viewer/macos/Podfile.lock rename to packages/pdfrx/example/viewer/macos/Podfile.lock diff --git a/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from example/viewer/macos/Runner.xcodeproj/project.pbxproj rename to packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj diff --git a/example/viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/pdfrx/example/viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/example/viewer/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/viewer/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/viewer/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/pdfrx/example/viewer/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/example/viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/pdfrx/example/viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/viewer/macos/Runner/AppDelegate.swift b/packages/pdfrx/example/viewer/macos/Runner/AppDelegate.swift similarity index 100% rename from example/viewer/macos/Runner/AppDelegate.swift rename to packages/pdfrx/example/viewer/macos/Runner/AppDelegate.swift diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/pdfrx/example/viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/example/viewer/macos/Runner/Base.lproj/MainMenu.xib b/packages/pdfrx/example/viewer/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from example/viewer/macos/Runner/Base.lproj/MainMenu.xib rename to packages/pdfrx/example/viewer/macos/Runner/Base.lproj/MainMenu.xib diff --git a/example/viewer/macos/Runner/Configs/AppInfo.xcconfig b/packages/pdfrx/example/viewer/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from example/viewer/macos/Runner/Configs/AppInfo.xcconfig rename to packages/pdfrx/example/viewer/macos/Runner/Configs/AppInfo.xcconfig diff --git a/example/viewer/macos/Runner/Configs/Debug.xcconfig b/packages/pdfrx/example/viewer/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from example/viewer/macos/Runner/Configs/Debug.xcconfig rename to packages/pdfrx/example/viewer/macos/Runner/Configs/Debug.xcconfig diff --git a/example/viewer/macos/Runner/Configs/Release.xcconfig b/packages/pdfrx/example/viewer/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from example/viewer/macos/Runner/Configs/Release.xcconfig rename to packages/pdfrx/example/viewer/macos/Runner/Configs/Release.xcconfig diff --git a/example/viewer/macos/Runner/Configs/Warnings.xcconfig b/packages/pdfrx/example/viewer/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from example/viewer/macos/Runner/Configs/Warnings.xcconfig rename to packages/pdfrx/example/viewer/macos/Runner/Configs/Warnings.xcconfig diff --git a/example/viewer/macos/Runner/DebugProfile.entitlements b/packages/pdfrx/example/viewer/macos/Runner/DebugProfile.entitlements similarity index 100% rename from example/viewer/macos/Runner/DebugProfile.entitlements rename to packages/pdfrx/example/viewer/macos/Runner/DebugProfile.entitlements diff --git a/example/viewer/macos/Runner/Info.plist b/packages/pdfrx/example/viewer/macos/Runner/Info.plist similarity index 100% rename from example/viewer/macos/Runner/Info.plist rename to packages/pdfrx/example/viewer/macos/Runner/Info.plist diff --git a/example/viewer/macos/Runner/MainFlutterWindow.swift b/packages/pdfrx/example/viewer/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from example/viewer/macos/Runner/MainFlutterWindow.swift rename to packages/pdfrx/example/viewer/macos/Runner/MainFlutterWindow.swift diff --git a/example/viewer/macos/Runner/Release.entitlements b/packages/pdfrx/example/viewer/macos/Runner/Release.entitlements similarity index 100% rename from example/viewer/macos/Runner/Release.entitlements rename to packages/pdfrx/example/viewer/macos/Runner/Release.entitlements diff --git a/example/viewer/macos/RunnerTests/RunnerTests.swift b/packages/pdfrx/example/viewer/macos/RunnerTests/RunnerTests.swift similarity index 100% rename from example/viewer/macos/RunnerTests/RunnerTests.swift rename to packages/pdfrx/example/viewer/macos/RunnerTests/RunnerTests.swift diff --git a/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock similarity index 96% rename from example/viewer/pubspec.lock rename to packages/pdfrx/example/viewer/pubspec.lock index 19035c51..e582568d 100644 --- a/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" async: dependency: transitive description: @@ -332,6 +340,13 @@ packages: relative: true source: path version: "1.3.2" + pdfrx_engine: + dependency: "direct main" + description: + path: "../../../pdfrx_engine" + relative: true + source: path + version: "1.0.0" platform: dependency: transitive description: @@ -348,6 +363,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" rxdart: dependency: "direct main" description: @@ -531,4 +554,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.27.0" diff --git a/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml similarity index 91% rename from example/viewer/pubspec.yaml rename to packages/pdfrx/example/viewer/pubspec.yaml index fff04372..814391bd 100644 --- a/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: pdfrx: path: ../../ + pdfrx_engine: + path: ../../../pdfrx_engine cupertino_icons: ^1.0.8 rxdart: ^0.28.0 diff --git a/packages/pdfrx/example/viewer/pubspec_overrides.yaml b/packages/pdfrx/example/viewer/pubspec_overrides.yaml new file mode 100644 index 00000000..8ea2f941 --- /dev/null +++ b/packages/pdfrx/example/viewer/pubspec_overrides.yaml @@ -0,0 +1,6 @@ +# melos_managed_dependency_overrides: pdfrx,pdfrx_engine +dependency_overrides: + pdfrx: + path: ../.. + pdfrx_engine: + path: ../../../pdfrx_engine diff --git a/example/viewer/web/favicon.png b/packages/pdfrx/example/viewer/web/favicon.png similarity index 100% rename from example/viewer/web/favicon.png rename to packages/pdfrx/example/viewer/web/favicon.png diff --git a/example/viewer/web/icons/Icon-192.png b/packages/pdfrx/example/viewer/web/icons/Icon-192.png similarity index 100% rename from example/viewer/web/icons/Icon-192.png rename to packages/pdfrx/example/viewer/web/icons/Icon-192.png diff --git a/example/viewer/web/icons/Icon-512.png b/packages/pdfrx/example/viewer/web/icons/Icon-512.png similarity index 100% rename from example/viewer/web/icons/Icon-512.png rename to packages/pdfrx/example/viewer/web/icons/Icon-512.png diff --git a/example/viewer/web/icons/Icon-maskable-192.png b/packages/pdfrx/example/viewer/web/icons/Icon-maskable-192.png similarity index 100% rename from example/viewer/web/icons/Icon-maskable-192.png rename to packages/pdfrx/example/viewer/web/icons/Icon-maskable-192.png diff --git a/example/viewer/web/icons/Icon-maskable-512.png b/packages/pdfrx/example/viewer/web/icons/Icon-maskable-512.png similarity index 100% rename from example/viewer/web/icons/Icon-maskable-512.png rename to packages/pdfrx/example/viewer/web/icons/Icon-maskable-512.png diff --git a/example/viewer/web/index.html b/packages/pdfrx/example/viewer/web/index.html similarity index 100% rename from example/viewer/web/index.html rename to packages/pdfrx/example/viewer/web/index.html diff --git a/example/viewer/web/manifest.json b/packages/pdfrx/example/viewer/web/manifest.json similarity index 100% rename from example/viewer/web/manifest.json rename to packages/pdfrx/example/viewer/web/manifest.json diff --git a/example/viewer/windows/.gitignore b/packages/pdfrx/example/viewer/windows/.gitignore similarity index 100% rename from example/viewer/windows/.gitignore rename to packages/pdfrx/example/viewer/windows/.gitignore diff --git a/example/viewer/windows/CMakeLists.txt b/packages/pdfrx/example/viewer/windows/CMakeLists.txt similarity index 100% rename from example/viewer/windows/CMakeLists.txt rename to packages/pdfrx/example/viewer/windows/CMakeLists.txt diff --git a/example/viewer/windows/flutter/CMakeLists.txt b/packages/pdfrx/example/viewer/windows/flutter/CMakeLists.txt similarity index 100% rename from example/viewer/windows/flutter/CMakeLists.txt rename to packages/pdfrx/example/viewer/windows/flutter/CMakeLists.txt diff --git a/example/viewer/windows/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/viewer/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from example/viewer/windows/flutter/generated_plugin_registrant.cc rename to packages/pdfrx/example/viewer/windows/flutter/generated_plugin_registrant.cc diff --git a/example/viewer/windows/flutter/generated_plugin_registrant.h b/packages/pdfrx/example/viewer/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from example/viewer/windows/flutter/generated_plugin_registrant.h rename to packages/pdfrx/example/viewer/windows/flutter/generated_plugin_registrant.h diff --git a/example/viewer/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake similarity index 100% rename from example/viewer/windows/flutter/generated_plugins.cmake rename to packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake diff --git a/example/viewer/windows/runner/CMakeLists.txt b/packages/pdfrx/example/viewer/windows/runner/CMakeLists.txt similarity index 100% rename from example/viewer/windows/runner/CMakeLists.txt rename to packages/pdfrx/example/viewer/windows/runner/CMakeLists.txt diff --git a/example/viewer/windows/runner/Runner.rc b/packages/pdfrx/example/viewer/windows/runner/Runner.rc similarity index 100% rename from example/viewer/windows/runner/Runner.rc rename to packages/pdfrx/example/viewer/windows/runner/Runner.rc diff --git a/example/viewer/windows/runner/flutter_window.cpp b/packages/pdfrx/example/viewer/windows/runner/flutter_window.cpp similarity index 100% rename from example/viewer/windows/runner/flutter_window.cpp rename to packages/pdfrx/example/viewer/windows/runner/flutter_window.cpp diff --git a/example/viewer/windows/runner/flutter_window.h b/packages/pdfrx/example/viewer/windows/runner/flutter_window.h similarity index 100% rename from example/viewer/windows/runner/flutter_window.h rename to packages/pdfrx/example/viewer/windows/runner/flutter_window.h diff --git a/example/viewer/windows/runner/main.cpp b/packages/pdfrx/example/viewer/windows/runner/main.cpp similarity index 100% rename from example/viewer/windows/runner/main.cpp rename to packages/pdfrx/example/viewer/windows/runner/main.cpp diff --git a/example/viewer/windows/runner/resource.h b/packages/pdfrx/example/viewer/windows/runner/resource.h similarity index 100% rename from example/viewer/windows/runner/resource.h rename to packages/pdfrx/example/viewer/windows/runner/resource.h diff --git a/example/viewer/windows/runner/resources/app_icon.ico b/packages/pdfrx/example/viewer/windows/runner/resources/app_icon.ico similarity index 100% rename from example/viewer/windows/runner/resources/app_icon.ico rename to packages/pdfrx/example/viewer/windows/runner/resources/app_icon.ico diff --git a/example/viewer/windows/runner/runner.exe.manifest b/packages/pdfrx/example/viewer/windows/runner/runner.exe.manifest similarity index 100% rename from example/viewer/windows/runner/runner.exe.manifest rename to packages/pdfrx/example/viewer/windows/runner/runner.exe.manifest diff --git a/example/viewer/windows/runner/utils.cpp b/packages/pdfrx/example/viewer/windows/runner/utils.cpp similarity index 100% rename from example/viewer/windows/runner/utils.cpp rename to packages/pdfrx/example/viewer/windows/runner/utils.cpp diff --git a/example/viewer/windows/runner/utils.h b/packages/pdfrx/example/viewer/windows/runner/utils.h similarity index 100% rename from example/viewer/windows/runner/utils.h rename to packages/pdfrx/example/viewer/windows/runner/utils.h diff --git a/example/viewer/windows/runner/win32_window.cpp b/packages/pdfrx/example/viewer/windows/runner/win32_window.cpp similarity index 100% rename from example/viewer/windows/runner/win32_window.cpp rename to packages/pdfrx/example/viewer/windows/runner/win32_window.cpp diff --git a/example/viewer/windows/runner/win32_window.h b/packages/pdfrx/example/viewer/windows/runner/win32_window.h similarity index 100% rename from example/viewer/windows/runner/win32_window.h rename to packages/pdfrx/example/viewer/windows/runner/win32_window.h diff --git a/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart similarity index 81% rename from lib/pdfrx.dart rename to packages/pdfrx/lib/pdfrx.dart index dfaf56ba..28fe15ac 100644 --- a/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -1,6 +1,5 @@ -export 'src/pdf_api.dart'; export 'src/pdf_document_ref.dart'; -export 'src/pdfrx_flutter_initialize.dart'; +export 'src/pdfrx_flutter.dart'; export 'src/widgets/pdf_page_text_overlay.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; diff --git a/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart similarity index 99% rename from lib/src/pdf_document_ref.dart rename to packages/pdfrx/lib/src/pdf_document_ref.dart index cec05536..00f01f22 100644 --- a/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -1,10 +1,9 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:synchronized/extension.dart'; -import '../pdfrx.dart'; - /// Callback function to notify download progress. /// /// [downloadedBytes] is the number of bytes downloaded so far. diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart new file mode 100644 index 00000000..43c116c9 --- /dev/null +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -0,0 +1,102 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +import 'utils/platform.dart'; + +bool _isInitialized = false; + +/// Explicitly initializes the Pdfrx library for Flutter. +/// +/// This function actually sets up the following functions: +/// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. +/// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. +void pdfrxFlutterInitialize() { + if (_isInitialized) return; + + if (pdfDocumentFactoryOverride != null) { + PdfDocumentFactory.instance = pdfDocumentFactoryOverride!; + } + + Pdfrx.loadAsset ??= (name) async { + final asset = await rootBundle.load(name); + return asset.buffer.asUint8List(); + }; + Pdfrx.getCacheDirectory ??= () async { + final dir = await getTemporaryDirectory(); + return dir.path; + }; + + _isInitialized = true; +} + +extension PdfPageExt on PdfPage { + /// PDF page size in points (size in pixels at 72 dpi) (rotated). + Size get size => Size(width, height); +} + +extension PdfImageExt on PdfImage { + /// Create [Image] from the rendered image. + Future createImage() { + final comp = Completer(); + decodeImageFromPixels(pixels, width, height, PixelFormat.bgra8888, (image) => comp.complete(image)); + return comp.future; + } +} + +extension PdfRectExt on PdfRect { + /// Convert to [Rect] in Flutter coordinate. + /// [page] is the page to convert the rectangle. + /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. + /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. + Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final rotated = rotate(rotation ?? page.rotation.index, page); + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + return Rect.fromLTRB( + rotated.left * scale, + (page.height - rotated.top) * scale, + rotated.right * scale, + (page.height - rotated.bottom) * scale, + ); + } + + /// Convert to [Rect] in Flutter coordinate using [pageRect] as the page's bounding rectangle. + Rect toRectInPageRect({required PdfPage page, required Rect pageRect}) => + toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); +} + +extension RectPdfRectExt on Rect { + /// Convert to [PdfRect] in PDF page coordinates. + PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + return PdfRect( + left / scale, + page.height - top / scale, + right / scale, + page.height - bottom / scale, + ).rotateReverse(rotation ?? page.rotation.index, page); + } +} + +extension PdfPointExt on PdfPoint { + /// Convert to [Offset] in Flutter coordinate. + /// [page] is the page to convert the rectangle. + /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. + /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. + Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final rotated = rotate(rotation ?? page.rotation.index, page); + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + return Offset(rotated.x * scale, (page.height - rotated.y) * scale); + } +} + +extension OffsetPdfPointExt on Offset { + /// Convert to [PdfPoint] in PDF page coordinates. + PdfPoint toPdfPoint({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; + return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); + } +} diff --git a/lib/src/utils/double_extensions.dart b/packages/pdfrx/lib/src/utils/double_extensions.dart similarity index 100% rename from lib/src/utils/double_extensions.dart rename to packages/pdfrx/lib/src/utils/double_extensions.dart diff --git a/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart similarity index 65% rename from lib/src/utils/native/native.dart rename to packages/pdfrx/lib/src/utils/native/native.dart index 5780cf08..05107018 100644 --- a/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; final isApple = Platform.isMacOS || Platform.isIOS; final isWindows = Platform.isWindows; @@ -8,3 +9,6 @@ final isWindows = Platform.isWindows; /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => isApple ? HardwareKeyboard.instance.isMetaPressed : HardwareKeyboard.instance.isControlPressed; + +/// Override for the [PdfDocumentFactory] for native platforms; it is null. +final PdfDocumentFactory? pdfDocumentFactoryOverride = null; diff --git a/lib/src/utils/platform.dart b/packages/pdfrx/lib/src/utils/platform.dart similarity index 100% rename from lib/src/utils/platform.dart rename to packages/pdfrx/lib/src/utils/platform.dart diff --git a/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart similarity index 51% rename from lib/src/utils/web/web.dart rename to packages/pdfrx/lib/src/utils/web/web.dart index 5696510c..502cbec1 100644 --- a/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,7 +1,13 @@ import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +import '../../wasm/pdfrx_wasm.dart'; final isApple = false; final isWindows = false; /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isControlPressed; + +/// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. +final PdfDocumentFactory? pdfDocumentFactoryOverride = PdfDocumentFactoryWasmImpl(); diff --git a/lib/src/web/js_utils.dart b/packages/pdfrx/lib/src/wasm/js_utils.dart similarity index 100% rename from lib/src/web/js_utils.dart rename to packages/pdfrx/lib/src/wasm/js_utils.dart diff --git a/lib/src/web/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart similarity index 91% rename from lib/src/web/pdfrx_wasm.dart rename to packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 84798de3..d91fb9cd 100644 --- a/lib/src/web/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -1,15 +1,13 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:typed_data'; -import 'dart:ui' as ui; import 'dart:ui_web' as ui_web; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:web/web.dart' as web; -import '../pdf_api.dart'; - /// The PDFium WASM communicator object @JS('PdfiumWasmCommunicator') extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { @@ -31,8 +29,8 @@ extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { external _PdfiumWasmCommunicator get _pdfiumWasmCommunicator; /// A handle to a registered callback that can be unregistered -class PdfiumWasmCallback { - PdfiumWasmCallback.register(JSFunction callback) +class _PdfiumWasmCallback { + _PdfiumWasmCallback.register(JSFunction callback) : id = _pdfiumWasmCommunicator._registerCallback(callback), _communicator = _pdfiumWasmCommunicator; @@ -44,6 +42,11 @@ class PdfiumWasmCallback { } } +Future> _sendCommand(String command, {Map? parameters}) async { + final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; + return (result.dartify()) as Map; +} + /// The URL of the PDFium WASM worker script; pdfium_client.js tries to load worker script from this URL.' /// /// [PdfDocumentFactory._init] will initializes its value. @@ -51,11 +54,7 @@ class PdfiumWasmCallback { external String pdfiumWasmWorkerUrl; /// [PdfDocumentFactory] for PDFium WASM implementation. -class PdfDocumentFactory { - PdfDocumentFactory._(); - - static final instance = PdfDocumentFactory._(); - +class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { /// Default path to the WASM modules /// /// Normally, the WASM modules are provided by pdfrx_wasm package and this is the path to its assets. @@ -91,7 +90,7 @@ class PdfDocumentFactory { } // Send init command to worker with authentication options - await sendCommand( + await _sendCommand( 'init', parameters: { if (Pdfrx.pdfiumWasmHeaders != null) 'headers': Pdfrx.pdfiumWasmHeaders, @@ -131,11 +130,7 @@ class PdfDocumentFactory { return Uri.parse(baseUrl ?? baseHref ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); } - Future> sendCommand(String command, {Map? parameters}) async { - final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; - return (result.dartify()) as Map; - } - + @override Future openAsset( String name, { PdfPasswordProvider? passwordProvider, @@ -156,6 +151,7 @@ class PdfDocumentFactory { ); } + @override Future openCustom({ required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, @@ -169,6 +165,7 @@ class PdfDocumentFactory { throw UnimplementedError('PdfDocumentFactoryWasmImpl.openCustom is not implemented.'); } + @override Future openData( Uint8List data, { PdfPasswordProvider? passwordProvider, @@ -178,34 +175,34 @@ class PdfDocumentFactory { bool allowDataOwnershipTransfer = false, void Function()? onDispose, }) => _openByFunc( - (password) => sendCommand( + (password) => _sendCommand( 'loadDocumentFromData', parameters: {'data': data, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), sourceName: sourceName ?? 'data', - factory: this, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: onDispose, ); + @override Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, }) => _openByFunc( - (password) => sendCommand( + (password) => _sendCommand( 'loadDocumentFromUrl', parameters: {'url': filePath, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), sourceName: filePath, - factory: this, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: null, ); + @override Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -216,19 +213,19 @@ class PdfDocumentFactory { Map? headers, bool withCredentials = false, }) async { - PdfiumWasmCallback? progressCallbackReg; + _PdfiumWasmCallback? progressCallbackReg; void cleanupCallbacks() => progressCallbackReg?.unregister(); try { if (progressCallback != null) { await _init(); - progressCallbackReg = PdfiumWasmCallback.register( + progressCallbackReg = _PdfiumWasmCallback.register( ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, ); } return _openByFunc( - (password) => sendCommand( + (password) => _sendCommand( 'loadDocumentFromUrl', parameters: { 'url': uri.toString(), @@ -241,7 +238,6 @@ class PdfDocumentFactory { }, ), sourceName: uri.toString(), - factory: this, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: cleanupCallbacks, @@ -255,7 +251,6 @@ class PdfDocumentFactory { Future _openByFunc( Future> Function(String? password) openDocument, { required String sourceName, - required PdfDocumentFactory factory, required PdfPasswordProvider? passwordProvider, required bool firstAttemptByEmptyPassword, required void Function()? onDispose, @@ -284,19 +279,18 @@ class PdfDocumentFactory { throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); } - return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose, factory: factory); + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose); } } } class _PdfDocumentWasm extends PdfDocument { - _PdfDocumentWasm._(this.document, {required super.sourceName, required this.factory, this.disposeCallback}) + _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) : permissions = parsePermissions(document) { pages = parsePages(this, document['pages'] as List); } final Map document; - final PdfDocumentFactory factory; final void Function()? disposeCallback; bool isDisposed = false; final subject = BehaviorSubject(); @@ -315,7 +309,7 @@ class _PdfDocumentWasm extends PdfDocument { if (!isDisposed) { isDisposed = true; subject.close(); - await factory.sendCommand('closeDocument', parameters: document); + await _sendCommand('closeDocument', parameters: document); disposeCallback?.call(); } } @@ -327,7 +321,7 @@ class _PdfDocumentWasm extends PdfDocument { @override Future> loadOutline() async { - final result = await factory.sendCommand('loadOutline', parameters: {'docHandle': document['docHandle']}); + final result = await _sendCommand('loadOutline', parameters: {'docHandle': document['docHandle']}); final outlineList = result['outline'] as List; return outlineList.map((node) => _nodeFromMap(node)).toList(); } @@ -351,7 +345,7 @@ class _PdfDocumentWasm extends PdfDocument { if (firstPageIndex < 0) return; // All pages are already loaded for (; firstPageIndex < pages.length;) { - final result = await factory.sendCommand( + final result = await _sendCommand( 'loadPagesProgressively', parameters: { 'docHandle': document['docHandle'], @@ -434,7 +428,7 @@ class _PdfPageWasm extends PdfPage { @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { - final result = await document.factory.sendCommand( + final result = await _sendCommand( 'loadLinks', parameters: { 'docHandle': document.document['docHandle'], @@ -465,7 +459,7 @@ class _PdfPageWasm extends PdfPage { @override Future loadText() async { - final result = await document.factory.sendCommand( + final result = await _sendCommand( 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); @@ -512,7 +506,7 @@ class _PdfPageWasm extends PdfPage { int? height, double? fullWidth, double? fullHeight, - ui.Color? backgroundColor, + int? backgroundColor, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -521,9 +515,9 @@ class _PdfPageWasm extends PdfPage { fullHeight ??= this.height; width ??= fullWidth.toInt(); height ??= fullHeight.toInt(); - backgroundColor ??= const ui.Color(0xffffffff); // white background + backgroundColor ??= 0xffffffff; // white background - final result = await document.factory.sendCommand( + final result = await _sendCommand( 'renderPage', parameters: { 'docHandle': document.document['docHandle'], @@ -534,7 +528,7 @@ class _PdfPageWasm extends PdfPage { 'height': height, 'fullWidth': fullWidth, 'fullHeight': fullHeight, - 'backgroundColor': backgroundColor.toARGB32(), + 'backgroundColor': backgroundColor, 'annotationRenderingMode': annotationRenderingMode.index, 'flags': flags, 'formHandle': document.document['formHandle'], @@ -542,12 +536,12 @@ class _PdfPageWasm extends PdfPage { ); final bb = result['imageData'] as ByteBuffer; final pixels = Uint8List.view(bb.asByteData().buffer, 0, bb.lengthInBytes); - return PdfImageWeb(width: width, height: height, pixels: pixels, format: ui.PixelFormat.bgra8888); + return PdfImageWeb(width: width, height: height, pixels: pixels); } } class PdfImageWeb extends PdfImage { - PdfImageWeb({required this.width, required this.height, required this.pixels, this.format = ui.PixelFormat.rgba8888}); + PdfImageWeb({required this.width, required this.height, required this.pixels}); @override final int width; @@ -556,8 +550,6 @@ class PdfImageWeb extends PdfImage { @override final Uint8List pixels; @override - final ui.PixelFormat format; - @override void dispose() {} } diff --git a/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart similarity index 100% rename from lib/src/widgets/interactive_viewer.dart rename to packages/pdfrx/lib/src/widgets/interactive_viewer.dart diff --git a/lib/src/widgets/pdf_error_widget.dart b/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart similarity index 98% rename from lib/src/widgets/pdf_error_widget.dart rename to packages/pdfrx/lib/src/widgets/pdf_error_widget.dart index 11844ba9..b1565e6d 100644 --- a/lib/src/widgets/pdf_error_widget.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import '../../pdfrx.dart'; import '../utils/platform.dart'; /// Show error widget when pdf viewer failed to load pdf. diff --git a/lib/src/widgets/pdf_page_links_overlay.dart b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart similarity index 97% rename from lib/src/widgets/pdf_page_links_overlay.dart rename to packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart index 0f207bf0..2614f89f 100644 --- a/lib/src/widgets/pdf_page_links_overlay.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/lib/src/widgets/pdf_page_text_overlay.dart b/packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart similarity index 99% rename from lib/src/widgets/pdf_page_text_overlay.dart rename to packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart index c782651e..277813b2 100644 --- a/lib/src/widgets/pdf_page_text_overlay.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; import '../utils/double_extensions.dart'; diff --git a/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart similarity index 99% rename from lib/src/widgets/pdf_text_searcher.dart rename to packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index cee964d7..4bb04edc 100644 --- a/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart similarity index 99% rename from lib/src/widgets/pdf_viewer.dart rename to packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 5bda46d4..8ce698d5 100644 --- a/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:vector_math/vector_math_64.dart' as vec; @@ -1078,7 +1079,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final img = await page.render( fullWidth: width, fullHeight: height, - backgroundColor: Colors.white, + backgroundColor: 0xffffffff, annotationRenderingMode: widget.params.annotationRenderingMode, flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, @@ -1140,7 +1141,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix height: (inPageRect.height * scale).toInt(), fullWidth: pageRect.width * scale, fullHeight: pageRect.height * scale, - backgroundColor: Colors.white, + backgroundColor: 0xffffffff, annotationRenderingMode: widget.params.annotationRenderingMode, flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, diff --git a/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart similarity index 99% rename from lib/src/widgets/pdf_viewer_params.dart rename to packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 9585deab..ee58bd61 100644 --- a/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -2,6 +2,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart similarity index 100% rename from lib/src/widgets/pdf_viewer_scroll_thumb.dart rename to packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart diff --git a/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart similarity index 99% rename from lib/src/widgets/pdf_widgets.dart rename to packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 0db691d5..ba279e18 100644 --- a/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/linux/CMakeLists.txt b/packages/pdfrx/linux/CMakeLists.txt similarity index 100% rename from linux/CMakeLists.txt rename to packages/pdfrx/linux/CMakeLists.txt diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml new file mode 100644 index 00000000..2cf702de --- /dev/null +++ b/packages/pdfrx/pubspec.yaml @@ -0,0 +1,57 @@ +name: pdfrx +description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. +version: 1.3.2 +homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx +issue_tracker: https://github.com/espresso3389/pdfrx/issues +screenshots: + - description: 'Viewer example on Web' + path: screenshot.jpg + +environment: + sdk: '>=3.7.0 <4.0.0' + flutter: ">=3.29.0" + +dependencies: + pdfrx_engine: + collection: + crypto: ^3.0.6 + ffi: + flutter: + sdk: flutter + http: + path: + path_provider: ^2.1.5 + rxdart: + synchronized: ^3.3.1 + url_launcher: ^6.3.1 + vector_math: ^2.1.4 + web: ^1.1.1 + dart_pubspec_licenses: ^3.0.4 + +dev_dependencies: + ffigen: ^19.0.0 + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + archive: ^4.0.7 + +flutter: + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + sharedDarwinSource: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + sharedDarwinSource: true + windows: + ffiPlugin: true + web: + + assets: + - assets/ diff --git a/packages/pdfrx/pubspec_overrides.yaml b/packages/pdfrx/pubspec_overrides.yaml new file mode 100644 index 00000000..1a6ef295 --- /dev/null +++ b/packages/pdfrx/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: pdfrx_engine +dependency_overrides: + pdfrx_engine: + path: ../pdfrx_engine diff --git a/screenshot.jpg b/packages/pdfrx/screenshot.jpg similarity index 100% rename from screenshot.jpg rename to packages/pdfrx/screenshot.jpg diff --git a/src/CMakeLists.txt b/packages/pdfrx/src/CMakeLists.txt similarity index 100% rename from src/CMakeLists.txt rename to packages/pdfrx/src/CMakeLists.txt diff --git a/src/pdfium_interop.cpp b/packages/pdfrx/src/pdfium_interop.cpp similarity index 100% rename from src/pdfium_interop.cpp rename to packages/pdfrx/src/pdfium_interop.cpp diff --git a/test/pdf_viewer_test.dart b/packages/pdfrx/test/pdf_viewer_test.dart similarity index 91% rename from test/pdf_viewer_test.dart rename to packages/pdfrx/test/pdf_viewer_test.dart index 2a5050b4..a360d78b 100644 --- a/test/pdf_viewer_test.dart +++ b/packages/pdfrx/test/pdf_viewer_test.dart @@ -5,8 +5,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import 'setup.dart'; +import '../../pdfrx_engine/test/setup.dart'; final testPdfFile = File('example/viewer/assets/hello.pdf'); final binding = TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/windows/.gitignore b/packages/pdfrx/windows/.gitignore similarity index 100% rename from windows/.gitignore rename to packages/pdfrx/windows/.gitignore diff --git a/windows/CMakeLists.txt b/packages/pdfrx/windows/CMakeLists.txt similarity index 100% rename from windows/CMakeLists.txt rename to packages/pdfrx/windows/CMakeLists.txt diff --git a/packages/pdfrx_engine/.gitignore b/packages/pdfrx_engine/.gitignore new file mode 100644 index 00000000..f76622cf --- /dev/null +++ b/packages/pdfrx_engine/.gitignore @@ -0,0 +1,11 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock +pubspec_overrides.yaml + +/build/ +/test/.tmp/ diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md new file mode 100644 index 00000000..effe43c8 --- /dev/null +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/pdfrx_engine/CODE_OF_CONDUCT.md b/packages/pdfrx_engine/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7a7ac839 --- /dev/null +++ b/packages/pdfrx_engine/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at espresso3389@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see + diff --git a/packages/pdfrx_engine/LICENSE b/packages/pdfrx_engine/LICENSE new file mode 100644 index 00000000..575416cb --- /dev/null +++ b/packages/pdfrx_engine/LICENSE @@ -0,0 +1,11 @@ + +The MIT License (MIT) +=============== + +Copyright (c) 2018 @espresso3389 (Takashi Kawasaki) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md new file mode 100644 index 00000000..1c8bba4d --- /dev/null +++ b/packages/pdfrx_engine/README.md @@ -0,0 +1,41 @@ +# pdfrx_engine + +[![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) + +[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a PDF engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/) and is used by the [pdfrx](https://pub.dartlang.org/packages/pdfrx) plugin. The package supports Android, iOS, Windows, macOS, Linux, and Web. + +## Multi-platform support + +- Android +- iOS +- Windows +- macOS +- Linux (even on Raspberry Pi) +- Web (WASM) + +## Example Code + +The following fragment illustrates how to use the PDF engine to load and render a PDF file: + +```dart +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +void main() async { + final document = await PdfDocument.openFile('test.pdf'); + final page = document.pages[0]; + final image = await page.render( + width: page.width * 200 / 72, + height: page.height * 200 / 72, + ); + image.dispose(); + document.close(); +} + +``` + +## PDF API + +- Easy to use PDF APIs + - [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) +- PDFium bindings + - Not encouraged but you can import [`package:pdfrx/src/pdfium/pdfium_bindings.dart`](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart) diff --git a/packages/pdfrx_engine/analysis_options.yaml b/packages/pdfrx_engine/analysis_options.yaml new file mode 100644 index 00000000..dee8927a --- /dev/null +++ b/packages/pdfrx_engine/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart new file mode 100644 index 00000000..8f38a963 --- /dev/null +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -0,0 +1,3 @@ +library; + +export 'src/pdf_api.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart new file mode 100644 index 00000000..48b8cfce --- /dev/null +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -0,0 +1,70 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import '../../pdfrx_engine.dart'; + +/// This is an empty implementation of [PdfDocumentFactory] that just throws [UnimplementedError]. +/// +/// This is used to indicate that the factory is not initialized. +class PdfDocumentFactoryImpl implements PdfDocumentFactory { + PdfDocumentFactoryImpl(); + + Future unimplemented() { + throw UnimplementedError( + 'PdfDocumentFactory.instance is not initialized. ' + 'Please call pdfrxFlutterInitialize() or explicitly set PdfDocumentFactory.instance.', + ); + } + + @override + Future openAsset( + String name, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) => unimplemented(); + + @override + Future openCustom({ + required FutureOr Function(Uint8List buffer, int position, int size) + read, + required int fileSize, + required String sourceName, + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + int? maxSizeToCacheOnMemory, + void Function()? onDispose, + }) => unimplemented(); + + @override + Future openData( + Uint8List data, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + String? sourceName, + bool allowDataOwnershipTransfer = false, + bool useProgressiveLoading = false, + void Function()? onDispose, + }) => unimplemented(); + + @override + Future openFile( + String filePath, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) => unimplemented(); + + @override + Future openUri( + Uri uri, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + PdfDownloadProgressCallback? progressCallback, + bool preferRangeAccess = false, + Map? headers, + bool withCredentials = false, + }) => unimplemented(); +} diff --git a/lib/src/pdfium/http_cache_control.dart b/packages/pdfrx_engine/lib/src/native/http_cache_control.dart similarity index 100% rename from lib/src/pdfium/http_cache_control.dart rename to packages/pdfrx_engine/lib/src/native/http_cache_control.dart diff --git a/lib/src/pdfium/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart similarity index 86% rename from lib/src/pdfium/pdf_file_cache.dart rename to packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index edfb4611..43fba9ab 100644 --- a/lib/src/pdfium/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -9,7 +9,7 @@ import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:synchronized/extension.dart'; -import '../../pdfrx.dart'; +import '../pdf_api.dart'; import 'http_cache_control.dart'; final _rafFinalizer = Finalizer((raf) { @@ -76,7 +76,9 @@ class PdfFileCache { Future close() async { final raf = _raf; if (raf != null) { - _rafFinalizer.detach(this); // Detach from finalizer since we are closing explicitly + _rafFinalizer.detach( + this, + ); // Detach from finalizer since we are closing explicitly _raf = null; await raf.close(); } @@ -90,7 +92,12 @@ class PdfFileCache { } } - Future _read(List buffer, int bufferPosition, int position, int size) async { + Future _read( + List buffer, + int bufferPosition, + int position, + int size, + ) async { await _ensureFileOpen(); await _raf!.setPosition(position); await _raf!.readInto(buffer, bufferPosition, bufferPosition + size); @@ -107,10 +114,15 @@ class PdfFileCache { return await _raf!.length(); } - Future read(List buffer, int bufferPosition, int position, int size) => - _read(buffer, bufferPosition, _headerSize! + position, size); + Future read( + List buffer, + int bufferPosition, + int position, + int size, + ) => _read(buffer, bufferPosition, _headerSize! + position, size); - Future write(int position, List bytes) => _write(_headerSize! + position, bytes); + Future write(int position, List bytes) => + _write(_headerSize! + position, bytes); bool isCached(int block) => _cacheState[block >> 3] & (1 << (block & 7)) != 0; @@ -158,7 +170,9 @@ class PdfFileCache { if (dataStrSize != 0) { final dataStrBytes = Uint8List(dataStrSize); await _read(dataStrBytes, 0, header1Size, dataStrBytes.length); - _cacheControlState = HttpCacheControlState.parseDataStr(utf8.decode(dataStrBytes)); + _cacheControlState = HttpCacheControlState.parseDataStr( + utf8.decode(dataStrBytes), + ); } else { _cacheControlState = HttpCacheControlState.empty; } @@ -199,7 +213,10 @@ class PdfFileCache { return true; } - Future initializeWithFileSize(int fileSize, {required bool truncateExistingContent}) async { + Future initializeWithFileSize( + int fileSize, { + required bool truncateExistingContent, + }) async { if (truncateExistingContent) { await invalidateCache(); } @@ -233,18 +250,25 @@ class PdfFileCache { await _write(header1Size, dataStrEncoded); } - Future setCacheControlState(HttpCacheControlState cacheControlState) async { + Future setCacheControlState( + HttpCacheControlState cacheControlState, + ) async { _cacheControlState = cacheControlState; await _saveCacheControlState(); } static Future getCacheFilePathForUri(Uri uri) async { if (Pdfrx.getCacheDirectory == null) { - throw StateError('Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.'); + throw StateError( + 'Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.', + ); } final cacheDir = await Pdfrx.getCacheDirectory!(); - final fnHash = - sha1.convert(utf8.encode(uri.toString())).bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); + final fnHash = sha1 + .convert(utf8.encode(uri.toString())) + .bytes + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(); final dir1 = fnHash.substring(0, 2); final dir2 = fnHash.substring(2, 4); final body = fnHash.substring(4); @@ -287,7 +311,9 @@ Future pdfDocumentFromUri( }) async { progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); - final httpClientWrapper = _HttpClientWrapper(Pdfrx.createHttpClient ?? () => http.Client()); + final httpClientWrapper = _HttpClientWrapper( + Pdfrx.createHttpClient ?? () => http.Client(), + ); try { if (!cache.isInitialized) { @@ -310,7 +336,8 @@ Future pdfDocumentFromUri( } } else { // Check if the file is fresh (no-need-to-reload). - if (cache.cacheControlState.cacheControl.mustRevalidate && cache.cacheControlState.isFresh(now: DateTime.now())) { + if (cache.cacheControlState.cacheControl.mustRevalidate && + cache.cacheControlState.isFresh(now: DateTime.now())) { // cache is valid; no need to download. } else { final result = await _downloadBlock( @@ -347,7 +374,14 @@ Future pdfDocumentFromUri( final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { - await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers); + await _downloadBlock( + httpClientWrapper, + uri, + cache, + progressCallback, + blockId, + headers: headers, + ); } final readEnd = min(p + size, (blockId + 1) * cache.blockSize); final sizeToRead = readEnd - p; @@ -410,12 +444,15 @@ Future<_DownloadResult> _downloadBlock( final request = http.Request('GET', uri) ..headers.addAll({ if (useRangeAccess) 'Range': 'bytes=$blockOffset-${end - 1}', - if (addCacheControlHeaders) ...cache.cacheControlState.getHeadersForFetch(), + if (addCacheControlHeaders) + ...cache.cacheControlState.getHeadersForFetch(), if (headers != null) ...headers, }); late final http.StreamedResponse response; try { - response = await httpClientWrapper.client.send(request).timeout(Duration(seconds: 5)); + response = await httpClientWrapper.client + .send(request) + .timeout(Duration(seconds: 5)); } on TimeoutException { httpClientWrapper.reset(); rethrow; @@ -428,7 +465,9 @@ Future<_DownloadResult> _downloadBlock( } if (response.statusCode != 200 && response.statusCode != 206) { - throw PdfException('Failed to download PDF file: ${response.statusCode} ${response.reasonPhrase}'); + throw PdfException( + 'Failed to download PDF file: ${response.statusCode} ${response.reasonPhrase}', + ); } if (addCacheControlHeaders) { @@ -446,9 +485,14 @@ Future<_DownloadResult> _downloadBlock( isFullDownload = true; } if (!cache.isInitialized) { - await cache.initializeWithFileSize(fileSize ?? 0, truncateExistingContent: true); + await cache.initializeWithFileSize( + fileSize ?? 0, + truncateExistingContent: true, + ); } - await cache.setCacheControlState(HttpCacheControlState.fromHeaders(response.headers)); + await cache.setCacheControlState( + HttpCacheControlState.fromHeaders(response.headers), + ); var offset = blockOffset; var cachedBytesSoFar = cache.cachedBytes; @@ -461,7 +505,10 @@ Future<_DownloadResult> _downloadBlock( if (isFullDownload) { fileSize ??= cachedBytesSoFar; - await cache.initializeWithFileSize(fileSize, truncateExistingContent: false); + await cache.initializeWithFileSize( + fileSize, + truncateExistingContent: false, + ); await cache.setCached(0, lastBlock: cache.totalBlocks - 1); } else { await cache.setCached(blockId, lastBlock: blockId + blockCount - 1); diff --git a/lib/src/pdfium/pdfium_bindings.dart b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart similarity index 100% rename from lib/src/pdfium/pdfium_bindings.dart rename to packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart diff --git a/lib/src/pdfium/pdfium_interop.dart b/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart similarity index 100% rename from lib/src/pdfium/pdfium_interop.dart rename to packages/pdfrx_engine/lib/src/native/pdfium_interop.dart diff --git a/lib/src/pdfium/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart similarity index 70% rename from lib/src/pdfium/pdfrx_pdfium.dart rename to packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 6560382b..97d5276e 100644 --- a/lib/src/pdfium/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -4,7 +4,6 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:ui' as ui; import 'package:ffi/ffi.dart'; import 'package:rxdart/rxdart.dart'; @@ -45,11 +44,15 @@ bool _initialized = false; void _init() { if (_initialized) return; using((arena) { - final config = arena.allocate(sizeOf()); + final config = arena.allocate( + sizeOf(), + ); config.ref.version = 2; if (Pdfrx.fontPaths.isNotEmpty) { - final fontPathArray = arena.allocate>(sizeOf>() * (Pdfrx.fontPaths.length + 1)); + final fontPathArray = arena.allocate>( + sizeOf>() * (Pdfrx.fontPaths.length + 1), + ); for (int i = 0; i < Pdfrx.fontPaths.length; i++) { fontPathArray[i] = Pdfrx.fontPaths[i].toUtf8(arena); } @@ -68,11 +71,10 @@ void _init() { final backgroundWorker = BackgroundWorker.create(); -class PdfDocumentFactory { - PdfDocumentFactory._(); - - static final instance = PdfDocumentFactory._(); +class PdfDocumentFactoryImpl implements PdfDocumentFactory { + PdfDocumentFactoryImpl(); + @override Future openAsset( String name, { PdfPasswordProvider? passwordProvider, @@ -80,7 +82,9 @@ class PdfDocumentFactory { bool useProgressiveLoading = false, }) async { if (Pdfrx.loadAsset == null) { - throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); + throw StateError( + 'Pdfrx.loadAsset is not set. Please set it to load assets.', + ); } final asset = await Pdfrx.loadAsset!(name); return await _openData( @@ -94,6 +98,7 @@ class PdfDocumentFactory { ); } + @override Future openData( Uint8List data, { PdfPasswordProvider? passwordProvider, @@ -112,6 +117,7 @@ class PdfDocumentFactory { onDispose: onDispose, ); + @override Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, @@ -120,10 +126,14 @@ class PdfDocumentFactory { }) { _init(); return _openByFunc( - (password) async => (await backgroundWorker).computeWithArena((arena, params) { - final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); - return doc.address; - }, (filePath: filePath, password: password)), + (password) async => + (await backgroundWorker).computeWithArena((arena, params) { + final doc = pdfium.FPDF_LoadDocument( + params.filePath.toUtf8(arena), + params.password?.toUtf8(arena) ?? nullptr, + ); + return doc.address; + }, (filePath: filePath, password: password)), sourceName: filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -161,8 +171,10 @@ class PdfDocumentFactory { ); } + @override Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, + required FutureOr Function(Uint8List buffer, int position, int size) + read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -182,12 +194,11 @@ class PdfDocumentFactory { await read(buffer.asTypedList(fileSize), 0, fileSize); return _openByFunc( (password) async => (await backgroundWorker).computeWithArena( - (arena, params) => - pdfium.FPDF_LoadMemDocument( - Pointer.fromAddress(params.buffer), - params.fileSize, - params.password?.toUtf8(arena) ?? nullptr, - ).address, + (arena, params) => pdfium.FPDF_LoadMemDocument( + Pointer.fromAddress(params.buffer), + params.fileSize, + params.password?.toUtf8(arena) ?? nullptr, + ).address, (buffer: buffer.address, fileSize: fileSize, password: password), ), sourceName: sourceName, @@ -213,11 +224,12 @@ class PdfDocumentFactory { try { return _openByFunc( (password) async => (await backgroundWorker).computeWithArena( - (arena, params) => - pdfium.FPDF_LoadCustomDocument( - Pointer.fromAddress(params.fileAccess), - params.password?.toUtf8(arena) ?? nullptr, - ).address, + (arena, params) => pdfium.FPDF_LoadCustomDocument( + Pointer.fromAddress( + params.fileAccess, + ), + params.password?.toUtf8(arena) ?? nullptr, + ).address, (fileAccess: fa.fileAccess.address, password: password), ), sourceName: sourceName, @@ -238,6 +250,7 @@ class PdfDocumentFactory { } } + @override Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -282,7 +295,9 @@ class PdfDocumentFactory { } else { password = await passwordProvider?.call(); if (password == null) { - throw const PdfPasswordException('No password supplied by PasswordProvider.'); + throw const PdfPasswordException( + 'No password supplied by PasswordProvider.', + ); } } final doc = await openPdfDocument(password); @@ -297,13 +312,16 @@ class PdfDocumentFactory { if (_isPasswordError()) { continue; } - throw PdfException('Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).'); + throw PdfException( + 'Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).', + ); } } } extension _FpdfUtf8StringExt on String { - Pointer toUtf8(Arena arena) => Pointer.fromAddress(toNativeUtf8(allocator: arena).address); + Pointer toUtf8(Arena arena) => + Pointer.fromAddress(toNativeUtf8(allocator: arena).address); } class _PdfDocumentPdfium extends PdfDocument { @@ -351,9 +369,12 @@ class _PdfDocumentPdfium extends PdfDocument { pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; try { final permissions = pdfium.FPDF_GetDocPermissions(doc); - final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); + final securityHandlerRevision = + pdfium.FPDF_GetSecurityHandlerRevision(doc); - formInfo = calloc.allocate(sizeOf()); + formInfo = calloc.allocate( + sizeOf(), + ); formInfo.ref.version = 1; formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); return ( @@ -374,12 +395,15 @@ class _PdfDocumentPdfium extends PdfDocument { doc, sourceName: sourceName, securityHandlerRevision: result.securityHandlerRevision, - permissions: - result.securityHandlerRevision != -1 - ? PdfPermissions(result.permissions, result.securityHandlerRevision) - : null, - formHandle: pdfium_bindings.FPDF_FORMHANDLE.fromAddress(result.formHandle), - formInfo: Pointer.fromAddress(result.formInfo), + permissions: result.securityHandlerRevision != -1 + ? PdfPermissions(result.permissions, result.securityHandlerRevision) + : null, + formHandle: pdfium_bindings.FPDF_FORMHANDLE.fromAddress( + result.formHandle, + ), + formInfo: Pointer.fromAddress( + result.formInfo, + ), disposeCallback: disposeCallback, ); final pages = await pdfDoc._loadPagesInLimitedTime( @@ -415,21 +439,32 @@ class _PdfDocumentPdfium extends PdfDocument { if (isDisposed) return; _pages = List.unmodifiable(loaded.pages); - subject.add(PdfDocumentPageStatusChangedEvent(this, _pages.sublist(firstUnloadedPageIndex))); + subject.add( + PdfDocumentPageStatusChangedEvent( + this, + _pages.sublist(firstUnloadedPageIndex), + ), + ); if (onPageLoadProgress != null) { - final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); + final result = await onPageLoadProgress( + loaded.pageCountLoadedTotal, + loaded.pages.length, + data, + ); if (result == false) { // If the callback returns false, stop loading pages return; } } - if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) return; + if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) + return; } } /// Loads pages in the document in a time-limited manner. - Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ + Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> + _loadPagesInLimitedTime({ List<_PdfPagePdfium> pagesLoadedSoFar = const [], int? maxPageCountToLoadAdditionally, Duration? timeout, @@ -437,14 +472,21 @@ class _PdfDocumentPdfium extends PdfDocument { try { final results = await (await backgroundWorker).compute( (params) { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.docAddress, + ); return using((arena) { final pageCount = pdfium.FPDF_GetPageCount(doc); - final end = - maxPageCountToLoadAdditionally == null - ? pageCount - : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); - final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; + final end = maxPageCountToLoadAdditionally == null + ? pageCount + : min( + pageCount, + params.pagesCountLoadedSoFar + + params.maxPageCountToLoadAdditionally!, + ); + final t = params.timeoutUs != null + ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) + : null; final pages = <({double width, double height, int rotation})>[]; for (int i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); @@ -522,31 +564,50 @@ class _PdfDocumentPdfium extends PdfDocument { if (!isDisposed) { isDisposed = true; subject.close(); - await (await backgroundWorker).compute((params) { - final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle); - final formInfo = Pointer.fromAddress(params.formInfo); - pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); - calloc.free(formInfo); + await (await backgroundWorker).compute( + (params) { + final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress( + params.formHandle, + ); + final formInfo = + Pointer.fromAddress( + params.formInfo, + ); + pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); + calloc.free(formInfo); - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - pdfium.FPDF_CloseDocument(doc); - }, (formHandle: formHandle.address, formInfo: formInfo.address, document: document.address)); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.document, + ); + pdfium.FPDF_CloseDocument(doc); + }, + ( + formHandle: formHandle.address, + formInfo: formInfo.address, + document: document.address, + ), + ); disposeCallback?.call(); } } @override - Future> loadOutline() async => - isDisposed - ? [] - : await (await backgroundWorker).compute( - (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); - }), - (document: document.address), - ); + Future> loadOutline() async => isDisposed + ? [] + : await (await backgroundWorker).compute( + (params) => using((arena) { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.document, + ); + return _getOutlineNodeSiblings( + pdfium.FPDFBookmark_GetFirstChild(document, nullptr), + document, + arena, + ); + }), + (document: document.address), + ); static List _getOutlineNodeSiblings( pdfium_bindings.FPDF_BOOKMARK bookmark, @@ -561,8 +622,16 @@ class _PdfDocumentPdfium extends PdfDocument { siblings.add( PdfOutlineNode( title: titleBuf.cast().toDartString(), - dest: _pdfDestFromDest(pdfium.FPDFBookmark_GetDest(document, bookmark), document, arena), - children: _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, bookmark), document, arena), + dest: _pdfDestFromDest( + pdfium.FPDFBookmark_GetDest(document, bookmark), + document, + arena, + ), + children: _getOutlineNodeSiblings( + pdfium.FPDFBookmark_GetFirstChild(document, bookmark), + document, + arena, + ), ), ); bookmark = pdfium.FPDFBookmark_GetNextSibling(document, bookmark); @@ -604,12 +673,14 @@ class _PdfPagePdfium extends PdfPage { int? height, double? fullWidth, double? fullHeight, - ui.Color? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int? backgroundColor, + PdfAnnotationRenderingMode annotationRenderingMode = + PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { - if (cancellationToken != null && cancellationToken is! PdfPageRenderCancellationTokenPdfium) { + if (cancellationToken != null && + cancellationToken is! PdfPageRenderCancellationTokenPdfium) { throw ArgumentError( 'cancellationToken must be created by PdfPage.createCancellationToken().', 'cancellationToken', @@ -621,7 +692,7 @@ class _PdfPagePdfium extends PdfPage { fullHeight ??= this.height; width ??= fullWidth.toInt(); height ??= fullHeight.toInt(); - backgroundColor ??= const ui.Color(0xffffffff); // white background + backgroundColor ??= 0xffffffff; // white background const rgbaSize = 4; Pointer buffer = nullptr; try { @@ -643,16 +714,29 @@ class _PdfPagePdfium extends PdfPage { params.width * rgbaSize, ); if (bmp == nullptr) { - throw PdfException('FPDFBitmap_CreateEx(${params.width}, ${params.height}) failed.'); + throw PdfException( + 'FPDFBitmap_CreateEx(${params.width}, ${params.height}) failed.', + ); } pdfium_bindings.FPDF_PAGE page = nullptr; try { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.document, + ); page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); if (page == nullptr) { - throw PdfException('FPDF_LoadPage(${params.pageNumber}) failed.'); + throw PdfException( + 'FPDF_LoadPage(${params.pageNumber}) failed.', + ); } - pdfium.FPDFBitmap_FillRect(bmp, 0, 0, params.width, params.height, params.backgroundColor); + pdfium.FPDFBitmap_FillRect( + bmp, + 0, + 0, + params.width, + params.height, + params.backgroundColor!, + ); pdfium.FPDF_RenderPageBitmap( bmp, @@ -663,15 +747,19 @@ class _PdfPagePdfium extends PdfPage { params.fullHeight, 0, params.flags | - (params.annotationRenderingMode != PdfAnnotationRenderingMode.none + (params.annotationRenderingMode != + PdfAnnotationRenderingMode.none ? pdfium_bindings.FPDF_ANNOT : 0), ); if (params.formHandle != 0 && - params.annotationRenderingMode == PdfAnnotationRenderingMode.annotationAndForms) { + params.annotationRenderingMode == + PdfAnnotationRenderingMode.annotationAndForms) { pdfium.FPDF_FFLDraw( - pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle), + pdfium_bindings.FPDF_FORMHANDLE.fromAddress( + params.formHandle, + ), bmp, page, -params.x, @@ -698,7 +786,7 @@ class _PdfPagePdfium extends PdfPage { height: height!, fullWidth: fullWidth!.toInt(), fullHeight: fullHeight!.toInt(), - backgroundColor: backgroundColor!.toARGB32(), + backgroundColor: backgroundColor, annotationRenderingMode: annotationRenderingMode, flags: flags, formHandle: document.formHandle.address, @@ -714,7 +802,11 @@ class _PdfPagePdfium extends PdfPage { final resultBuffer = buffer; buffer = nullptr; - return _PdfImagePdfium._(width: width, height: height, buffer: resultBuffer); + return _PdfImagePdfium._( + width: width, + height: height, + buffer: resultBuffer, + ); } catch (e) { return null; } finally { @@ -724,13 +816,17 @@ class _PdfPagePdfium extends PdfPage { } @override - PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this); + PdfPageRenderCancellationTokenPdfium createCancellationToken() => + PdfPageRenderCancellationTokenPdfium(this); @override Future loadText() => _PdfPageTextPdfium._loadText(this); @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { + Future> loadLinks({ + bool compact = false, + bool enableAutoLinkDetection = true, + }) async { final links = await _loadAnnotLinks(); if (enableAutoLinkDetection) { links.addAll(await _loadWebLinks()); @@ -743,26 +839,31 @@ class _PdfPagePdfium extends PdfPage { return List.unmodifiable(links); } - Future> _loadWebLinks() async => - document.isDisposed - ? [] - : await (await backgroundWorker).compute((params) { - pdfium_bindings.FPDF_PAGE page = nullptr; - pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; - pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; - try { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); - textPage = pdfium.FPDFText_LoadPage(page); - if (textPage == nullptr) return []; - linkPage = pdfium.FPDFLink_LoadWebLinks(textPage); - if (linkPage == nullptr) return []; - - final doubleSize = sizeOf(); - return using((arena) { - final rectBuffer = arena.allocate(4 * doubleSize); - return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), (index) { - final rects = List.generate(pdfium.FPDFLink_CountRects(linkPage, index), (rectIndex) { + Future> _loadWebLinks() async => document.isDisposed + ? [] + : await (await backgroundWorker).compute((params) { + pdfium_bindings.FPDF_PAGE page = nullptr; + pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; + pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; + try { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.document, + ); + page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); + textPage = pdfium.FPDFText_LoadPage(page); + if (textPage == nullptr) return []; + linkPage = pdfium.FPDFLink_LoadWebLinks(textPage); + if (linkPage == nullptr) return []; + + final doubleSize = sizeOf(); + return using((arena) { + final rectBuffer = arena.allocate(4 * doubleSize); + return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), ( + index, + ) { + final rects = List.generate( + pdfium.FPDFLink_CountRects(linkPage, index), + (rectIndex) { pdfium.FPDFLink_GetRect( linkPage, index, @@ -773,63 +874,80 @@ class _PdfPagePdfium extends PdfPage { rectBuffer.offset(doubleSize * 3), ); return _rectFromLTRBBuffer(rectBuffer); - }); - return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); - }); + }, + ); + return PdfLink( + rects, + url: Uri.tryParse(_getLinkUrl(linkPage, index, arena)), + ); }); - } finally { - pdfium.FPDFLink_CloseWebLinks(linkPage); - pdfium.FPDFText_ClosePage(textPage); - pdfium.FPDF_ClosePage(page); - } - }, (document: document.document.address, pageNumber: pageNumber)); + }); + } finally { + pdfium.FPDFLink_CloseWebLinks(linkPage); + pdfium.FPDFText_ClosePage(textPage); + pdfium.FPDF_ClosePage(page); + } + }, (document: document.document.address, pageNumber: pageNumber)); - static String _getLinkUrl(pdfium_bindings.FPDF_PAGELINK linkPage, int linkIndex, Arena arena) { + static String _getLinkUrl( + pdfium_bindings.FPDF_PAGELINK linkPage, + int linkIndex, + Arena arena, + ) { final urlLength = pdfium.FPDFLink_GetURL(linkPage, linkIndex, nullptr, 0); - final urlBuffer = arena.allocate(urlLength * sizeOf()); + final urlBuffer = arena.allocate( + urlLength * sizeOf(), + ); pdfium.FPDFLink_GetURL(linkPage, linkIndex, urlBuffer, urlLength); return urlBuffer.cast().toDartString(); } - Future> _loadAnnotLinks() async => - document.isDisposed - ? [] - : await (await backgroundWorker).compute( - (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); - try { - final count = pdfium.FPDFPage_GetAnnotCount(page); - final rectf = arena.allocate(sizeOf()); - final links = []; - for (int i = 0; i < count; i++) { - final annot = pdfium.FPDFPage_GetAnnot(page, i); - pdfium.FPDFAnnot_GetRect(annot, rectf); - final r = rectf.ref; - final rect = PdfRect( - r.left, - r.top > r.bottom ? r.top : r.bottom, - r.right, - r.top > r.bottom ? r.bottom : r.top, + Future> _loadAnnotLinks() async => document.isDisposed + ? [] + : await (await backgroundWorker).compute( + (params) => using((arena) { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( + params.document, + ); + final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); + try { + final count = pdfium.FPDFPage_GetAnnotCount(page); + final rectf = arena.allocate( + sizeOf(), + ); + final links = []; + for (int i = 0; i < count; i++) { + final annot = pdfium.FPDFPage_GetAnnot(page, i); + pdfium.FPDFAnnot_GetRect(annot, rectf); + final r = rectf.ref; + final rect = PdfRect( + r.left, + r.top > r.bottom ? r.top : r.bottom, + r.right, + r.top > r.bottom ? r.bottom : r.top, + ); + final dest = _processAnnotDest(annot, document, arena); + if (dest != nullptr) { + links.add( + PdfLink([ + rect, + ], dest: _pdfDestFromDest(dest, document, arena)), ); - final dest = _processAnnotDest(annot, document, arena); - if (dest != nullptr) { - links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena))); - } else { - final uri = _processAnnotLink(annot, document, arena); - if (uri != null) { - links.add(PdfLink([rect], url: uri)); - } + } else { + final uri = _processAnnotLink(annot, document, arena); + if (uri != null) { + links.add(PdfLink([rect], url: uri)); } - pdfium.FPDFPage_CloseAnnot(annot); } - return links; - } finally { - pdfium.FPDF_ClosePage(page); + pdfium.FPDFPage_CloseAnnot(annot); } - }), - (document: document.document.address, pageNumber: pageNumber), - ); + return links; + } finally { + pdfium.FPDF_ClosePage(page); + } + }), + (document: document.document.address, pageNumber: pageNumber), + ); static pdfium_bindings.FPDF_DEST _processAnnotDest( pdfium_bindings.FPDF_ANNOTATION annot, @@ -864,7 +982,12 @@ class _PdfPagePdfium extends PdfPage { case pdfium_bindings.PDFACTION_URI: final size = pdfium.FPDFAction_GetURIPath(document, action, nullptr, 0); final buffer = arena.allocate(size); - pdfium.FPDFAction_GetURIPath(document, action, buffer.cast(), size); + pdfium.FPDFAction_GetURIPath( + document, + action, + buffer.cast(), + size, + ); try { final String newBuffer = buffer.toDartString(); return Uri.tryParse(newBuffer); @@ -877,7 +1000,8 @@ class _PdfPagePdfium extends PdfPage { } } -class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { +class PdfPageRenderCancellationTokenPdfium + extends PdfPageRenderCancellationToken { PdfPageRenderCancellationTokenPdfium(this.page); final PdfPage page; Pointer? _cancelFlag; @@ -910,13 +1034,15 @@ class _PdfImagePdfium extends PdfImage { @override final int height; @override - ui.PixelFormat get format => ui.PixelFormat.bgra8888; - @override Uint8List get pixels => _buffer.asTypedList(width * height * 4); final Pointer _buffer; - _PdfImagePdfium._({required this.width, required this.height, required Pointer buffer}) : _buffer = buffer; + _PdfImagePdfium._({ + required this.width, + required this.height, + required Pointer buffer, + }) : _buffer = buffer; @override void dispose() { @@ -925,7 +1051,13 @@ class _PdfImagePdfium extends PdfImage { } class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { - _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); + _PdfPageTextFragmentPdfium( + this.pageText, + this.index, + this.length, + this.bounds, + this.charRects, + ); final PdfPageText pageText; @@ -944,7 +1076,11 @@ class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { } class _PdfPageTextPdfium extends PdfPageText { - _PdfPageTextPdfium({required this.pageNumber, required this.fullText, required this.fragments}); + _PdfPageTextPdfium({ + required this.pageNumber, + required this.fullText, + required this.fragments, + }); @override final int pageNumber; @@ -956,32 +1092,61 @@ class _PdfPageTextPdfium extends PdfPageText { static Future<_PdfPageTextPdfium> _loadText(_PdfPagePdfium page) async { final result = await _load(page); - final pageText = _PdfPageTextPdfium(pageNumber: page.pageNumber, fullText: result.fullText, fragments: []); + final pageText = _PdfPageTextPdfium( + pageNumber: page.pageNumber, + fullText: result.fullText, + fragments: [], + ); int pos = 0; for (final fragment in result.fragments) { final charRects = result.charRects.sublist(pos, pos + fragment); - pageText.fragments.add(_PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects)); + pageText.fragments.add( + _PdfPageTextFragmentPdfium( + pageText, + pos, + fragment, + charRects.boundingRect(), + charRects, + ), + ); pos += fragment; } return pageText; } - static Future<({String fullText, List charRects, List fragments})> _load(_PdfPagePdfium page) async { + static Future< + ({String fullText, List charRects, List fragments}) + > + _load(_PdfPagePdfium page) async { if (page.document.isDisposed) { return (fullText: '', charRects: [], fragments: []); } return await (await backgroundWorker).compute( (params) => using((arena) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); - final pdfium_bindings.FPDF_PAGE page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); + final pdfium_bindings.FPDF_PAGE page = pdfium.FPDF_LoadPage( + doc, + params.pageNumber - 1, + ); final textPage = pdfium.FPDFText_LoadPage(page); try { final charCount = pdfium.FPDFText_CountChars(textPage); final charRects = []; final fragments = []; - final fullText = _loadInternal(textPage, 0, charCount, arena, charRects, fragments); - return (fullText: fullText, charRects: charRects, fragments: fragments); + final fullText = _loadInternal( + textPage, + 0, + charCount, + arena, + charRects, + fragments, + ); + return ( + fullText: fullText, + charRects: charRects, + fragments: fragments, + ); } finally { pdfium.FPDFText_ClosePage(textPage); pdfium.FPDF_ClosePage(page); @@ -1052,7 +1217,8 @@ class _PdfPageTextPdfium extends PdfPageText { if (sb.length > lineStart) { const columnHeightThreshold = 72.0; // 1 inch final prev = charRects.last; - if (prev.left > rect.left || prev.bottom + columnHeightThreshold < rect.bottom) { + if (prev.left > rect.left || + prev.bottom + columnHeightThreshold < rect.bottom) { if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { if (sb.length > wordStart) { fragments.add(sb.length - wordStart); @@ -1076,7 +1242,12 @@ class _PdfPageTextPdfium extends PdfPageText { } /// return true if any meaningful characters in the line (start -> end) - static bool _makeLineFlat(List rects, int start, int end, StringBuffer sb) { + static bool _makeLineFlat( + List rects, + int start, + int end, + StringBuffer sb, + ) { if (start >= end) return false; final str = sb.toString(); final bounds = rects.skip(start).take(end - start).boundingRect(); @@ -1086,17 +1257,32 @@ class _PdfPageTextPdfium extends PdfPageText { final char = str.codeUnitAt(i); if (char == _charSpace) { final next = i + 1 < end ? rects[i + 1].left : null; - rects[i] = PdfRect(prev ?? rect.left, bounds.top, next ?? rect.right, bounds.bottom); + rects[i] = PdfRect( + prev ?? rect.left, + bounds.top, + next ?? rect.right, + bounds.bottom, + ); prev = null; } else { - rects[i] = PdfRect(prev ?? rect.left, bounds.top, rect.right, bounds.bottom); + rects[i] = PdfRect( + prev ?? rect.left, + bounds.top, + rect.right, + bounds.bottom, + ); prev = rect.right; } } return true; } - static String _getText(pdfium_bindings.FPDF_TEXTPAGE textPage, int from, int length, Arena arena) { + static String _getText( + pdfium_bindings.FPDF_TEXTPAGE textPage, + int from, + int length, + Arena arena, + ) { // Since FPDFText_GetText could not handle '\0' in the middle of the text, // we'd better use FPDFText_GetUnicode to obtain the text here. final count = pdfium.FPDFText_CountChars(textPage); @@ -1108,10 +1294,12 @@ class _PdfPageTextPdfium extends PdfPageText { } } -PdfRect _rectFromLTRBBuffer(Pointer buffer) => PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); +PdfRect _rectFromLTRBBuffer(Pointer buffer) => + PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); extension _PointerExt on Pointer { - Pointer offset(int offsetInBytes) => Pointer.fromAddress(address + offsetInBytes); + Pointer offset(int offsetInBytes) => + Pointer.fromAddress(address + offsetInBytes); } extension _PdfRectsExt on List { @@ -1123,14 +1311,24 @@ extension _PdfRectsExt on List { } } -PdfDest? _pdfDestFromDest(pdfium_bindings.FPDF_DEST dest, pdfium_bindings.FPDF_DOCUMENT document, Arena arena) { +PdfDest? _pdfDestFromDest( + pdfium_bindings.FPDF_DEST dest, + pdfium_bindings.FPDF_DOCUMENT document, + Arena arena, +) { if (dest == nullptr) return null; final pul = arena.allocate(sizeOf()); - final values = arena.allocate(sizeOf() * 4); + final values = arena.allocate( + sizeOf() * 4, + ); final pageIndex = pdfium.FPDFDest_GetDestPageIndex(document, dest); final type = pdfium.FPDFDest_GetView(dest, pul, values); if (type != 0) { - return PdfDest(pageIndex + 1, PdfDestCommand.values[type], List.generate(pul.value, (index) => values[index])); + return PdfDest( + pageIndex + 1, + PdfDestCommand.values[type], + List.generate(pul.value, (index) => values[index]), + ); } return null; } diff --git a/lib/src/pdfium/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart similarity index 81% rename from lib/src/pdfium/worker.dart rename to packages/pdfrx_engine/lib/src/native/worker.dart index da782b33..c69173d0 100644 --- a/lib/src/pdfium/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -4,7 +4,7 @@ import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import '../../pdfrx.dart'; +import '../pdf_api.dart'; typedef PdfrxComputeCallback = FutureOr Function(M message); @@ -17,8 +17,15 @@ class BackgroundWorker { static Future create({String? debugName}) async { final receivePort = ReceivePort(); - await Isolate.spawn(_workerEntry, receivePort.sendPort, debugName: debugName); - final worker = BackgroundWorker._(receivePort, await receivePort.first as SendPort); + await Isolate.spawn( + _workerEntry, + receivePort.sendPort, + debugName: debugName, + ); + final worker = BackgroundWorker._( + receivePort, + await receivePort.first as SendPort, + ); // propagate the pdfium module path to the worker worker.compute((params) { @@ -43,7 +50,10 @@ class BackgroundWorker { }); } - Future compute(PdfrxComputeCallback callback, M message) async { + Future compute( + PdfrxComputeCallback callback, + M message, + ) async { if (_isDisposed) { throw StateError('Worker is already disposed'); } @@ -53,7 +63,10 @@ class BackgroundWorker { } /// [compute] wrapper that also provides [Arena] for temporary memory allocation. - Future computeWithArena(R Function(Arena arena, M message) callback, M message) => + Future computeWithArena( + R Function(Arena arena, M message) callback, + M message, + ) => compute((message) => using((arena) => callback(arena, message)), message); void dispose() { diff --git a/lib/src/pdf_api.dart b/packages/pdfrx_engine/lib/src/pdf_api.dart similarity index 87% rename from lib/src/pdf_api.dart rename to packages/pdfrx_engine/lib/src/pdf_api.dart index b6b8978a..ebe98420 100644 --- a/lib/src/pdf_api.dart +++ b/packages/pdfrx_engine/lib/src/pdf_api.dart @@ -2,16 +2,12 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:ui' as ui; -import 'dart:ui'; import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; -// The trick to support Flutter Web is to use conditional import -// Both of the files define PdfDocumentFactoryImpl class but only one of them is imported. -import '../pdfrx.dart'; -import 'web/pdfrx_wasm.dart' if (dart.library.io) 'pdfium/pdfrx_pdfium.dart'; +import './mock/pdfrx_mock.dart' + if (dart.library.io) './native/pdfrx_pdfium.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. @@ -62,14 +58,66 @@ class Pdfrx { /// /// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. /// For Dart only, you can set this function to obtain the cache directory from your own file system. - static Future Function()? getCacheDirectory; + static FutureOr Function()? getCacheDirectory; +} + +abstract class PdfDocumentFactory { + static PdfDocumentFactory instance = PdfDocumentFactoryImpl(); + + Future openAsset( + String name, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }); + + Future openData( + Uint8List data, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + String? sourceName, + bool allowDataOwnershipTransfer = false, // only for Web + bool useProgressiveLoading = false, + void Function()? onDispose, + }); + + Future openFile( + String filePath, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }); + + Future openCustom({ + required FutureOr Function(Uint8List buffer, int position, int size) + read, + required int fileSize, + required String sourceName, + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + int? maxSizeToCacheOnMemory, + void Function()? onDispose, + }); + + Future openUri( + Uri uri, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + PdfDownloadProgressCallback? progressCallback, + bool preferRangeAccess = false, + Map? headers, + bool withCredentials = false, + }); } /// 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]); +typedef PdfDownloadProgressCallback = + void Function(int downloadedBytes, [int? totalBytes]); /// Function to provide password for encrypted PDF. /// @@ -208,7 +256,8 @@ abstract class PdfDocument { /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. static Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, + required FutureOr Function(Uint8List buffer, int position, int size) + read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -296,7 +345,8 @@ abstract class PdfDocument { bool isIdenticalDocumentHandle(Object? other); } -typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); +typedef PdfPageLoadingCallback = + FutureOr Function(int currentPageNumber, int totalPageCount, T? data); /// PDF document event types. enum PdfDocumentEventType { pageStatusChanged } @@ -340,9 +390,6 @@ abstract class PdfPage { /// PDF page height in points (height in pixels at 72 dpi) (rotated). double get height; - /// PDF page size in points (size in pixels at 72 dpi) (rotated). - Size get size => Size(width, height); - /// PDF page rotation. PdfPageRotation get rotation; @@ -357,9 +404,9 @@ abstract class PdfPage { /// [x], [y], [width], [height] specify sub-area to render in pixels. /// [fullWidth], [fullHeight] specify virtual full size of the page to render in pixels. /// - If [x], [y] are not specified, (0,0) is used. - /// - If [width], [height] is not specified, [fullWidth], [fullHeight] is used. + /// - If [width], [height] are not specified, [fullWidth], [fullHeight] are used. /// - If [fullWidth], [fullHeight] are not specified, [PdfPage.width] and [PdfPage.height] are used (it means rendered at 72-dpi). - /// [backgroundColor] is used to fill the background of the page. If no color is specified, [Colors.white] is used. + /// [backgroundColor] is `AARRGGBB` integer color notation used to fill the background of the page. If no color is specified, 0xffffffff (white) is used. /// - [annotationRenderingMode] controls to render annotations or not. The default is [PdfAnnotationRenderingMode.annotationAndForms]. /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderingFlags.none]. /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. @@ -386,8 +433,9 @@ abstract class PdfPage { int? height, double? fullWidth, double? fullHeight, - Color? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int? backgroundColor, + PdfAnnotationRenderingMode annotationRenderingMode = + PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }); @@ -406,7 +454,10 @@ abstract class PdfPage { /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. /// The default is true. - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); + Future> loadLinks({ + bool compact = false, + bool enableAutoLinkDetection = true, + }); } /// Page rotation. @@ -482,6 +533,8 @@ class PdfPermissions { bool get allowsModifyAnnotations => (permissions & 32) != 0; } +enum PdfImageFormat { rgba8888, bgra8888 } + /// Image rendered from PDF page. /// /// See [PdfPage.render]. @@ -492,21 +545,11 @@ abstract class PdfImage { /// Number of pixels in vertical direction. int get height; - /// Pixel format in either [ui.PixelFormat.rgba8888] or [ui.PixelFormat.bgra8888]. - ui.PixelFormat get format; - - /// Raw pixel data. The actual format is platform dependent. + /// BGRA8888 Raw pixel data. Uint8List get pixels; /// Dispose the image. void dispose(); - - /// Create [ui.Image] from the rendered image. - Future createImage() { - final comp = Completer(); - ui.decodeImageFromPixels(pixels, width, height, format, (image) => comp.complete(image)); - return comp.future; - } } /// Handles text extraction from PDF page. @@ -534,7 +577,10 @@ abstract class PdfPageText { if (textIndex == fullText.length) { return fragments.length; // the end of the text } - final index = fragments.lowerBound(_PdfPageTextFragmentForSearch(textIndex), (a, b) => a.index - b.index); + final index = fragments.lowerBound( + _PdfPageTextFragmentForSearch(textIndex), + (a, b) => a.index - b.index, + ); if (index > fragments.length) { return -1; // range error } @@ -557,7 +603,10 @@ abstract class PdfPageText { /// /// Just work like [Pattern.allMatches] but it returns stream of [PdfTextRangeWithFragments]. /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { + Stream allMatches( + Pattern pattern, { + bool caseInsensitive = true, + }) async* { final String text; if (pattern is RegExp) { caseInsensitive = pattern.isCaseSensitive; @@ -571,7 +620,11 @@ abstract class PdfPageText { final matches = pattern.allMatches(text); for (final match in matches) { if (match.start == match.end) continue; - final m = PdfTextRangeWithFragments.fromTextRange(this, match.start, match.end); + final m = PdfTextRangeWithFragments.fromTextRange( + this, + match.start, + match.end, + ); if (m != null) { yield m; } @@ -640,12 +693,23 @@ abstract class PdfPageTextFragment { int get hashCode => index.hashCode ^ bounds.hashCode ^ text.hashCode; /// Create a [PdfPageTextFragment]. - static PdfPageTextFragment fromParams(int index, int length, PdfRect bounds, String text, List charRects) => - _PdfPageTextFragment(index, length, bounds, text, charRects); + static PdfPageTextFragment fromParams( + int index, + int length, + PdfRect bounds, + String text, + List charRects, + ) => _PdfPageTextFragment(index, length, bounds, text, charRects); } class _PdfPageTextFragment extends PdfPageTextFragment { - _PdfPageTextFragment(this.index, this.length, this.bounds, this.text, this.charRects); + _PdfPageTextFragment( + this.index, + this.length, + this.bounds, + this.text, + this.charRects, + ); @override final int index; @@ -687,7 +751,8 @@ class PdfTextRange { /// Text end index in [PdfPageText.fullText]. final int end; - PdfTextRange copyWith({int? start, int? end}) => PdfTextRange(start: start ?? this.start, end: end ?? this.end); + PdfTextRange copyWith({int? start, int? end}) => + PdfTextRange(start: start ?? this.start, end: end ?? this.end); @override int get hashCode => start ^ end.hashCode; @@ -747,10 +812,13 @@ class PdfTextRanges { int get pageNumber => pageText.pageNumber; /// Bounds of the text ranges. - PdfRect get bounds => ranges.map((r) => r.toTextRangeWithFragments(pageText)!.bounds).boundingRect(); + PdfRect get bounds => ranges + .map((r) => r.toTextRangeWithFragments(pageText)!.bounds) + .boundingRect(); /// The composed text of the text ranges. - String get text => ranges.map((r) => pageText.fullText.substring(r.start, r.end)).join(); + String get text => + ranges.map((r) => pageText.fullText.substring(r.start, r.end)).join(); } /// For backward compatibility; [PdfTextRangeWithFragments] is previously named [PdfTextMatch]. @@ -758,7 +826,13 @@ typedef PdfTextMatch = PdfTextRangeWithFragments; /// Text range (start/end index) in PDF page and it's associated text and bounding rectangle. class PdfTextRangeWithFragments { - PdfTextRangeWithFragments(this.pageNumber, this.fragments, this.start, this.end, this.bounds); + PdfTextRangeWithFragments( + this.pageNumber, + this.fragments, + this.start, + this.end, + this.bounds, + ); /// Page number of the page. final int pageNumber; @@ -806,14 +880,22 @@ class PdfTextRangeWithFragments { /// Enumerate all the character bounding rectangles for the text range. /// /// The function is useful when you implement text selection algorithm or such. - Iterable enumerateRectsForRange({int? start, int? end, double? widthForEmpty}) sync* { + Iterable enumerateRectsForRange({ + int? start, + int? end, + double? widthForEmpty, + }) sync* { start ??= fragments.first.index + this.start; end ??= fragments.last.index + this.end; for (final f in fragments) { if (f.end <= start || end <= f.index) continue; final s = max(start - f.index, 0); final e = min(end - f.index, f.length); - yield f.getBoundsForRange(start: s, end: e, widthForEmpty: widthForEmpty)!; + yield f.getBoundsForRange( + start: s, + end: e, + widthForEmpty: widthForEmpty, + )!; } } @@ -833,7 +915,11 @@ class PdfTextRangeWithFragments { /// ``` /// /// To paint text highlights on PDF pages, see [PdfViewerParams.pagePaintCallbacks] and [PdfViewerPagePaintCallback]. - static PdfTextRangeWithFragments? fromTextRange(PdfPageText pageText, int start, [int? end]) { + static PdfTextRangeWithFragments? fromTextRange( + PdfPageText pageText, + int start, [ + int? end, + ]) { end ??= pageText.fullText.length; if (start >= end) { return null; @@ -885,7 +971,9 @@ class PdfTextRangeWithFragments { pageText.pageNumber, pageText.fragments.sublist(s, containLastFragment ? l + 1 : l), start - sf.index, - containLastFragment ? end - lf.index : end - pageText.fragments[l - 1].index, + containLastFragment + ? end - lf.index + : end - pageText.fragments[l - 1].index, bounds, ); } @@ -964,10 +1052,14 @@ class PdfRect { /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). bool containsXy(double x, double y, {double margin = 0}) => - x >= left - margin && x <= right + margin && y >= bottom - margin && y <= top + margin; + x >= left - margin && + x <= right + margin && + y >= bottom - margin && + y <= top + margin; /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); + bool containsPoint(PdfPoint offset, {double margin = 0}) => + containsXy(offset.x, offset.y, margin: margin); /// Determine whether the rectangle overlaps the specified rectangle (in the PDF page coordinates). bool overlaps(PdfRect other) { @@ -980,25 +1072,6 @@ class PdfRect { /// Empty rectangle. static const empty = PdfRect(0, 0, 0, 0); - /// Convert to [Rect] in Flutter coordinate. - /// [page] is the page to convert the rectangle. - /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. - /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. - Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; - return Rect.fromLTRB( - rotated.left * scale, - (page.height - rotated.top) * scale, - rotated.right * scale, - (page.height - rotated.bottom) * scale, - ); - } - - /// Convert to [Rect] in Flutter coordinate using [pageRect] as the page's bounding rectangle. - Rect toRectInPageRect({required PdfPage page, required Rect pageRect}) => - toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); - /// Rotate the rectangle. PdfRect rotate(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; @@ -1010,7 +1083,12 @@ class PdfRect { case 1: return PdfRect(bottom, width - left, top, width - right); case 2: - return PdfRect(width - right, height - bottom, width - left, height - top); + return PdfRect( + width - right, + height - bottom, + width - left, + height - top, + ); case 3: return PdfRect(height - top, right, height - bottom, left); default: @@ -1029,7 +1107,12 @@ class PdfRect { case 1: return PdfRect(width - top, right, width - bottom, left); case 2: - return PdfRect(width - right, height - bottom, width - left, height - top); + return PdfRect( + width - right, + height - bottom, + width - left, + height - top, + ); case 3: return PdfRect(bottom, height - left, top, height - right); default: @@ -1037,17 +1120,23 @@ class PdfRect { } } - PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); + PdfRect inflate(double dx, double dy) => + PdfRect(left - dx, top + dy, right + dx, bottom - dy); @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is PdfRect && other.left == left && other.top == top && other.right == right && other.bottom == bottom; + return other is PdfRect && + other.left == left && + other.top == top && + other.right == right && + other.bottom == bottom; } @override - int get hashCode => left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; + int get hashCode => + left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; @override String toString() { @@ -1055,19 +1144,6 @@ class PdfRect { } } -extension RectPdfRectExt on Rect { - /// Convert to [PdfRect] in PDF page coordinates. - PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; - return PdfRect( - left / scale, - page.height - top / scale, - right / scale, - page.height - bottom / scale, - ).rotateReverse(rotation ?? page.rotation.index, page); - } -} - /// Extension methods for List of [PdfRect]. extension PdfRectsExt on Iterable { /// Merge all rectangles to calculate bounding rectangle. @@ -1112,14 +1188,17 @@ class PdfDest { final List? params; @override - String toString() => 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; + String toString() => + 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; /// Compact the destination. /// /// The method is used to compact the destination to reduce memory usage. /// [params] is typically growable and also modifiable. The method ensures that [params] is unmodifiable. PdfDest compact() { - return params == null ? this : PdfDest(pageNumber, command, List.unmodifiable(params!)); + return params == null + ? this + : PdfDest(pageNumber, command, List.unmodifiable(params!)); } } @@ -1143,7 +1222,10 @@ enum PdfDestCommand { /// Parse the command name to [PdfDestCommand]. factory PdfDestCommand.parse(String name) { final nameLow = name.toLowerCase(); - return PdfDestCommand.values.firstWhere((e) => e.name == nameLow, orElse: () => PdfDestCommand.unknown); + return PdfDestCommand.values.firstWhere( + (e) => e.name == nameLow, + orElse: () => PdfDestCommand.unknown, + ); } } @@ -1184,7 +1266,11 @@ class PdfLink { /// /// See [PdfDocument.loadOutline]. class PdfOutlineNode { - const PdfOutlineNode({required this.title, required this.dest, required this.children}); + const PdfOutlineNode({ + required this.title, + required this.dest, + required this.children, + }); /// Outline node title. final String title; @@ -1239,16 +1325,6 @@ class PdfPoint { return dx * dx + dy * dy; } - /// Convert to [Offset] in Flutter coordinate. - /// [page] is the page to convert the rectangle. - /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. - /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. - Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; - return Offset(rotated.x * scale, (page.height - rotated.y) * scale); - } - /// Rotate the point. PdfPoint rotate(int rotation, PdfPage page) { final swap = (page.rotation.index & 1) == 1; @@ -1288,14 +1364,6 @@ class PdfPoint { } } -extension OffsetPdfPointExt on Offset { - /// Convert to [PdfPoint] in PDF page coordinates. - PdfPoint toPdfPoint({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final scale = scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; - return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); - } -} - /// Compares two lists for element-by-element equality. /// /// **NOTE: This function is copiedd from flutter's `foundation` library to remove dependency to Flutter** diff --git a/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart new file mode 100644 index 00000000..e38c5c91 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart @@ -0,0 +1,117 @@ +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:http/http.dart' as http; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +bool _isInitialized = false; + +/// The release of pdfium to download. +const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; + +/// Initializes the Pdfrx library for Dart. +/// +/// [tmpPath] is the path to the temporary directory for caching. +/// [pdfiumRelease] is the release of pdfium to download if not already present. +/// +/// The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. +Future pdfrxEngineDartInitialize({ + String? tmpPath, + String? pdfiumRelease = pdfrxCurrentPdfiumRelease, +}) async { + if (_isInitialized) return; + + Pdfrx.loadAsset ??= (name) async { + throw UnimplementedError( + 'By default, Pdfrx.loadAsset is not implemented for Dart.', + ); + }; + + final tmpDir = Directory.systemTemp; + Pdfrx.getCacheDirectory ??= () => tmpDir.path; + final pdfiumPath = Directory( + Platform.environment['PDFIUM_PATH'] ?? "${tmpDir.path}/pdfrx.cache/pdfium", + ); + Pdfrx.pdfiumModulePath ??= pdfiumPath.path; + + if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { + pdfiumPath.createSync(recursive: true); + Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath( + pdfiumPath.path, + ); + } + + _isInitialized = true; +} + +/// Downloads the pdfium module for the current platform and architecture. +/// +/// Currently, the following platforms are supported: +/// - Windows x64 +/// - Linux x64, arm64 +/// - macOS x64, arm64 +Future downloadAndGetPdfiumModulePath( + String tmpPath, { + String? pdfiumRelease = pdfrxCurrentPdfiumRelease, +}) async { + final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; + final platform = pa[1]!; + final arch = pa[2]!; + if (platform == 'windows' && arch == 'x64') { + return await _downloadPdfium( + tmpPath, + 'win', + arch, + 'bin/pdfium.dll', + pdfiumRelease, + ); + } + if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { + return await _downloadPdfium( + tmpPath, + platform, + arch, + 'lib/libpdfium.so', + pdfiumRelease, + ); + } + if (platform == 'macos') { + return await _downloadPdfium( + tmpPath, + 'mac', + arch, + 'lib/libpdfium.dylib', + pdfiumRelease, + ); + } else { + throw Exception('Unsupported platform: $platform-$arch'); + } +} + +/// Downloads the pdfium module for the given platform and architecture. +Future _downloadPdfium( + String tmpRoot, + String platform, + String arch, + String modulePath, + String? pdfiumRelease, +) async { + final tmpDir = Directory('$tmpRoot/$platform-$arch'); + final targetPath = '${tmpDir.path}/$modulePath'; + if (await File(targetPath).exists()) return targetPath; + + final uri = + 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; + final tgz = await http.Client().get(Uri.parse(uri)); + if (tgz.statusCode != 200) { + throw Exception('Failed to download pdfium: $uri'); + } + final archive = TarDecoder().decodeBytes( + GZipDecoder().decodeBytes(tgz.bodyBytes), + ); + try { + await tmpDir.delete(recursive: true); + } catch (_) {} + await extractArchiveToDisk(archive, tmpDir.path); + return targetPath; +} diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml new file mode 100644 index 00000000..dc8eaef1 --- /dev/null +++ b/packages/pdfrx_engine/pubspec.yaml @@ -0,0 +1,52 @@ +name: pdfrx_engine +description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. +version: 1.0.0 +homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx +issue_tracker: https://github.com/espresso3389/pdfrx/issues + +environment: + sdk: ^3.8.1 + +# Add regular dependencies here. +dependencies: + collection: + crypto: ^3.0.6 + ffi: + http: + path: + rxdart: + synchronized: ^3.3.1 + vector_math: ^2.1.4 + web: ^1.1.1 + archive: ^4.0.7 + +dev_dependencies: + ffigen: ^19.0.0 + lints: ^5.0.0 + test: ^1.24.0 + +# To generate the bindings, firstly you must build example/viewer on x64 linux and +# then run the following command: +# dart run ffigen +ffigen: + output: + bindings: "lib/src/native/pdfium_bindings.dart" + headers: + entry-points: + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdfview.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_annot.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_text.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_edit.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_formfill.h" + include-directives: + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/**" + preamble: | + // ignore_for_file: unused_field + // dart format off + name: "pdfium" + comments: + style: any + length: full + diff --git a/packages/pdfrx_engine/test/pdf_document_test.dart b/packages/pdfrx_engine/test/pdf_document_test.dart new file mode 100644 index 00000000..03d016dd --- /dev/null +++ b/packages/pdfrx_engine/test/pdf_document_test.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx_engine/src/pdfrx_engine_dart.dart'; +import 'package:test/test.dart'; + +import 'utils.dart'; + +final testPdfFile = File('../pdfrx/example/viewer/assets/hello.pdf'); + +void main() { + setUp(() => pdfrxEngineDartInitialize(tmpPath: tmpRoot.path)); + + test( + 'PdfDocument.openFile', + () async => + await testDocument(await PdfDocument.openFile(testPdfFile.path)), + ); + test('PdfDocument.openData', () async { + final data = await testPdfFile.readAsBytes(); + await testDocument(await PdfDocument.openData(data)); + }); + test('PdfDocument.openUri', () async { + Pdfrx.createHttpClient = () => MockClient( + (request) async => + http.Response.bytes(await testPdfFile.readAsBytes(), 200), + ); + await testDocument( + await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf')), + ); + }); +} diff --git a/test/utils.dart b/packages/pdfrx_engine/test/utils.dart similarity index 72% rename from test/utils.dart rename to packages/pdfrx_engine/test/utils.dart index a5ff2d17..3af1e21d 100644 --- a/test/utils.dart +++ b/packages/pdfrx_engine/test/utils.dart @@ -1,10 +1,7 @@ import 'dart:io'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pdfrx/pdfrx.dart'; - -/// The release of pdfium to download. -const pdfiumRelease = 'chromium%2F7202'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:test/test.dart'; /// Temporary directory for testing. final tmpRoot = Directory('${Directory.current.path}/test/.tmp'); @@ -28,9 +25,7 @@ Future testPage(PdfDocument doc, int pageNumber) async { expect(pageImage, isNotNull); expect(pageImage!.width, page.width.toInt(), reason: 'pageImage.width'); expect(pageImage.height, page.height.toInt(), reason: 'pageImage.height'); - final image = await pageImage.createImage(); - expect(image.width, page.width.toInt(), reason: 'image.width'); - expect(image.height, page.height.toInt(), reason: 'image.height'); - image.dispose(); + expect(pageImage.width, page.width.toInt(), reason: 'image.width'); + expect(pageImage.height, page.height.toInt(), reason: 'image.height'); pageImage.dispose(); } diff --git a/pubspec.yaml b/pubspec.yaml index 05f3ff81..c399324d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,81 +1,7 @@ -name: pdfrx -description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.2 -homepage: https://github.com/espresso3389/pdfrx -repository: https://github.com/espresso3389/pdfrx -issue_tracker: https://github.com/espresso3389/pdfrx/issues -screenshots: - - description: 'Viewer example on Web' - path: screenshot.jpg - +name: pdfrx_monorepo environment: - sdk: '>=3.7.0 <4.0.0' - flutter: ">=3.29.0" - -dependencies: - collection: - crypto: ^3.0.6 - ffi: - flutter: - sdk: flutter - http: - path: - path_provider: ^2.1.5 - rxdart: - synchronized: ^3.3.1 - url_launcher: ^6.3.1 - vector_math: ^2.1.4 - web: ^1.1.1 - dart_pubspec_licenses: ^3.0.4 + sdk: '>=3.0.0 <4.0.0' dev_dependencies: - ffigen: ^18.0.0 - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 - archive: ^4.0.2 - -flutter: - plugin: - platforms: - android: - ffiPlugin: true - ios: - ffiPlugin: true - sharedDarwinSource: true - linux: - ffiPlugin: true - macos: - ffiPlugin: true - sharedDarwinSource: true - windows: - ffiPlugin: true - web: - - assets: - - assets/ - -# To generate the bindings, firstly you must build example/viewer on x64 linux and -# then run the following command: -# dart run ffigen -ffigen: - output: - bindings: "lib/src/pdfium/pdfium_bindings.dart" - headers: - entry-points: - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdfview.h" - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_annot.h" - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_text.h" - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_doc.h" - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_edit.h" - - "example/viewer/build/linux/x64/release/.lib/latest/include/fpdf_formfill.h" - include-directives: - - "example/viewer/build/linux/x64/release/.lib/latest/include/**" - preamble: | - // ignore_for_file: unused_field - // dart format off - name: "pdfium" - comments: - style: any - length: full + melos: ^6.3.3 diff --git a/test/pdf_document_test.dart b/test/pdf_document_test.dart deleted file mode 100644 index 86b20a6c..00000000 --- a/test/pdf_document_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:pdfrx/pdfrx.dart'; - -import 'setup.dart'; -import 'utils.dart'; - -final testPdfFile = File('example/viewer/assets/hello.pdf'); - -void main() { - setUp(() => setup()); - - test('PdfDocument.openFile', () async => await testDocument(await PdfDocument.openFile(testPdfFile.path))); - test('PdfDocument.openData', () async { - final data = await testPdfFile.readAsBytes(); - await testDocument(await PdfDocument.openData(data)); - }); - test('PdfDocument.openUri', () async { - Pdfrx.createHttpClient = - () => MockClient((request) async => http.Response.bytes(await testPdfFile.readAsBytes(), 200)); - await testDocument(await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf'))); - }); -} diff --git a/test/setup.dart b/test/setup.dart deleted file mode 100644 index 9384c62c..00000000 --- a/test/setup.dart +++ /dev/null @@ -1,82 +0,0 @@ -// Tests can skip PDFium download by setting the `PDFIUM_PATH` environment -// variable to an existing module file. -import 'dart:io'; - -import 'package:archive/archive_io.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; -import 'package:pdfrx/pdfrx.dart'; - -import 'utils.dart'; - -final cacheRoot = Directory('${tmpRoot.path}/cache'); - -/// Sets up the test environment. -Future setup() async { - pdfrxFlutterInitialize(); - - final envPath = Platform.environment['PDFIUM_PATH']; - if (envPath != null && await File(envPath).exists()) { - Pdfrx.pdfiumModulePath = envPath; - } else { - Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath(); - } - - TestWidgetsFlutterBinding.ensureInitialized(); - - const channel = MethodChannel('plugins.flutter.io/path_provider'); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( - methodCall, - ) async { - return cacheRoot.path; - }); - try { - await cacheRoot.delete(recursive: true); - } catch (e) { - /**/ - } -} - -/// Downloads the pdfium module for the current platform and architecture. -/// -/// Currently, the following platforms are supported: -/// - Windows x64 -/// - Linux x64, arm64 -/// - macOS x64, arm64 -Future downloadAndGetPdfiumModulePath() async { - final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; - final platform = pa[1]!; - final arch = pa[2]!; - if (platform == 'windows' && arch == 'x64') { - return await _downloadPdfium('win', arch, 'bin/pdfium.dll'); - } - if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await _downloadPdfium(platform, arch, 'lib/libpdfium.so'); - } - if (platform == 'macos') { - return await _downloadPdfium('mac', arch, 'lib/libpdfium.dylib'); - } else { - throw Exception('Unsupported platform: $platform-$arch'); - } -} - -/// Downloads the pdfium module for the given platform and architecture. -Future _downloadPdfium(String platform, String arch, String modulePath) async { - final tmpDir = Directory('${tmpRoot.path}/$platform-$arch'); - final targetPath = '${tmpDir.path}/$modulePath'; - if (await File(targetPath).exists()) return targetPath; - - final uri = - 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; - final tgz = await http.Client().get(Uri.parse(uri)); - if (tgz.statusCode != 200) { - throw Exception('Failed to download pdfium: $uri'); - } - final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); - try { - await tmpDir.delete(recursive: true); - } catch (_) {} - await extractArchiveToDisk(archive, tmpDir.path); - return targetPath; -} From 86067d691b32dfd26a693326a569cdbb99ab8f37 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 03:25:51 +0900 Subject: [PATCH 182/663] chmod +x shell scripts --- packages/pdfrx/darwin/pdfium/build | 0 packages/pdfrx/darwin/pdfium/build-config.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packages/pdfrx/darwin/pdfium/build mode change 100644 => 100755 packages/pdfrx/darwin/pdfium/build-config.sh diff --git a/packages/pdfrx/darwin/pdfium/build b/packages/pdfrx/darwin/pdfium/build old mode 100644 new mode 100755 diff --git a/packages/pdfrx/darwin/pdfium/build-config.sh b/packages/pdfrx/darwin/pdfium/build-config.sh old mode 100644 new mode 100755 From 86b8b5c8a4d082e0bd6ed332ea631bf70b18d040 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 03:46:19 +0900 Subject: [PATCH 183/663] WIP --- CLAUDE.md | 141 ++++++++++++++---- README.md | 83 +++++++++++ packages/pdfrx/README.md | 28 +++- packages/pdfrx/test/pdf_viewer_test.dart | 10 +- packages/pdfrx_engine/README.md | 24 ++- packages/pdfrx_engine/lib/pdfrx_engine.dart | 1 + .../lib/src/native/pdfrx_pdfium.dart | 3 +- .../pdfrx_engine/test/pdf_document_test.dart | 1 - 8 files changed, 241 insertions(+), 50 deletions(-) create mode 100644 README.md diff --git a/CLAUDE.md b/CLAUDE.md index 0dd80f90..c873451a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,24 +4,59 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -pdfrx is a cross-platform PDF viewer plugin for Flutter that supports iOS, Android, Windows, macOS, Linux, and Web. It uses PDFium for native platforms and supports both PDF.js and PDFium WASM for web platforms. +pdfrx is a monorepo containing two packages: + +1. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium + - Pure Dart package with no Flutter dependencies + - Provides core PDF document API and PDFium bindings + - Can be used independently for non-Flutter Dart applications + +2. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter + - Depends on pdfrx_engine for PDF rendering functionality + - Provides Flutter widgets and UI components + - Supports iOS, Android, Windows, macOS, Linux, and Web + - Uses PDFium for native platforms and PDFium WASM for web platforms ## Development Commands +### Monorepo Management + +This project uses Melos for managing the multi-package repository: + +```bash +# Install melos globally (if not already installed) +dart pub global activate melos + +# Bootstrap the project (install dependencies for all packages) +melos bootstrap + +# Run analysis on all packages +melos analyze +``` + ### Basic Flutter Commands ```bash +# For the main pdfrx package +cd packages/pdfrx flutter pub get # Install dependencies flutter analyze # Run static analysis flutter test # Run all tests flutter format . # Format code (120 char line width) + +# For the pdfrx_engine package +cd packages/pdfrx_engine +dart pub get # Install dependencies +dart analyze # Run static analysis +dart test # Run all tests +dart format . # Format code (120 char line width) ``` ### Platform-Specific Builds ```bash # Example app -cd example/viewer +cd packages/pdfrx/example/viewer flutter run # Run on connected device/emulator flutter build appbundle # Build Android App Bundle flutter build ios # Build iOS (requires macOS) @@ -31,40 +66,52 @@ flutter build windows # Build for Windows flutter build macos # Build for macOS ``` -### FFI Bindings Generation +### FFI Bindings Generation (pdfrx_engine) -- FFI bindings for PDFium are generated using `ffigen`. -- FFI bindings depends on the Pdfium headers installed on `example/viewer/build/linux/x64/release/.lib/latest/include` - - The headers are downloaded automatically during the build process; `flutter build linux` must be run at least once +- FFI bindings for PDFium are generated using `ffigen` in the pdfrx_engine package. +- FFI bindings depends on the Pdfium headers which are downloaded during `dart test` on pdfrx_engine (Linux only). ```bash -cd example/viewer && flutter build linux +# Run on Linux +cd packages/pdfrx_engine +dart test dart run ffigen # Regenerate PDFium FFI bindings ``` ## Release Process -1. Update version in `pubspec.yaml` +Both packages may need to be released when changes are made: + +### For pdfrx_engine package updates + +1. Update version in `packages/pdfrx_engine/pubspec.yaml` - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) -2. Update `CHANGELOG.md` with changes +2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes - Don't mention CI/CD changes and `CLAUDE.md` related changes (unless they are significant) -3. Update `README.md` with new version information +3. Update `packages/pdfrx_engine/README.md` if needed +4. Run `dart pub publish` in `packages/pdfrx_engine/` + +### For pdfrx package updates + +1. Update version in `packages/pdfrx/pubspec.yaml` + - If pdfrx_engine was updated, update the dependency version +2. Update `packages/pdfrx/CHANGELOG.md` with changes +3. Update `packages/pdfrx/README.md` with new version information - Changes version in example fragments - Consider to add notes for new features or breaking changes - Notify the owner if you find any issues with the example app or documentation -4. Run `flutter pub get` on all affected directories - - This includes the main package, example app, and wasm package if applicable - - Ensure all dependencies are resolved and up-to-date +4. Run `melos bootstrap` to update all dependencies 5. Run tests to ensure everything works - - Run `flutter test` to execute all tests on root directory (not in `example/viewer`) + - Run `dart test` in `packages/pdfrx_engine/` + - Run `flutter test` in `packages/pdfrx/` 6. Ensure the example app builds correctly - - Run `flutter build web --wasm` in `example/viewer` to test the example app -7. Commit changes with message "Release vX.Y.Z" -8. Tag the commit with `git tag vX.Y.Z` + - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app +7. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" +8. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` 9. Push changes and tags to remote -10. Do `flutter pub publish` to publish the package +10. Run `flutter pub publish` in `packages/pdfrx/` 11. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - If the PR references issues, please also comment on the issues @@ -83,32 +130,46 @@ dart run ffigen # Regenerate PDFium FFI bindings ## Architecture Overview -### Platform Abstraction +### Package Architecture + +The project is split into two packages with clear separation of concerns: + +#### pdfrx_engine (`packages/pdfrx_engine/`) + +- Platform-agnostic PDF rendering engine +- Conditional imports to support different platforms: + - `lib/src/native/` - Native platform implementation using PDFium via FFI + - `lib/src/web/` - Web implementation using PDFium WASM + - Platform-specific code determined at import time based on `dart:library.io` availability +- Main exports: + - `pdf_api.dart` - Core PDF document interfaces -The plugin uses conditional imports to support different platforms: +#### pdfrx (`packages/pdfrx/`) -- `lib/src/pdfium/` - Native platform implementation using PDFium via FFI -- `lib/src/web/` - Web implementation by PDFium WASM -- Platform-specific code determined at import time based on `dart:library.io` availability +- Flutter plugin built on top of pdfrx_engine +- Contains all Flutter-specific code: + - Widget layer + - Platform channel implementations + - UI components and overlays ### Core Components -1. **Document API** (`lib/src/pdf_api.dart`) +1. **Document API** (in `packages/pdfrx_engine/lib/src/pdf_api.dart`) - `PdfDocument` - Main document interface - `PdfPage` - Page representation - `PdfDocumentRef` - Reference counting for document lifecycle - Platform-agnostic interfaces implemented differently per platform -2. **Widget Layer** (`lib/src/widgets/`) +2. **Widget Layer** (in `packages/pdfrx/lib/src/widgets/`) - `PdfViewer` - Main viewer widget with multiple constructors - `PdfPageView` - Single page display - `PdfDocumentViewBuilder` - Safe document loading pattern - Overlay widgets for text selection, links, search 3. **Native Integration** - - Uses Flutter FFI for PDFium integration - - Native code in `src/pdfium_interop.cpp` - - Platform folders contain build configurations + - pdfrx_engine uses Dart FFI for PDFium integration + - Native code in `packages/pdfrx_engine/src/pdfium_interop.cpp` + - Platform folders in `packages/pdfrx/` contain Flutter plugin build configurations ### Key Patterns @@ -122,8 +183,16 @@ The plugin uses conditional imports to support different platforms: Tests download PDFium binaries automatically for supported platforms. Run tests with: ```bash +# Test pdfrx_engine +cd packages/pdfrx_engine +dart test + +# Test pdfrx Flutter plugin +cd packages/pdfrx flutter test -flutter test test/pdf_document_test.dart # Run specific test file + +# Run all tests using melos +melos run test ``` ## Platform-Specific Notes @@ -131,7 +200,7 @@ flutter test test/pdf_document_test.dart # Run specific test file ### iOS/macOS - Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) -- CocoaPods integration via `darwin/pdfrx.podspec` +- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` - Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) ### Android @@ -142,9 +211,9 @@ flutter test test/pdf_document_test.dart # Run specific test file ### Web -- `assets/pdfium.wasm` is prebuilt PDFium WASM binary -- `assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim -- `assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by `lib/src/web/pdfrx_wasm.dart` +- `packages/pdfrx/assets/pdfium.wasm` is prebuilt PDFium WASM binary +- `packages/pdfrx/assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim +- `packages/pdfrx/assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by pdfrx_engine's web implementation ### Windows/Linux @@ -160,6 +229,12 @@ flutter test test/pdf_document_test.dart # Run specific test file ## Dependency Version Policy +### pdfrx_engine + +This package follows standard Dart package versioning practices. + +### pdfrx + This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: - Flutter SDK to manage these dependencies based on the user's Flutter version diff --git a/README.md b/README.md new file mode 100644 index 00000000..ea04e198 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# pdfrx + +This repository contains two Dart/Flutter packages for PDF rendering and viewing: + +## Packages + +### [pdfrx_engine](packages/pdfrx_engine/) + +A platform-agnostic PDF rendering API built on top of PDFium. + +- Pure Dart package (no Flutter dependencies) +- Provides low-level PDF document API +- Can be used in CLI applications or non-Flutter Dart projects +- Supports all platforms: Android, iOS, Windows, macOS, Linux + +### [pdfrx](packages/pdfrx/) + +A cross-platform PDF viewer plugin for Flutter. + +- Flutter plugin with UI widgets +- Built on top of pdfrx_engine +- Provides high-level viewer widgets and overlays +- Includes text selection, search, zoom controls, and more + +## When to Use Which Package + +- **Use `pdfrx`** if you're building a Flutter application and need PDF viewing capabilities with UI +- **Use `pdfrx_engine`** if you need PDF rendering without Flutter dependencies (e.g., server-side PDF processing, CLI tools) + +## Getting Started + +### For Flutter Applications + +Add `pdfrx` to your `pubspec.yaml`: + +```yaml +dependencies: + pdfrx: ^1.3.2 +``` + +### For Pure Dart Applications + +Add `pdfrx_engine` to your `pubspec.yaml`: + +```yaml +dependencies: + pdfrx_engine: ^1.0.0 +``` + +## Development + +This is a monorepo managed with [Melos](https://melos.invertase.dev/). To work with the packages: + +```bash +# Install melos globally +dart pub global activate melos + +# Bootstrap the project +melos bootstrap + +# Run analysis on all packages +melos analyze + +# Run tests on all packages +melos test +``` + +## Example Application + +The example viewer application is located in `packages/pdfrx/example/viewer/`. It demonstrates the full capabilities of the pdfrx Flutter plugin. + +```bash +cd packages/pdfrx/example/viewer +flutter run +``` + +## Contributing + +Contributions are welcome! Please read the individual package READMEs for specific development guidelines. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index e73c008b..be7ef739 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -2,7 +2,12 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer implementation built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). +[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer plugin for Flutter. It provides ready-to-use widgets for displaying PDF documents in your Flutter applications. + +This plugin is built on top of [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine), which handles the low-level PDF rendering using [PDFium](https://pdfium.googlesource.com/pdfium/). The separation allows for a clean architecture where: +- **pdfrx** (this package) - Provides Flutter widgets, UI components, and platform integration +- **pdfrx_engine** - Handles PDF parsing and rendering without Flutter dependencies + The plugin supports Android, iOS, Windows, macOS, Linux, and Web. ## Interactive Demo @@ -57,6 +62,8 @@ dependencies: pdfrx: ^1.3.2 ``` +**Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. + ### Note for Windows **REQUIRED: You must enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode) to build pdfrx on Windows.** @@ -184,11 +191,16 @@ PdfDocumentViewBuilder.asset( [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) can accept [PdfDocumentRef](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentRef-class.html) from [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) to safely share the same [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) instance. For more information, see [`example/viewer/lib/thumbnails_view.dart`](example/viewer/lib/thumbnails_view.dart). -## Low Level PDF API +## API Documentation + +### Flutter Widgets (pdfrx) + +- [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) - Main PDF viewer widget +- [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) - Builder for safe async document loading +- [PdfPageView](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageView-class.html) - Single page display widget + +### Low-Level PDF API (pdfrx_engine) -- Easy to use Flutter widgets - - [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) - - [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) - - [PdfPageView](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageView-class.html) -- Easy to use PDF APIs - - [pdfrx_engine API reference](https://pub.dev/documentation/pdfrx_engine/latest/) +For advanced use cases requiring direct PDF manipulation without Flutter widgets, see the [pdfrx_engine API reference](https://pub.dev/documentation/pdfrx_engine/latest/). This includes: +- [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Core document interface +- [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page rendering and manipulation diff --git a/packages/pdfrx/test/pdf_viewer_test.dart b/packages/pdfrx/test/pdf_viewer_test.dart index a360d78b..c63d3d3e 100644 --- a/packages/pdfrx/test/pdf_viewer_test.dart +++ b/packages/pdfrx/test/pdf_viewer_test.dart @@ -7,13 +7,13 @@ import 'package:http/testing.dart'; import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../pdfrx_engine/test/setup.dart'; - final testPdfFile = File('example/viewer/assets/hello.pdf'); final binding = TestWidgetsFlutterBinding.ensureInitialized(); void main() { - setUp(() => setup()); + // For testing purpose, we should run on the command line + // and pdfrxEngineDartInitialize is a better way to initialize the library. + setUp(() => pdfrxEngineDartInitialize()); Pdfrx.createHttpClient = () => MockClient((request) async { return http.Response.bytes(await testPdfFile.readAsBytes(), 200); @@ -24,7 +24,9 @@ void main() { await tester.pumpWidget( MaterialApp( // FIXME: Just a workaround for "A RenderFlex overflowed..." - home: SingleChildScrollView(child: PdfViewer.uri(Uri.parse('https://example.com/hello.pdf'))), + home: SingleChildScrollView( + child: PdfViewer.uri(Uri.parse('https://example.com/hello.pdf')), + ), ), ); diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 1c8bba4d..1276ea45 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -2,7 +2,9 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a PDF engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/) and is used by the [pdfrx](https://pub.dartlang.org/packages/pdfrx) plugin. The package supports Android, iOS, Windows, macOS, Linux, and Web. +[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side processing. + +This package is part of the pdfrx monorepo and serves as the foundation for the [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. ## Multi-platform support @@ -36,6 +38,22 @@ void main() async { ## PDF API - Easy to use PDF APIs - - [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) + - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document interface + - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation and rendering - PDFium bindings - - Not encouraged but you can import [`package:pdfrx/src/pdfium/pdfium_bindings.dart`](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart) + - For advanced use cases, you can access the raw PDFium bindings via `package:pdfrx_engine/src/native/pdfium_bindings.dart` + - Note: Direct use of PDFium bindings is not recommended for most use cases + +## When to Use pdfrx_engine vs pdfrx + +**Use pdfrx_engine when:** +- Building CLI tools or server applications +- You need PDF rendering without Flutter UI +- Creating custom PDF processing pipelines +- Working in pure Dart environments + +**Use pdfrx when:** +- Building Flutter applications +- You need ready-to-use PDF viewer widgets +- You want features like text selection, search, and zoom controls +- You prefer high-level APIs with Flutter integration diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 8f38a963..6fb27fcf 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -1,3 +1,4 @@ library; export 'src/pdf_api.dart'; +export 'src/pdfrx_engine_dart.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 97d5276e..b812a073 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -457,8 +457,9 @@ class _PdfDocumentPdfium extends PdfDocument { return; } } - if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) + if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) { return; + } } } diff --git a/packages/pdfrx_engine/test/pdf_document_test.dart b/packages/pdfrx_engine/test/pdf_document_test.dart index 03d016dd..3380f10e 100644 --- a/packages/pdfrx_engine/test/pdf_document_test.dart +++ b/packages/pdfrx_engine/test/pdf_document_test.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; -import 'package:pdfrx_engine/src/pdfrx_engine_dart.dart'; import 'package:test/test.dart'; import 'utils.dart'; From 5115298801cef76eeeedc933393a32c0c4dba044 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 03:47:19 +0900 Subject: [PATCH 184/663] WIP --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ea04e198..bb25c889 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,6 @@ melos bootstrap # Run analysis on all packages melos analyze - -# Run tests on all packages -melos test ``` ## Example Application From 5b29cfb520dd88165cd88dcf7bdeae0959b5aa31 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 04:21:55 +0900 Subject: [PATCH 185/663] WIP --- packages/pdfrx/darwin/pdfium/.gitignore | 2 - .../pdfium/patches/ios/libjpeg_turbo.patch | 13 +++++++ .../darwin/pdfium/patches/ios/pdfium.patch | 39 +++++++++++++++++++ .../pdfium/patches/macos/build-config.patch | 13 +++++++ .../darwin/pdfium/patches/macos/pdfium.patch | 13 +++++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch create mode 100644 packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch create mode 100644 packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch create mode 100644 packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch diff --git a/packages/pdfrx/darwin/pdfium/.gitignore b/packages/pdfrx/darwin/pdfium/.gitignore index 58c18afd..1297b6c9 100644 --- a/packages/pdfrx/darwin/pdfium/.gitignore +++ b/packages/pdfrx/darwin/pdfium/.gitignore @@ -1,5 +1,3 @@ .lib/ .tmp/ -ios/ -macos/ *.tgz diff --git a/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch b/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch new file mode 100644 index 00000000..5f633124 --- /dev/null +++ b/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch @@ -0,0 +1,13 @@ +diff --git a/BUILD.gn b/BUILD.gn +index b39d278..596ea7a 100644 +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -12,7 +12,7 @@ if (current_cpu == "arm" || current_cpu == "arm64") { + } + + assert( +- use_blink, ++ true, + "This is not used if blink is not enabled, don't drag it in unintentionally") + + source_set("libjpeg_headers") { diff --git a/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch b/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch new file mode 100644 index 00000000..673d3f5a --- /dev/null +++ b/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch @@ -0,0 +1,39 @@ +diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn +index a07bcd089..3d1d197d2 100644 +--- a/core/fxge/BUILD.gn ++++ b/core/fxge/BUILD.gn +@@ -164,7 +164,7 @@ source_set("fxge") { + sources += [ "linux/fx_linux_impl.cpp" ] + } + +- if (is_mac) { ++ if (is_mac || is_ios) { + sources += [ + "apple/fx_apple_impl.cpp", + "apple/fx_apple_platform.cpp", +diff --git a/public/fpdfview.h b/public/fpdfview.h +index d96556b47..79b6f3ebb 100644 +--- a/public/fpdfview.h ++++ b/public/fpdfview.h +@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; + #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(WIN32) + #else +-#define FPDF_EXPORT ++#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) + #endif // defined(COMPONENT_BUILD) + + #if defined(WIN32) && defined(FPDFSDK_EXPORTS) +diff --git a/testing/test.gni b/testing/test.gni +index 6ad2c2d4a..bae5117d8 100644 +--- a/testing/test.gni ++++ b/testing/test.gni +@@ -207,7 +207,7 @@ template("test") { + } + + _bundle_id_suffix = target_name +- if (ios_automatically_manage_certs) { ++ if (true) { + # Use the same bundle identifier for all unit tests when managing + # certificates automatically as the number of free certs is limited. + _bundle_id_suffix = "generic-unit-test" diff --git a/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch b/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch new file mode 100644 index 00000000..7cd856db --- /dev/null +++ b/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch @@ -0,0 +1,13 @@ +diff --git a/config/mac/BUILD.gn b/config/mac/BUILD.gn +index 85a668d33..54691a2e0 100644 +--- a/config/mac/BUILD.gn ++++ b/config/mac/BUILD.gn +@@ -52,6 +52,8 @@ config("compiler") { + if (export_libcxxabi_from_executables) { + ldflags += [ "-Wl,-undefined,dynamic_lookup" ] + } ++ ++ cflags += [ "-Wno-unknown-warning-option" ] + } + + # This is included by reference in the //build/config/compiler:runtime_library diff --git a/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch b/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch new file mode 100644 index 00000000..53ac1c23 --- /dev/null +++ b/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch @@ -0,0 +1,13 @@ +diff --git a/public/fpdfview.h b/public/fpdfview.h +index d96556b47..79b6f3ebb 100644 +--- a/public/fpdfview.h ++++ b/public/fpdfview.h +@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; + #endif // defined(FPDF_IMPLEMENTATION) + #endif // defined(WIN32) + #else +-#define FPDF_EXPORT ++#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) + #endif // defined(COMPONENT_BUILD) + + #if defined(WIN32) && defined(FPDFSDK_EXPORTS) From 0ee298e88292040e6eeb83f861477ef1c58e65af Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 12:05:57 +0900 Subject: [PATCH 186/663] WIP --- packages/pdfrx_engine/pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index dc8eaef1..b49d23db 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 1.0.0 +version: 0.1.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -26,6 +26,9 @@ dev_dependencies: lints: ^5.0.0 test: ^1.24.0 +executables: + pdf2jpeg: + # To generate the bindings, firstly you must build example/viewer on x64 linux and # then run the following command: # dart run ffigen From 9466bdc235d996efa59a93da10f80686f874e93f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 12:23:15 +0900 Subject: [PATCH 187/663] WIP --- packages/pdfrx_engine/README.md | 10 +++++++--- packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 1276ea45..94ec9219 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -1,10 +1,12 @@ # pdfrx_engine +**PRERELEASE NOTE**: This package is currently in pre-release. The APIs may change before the final release. + [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side processing. -This package is part of the pdfrx monorepo and serves as the foundation for the [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. +This package is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. ## Multi-platform support @@ -13,7 +15,7 @@ This package is part of the pdfrx monorepo and serves as the foundation for the - Windows - macOS - Linux (even on Raspberry Pi) -- Web (WASM) +- Web (WASM) supported only on Flutter by [pdfrx](https://pub.dartlang.org/packages/pdfrx) ## Example Code @@ -47,13 +49,15 @@ void main() async { ## When to Use pdfrx_engine vs pdfrx **Use pdfrx_engine when:** + - Building CLI tools or server applications - You need PDF rendering without Flutter UI - Creating custom PDF processing pipelines - Working in pure Dart environments **Use pdfrx when:** + - Building Flutter applications -- You need ready-to-use PDF viewer widgets +- You need ready-to-use [PDF viewer widgets](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) - You want features like text selection, search, and zoom controls - You prefer high-level APIs with Flutter integration diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index b49d23db..f5719747 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.0 +version: 0.1.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 5acd140f0f968b193872d0ac1568f8176173e8ac Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 21:23:45 +0900 Subject: [PATCH 188/663] WIP: pdfrx_engine --- .../lib/src/native/pdfrx_pdfium.dart | 291 +----- packages/pdfrx_engine/lib/src/pdf_api.dart | 846 +++++++++++------- .../lib/src/utils/unmodifiable_list.dart | 30 + packages/pdfrx_engine/pubspec.yaml | 3 - 4 files changed, 590 insertions(+), 580 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/utils/unmodifiable_list.dart diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index b812a073..0f561909 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -821,7 +821,43 @@ class _PdfPagePdfium extends PdfPage { PdfPageRenderCancellationTokenPdfium(this); @override - Future loadText() => _PdfPageTextPdfium._loadText(this); + Future loadRawText() async { + if (document.isDisposed) { + return null; + } + return await (await backgroundWorker).compute( + (params) => using((arena) { + final doubleSize = sizeOf(); + final rectBuffer = arena.allocate(4 * sizeOf()); + + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); + final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); + final textPage = pdfium.FPDFText_LoadPage(page); + try { + final charCount = pdfium.FPDFText_CountChars(textPage); + final charRects = []; + final sb = StringBuffer(); + for (int i = 0; i < charCount; i++) { + sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); + pdfium.FPDFText_GetCharBox( + textPage, + i, + rectBuffer, // L + rectBuffer.offset(doubleSize * 2), // R + rectBuffer.offset(doubleSize * 3), // B + rectBuffer.offset(doubleSize), // T + ); + charRects.add(_rectFromLTRBBuffer(rectBuffer)); + } + return PdfPageRawText(sb.toString(), charRects); + } finally { + pdfium.FPDFText_ClosePage(textPage); + pdfium.FPDF_ClosePage(page); + } + }), + (docHandle: document.document.address, pageNumber: pageNumber), + ); + } @override Future> loadLinks({ @@ -1051,250 +1087,6 @@ class _PdfImagePdfium extends PdfImage { } } -class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { - _PdfPageTextFragmentPdfium( - this.pageText, - this.index, - this.length, - this.bounds, - this.charRects, - ); - - final PdfPageText pageText; - - @override - final int index; - @override - final int length; - @override - int get end => index + length; - @override - final PdfRect bounds; - @override - final List charRects; - @override - String get text => pageText.fullText.substring(index, index + length); -} - -class _PdfPageTextPdfium extends PdfPageText { - _PdfPageTextPdfium({ - required this.pageNumber, - required this.fullText, - required this.fragments, - }); - - @override - final int pageNumber; - - @override - final String fullText; - @override - final List fragments; - - static Future<_PdfPageTextPdfium> _loadText(_PdfPagePdfium page) async { - final result = await _load(page); - final pageText = _PdfPageTextPdfium( - pageNumber: page.pageNumber, - fullText: result.fullText, - fragments: [], - ); - int pos = 0; - for (final fragment in result.fragments) { - final charRects = result.charRects.sublist(pos, pos + fragment); - pageText.fragments.add( - _PdfPageTextFragmentPdfium( - pageText, - pos, - fragment, - charRects.boundingRect(), - charRects, - ), - ); - pos += fragment; - } - return pageText; - } - - static Future< - ({String fullText, List charRects, List fragments}) - > - _load(_PdfPagePdfium page) async { - if (page.document.isDisposed) { - return (fullText: '', charRects: [], fragments: []); - } - return await (await backgroundWorker).compute( - (params) => using((arena) { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); - final pdfium_bindings.FPDF_PAGE page = pdfium.FPDF_LoadPage( - doc, - params.pageNumber - 1, - ); - - final textPage = pdfium.FPDFText_LoadPage(page); - try { - final charCount = pdfium.FPDFText_CountChars(textPage); - final charRects = []; - final fragments = []; - final fullText = _loadInternal( - textPage, - 0, - charCount, - arena, - charRects, - fragments, - ); - return ( - fullText: fullText, - charRects: charRects, - fragments: fragments, - ); - } finally { - pdfium.FPDFText_ClosePage(textPage); - pdfium.FPDF_ClosePage(page); - } - }), - (docHandle: page.document.document.address, pageNumber: page.pageNumber), - ); - } - - static const _charLF = 10, _charCR = 13, _charSpace = 32; - - static String _loadInternal( - pdfium_bindings.FPDF_TEXTPAGE textPage, - int from, - int length, - Arena arena, - List charRects, - List fragments, - ) { - final fullText = _getText(textPage, from, length, arena); - final doubleSize = sizeOf(); - final buffer = arena.allocate(4 * doubleSize); - final sb = StringBuffer(); - int lineStart = 0, wordStart = 0; - int? lastChar; - for (int i = 0; i < length; i++) { - final char = fullText.codeUnitAt(i); - if (char == _charCR) { - if (i + 1 < length && fullText.codeUnitAt(i + 1) == _charLF) { - lastChar = char; - continue; - } - } - if (char == _charCR || char == _charLF) { - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - sb.write('\r\n'); - charRects.appendDummy(); - charRects.appendDummy(); - fragments.add(sb.length - wordStart); - lineStart = wordStart = sb.length; - } - lastChar = char; - continue; - } - - pdfium.FPDFText_GetCharBox( - textPage, - from + i, - buffer, // L - buffer.offset(doubleSize * 2), // R - buffer.offset(doubleSize * 3), // B - buffer.offset(doubleSize), // T - ); - final rect = _rectFromLTRBBuffer(buffer); - if (char == _charSpace) { - if (lastChar == _charSpace) continue; - if (sb.length > wordStart) { - fragments.add(sb.length - wordStart); - } - sb.writeCharCode(char); - charRects.add(rect); - fragments.add(1); - wordStart = sb.length; - lastChar = char; - continue; - } - - if (sb.length > lineStart) { - const columnHeightThreshold = 72.0; // 1 inch - final prev = charRects.last; - if (prev.left > rect.left || - prev.bottom + columnHeightThreshold < rect.bottom) { - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - if (sb.length > wordStart) { - fragments.add(sb.length - wordStart); - } - lineStart = wordStart = sb.length; - } - } - } - - sb.writeCharCode(char); - charRects.add(rect); - lastChar = char; - } - - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - if (sb.length > wordStart) { - fragments.add(sb.length - wordStart); - } - } - return sb.toString(); - } - - /// return true if any meaningful characters in the line (start -> end) - static bool _makeLineFlat( - List rects, - int start, - int end, - StringBuffer sb, - ) { - if (start >= end) return false; - final str = sb.toString(); - final bounds = rects.skip(start).take(end - start).boundingRect(); - double? prev; - for (int i = start; i < end; i++) { - final rect = rects[i]; - final char = str.codeUnitAt(i); - if (char == _charSpace) { - final next = i + 1 < end ? rects[i + 1].left : null; - rects[i] = PdfRect( - prev ?? rect.left, - bounds.top, - next ?? rect.right, - bounds.bottom, - ); - prev = null; - } else { - rects[i] = PdfRect( - prev ?? rect.left, - bounds.top, - rect.right, - bounds.bottom, - ); - prev = rect.right; - } - } - return true; - } - - static String _getText( - pdfium_bindings.FPDF_TEXTPAGE textPage, - int from, - int length, - Arena arena, - ) { - // Since FPDFText_GetText could not handle '\0' in the middle of the text, - // we'd better use FPDFText_GetUnicode to obtain the text here. - final count = pdfium.FPDFText_CountChars(textPage); - final sb = StringBuffer(); - for (int i = 0; i < count; i++) { - sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); - } - return sb.toString(); - } -} - PdfRect _rectFromLTRBBuffer(Pointer buffer) => PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); @@ -1303,15 +1095,6 @@ extension _PointerExt on Pointer { Pointer.fromAddress(address + offsetInBytes); } -extension _PdfRectsExt on List { - /// add dummy rect for control characters - void appendDummy({double width = 1}) { - if (isEmpty) return; - final prev = last; - add(PdfRect(prev.right, prev.top, prev.right + width, prev.bottom)); - } -} - PdfDest? _pdfDestFromDest( pdfium_bindings.FPDF_DEST dest, pdfium_bindings.FPDF_DOCUMENT document, diff --git a/packages/pdfrx_engine/lib/src/pdf_api.dart b/packages/pdfrx_engine/lib/src/pdf_api.dart index ebe98420..924712b3 100644 --- a/packages/pdfrx_engine/lib/src/pdf_api.dart +++ b/packages/pdfrx_engine/lib/src/pdf_api.dart @@ -5,6 +5,8 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; +import 'package:pdfrx_engine/src/utils/unmodifiable_list.dart'; +import 'package:vector_math/vector_math_64.dart' hide Colors; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; @@ -443,8 +445,331 @@ abstract class PdfPage { /// Create [PdfPageRenderCancellationToken] to cancel the rendering process. PdfPageRenderCancellationToken createCancellationToken(); + static final reSpaces = RegExp(r'(\s+)', unicode: true); + static final reNewLine = RegExp(r'\r?\n', unicode: true); + /// Load text. - Future loadText(); + Future loadText() async { + final raw = await _loadFormattedText(); + if (raw == null) { + return PdfPageText( + pageNumber: pageNumber, + fullText: '', + charRects: [], + fragments: [], + ); + } + final inputCharRects = raw.charRects; + final inputFullText = raw.fullText; + + final fragmentsTmp = <({int length, PdfTextDirection direction})>[]; + final outputText = StringBuffer(); + final outputCharRects = []; + + PdfTextDirection vector2direction(Vector2 v) { + if (v.x.abs() > v.y.abs()) { + return v.x > 0 ? PdfTextDirection.ltr : PdfTextDirection.rtl; + } else { + return PdfTextDirection.vrtl; + } + } + + PdfTextDirection getLineDirection(int start, int end) { + if (start == end || start + 1 == end) return PdfTextDirection.unknown; + return vector2direction( + inputCharRects[start].center.differenceTo( + inputCharRects[end - 1].center, + ), + ); + } + + void addWord( + int wordStart, + int wordEnd, + PdfTextDirection dir, + PdfRect bounds, { + bool isSpace = false, + bool isNewLine = false, + }) { + if (wordStart < wordEnd) { + final pos = outputText.length; + if (isSpace) { + if (wordStart > 0 && wordEnd < inputCharRects.length) { + // combine several spaces into one space + final a = inputCharRects[wordStart - 1]; + final b = inputCharRects[wordEnd]; + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + outputCharRects.add( + PdfRect( + a.right, + bounds.top, + a.right < b.left ? b.left : a.right, + bounds.bottom, + ), + ); + case PdfTextDirection.rtl: + outputCharRects.add( + PdfRect( + b.right, + bounds.top, + b.right < a.left ? a.left : b.right, + bounds.bottom, + ), + ); + case PdfTextDirection.vrtl: + outputCharRects.add( + PdfRect( + bounds.left, + a.bottom, + bounds.right, + a.bottom > b.top ? b.top : a.bottom, + ), + ); + } + outputText.write(' '); + } + } else if (isNewLine) { + if (wordStart > 0) { + // new line (\n) + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + outputCharRects.add( + PdfRect( + bounds.right, + bounds.top, + bounds.right, + bounds.bottom, + ), + ); + case PdfTextDirection.rtl: + outputCharRects.add( + PdfRect(bounds.left, bounds.top, bounds.left, bounds.bottom), + ); + case PdfTextDirection.vrtl: + outputCharRects.add( + PdfRect( + bounds.left, + bounds.bottom, + bounds.right, + bounds.bottom, + ), + ); + } + outputText.write('\n'); + } + } else { + // Adjust character bounding box based on text direction. + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.rtl: + case PdfTextDirection.unknown: + for (int i = wordStart; i < wordEnd; i++) { + final r = inputCharRects[i]; + outputCharRects.add( + PdfRect(r.left, bounds.top, r.right, bounds.bottom), + ); + } + case PdfTextDirection.vrtl: + for (int i = wordStart; i < wordEnd; i++) { + final r = inputCharRects[i]; + outputCharRects.add( + PdfRect(bounds.left, r.top, bounds.right, r.bottom), + ); + } + } + outputText.write(inputFullText.substring(wordStart, wordEnd)); + } + if (outputText.length > pos) + fragmentsTmp.add((length: outputText.length - pos, direction: dir)); + } + } + + int addWords(int start, int end, PdfTextDirection dir, PdfRect bounds) { + final firstIndex = fragmentsTmp.length; + final matches = reSpaces.allMatches(inputFullText.substring(start, end)); + int wordStart = start; + for (final match in matches) { + final spaceStart = start + match.start; + addWord(wordStart, spaceStart, dir, bounds); + wordStart = start + match.end; + addWord(spaceStart, wordStart, dir, bounds, isSpace: true); + } + addWord(wordStart, end, dir, bounds); + return fragmentsTmp.length - firstIndex; + } + + Vector2 charVec(int index, Vector2 prev) { + if (index + 1 >= inputCharRects.length) { + return prev; + } + final next = inputCharRects[index + 1]; + if (next.isEmpty) { + return prev; + } + final cur = inputCharRects[index]; + return cur.center.differenceTo(next.center); + } + + List<({int start, int end, PdfTextDirection dir})> splitLine( + int start, + int end, + ) { + final list = <({int start, int end, PdfTextDirection dir})>[]; + final lineThreshold = 1.5; // radians + final last = end - 1; + var curStart = start; + var curVec = charVec(start, Vector2(1, 0)); + for (int next = start + 1; next < last;) { + final nextVec = charVec(next, curVec); + if (curVec.angleTo(nextVec) > lineThreshold) { + list.add(( + start: curStart, + end: next + 1, + dir: vector2direction(curVec), + )); + curStart = next + 1; + if (next + 2 == end) break; + curVec = charVec(next + 1, nextVec); + next += 2; + continue; + } + curVec += nextVec; + next++; + } + if (curStart < end) { + list.add((start: curStart, end: end, dir: vector2direction(curVec))); + } + return list; + } + + void handleLine(int start, int end, {int? newLineEnd}) { + final dir = getLineDirection(start, end); + final segments = splitLine(start, end).toList(); + if (segments.length >= 2) { + for (int i = 0; i < segments.length; i++) { + final seg = segments[i]; + final bounds = inputCharRects.boundingRect( + start: seg.start, + end: seg.end, + ); + addWords(seg.start, seg.end, seg.dir, bounds); + if (i + 1 == segments.length && newLineEnd != null) { + addWord(seg.end, newLineEnd, seg.dir, bounds, isNewLine: true); + } + } + } else { + final bounds = inputCharRects.boundingRect(start: start, end: end); + addWords(start, end, dir, bounds); + if (newLineEnd != null) { + addWord(end, newLineEnd, dir, bounds, isNewLine: true); + } + } + } + + int lineStart = 0; + for (final match in reNewLine.allMatches(inputFullText)) { + if (lineStart < match.start) { + handleLine(lineStart, match.start, newLineEnd: match.end); + } else { + final lastRect = outputCharRects.last; + outputCharRects.add( + PdfRect(lastRect.left, lastRect.top, lastRect.left, lastRect.bottom), + ); + outputText.write('\n'); + } + lineStart = match.end; + } + if (lineStart < inputFullText.length) { + handleLine(lineStart, inputFullText.length); + } + + final fragments = []; + final text = PdfPageText( + pageNumber: pageNumber, + fullText: outputText.toString(), + charRects: outputCharRects, + fragments: UnmodifiableListView(fragments), + ); + + int start = 0; + for (int i = 0; i < fragmentsTmp.length; i++) { + final length = fragmentsTmp[i].length; + final direction = fragmentsTmp[i].direction; + final end = start + length; + final fragmentRects = UnmodifiableSublist( + outputCharRects, + start: start, + end: end, + ); + fragments.add( + PdfPageTextFragment( + pageText: text, + index: start, + length: length, + charRects: fragmentRects, + bounds: fragmentRects.boundingRect(), + direction: direction, + ), + ); + start = end; + } + + return text; + } + + Future _loadFormattedText() async { + final raw = await loadRawText(); + if (raw == null) { + return null; + } + + final fullText = StringBuffer(); + final charRects = []; + + // Process the whole text + final lnMatches = reNewLine.allMatches(raw.fullText).toList(); + int lineStart = 0; + int prevEnd = 0; + for (int i = 0; i < lnMatches.length; i++) { + lineStart = prevEnd; + final match = lnMatches[i]; + fullText.write(raw.fullText.substring(lineStart, match.start)); + charRects.addAll(raw.charRects.sublist(lineStart, match.start)); + prevEnd = match.end; + + // Microsoft Word sometimes outputs vertical text like this: "縦\n書\nき\nの\nテ\nキ\nス\nト\nで\nす\n。\n" + // And, we want to remove these line-feeds. + if (i + 1 < lnMatches.length) { + final next = lnMatches[i + 1]; + final len = match.start - lineStart; + final nextLen = next.start - match.end; + if (len == 1 && nextLen == 1) { + final rect = raw.charRects[lineStart]; + final nextRect = raw.charRects[match.end]; + final nextCenterX = nextRect.center.x; + if (rect.left < nextCenterX && + nextCenterX < rect.right && + rect.top > nextRect.top) { + // The line is vertical, and the line-feed is virtual + continue; + } + } + } + fullText.write(raw.fullText.substring(match.start, match.end)); + charRects.addAll(raw.charRects.sublist(match.start, match.end)); + } + if (prevEnd < raw.fullText.length) { + fullText.write(raw.fullText.substring(prevEnd)); + charRects.addAll(raw.charRects.sublist(prevEnd)); + } + return PdfPageRawText(fullText.toString(), charRects); + } + + /// Load raw text and its associated character bounding boxes. + Future loadRawText(); /// Load links. /// @@ -460,6 +785,17 @@ abstract class PdfPage { }); } +/// PDF's raw text and its associated character bounding boxes. +class PdfPageRawText { + PdfPageRawText(this.fullText, this.charRects); + + /// Full text of the page. + final String fullText; + + /// Bounds corresponding to characters in the full text. + final List charRects; +} + /// Page rotation. enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } @@ -555,18 +891,28 @@ abstract class PdfImage { /// Handles text extraction from PDF page. /// /// See [PdfPage.loadText]. -abstract class PdfPageText { +class PdfPageText { + const PdfPageText({ + required this.pageNumber, + required this.fullText, + required this.charRects, + required this.fragments, + }); + /// Page number. The first page is 1. - int get pageNumber; + final int pageNumber; /// Full text of the page. - String get fullText; + final String fullText; + + /// Bounds corresponding to characters in the full text. + final List charRects; /// Get text fragments that organizes the full text structure. /// /// The [fullText] is the composed result of all fragments' text. /// Any character in [fullText] must be included in one of the fragments. - List get fragments; + final List fragments; /// Find text fragment index for the specified text index. /// @@ -577,8 +923,16 @@ abstract class PdfPageText { if (textIndex == fullText.length) { return fragments.length; // the end of the text } + final searchIndex = PdfPageTextFragment( + pageText: this, + index: textIndex, + length: 0, + bounds: PdfRect.empty, + charRects: const [], + direction: PdfTextDirection.unknown, + ); final index = fragments.lowerBound( - _PdfPageTextFragmentForSearch(textIndex), + searchIndex, (a, b) => a.index - b.index, ); if (index > fragments.length) { @@ -599,11 +953,22 @@ abstract class PdfPageText { return index; } + /// Get text fragment for the specified text index. + /// + /// If the specified text index is out of range, it returns null. + PdfPageTextFragment? getFragmentForTextIndex(int textIndex) { + final index = getFragmentIndexForTextIndex(textIndex); + if (index < 0 || index >= fragments.length) { + return null; // range error + } + return fragments[index]; + } + /// Search text with [pattern]. /// - /// Just work like [Pattern.allMatches] but it returns stream of [PdfTextRangeWithFragments]. + /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Stream allMatches( + Stream allMatches( Pattern pattern, { bool caseInsensitive = true, }) async* { @@ -620,64 +985,73 @@ abstract class PdfPageText { final matches = pattern.allMatches(text); for (final match in matches) { if (match.start == match.end) continue; - final m = PdfTextRangeWithFragments.fromTextRange( - this, - match.start, - match.end, + final m = PdfPageTextRange( + pageText: this, + start: match.start, + end: match.end, ); - if (m != null) { - yield m; - } + yield m; } } + + /// Create a [PdfPageTextRange] from two character indices. + /// + /// Unlike [PdfPageTextRange.end], both [a] and [b] are inclusive character indices in [fullText] and + /// [a] and [b] can be in any order (e.g., [a] can be greater than [b]). + PdfPageTextRange getRangeFromAB(int a, int b) { + final min = a < b ? a : b; + final max = a < b ? b : a; + if (min < 0 || max > fullText.length) { + throw RangeError( + 'Indices out of range: $min, $max for fullText length ${fullText.length}.', + ); + } + return PdfPageTextRange(pageText: this, start: min, end: max + 1); + } } +/// Text direction in PDF page. +/// +/// - [ltr]: left to right +/// - [rtl]: right to left +/// - [vrtl]: vertical (top to bottom), right to left. +/// - [unknown]: unknown direction, e.g., no text or no text direction can be determined. +enum PdfTextDirection { ltr, rtl, vrtl, unknown } + /// Text fragment in PDF page. -abstract class PdfPageTextFragment { +class PdfPageTextFragment { + PdfPageTextFragment({ + required this.pageText, + required this.index, + required this.length, + required this.bounds, + required this.charRects, + required this.direction, + }); + + /// Owner of the fragment. + final PdfPageText pageText; + /// Fragment's index on [PdfPageText.fullText]; [text] is the substring of [PdfPageText.fullText] at [index]. - int get index; + final int index; /// Length of the text fragment. - int get length; + final int length; /// End index of the text fragment on [PdfPageText.fullText]. int get end => index + length; /// Bounds of the text fragment in PDF page coordinates. - PdfRect get bounds; + final PdfRect bounds; - /// Fragment's child character bounding boxes in PDF page coordinates. - List get charRects; + /// The fragment's child character bounding boxes in PDF page coordinates. + final List charRects; - /// Text for the fragment. - String get text; + /// Text direction of the fragment. + final PdfTextDirection direction; - /// Get the bounds of the subrange of the text fragment. - /// - /// If the specified range is out of bounds, it returns null. - PdfRect? getBoundsForRange({int? start, int? end, double? widthForEmpty}) { - start ??= 0; - end ??= length; - if (start < 0 || start >= length) { - throw RangeError.range(start, 0, length, 'start', 'Invalid start index'); - } - if (end < start || end > length) { - throw RangeError.range(end, start, length, 'end', 'Invalid end index'); - } - if (start == end) { - if (widthForEmpty == null) return null; // empty range - final cur = charRects[start]; - final next = charRects[start + 1]; - final w = next.left - cur.right; - final h = next.bottom - cur.top; // the Y-coord is bottom-up - if (w > h) { - return PdfRect(cur.left, cur.top, cur.left + widthForEmpty, cur.bottom); - } else { - return PdfRect(cur.left, cur.top, cur.right, cur.top - widthForEmpty); - } - } - return charRects.skip(start).take(end - start).boundingRect(); - } + /// Text for the fragment. + String get text => pageText.fullText.substring(index, index + length); @override bool operator ==(covariant PdfPageTextFragment other) { @@ -691,59 +1065,21 @@ abstract class PdfPageTextFragment { @override int get hashCode => index.hashCode ^ bounds.hashCode ^ text.hashCode; - - /// Create a [PdfPageTextFragment]. - static PdfPageTextFragment fromParams( - int index, - int length, - PdfRect bounds, - String text, - List charRects, - ) => _PdfPageTextFragment(index, length, bounds, text, charRects); } -class _PdfPageTextFragment extends PdfPageTextFragment { - _PdfPageTextFragment( - this.index, - this.length, - this.bounds, - this.text, - this.charRects, - ); - - @override - final int index; - @override - final int length; - @override - final PdfRect bounds; - @override - final List charRects; - @override - final String text; -} - -/// Used only for searching fragments with [lowerBound]. -class _PdfPageTextFragmentForSearch extends PdfPageTextFragment { - _PdfPageTextFragmentForSearch(this.index); - @override - final int index; - @override - int get length => throw UnimplementedError(); - @override - PdfRect get bounds => throw UnimplementedError(); - @override - String get text => throw UnimplementedError(); - @override - List get charRects => throw UnimplementedError(); -} +/// Text range in a PDF page, which is typically used to describe text selection. +class PdfPageTextRange { + /// Create a [PdfPageTextRange]. + /// + /// [start] is inclusive and [end] is exclusive. + const PdfPageTextRange({ + required this.pageText, + required this.start, + required this.end, + }); -/// Simple text range in a PDF page. -/// -/// The text range is used to describe text selection in a page but it does not indicate the actual page text; -/// [PdfTextRanges] contains multiple [PdfTextRange]s and the actual [PdfPageText] the ranges are associated with. -class PdfTextRange { - const PdfTextRange({required this.start, required this.end}); + /// The page text the text range are associated with. + final PdfPageText pageText; /// Text start index in [PdfPageText.fullText]. final int start; @@ -751,244 +1087,53 @@ class PdfTextRange { /// Text end index in [PdfPageText.fullText]. final int end; - PdfTextRange copyWith({int? start, int? end}) => - PdfTextRange(start: start ?? this.start, end: end ?? this.end); - - @override - int get hashCode => start ^ end.hashCode; - - @override - bool operator ==(Object other) { - return other is PdfTextRange && other.start == start && other.end == end; - } + /// Page number of the text range. + int get pageNumber => pageText.pageNumber; - @override - String toString() => '[$start $end]'; + /// The composed text of the text range. + String get text => pageText.fullText.substring(start, end); - /// Convert to [PdfTextRangeWithFragments]. - /// - /// The method is used to convert [PdfTextRange] to [PdfTextRangeWithFragments] using [PdfPageText]. - PdfTextRangeWithFragments? toTextRangeWithFragments(PdfPageText pageText) => - PdfTextRangeWithFragments.fromTextRange(pageText, start, end); -} + PdfRect get bounds => pageText.charRects.boundingRect(start: start, end: end); -extension PdfTextRangeListExt on List { - void appendRange(PdfTextRange range) { - if (isNotEmpty && range.start >= last.start && range.start <= last.end) { - last = PdfTextRange(start: last.start, end: range.end); - } else { - add(range); - } + int get firstFragmentIndex { + return pageText.getFragmentIndexForTextIndex(start); } - void appendAllRanges(Iterable ranges) { - for (final r in ranges) { - appendRange(r); - } + int get lastFragmentIndex { + return pageText.getFragmentIndexForTextIndex(end - 1); } -} - -/// Text ranges in a PDF page typically used to describe text selection. -class PdfTextRanges { - /// Create a [PdfTextRanges]. - const PdfTextRanges({required this.pageText, required this.ranges}); - - /// Create a [PdfTextRanges] with empty ranges. - PdfTextRanges.createEmpty(this.pageText) : ranges = []; - - /// The page text the text ranges are associated with. - final PdfPageText pageText; - - /// Text ranges. - final List ranges; - - /// Determine whether the text ranges are empty. - bool get isEmpty => ranges.isEmpty; - - /// Determine whether the text ranges are *NOT* empty. - bool get isNotEmpty => ranges.isNotEmpty; - - /// Page number of the text ranges. - int get pageNumber => pageText.pageNumber; - - /// Bounds of the text ranges. - PdfRect get bounds => ranges - .map((r) => r.toTextRangeWithFragments(pageText)!.bounds) - .boundingRect(); - - /// The composed text of the text ranges. - String get text => - ranges.map((r) => pageText.fullText.substring(r.start, r.end)).join(); -} - -/// For backward compatibility; [PdfTextRangeWithFragments] is previously named [PdfTextMatch]. -typedef PdfTextMatch = PdfTextRangeWithFragments; - -/// Text range (start/end index) in PDF page and it's associated text and bounding rectangle. -class PdfTextRangeWithFragments { - PdfTextRangeWithFragments( - this.pageNumber, - this.fragments, - this.start, - this.end, - this.bounds, - ); - - /// Page number of the page. - final int pageNumber; - - /// Fragments that contains the text. - final List fragments; - - /// In-fragment text start index on the first fragment ([fragments.first]). - final int start; - - /// In-fragment text end index on the last fragment ([fragments.last]). - final int end; - - /// Bounding rectangle of the text. - final PdfRect bounds; - /// The first character's bounding rectangle. - /// - /// If the first fragment does not have character level bounding rectangles, - /// it returns the bounds of the first fragment. - /// - /// The function is useful when you implement text selection algorithm or such. - PdfRect get startCharRect { - final firstFragment = fragments.first; - if (firstFragment.charRects.isEmpty) { - return firstFragment.bounds; + PdfPageTextFragment? get firstFragment { + final index = firstFragmentIndex; + if (index < 0 || index >= pageText.fragments.length) { + return null; // range error } - return firstFragment.charRects[start]; + return pageText.fragments[index]; } - /// The last character's bounding rectangle. - /// - /// If the last fragment does not have character level bounding rectangles, - /// it returns the bounds of the last fragment. - /// - /// The function is useful when you implement text selection algorithm or such. - PdfRect get endCharRect { - final lastFragment = fragments.last; - if (lastFragment.charRects.isEmpty) { - return lastFragment.bounds; + PdfPageTextFragment? get lastFragment { + final index = lastFragmentIndex; + if (index < 0 || index >= pageText.fragments.length) { + return null; // range error } - return lastFragment.charRects[end - 1]; + return pageText.fragments[index]; } - /// Enumerate all the character bounding rectangles for the text range. + /// Enumerate all the fragment bounding rectangles for the text range. /// /// The function is useful when you implement text selection algorithm or such. - Iterable enumerateRectsForRange({ - int? start, - int? end, - double? widthForEmpty, - }) sync* { - start ??= fragments.first.index + this.start; - end ??= fragments.last.index + this.end; - for (final f in fragments) { + Iterable enumerateFragmentBoundingRects() sync* { + final fStart = firstFragmentIndex; + final fEnd = lastFragmentIndex; + for (int i = fStart; i <= fEnd; i++) { + final f = pageText.fragments[i]; if (f.end <= start || end <= f.index) continue; - final s = max(start - f.index, 0); - final e = min(end - f.index, f.length); - yield f.getBoundsForRange( - start: s, - end: e, - widthForEmpty: widthForEmpty, - )!; - } - } - - /// Create [PdfTextRangeWithFragments] from text range in [PdfPageText]. - /// - /// When you implement search-to-highlight feature, the most easiest way is to use [PdfTextSearcher] but you can - /// of course implement your own search algorithm and use this method to create [PdfTextRangeWithFragments]: - /// - /// ```dart - /// PdfPageText pageText = ...; - /// final searchPattern = 'search text'; - /// final textIndex = pageText.fullText.indexOf(searchPattern); - /// if (textIndex >= 0) { - /// final range = PdfTextRangeWithFragments.fromTextRange(pageText, textIndex, textIndex + searchPattern.length); - /// ... - /// } - /// ``` - /// - /// To paint text highlights on PDF pages, see [PdfViewerParams.pagePaintCallbacks] and [PdfViewerPagePaintCallback]. - static PdfTextRangeWithFragments? fromTextRange( - PdfPageText pageText, - int start, [ - int? end, - ]) { - end ??= pageText.fullText.length; - if (start >= end) { - return null; - } - final s = pageText.getFragmentIndexForTextIndex(start); - final sf = pageText.fragments[s]; - if (start + 1 == end) { - return PdfTextRangeWithFragments( - pageText.pageNumber, - [pageText.fragments[s]], - start - sf.index, - end - sf.index, - sf.charRects.skip(start - sf.index).take(end - start).boundingRect(), + yield PdfTextFragmentBoundingRect( + f, + max(start - f.index, 0), + min(end - f.index, f.length), ); } - - final l = pageText.getFragmentIndexForTextIndex(end); - if (s == l) { - return PdfTextRangeWithFragments( - pageText.pageNumber, - [pageText.fragments[s]], - start - sf.index, - end - sf.index, - sf.charRects.skip(start - sf.index).take(end - start).boundingRect(), - ); - } - - var bounds = sf.charRects.skip(start - sf.index).boundingRect(); - for (int i = s + 1; i < l; i++) { - bounds = bounds.merge(pageText.fragments[i].bounds); - } - if (l == pageText.fragments.length) { - return PdfTextRangeWithFragments( - pageText.pageNumber, - pageText.fragments.sublist(s), - start - sf.index, - pageText.fragments.last.length, - bounds, - ); - } - - final lf = pageText.fragments[l]; - final containLastFragment = end > lf.index; - if (containLastFragment) { - bounds = bounds.merge(lf.charRects.take(end - lf.index).boundingRect()); - } - - return PdfTextRangeWithFragments( - pageText.pageNumber, - pageText.fragments.sublist(s, containLastFragment ? l + 1 : l), - start - sf.index, - containLastFragment - ? end - lf.index - : end - pageText.fragments[l - 1].index, - bounds, - ); - } - - @override - int get hashCode => pageNumber ^ start ^ end; - - @override - bool operator ==(Object other) { - return other is PdfTextRangeWithFragments && - other.pageNumber == pageNumber && - other.start == start && - other.end == end && - other.bounds == bounds && - _listEquals(other.fragments, fragments); } } @@ -999,7 +1144,15 @@ class PdfTextRangeWithFragments { /// [bottom] is generally smaller than [top]. /// The unit is normally in points (1/72 inch). class PdfRect { - const PdfRect(this.left, this.top, this.right, this.bottom); + const PdfRect(this.left, this.top, this.right, this.bottom) + : assert( + left <= right, + 'Left coordinate must be less than or equal to right coordinate.', + ), + assert( + top >= bottom, + 'Top coordinate must be greater than or equal to bottom coordinate.', + ); /// Left coordinate. final double left; @@ -1061,6 +1214,15 @@ class PdfRect { bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); + double distanceSquaredTo(PdfPoint point) { + if (containsPoint(point)) { + return 0.0; // inside the rectangle + } + final dx = point.x.clamp(left, right) - point.x; + final dy = point.y.clamp(bottom, top) - point.y; + return dx * dx + dy * dy; + } + /// Determine whether the rectangle overlaps the specified rectangle (in the PDF page coordinates). bool overlaps(PdfRect other) { return left < other.right && @@ -1146,13 +1308,15 @@ class PdfRect { /// Extension methods for List of [PdfRect]. extension PdfRectsExt on Iterable { - /// Merge all rectangles to calculate bounding rectangle. - PdfRect boundingRect() { + /// Calculate the bounding rectangle of the list of rectangles. + PdfRect boundingRect({int? start, int? end}) { + start ??= 0; + end ??= length; var left = double.infinity; var top = double.negativeInfinity; var right = double.negativeInfinity; var bottom = double.infinity; - for (final r in this) { + for (final r in skip(start).take(end - start)) { if (r.left < left) { left = r.left; } @@ -1174,6 +1338,39 @@ extension PdfRectsExt on Iterable { } } +/// Bounding rectangle for a text range in a PDF page. +class PdfTextFragmentBoundingRect { + const PdfTextFragmentBoundingRect(this.fragment, this.sif, this.eif); + + /// Associated text fragment. + final PdfPageTextFragment fragment; + + /// In fragment text start index (Start-In-Fragment) + /// + /// It is the character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] + /// of the associated [fragment]. + final int sif; + + /// In fragment text end index (End-In-Fragment). + /// + /// It is the end character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] + /// of the associated [fragment]. + final int eif; + + /// Rectangle in PDF page coordinates. + PdfRect get bounds => + fragment.pageText.charRects.boundingRect(start: start, end: end); + + /// Start index of the text range in page's full text. + int get start => fragment.index + sif; + + /// End index of the text range in page's full text. + int get end => fragment.index + eif; + + /// Text direction of the text range. + PdfTextDirection get direction => fragment.direction; +} + /// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. class PdfDest { const PdfDest(this.pageNumber, this.command, this.params); @@ -1306,6 +1503,9 @@ class PdfPoint { /// Y coordinate. final double y; + /// Calculate the vector to another point. + Vector2 differenceTo(PdfPoint other) => Vector2(other.x - x, other.y - y); + @override String toString() => 'PdfOffset($x, $y)'; diff --git a/packages/pdfrx_engine/lib/src/utils/unmodifiable_list.dart b/packages/pdfrx_engine/lib/src/utils/unmodifiable_list.dart new file mode 100644 index 00000000..42f4f7ed --- /dev/null +++ b/packages/pdfrx_engine/lib/src/utils/unmodifiable_list.dart @@ -0,0 +1,30 @@ +import 'dart:collection'; + +/// An unmodifiable sublist view of a list. +class UnmodifiableSublist extends ListBase { + /// Creates an unmodifiable sublist view of the provided list. + /// + /// Please note that the class assumes the underlying list is also unmodifiable. + /// If the underlying list is modified, the behavior of this class is undefined. + UnmodifiableSublist(this._list, {int start = 0, int? end}) + : assert(start >= 0 && start <= _list.length && (end == null || end >= start)), + _start = start, + length = (end ?? _list.length) - start; + final List _list; + final int _start; + + @override + final int length; + + @override + set length(int newLength) => throw UnsupportedError('Cannot modify the length of an unmodifiable list'); + + @override + T operator [](int index) => _list[index + _start]; + + @override + void operator []=(int index, T value) => throw UnsupportedError('Cannot modify an unmodifiable list'); + + @override + Iterator get iterator => _list.getRange(_start, _start + length).iterator; +} diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index f5719747..52db99fd 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -26,9 +26,6 @@ dev_dependencies: lints: ^5.0.0 test: ^1.24.0 -executables: - pdf2jpeg: - # To generate the bindings, firstly you must build example/viewer on x64 linux and # then run the following command: # dart run ffigen From 7bd1c42c42013989ea8b3100d4c0ef7fd374003f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 13 Jul 2025 21:45:18 +0900 Subject: [PATCH 189/663] Manually merged: new_test_select --- .vscode/settings.json | 1 + packages/pdfrx/assets/pdfium_worker.js | 170 +-- packages/pdfrx/example/viewer/lib/main.dart | 36 +- .../example/viewer/lib/markers_view.dart | 10 +- .../pdfrx/example/viewer/lib/search_view.dart | 16 +- packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/lib/pdfrx.dart | 1 - packages/pdfrx/lib/src/pdfrx_flutter.dart | 59 +- .../pdfrx/lib/src/utils/native/native.dart | 18 +- packages/pdfrx/lib/src/utils/web/web.dart | 19 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 221 +-- .../lib/src/widgets/interactive_viewer.dart | 4 +- .../src/widgets/pdf_page_text_overlay.dart | 649 --------- .../lib/src/widgets/pdf_text_searcher.dart | 10 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 1276 +++++++++++++---- .../lib/src/widgets/pdf_viewer_params.dart | 497 +++++-- 16 files changed, 1698 insertions(+), 1291 deletions(-) delete mode 100644 packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index d300fb58..d3bd2986 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -196,6 +196,7 @@ "VBEAM", "VIEWERREF", "viiii", + "vrtl", "vsync", "WCHAR", "WIDESTRING", diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index d7fefcfd..f86dc195 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -906,179 +906,39 @@ function _memset(ptr, value, num) { } } -const CR = 0x0d, - LF = 0x0a, - SPC = 0x20; - /** * * @param {{pageIndex: number, docHandle: number}} params * @returns {{fullText: string, charRects: number[][], fragments: number[]}} */ -function loadText(params) { +function loadRawText(params) { const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); if (textPage == null) return { fullText: '', charRects: [], fragments: [] }; - const charCount = Pdfium.wasmExports.FPDFText_CountChars(textPage); - /** @type {number[][]} */ - const charRects = []; - /** @type {number[]} */ - const fragments = []; - const fullText = _loadTextInternal(textPage, 0, charCount, charRects, fragments); - Pdfium.wasmExports.FPDFText_ClosePage(textPage); - Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - return { fullText, charRects, fragments }; -} -/** - * @param {number} textPage - * @param {number} from - * @param {number} length - * @param {number[][]} charRects - * @param {number[]} fragments - * @returns - */ -function _loadTextInternal(textPage, from, length, charRects, fragments) { - const fullText = _getText(textPage, from, length); const rectBuffer = Pdfium.wasmExports.malloc(8 * 4); // double[4] - const sb = { - str: '', - push(text) { - this.str += text; - }, - get length() { - return this.str.length; - }, - }; - let lineStart = 0, - wordStart = 0; - let lastChar; - for (let i = 0; i < length; i++) { - const char = fullText.charCodeAt(i); - if (char == CR) { - if (i + 1 < length && fullText.codePointAt(i + 1) == LF) { - lastChar = char; - continue; - } - } - if (char === CR || char === LF) { - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - sb.push('\r\n'); - _appendDummy(charRects); - _appendDummy(charRects); - fragments.push(sb.length - wordStart); - lineStart = wordStart = sb.length; - } - lastChar = char; - continue; - } - + const rect = new Float64Array(Pdfium.memory.buffer, rectBuffer, 4); + const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); + let fullText = ''; + let charRects = []; + for (let i = 0; i < count; i++) { + fullText += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); Pdfium.wasmExports.FPDFText_GetCharBox( textPage, - from + i, + i, rectBuffer, // L rectBuffer + 8 * 2, // R rectBuffer + 8 * 3, // B - rectBuffer + 8 - ); // T - const rect = Array.from(new Float64Array(Pdfium.memory.buffer, rectBuffer, 4)); - if (char === SPC) { - if (lastChar == SPC) continue; - if (sb.length > wordStart) { - fragments.push(sb.length - wordStart); - } - sb.push(String.fromCharCode(char)); - charRects.push(rect); - fragments.push(1); - wordStart = sb.length; - lastChar = char; - continue; - } - - if (sb.length > lineStart) { - const columnHeightThreshold = 72.0; // 1 inch - const prev = charRects[charRects.length - 1]; - if (prev[0] > rect[0] || prev[3] + columnHeightThreshold < rect[3]) { - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - if (sb.length > wordStart) { - fragments.push(sb.length - wordStart); - } - lineStart = wordStart = sb.length; - } - } - } - - sb.push(String.fromCharCode(char)); - charRects.push(rect); - lastChar = char; - } - - if (_makeLineFlat(charRects, lineStart, sb.length, sb)) { - if (sb.length > wordStart) { - fragments.push(sb.length - wordStart); - } + rectBuffer + 8 // T + ); + charRects.push(Array.from(rect)); } - Pdfium.wasmExports.free(rectBuffer); - return sb.str; -} - -function _appendDummy(rects, width = 1) { - if (rects.length === 0) return; - const last = rects[rects.length - 1]; - rects.push([last[0], last[1], last[2] + width, last[3]]); -} -/// return true if any meaningful characters in the line (start -> end) -function _makeLineFlat(rects, start, end, sb) { - if (start >= end) return false; - const str = sb.str; - const bounds = _boundingRect(rects, start, end); - let prev; - for (let i = start; i < end; i++) { - const rect = rects[i]; - const char = str.codePointAt(i); - if (char === SPC) { - const next = i + 1 < end ? rects[i + 1][0] : null; - rects[i] = [prev != null ? prev : rect[0], bounds[1], next != null ? next : rect[2], bounds[3]]; - prev = null; - } else { - rects[i] = [prev != null ? prev : rect[0], bounds[1], rect[2], bounds[3]]; - prev = rect[2]; // right - } - } - return true; -} - -function _boundingRect(rects, start, end) { - let l = Number.MAX_VALUE, - t = 0, - r = 0, - b = Number.MAX_VALUE; - for (let i = start; i < end; i++) { - const rect = rects[i]; - l = Math.min(l, rect[0]); - t = Math.max(t, rect[1]); - r = Math.max(r, rect[2]); - b = Math.min(b, rect[3]); - } - return [l, t, r, b]; -} - -function _getText(textPage, from, length) { - const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); - let sb = ''; - for (let i = 0; i < count; i++) { - sb += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); - } - return sb; - - // const textBuffer = Pdfium.wasmExports.malloc(length * 2 + 2); - // const count = Pdfium.wasmExports.FPDFText_GetText(textPage, from, length, textBuffer); - // const text = StringUtils.utf16BytesToString(new Uint8Array(Pdfium.memory.buffer, textBuffer, count * 2)); - // Pdfium.wasmExports.free(textBuffer); - // return text; + Pdfium.wasmExports.FPDFText_ClosePage(textPage); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + return { fullText, charRects }; } /** @@ -1264,7 +1124,7 @@ const functions = { loadPage, closePage, renderPage, - loadText, + loadRawText, loadLinks, }; diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index fa5de4db..0944b6e9 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -47,7 +47,7 @@ class _MainPageState extends State with WidgetsBindingObserver { final outline = ValueNotifier?>(null); final textSearcher = ValueNotifier(null); final _markers = >{}; - List? textSelections; + List? textSelections; void _update() { if (mounted) { @@ -266,13 +266,13 @@ class _MainPageState extends State with WidgetsBindingObserver { markers: _markers.values.expand((e) => e).toList(), onTap: (marker) { final rect = controller.calcRectForRectInsidePage( - pageNumber: marker.ranges.pageText.pageNumber, - rect: marker.ranges.bounds, + pageNumber: marker.range.pageText.pageNumber, + rect: marker.range.bounds, ); controller.ensureVisible(rect); }, onDeleteTap: (marker) { - _markers[marker.ranges.pageNumber]!.remove(marker); + _markers[marker.range.pageNumber]!.remove(marker); setState(() {}); }, ), @@ -318,7 +318,13 @@ class _MainPageState extends State with WidgetsBindingObserver { scrollHorizontallyByMouseWheel: isHorizontalLayout, pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, - enableTextSelection: true, + textSelectionParams: PdfTextSelectionParams( + enableSelectionHandles: true, + magnifier: PdfViewerSelectionMagnifierParams(enabled: true), + onTextSelectionChange: (textSelection) { + textSelections = textSelection.selectedTextRange; + }, + ), useAlternativeFitScaleAsMinScale: false, maxScale: 8, onViewSizeChanged: (viewSize, oldViewSize, controller) { @@ -436,9 +442,6 @@ class _MainPageState extends State with WidgetsBindingObserver { outline.value = await document.loadOutline(); textSearcher.value = PdfTextSearcher(controller)..addListener(_update); }, - onTextSelectionChange: (selections) { - textSelections = selections; - }, ), ); }), @@ -460,19 +463,10 @@ class _MainPageState extends State with WidgetsBindingObserver { ..color = marker.color.withAlpha(100) ..style = PaintingStyle.fill; - for (final range in marker.ranges.ranges) { - final f = PdfTextRangeWithFragments.fromTextRange( - marker.ranges.pageText, - range.start, - range.end, - ); - if (f != null) { - canvas.drawRect( - f.bounds.toRectInPageRect(page: page, pageRect: pageRect), - paint, - ); - } - } + canvas.drawRect( + marker.range.bounds.toRectInDocument(page: page, pageRect: pageRect), + paint, + ); } } diff --git a/packages/pdfrx/example/viewer/lib/markers_view.dart b/packages/pdfrx/example/viewer/lib/markers_view.dart index e0f102b0..b1aac319 100644 --- a/packages/pdfrx/example/viewer/lib/markers_view.dart +++ b/packages/pdfrx/example/viewer/lib/markers_view.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; class Marker { - Marker(this.color, this.ranges); + Marker(this.color, this.range); final Color color; - final PdfTextRanges ranges; + final PdfPageTextRange range; } class MarkersView extends StatefulWidget { @@ -16,8 +16,8 @@ class MarkersView extends StatefulWidget { }); final List markers; - final void Function(Marker ranges)? onTap; - final void Function(Marker ranges)? onDeleteTap; + final void Function(Marker marker)? onTap; + final void Function(Marker marker)? onDeleteTap; @override State createState() => _MarkersViewState(); @@ -40,7 +40,7 @@ class _MarkersViewState extends State { child: SizedBox( width: double.infinity, height: 40, - child: Text('Page #${marker.ranges.pageNumber} - ${marker.ranges.text}'), + child: Text('Page #${marker.range.pageNumber} - ${marker.range.text}'), ), ), ), diff --git a/packages/pdfrx/example/viewer/lib/search_view.dart b/packages/pdfrx/example/viewer/lib/search_view.dart index 4bd31f3c..9ad7bb0e 100644 --- a/packages/pdfrx/example/viewer/lib/search_view.dart +++ b/packages/pdfrx/example/viewer/lib/search_view.dart @@ -221,7 +221,7 @@ class SearchResultTile extends StatefulWidget { super.key, }); - final PdfTextRangeWithFragments match; + final PdfPageTextRange match; final void Function() onTap; final PdfPageTextCache pageTextStore; final double height; @@ -287,19 +287,19 @@ class _SearchResultTileState extends State { ); } - TextSpan createTextSpanForMatch(PdfPageText? pageText, PdfTextRangeWithFragments match, {TextStyle? style}) { + TextSpan createTextSpanForMatch(PdfPageText? pageText, PdfPageTextRange match, {TextStyle? style}) { style ??= const TextStyle( fontSize: 14, ); if (pageText == null) { return TextSpan( - text: match.fragments.map((f) => f.text).join(), + text: match.text, style: style, ); } final fullText = pageText.fullText; int first = 0; - for (int i = match.fragments.first.index - 1; i >= 0;) { + for (int i = match.start - 1; i >= 0;) { if (fullText[i] == '\n') { first = i + 1; break; @@ -307,16 +307,16 @@ class _SearchResultTileState extends State { i--; } int last = fullText.length; - for (int i = match.fragments.last.end; i < fullText.length; i++) { + for (int i = match.end; i < fullText.length; i++) { if (fullText[i] == '\n') { last = i; break; } } - final header = fullText.substring(first, match.fragments.first.index + match.start); - final body = fullText.substring(match.fragments.first.index + match.start, match.fragments.last.index + match.end); - final footer = fullText.substring(match.fragments.last.index + match.end, last); + final header = fullText.substring(first, match.start); + final body = fullText.substring(match.start, match.end); + final footer = fullText.substring(match.end, last); return TextSpan( children: [ diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index e582568d..de0adeda 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -346,7 +346,7 @@ packages: path: "../../../pdfrx_engine" relative: true source: path - version: "1.0.0" + version: "0.1.1" platform: dependency: transitive description: diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index 28fe15ac..79a92990 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -1,6 +1,5 @@ export 'src/pdf_document_ref.dart'; export 'src/pdfrx_flutter.dart'; -export 'src/widgets/pdf_page_text_overlay.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; export 'src/widgets/pdf_viewer_params.dart'; diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 43c116c9..6bfaeeb5 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -42,7 +42,13 @@ extension PdfImageExt on PdfImage { /// Create [Image] from the rendered image. Future createImage() { final comp = Completer(); - decodeImageFromPixels(pixels, width, height, PixelFormat.bgra8888, (image) => comp.complete(image)); + decodeImageFromPixels( + pixels, + width, + height, + PixelFormat.bgra8888, + (image) => comp.complete(image), + ); return comp.future; } } @@ -54,7 +60,8 @@ extension PdfRectExt on PdfRect { /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + final scale = + scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return Rect.fromLTRB( rotated.left * scale, (page.height - rotated.top) * scale, @@ -64,14 +71,22 @@ extension PdfRectExt on PdfRect { } /// Convert to [Rect] in Flutter coordinate using [pageRect] as the page's bounding rectangle. - Rect toRectInPageRect({required PdfPage page, required Rect pageRect}) => - toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); + Rect toRectInDocument({required PdfPage page, required Rect pageRect}) => + toRect( + page: page, + scaledPageSize: pageRect.size, + ).translate(pageRect.left, pageRect.top); } extension RectPdfRectExt on Rect { /// Convert to [PdfRect] in PDF page coordinates. - PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + PdfRect toPdfRect({ + required PdfPage page, + Size? scaledPageSize, + int? rotation, + }) { + final scale = + scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return PdfRect( left / scale, page.height - top / scale, @@ -86,17 +101,39 @@ extension PdfPointExt on PdfPoint { /// [page] is the page to convert the rectangle. /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. - Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { + Offset toOffset({ + required PdfPage page, + Size? scaledPageSize, + int? rotation, + }) { final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + final scale = + scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return Offset(rotated.x * scale, (page.height - rotated.y) * scale); } + + Offset toOffsetInDocument({required PdfPage page, required Rect pageRect}) { + final rotated = rotate(page.rotation.index, page); + final scale = pageRect.height / page.height; + return Offset( + rotated.x * scale, + (page.height - rotated.y) * scale, + ).translate(pageRect.left, pageRect.top); + } } extension OffsetPdfPointExt on Offset { /// Convert to [PdfPoint] in PDF page coordinates. - PdfPoint toPdfPoint({required PdfPage page, Size? scaledPageSize, int? rotation}) { - final scale = scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; - return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); + PdfPoint toPdfPoint({ + required PdfPage page, + Size? scaledPageSize, + int? rotation, + }) { + final scale = + scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; + return PdfPoint( + dx * scale, + page.height - dy * scale, + ).rotateReverse(rotation ?? page.rotation.index, page); } } diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 05107018..d6878f14 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -8,7 +8,23 @@ final isWindows = Platform.isWindows; /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => - isApple ? HardwareKeyboard.instance.isMetaPressed : HardwareKeyboard.instance.isControlPressed; + isApple + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed; + +void setClipboardData(String text) { + Clipboard.setData(ClipboardData(text: text)); +} + +/// Whether the current platform is mobile (Android, iOS, or Fuchsia). +final isMobile = Platform.isAndroid || Platform.isIOS || Platform.isFuchsia; + +/// Whether text selection should be triggered by swipe gestures or not. + +bool get shouldTextSelectionTriggeredBySwipe { + if (isMobile) return false; + return true; +} /// Override for the [PdfDocumentFactory] for native platforms; it is null. final PdfDocumentFactory? pdfDocumentFactoryOverride = null; diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 502cbec1..873a51ee 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,5 +1,6 @@ import 'package:flutter/services.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:web/web.dart' as web; import '../../wasm/pdfrx_wasm.dart'; @@ -7,7 +8,21 @@ final isApple = false; final isWindows = false; /// Key pressing state of ⌘ or Control depending on the platform. -bool get isCommandKeyPressed => HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isControlPressed; +bool get isCommandKeyPressed => + HardwareKeyboard.instance.isMetaPressed || + HardwareKeyboard.instance.isControlPressed; + +void setClipboardData(String text) { + web.window.navigator.clipboard.writeText(text); +} + +/// Whether the current platform is mobile (Android, iOS, or Fuchsia). +final isMobile = false; + +/// Whether text selection should be triggered by swipe gestures or not. + +bool get shouldTextSelectionTriggeredBySwipe => true; /// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. -final PdfDocumentFactory? pdfDocumentFactoryOverride = PdfDocumentFactoryWasmImpl(); +final PdfDocumentFactory? pdfDocumentFactoryOverride = + PdfDocumentFactoryWasmImpl(); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index d91fb9cd..fb6f4464 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui_web' as ui_web; @@ -13,7 +14,11 @@ import 'package:web/web.dart' as web; extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { /// Sends a command to the worker and returns a promise @JS('sendCommand') - external JSPromise sendCommand([String command, JSAny? parameters, JSArray? transfer]); + external JSPromise sendCommand([ + String command, + JSAny? parameters, + JSArray? transfer, + ]); /// Registers a callback function and returns its ID @JS('registerCallback') @@ -42,8 +47,14 @@ class _PdfiumWasmCallback { } } -Future> _sendCommand(String command, {Map? parameters}) async { - final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; +Future> _sendCommand( + String command, { + Map? parameters, +}) async { + final result = + await _pdfiumWasmCommunicator + .sendCommand(command, parameters?.jsify()) + .toDart; return (result.dartify()) as Map; } @@ -68,7 +79,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (_initialized) return; Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); pdfiumWasmWorkerUrl = _getWorkerUrl(); - final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); + final moduleUrl = _resolveUrl( + Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath, + ); final script = web.document.createElement('script') as web.HTMLScriptElement ..type = 'text/javascript' @@ -79,7 +92,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { web.document.querySelector('head')!.appendChild(script); final completer = Completer(); final sub1 = script.onLoad.listen((_) => completer.complete()); - final sub2 = script.onError.listen((event) => completer.completeError(event)); + final sub2 = script.onError.listen( + (event) => completer.completeError(event), + ); try { await completer.future; } catch (e) { @@ -93,7 +108,8 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { await _sendCommand( 'init', parameters: { - if (Pdfrx.pdfiumWasmHeaders != null) 'headers': Pdfrx.pdfiumWasmHeaders, + if (Pdfrx.pdfiumWasmHeaders != null) + 'headers': Pdfrx.pdfiumWasmHeaders, 'withCredentials': Pdfrx.pdfiumWasmWithCredentials, }, ); @@ -102,16 +118,21 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } static String? _pdfiumWasmModulesUrlFromMetaTag() { - final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; + final meta = + web.document.querySelector('meta[name="pdfium-wasm-module-url"]') + as web.HTMLMetaElement?; return meta?.content; } /// Workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments String _getWorkerUrl() { - final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); + final moduleUrl = _resolveUrl( + Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath, + ); final workerJsUrl = _resolveUrl('pdfium_worker.js', baseUrl: moduleUrl); final pdfiumWasmUrl = _resolveUrl('pdfium.wasm', baseUrl: moduleUrl); - final content = 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; + final content = + 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; final blob = web.Blob( [content].jsify() as JSArray, web.BlobPropertyBag(type: 'application/javascript'), @@ -127,7 +148,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { /// 3. The current browser window's URL (`web.window.location.href`). static String _resolveUrl(String relativeUrl, {String? baseUrl}) { final baseHref = ui_web.BrowserPlatformLocation().getBaseHref(); - return Uri.parse(baseUrl ?? baseHref ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); + return Uri.parse( + baseUrl ?? baseHref ?? web.window.location.href, + ).resolveUri(Uri.parse(relativeUrl)).toString(); } @override @@ -138,7 +161,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { bool useProgressiveLoading = false, }) async { if (Pdfrx.loadAsset == null) { - throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); + throw StateError( + 'Pdfrx.loadAsset is not set. Please set it to load assets.', + ); } final asset = await Pdfrx.loadAsset!(name); return await openData( @@ -153,7 +178,8 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { @override Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, + required FutureOr Function(Uint8List buffer, int position, int size) + read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -162,7 +188,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { - throw UnimplementedError('PdfDocumentFactoryWasmImpl.openCustom is not implemented.'); + throw UnimplementedError( + 'PdfDocumentFactoryWasmImpl.openCustom is not implemented.', + ); } @override @@ -177,7 +205,11 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => _sendCommand( 'loadDocumentFromData', - parameters: {'data': data, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + parameters: { + 'data': data, + 'password': password, + 'useProgressiveLoading': useProgressiveLoading, + }, ), sourceName: sourceName ?? 'data', passwordProvider: passwordProvider, @@ -194,7 +226,11 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => _sendCommand( 'loadDocumentFromUrl', - parameters: {'url': filePath, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, + parameters: { + 'url': filePath, + 'password': password, + 'useProgressiveLoading': useProgressiveLoading, + }, ), sourceName: filePath, passwordProvider: passwordProvider, @@ -220,7 +256,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (progressCallback != null) { await _init(); progressCallbackReg = _PdfiumWasmCallback.register( - ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, + ((int bytesReceived, int bytesTotal) => + progressCallback(bytesReceived, bytesTotal)) + .toJS, ); } @@ -231,7 +269,8 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { 'url': uri.toString(), 'password': password, 'useProgressiveLoading': useProgressiveLoading, - if (progressCallbackReg != null) 'progressCallbackId': progressCallbackReg.id, + if (progressCallbackReg != null) + 'progressCallbackId': progressCallbackReg.id, 'preferRangeAccess': preferRangeAccess, if (headers != null) 'headers': headers, 'withCredentials': withCredentials, @@ -264,7 +303,9 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } else { password = await passwordProvider?.call(); if (password == null) { - throw const PdfPasswordException('No password supplied by PasswordProvider.'); + throw const PdfPasswordException( + 'No password supplied by PasswordProvider.', + ); } } @@ -276,17 +317,26 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (errorCode == fpdfErrPassword) { continue; } - throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); + throw StateError( + 'Failed to open document: ${result['errorCodeStr']} ($errorCode)', + ); } - return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose); + return _PdfDocumentWasm._( + result, + sourceName: sourceName, + disposeCallback: onDispose, + ); } } } class _PdfDocumentWasm extends PdfDocument { - _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) - : permissions = parsePermissions(document) { + _PdfDocumentWasm._( + this.document, { + required super.sourceName, + this.disposeCallback, + }) : permissions = parsePermissions(document) { pages = parsePages(this, document['pages'] as List); } @@ -316,12 +366,16 @@ class _PdfDocumentWasm extends PdfDocument { @override bool isIdenticalDocumentHandle(Object? other) { - return other is _PdfDocumentWasm && other.document['docHandle'] == document['docHandle']; + return other is _PdfDocumentWasm && + other.document['docHandle'] == document['docHandle']; } @override Future> loadOutline() async { - final result = await _sendCommand('loadOutline', parameters: {'docHandle': document['docHandle']}); + final result = await _sendCommand( + 'loadOutline', + parameters: {'docHandle': document['docHandle']}, + ); final outlineList = result['outline'] as List; return outlineList.map((node) => _nodeFromMap(node)).toList(); } @@ -330,7 +384,10 @@ class _PdfDocumentWasm extends PdfDocument { return PdfOutlineNode( title: node['title'], dest: _pdfDestFromMap(node['dest']), - children: (node['children'] as List).map((child) => _nodeFromMap(child)).toList(), + children: + (node['children'] as List) + .map((child) => _nodeFromMap(child)) + .toList(), ); } @@ -375,7 +432,8 @@ class _PdfDocumentWasm extends PdfDocument { static PdfPermissions? parsePermissions(Map document) { final perms = (document['permissions'] as num).toInt(); - final securityHandlerRevision = (document['securityHandlerRevision'] as num).toInt(); + final securityHandlerRevision = + (document['securityHandlerRevision'] as num).toInt(); if (perms >= 0 && securityHandlerRevision >= 0) { return PdfPermissions(perms, securityHandlerRevision); } else { @@ -383,7 +441,10 @@ class _PdfDocumentWasm extends PdfDocument { } } - static List parsePages(_PdfDocumentWasm doc, List pageList) { + static List parsePages( + _PdfDocumentWasm doc, + List pageList, + ) { return pageList .map( (page) => _PdfPageWasm( @@ -399,7 +460,8 @@ class _PdfDocumentWasm extends PdfDocument { } } -class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { +class _PdfPageRenderCancellationTokenWasm + extends PdfPageRenderCancellationToken { _PdfPageRenderCancellationTokenWasm(); bool _isCanceled = false; @@ -414,8 +476,14 @@ class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken } class _PdfPageWasm extends PdfPage { - _PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) - : pageNumber = pageIndex + 1, + _PdfPageWasm( + this.document, + int pageIndex, + this.width, + this.height, + int rotation, + this.isLoaded, + ) : pageNumber = pageIndex + 1, rotation = PdfPageRotation.values[rotation]; @override @@ -427,7 +495,10 @@ class _PdfPageWasm extends PdfPage { final _PdfDocumentWasm document; @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { + Future> loadLinks({ + bool compact = false, + bool enableAutoLinkDetection = true, + }) async { final result = await _sendCommand( 'loadLinks', parameters: { @@ -443,7 +514,12 @@ class _PdfPageWasm extends PdfPage { final rects = (link['rects'] as List).map((r) { final rect = r as List; - return PdfRect(rect[0] as double, rect[1] as double, rect[2] as double, rect[3] as double); + return PdfRect( + rect[0] as double, + rect[1] as double, + rect[2] as double, + rect[3] as double, + ); }).toList(); final url = link['url']; if (url is String) { @@ -458,29 +534,28 @@ class _PdfPageWasm extends PdfPage { } @override - Future loadText() async { + Future loadRawText() async { final result = await _sendCommand( - 'loadText', - parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, + 'loadRawText', + parameters: { + 'docHandle': document.document['docHandle'], + 'pageIndex': pageNumber - 1, + }, + ); + final charRectsAll = + (result['charRects'] as List).map((rect) { + final r = rect as List; + return PdfRect( + r[0] as double, + r[1] as double, + r[2] as double, + r[3] as double, + ); + }).toList(); + return PdfPageRawText( + result['fullText'] as String, + UnmodifiableListView(charRectsAll), ); - final pageText = _PdfPageTextJs(pageNumber: pageNumber, fullText: result['fullText'], fragments: []); - final fragmentOffsets = result['fragments']; - final charRectsAll = result['charRects'] as List; - if (fragmentOffsets is List) { - int pos = 0; - for (final fragment in fragmentOffsets.map((n) => (n as num).toInt())) { - final charRects = - charRectsAll.sublist(pos, pos + fragment).map((rect) { - final r = rect as List; - return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); - }).toList(); - pageText.fragments.add( - _PdfPageTextFragmentPdfium(pageText, pos, fragment, charRects.boundingRect(), charRects), - ); - pos += fragment; - } - } - return pageText; } @override @@ -507,7 +582,8 @@ class _PdfPageWasm extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + PdfAnnotationRenderingMode annotationRenderingMode = + PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { @@ -541,7 +617,11 @@ class _PdfPageWasm extends PdfPage { } class PdfImageWeb extends PdfImage { - PdfImageWeb({required this.width, required this.height, required this.pixels}); + PdfImageWeb({ + required this.width, + required this.height, + required this.pixels, + }); @override final int width; @@ -553,25 +633,6 @@ class PdfImageWeb extends PdfImage { void dispose() {} } -class _PdfPageTextFragmentPdfium extends PdfPageTextFragment { - _PdfPageTextFragmentPdfium(this.pageText, this.index, this.length, this.bounds, this.charRects); - - final PdfPageText pageText; - - @override - final int index; - @override - final int length; - @override - int get end => index + length; - @override - final PdfRect bounds; - @override - final List charRects; - @override - String get text => pageText.fullText.substring(index, index + length); -} - PdfDest? _pdfDestFromMap(dynamic dest) { if (dest == null) return null; final params = dest['params'] as List; @@ -581,15 +642,3 @@ PdfDest? _pdfDestFromMap(dynamic dest) { params.map((p) => p as double).toList(), ); } - -class _PdfPageTextJs extends PdfPageText { - _PdfPageTextJs({required this.pageNumber, required this.fullText, required this.fragments}); - - @override - final int pageNumber; - - @override - final String fullText; - @override - final List fragments; -} diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 8b0f784b..cc834073 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -383,7 +383,7 @@ class InteractiveViewer extends StatefulWidget { /// To override the default mouse wheel behavior. /// - final void Function(Offset scrollDelta)? onWheelDelta; + final void Function(PointerScrollEvent)? onWheelDelta; // Used as the coefficient of friction in the inertial translation animation. // This value was eyeballed to give a feel similar to Google Photos. @@ -893,7 +893,7 @@ class _InteractiveViewerState extends State with TickerProvid // We can handle mouse-wheel event here for our own purposes if (widget.onWheelDelta != null) { - widget.onWheelDelta!(event.scrollDelta); + widget.onWheelDelta!(event); return; } diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart b/packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart deleted file mode 100644 index 277813b2..00000000 --- a/packages/pdfrx/lib/src/widgets/pdf_page_text_overlay.dart +++ /dev/null @@ -1,649 +0,0 @@ -import 'dart:collection'; - -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; - -import '../../pdfrx.dart'; -import '../utils/double_extensions.dart'; - -/// Function to be notified when the text selection is changed. -/// -/// [selection] is the selected text ranges. -/// If page selection is cleared on page dispose (it means, the page is scrolled out of the view), [selection] is null. -/// Otherwise, [selection] is the selected text ranges. If no selection is made, [selection] is an empty list. -typedef PdfViewerPageTextSelectionChangeCallback = void Function(PdfTextRanges selection); - -/// A widget that displays selectable text on a page. -/// -/// If [PdfDocument.permissions] does not allow copying, the widget does not show anything. -class PdfPageTextOverlay extends StatefulWidget { - const PdfPageTextOverlay({ - required this.selectables, - required this.page, - required this.pageRect, - required this.selectionColor, - required this.enabled, - this.textCursor = SystemMouseCursors.text, - this.onTextSelectionChange, - super.key, - }); - - final SplayTreeMap selectables; - final bool enabled; - final PdfPage page; - final Rect pageRect; - final PdfViewerPageTextSelectionChangeCallback? onTextSelectionChange; - final Color selectionColor; - final MouseCursor textCursor; - - @override - State createState() => _PdfPageTextOverlayState(); - - /// Whether to show debug information. - static bool isDebug = false; -} - -class _PdfPageTextOverlayState extends State { - PdfPageText? _pageText; - List? fragments; - bool selectionShouldBeEnabled = false; - - @override - void initState() { - super.initState(); - _initText(); - } - - @override - void didUpdateWidget(PdfPageTextOverlay oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.page != oldWidget.page) { - _initText(); - } - } - - @override - void dispose() { - _release(); - - super.dispose(); - } - - void _release() { - if (_pageText != null) { - _notifySelectionChange(PdfTextRanges.createEmpty(_pageText!)); - } - } - - void _notifySelectionChange(PdfTextRanges ranges) { - widget.onTextSelectionChange?.call(ranges); - } - - Future _initText() async { - _release(); - final pageText = _pageText = await widget.page.loadText(); - final fragments = []; - if (pageText.fragments.isNotEmpty) { - double y = pageText.fragments[0].bounds.bottom; - int start = 0; - for (int i = 1; i < pageText.fragments.length; i++) { - final fragment = pageText.fragments[i]; - if (!fragment.bounds.bottom.isAlmostIdentical(y, error: .25)) { - fragments.addAll(pageText.fragments.sublist(start, i)); - y = fragment.bounds.bottom; - start = i; - } - } - if (start < pageText.fragments.length) { - fragments.addAll(pageText.fragments.sublist(start, pageText.fragments.length)); - } - } - this.fragments = fragments; - if (mounted) { - setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - if (fragments == null || fragments!.isEmpty || widget.page.document.permissions?.allowsCopying == false) { - return const SizedBox(); - } - final registrar = SelectionContainer.maybeOf(context); - return MouseRegion( - hitTestBehavior: HitTestBehavior.translucent, - cursor: selectionShouldBeEnabled ? widget.textCursor : MouseCursor.defer, - onHover: _onHover, - child: IgnorePointer( - ignoring: !(selectionShouldBeEnabled || _anySelections), - child: _PdfTextWidget(registrar, this), - ), - ); - } - - bool get _anySelections { - if (_pageText == null) return false; - final pageSelection = widget.selectables[_pageText!.pageNumber]; - return pageSelection != null && pageSelection.value.hasSelection; - } - - void _onHover(PointerHoverEvent event) { - final point = event.localPosition.toPdfPoint(page: widget.page, scaledPageSize: widget.pageRect.size); - - final selectionShouldBeEnabled = isPointOnText(point); - if (this.selectionShouldBeEnabled != selectionShouldBeEnabled) { - this.selectionShouldBeEnabled = selectionShouldBeEnabled; - if (mounted) { - setState(() {}); - } - } - } - - bool isPointOnText(PdfPoint point, {double margin = 5}) { - for (final fragment in fragments!) { - if (fragment.bounds.containsPoint(point, margin: margin)) { - return true; - } - } - return false; - } -} - -/// The code is based on the code on [Making a widget selectable](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html#widgets).SelectableRegion.2] -class _PdfTextWidget extends LeafRenderObjectWidget { - const _PdfTextWidget(this._registrar, this._state); - - final SelectionRegistrar? _registrar; - - final _PdfPageTextOverlayState _state; - - @override - RenderObject createRenderObject(BuildContext context) { - final selectable = _PdfTextRenderBox(_state.widget.selectionColor, this); - _state.widget.selectables[_state._pageText!.pageNumber] = selectable; - return selectable; - } - - @override - void updateRenderObject(BuildContext context, _PdfTextRenderBox renderObject) { - renderObject - ..selectionColor = _state.widget.selectionColor - ..registrar = _registrar; - _state.widget.selectables[_state._pageText!.pageNumber] = renderObject; - } -} - -mixin PdfPageTextSelectable implements Selectable { - PdfTextRanges get selectedRanges; -} - -/// The code is based on the code on [Making a widget selectable](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html#widgets).SelectableRegion.2] -class _PdfTextRenderBox extends RenderBox with PdfPageTextSelectable, Selectable, SelectionRegistrant { - _PdfTextRenderBox(this._selectionColor, this._textWidget) - : _geometry = ValueNotifier(_noSelection) { - registrar = _textWidget._registrar; - _geometry.addListener(markNeedsPaint); - } - - final _PdfTextWidget _textWidget; - - static const SelectionGeometry _noSelection = SelectionGeometry(status: SelectionStatus.none, hasContent: true); - - final ValueNotifier _geometry; - - Color _selectionColor; - Color get selectionColor => _selectionColor; - set selectionColor(Color value) { - if (_selectionColor == value) return; - _selectionColor = value; - markNeedsPaint(); - } - - @override - void dispose() { - _geometry.dispose(); - super.dispose(); - } - - Rect get _pageRect => _textWidget._state.widget.pageRect; - PdfPage get _page => _textWidget._state.widget.page; - List get _fragments => _textWidget._state.fragments!; - - @override - late final List boundingBoxes = _fragments - .map((f) => f.bounds.toRect(page: _page, scaledPageSize: size)) - .toList(growable: false); - - @override - bool hitTestSelf(Offset position) { - final point = position.toPdfPoint(page: _page, scaledPageSize: _pageRect.size); - return _textWidget._state.isPointOnText(point); - } - - @override - void performLayout() { - size = _textWidget._state.widget.pageRect.size; - } - - @override - void addListener(VoidCallback listener) => _geometry.addListener(listener); - - @override - void removeListener(VoidCallback listener) => _geometry.removeListener(listener); - - @override - SelectionGeometry get value => _geometry.value; - - Rect _getSelectionHighlightRect() => Offset.zero & size; - - Offset? _start; - Offset? _end; - String? _selectedText; - Rect? _selectedRect; - Size? _sizeOnSelection; - late PdfTextRanges _selectedRanges = PdfTextRanges.createEmpty(_textWidget._state._pageText!); - - @override - PdfTextRanges get selectedRanges => _selectedRanges; - - void _notifySelectionChange() { - _textWidget._state._notifySelectionChange(_selectedRanges); - } - - void _updateGeometry() { - _updateGeometryInternal(); - _notifySelectionChange(); - } - - void _updateGeometryInternal() { - _selectedText = null; - _selectedRect = null; - _selectedRanges = PdfTextRanges.createEmpty(_textWidget._state._pageText!); - - if (_start == null || _end == null) { - _geometry.value = _noSelection; - return; - } - - final renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height); - var selectionRect = Rect.fromPoints(_start!, _end!); - if (renderObjectRect.intersect(selectionRect).isEmpty) { - _geometry.value = _noSelection; - return; - } - selectionRect = !selectionRect.isEmpty ? selectionRect : _getSelectionHighlightRect(); - - final selectionRects = []; - final sb = StringBuffer(); - - int searchLineEnd(int start) { - final lastIndex = _fragments.length - 1; - var last = _fragments[start]; - for (int i = start; i < lastIndex; i++) { - final next = _fragments[i + 1]; - if (last.bounds.bottom != next.bounds.bottom) { - return i + 1; - } - last = next; - } - return _fragments.length; - } - - Iterable<({Rect rect, String text, PdfTextRange range})> enumerateCharRects(int start, int end) sync* { - for (int i = start; i < end; i++) { - final fragment = _fragments[i]; - for (int j = 0; j < fragment.charRects.length; j++) { - yield ( - rect: fragment.charRects[j].toRect(page: _page, scaledPageSize: size), - text: fragment.text.substring(j, j + 1), - range: PdfTextRange(start: fragment.index + j, end: fragment.index + j + 1), - ); - } - } - } - - ({Rect? rect, String text, List ranges}) selectChars(int start, int end, Rect lineSelectRect) { - Rect? rect; - final ranges = []; - final sb = StringBuffer(); - for (final r in enumerateCharRects(start, end)) { - if (!r.rect.intersect(lineSelectRect).isEmpty || r.rect.bottom < lineSelectRect.bottom) { - sb.write(r.text); - ranges.appendRange(r.range); - if (rect == null) { - rect = r.rect; - } else { - rect = rect.expandToInclude(r.rect); - } - } - } - return (rect: rect, text: sb.toString(), ranges: ranges); - } - - int? lastLineEnd; - Rect? lastLineStartRect; - for (int i = 0; i < _fragments.length;) { - final bounds = _fragments[i].bounds.toRect(page: _page, scaledPageSize: size); - final intersects = !selectionRect.intersect(bounds).isEmpty; - if (intersects) { - final lineEnd = searchLineEnd(i); - final chars = selectChars( - lastLineEnd ?? i, - lineEnd, - lastLineStartRect != null ? lastLineStartRect.expandToInclude(selectionRect) : selectionRect, - ); - lastLineStartRect = bounds; - lastLineEnd = i = lineEnd; - if (chars.rect == null) continue; - sb.write(chars.text); - selectionRects.add(chars.rect!); - _selectedRanges.ranges.appendAllRanges(chars.ranges); - } else { - i++; - } - } - if (selectionRects.isEmpty) { - _geometry.value = _noSelection; - return; - } - - final selectedBounds = selectionRects.reduce((a, b) => a.expandToInclude(b)); - _selectedRect = Rect.fromLTRB( - _start?.dx ?? selectedBounds.left, - _start?.dy ?? selectedBounds.top, - _end?.dx ?? selectedBounds.right, - _end?.dy ?? selectedBounds.bottom, - ); - _selectedText = sb.toString(); - - final first = selectionRects.first; - final firstSelectionPoint = SelectionPoint( - localPosition: first.bottomLeft, - lineHeight: first.height, - handleType: TextSelectionHandleType.left, - ); - final last = selectionRects.last; - final secondSelectionPoint = SelectionPoint( - localPosition: last.bottomRight, - lineHeight: last.height, - handleType: TextSelectionHandleType.right, - ); - final bool isReversed; - if (_start!.dy > _end!.dy) { - isReversed = true; - } else if (_start!.dy < _end!.dy) { - isReversed = false; - } else { - isReversed = _start!.dx > _end!.dx; - } - - _sizeOnSelection = size; - _geometry.value = SelectionGeometry( - status: _selectedText!.isNotEmpty ? SelectionStatus.uncollapsed : SelectionStatus.collapsed, - hasContent: true, - startSelectionPoint: isReversed ? secondSelectionPoint : firstSelectionPoint, - endSelectionPoint: isReversed ? firstSelectionPoint : secondSelectionPoint, - selectionRects: selectionRects, - ); - } - - void _selectFragment(Offset point) { - _selectedRanges = PdfTextRanges.createEmpty(_textWidget._state._pageText!); - for (final fragment in _fragments) { - final bounds = fragment.bounds.toRect(page: _page, scaledPageSize: size); - if (bounds.contains(point)) { - _start = bounds.topLeft; - _end = bounds.bottomRight; - _selectedRect = bounds; - _selectedText = fragment.text; - _sizeOnSelection = size; - _geometry.value = SelectionGeometry( - status: _selectedText!.isNotEmpty ? SelectionStatus.uncollapsed : SelectionStatus.collapsed, - hasContent: true, - startSelectionPoint: SelectionPoint( - localPosition: _selectedRect!.bottomLeft, - lineHeight: _selectedRect!.height, - handleType: TextSelectionHandleType.left, - ), - endSelectionPoint: SelectionPoint( - localPosition: _selectedRect!.bottomRight, - lineHeight: _selectedRect!.height, - handleType: TextSelectionHandleType.right, - ), - selectionRects: [bounds], - ); - _selectedRanges.ranges.appendRange(PdfTextRange(start: fragment.index, end: fragment.end)); - return; - } - } - _geometry.value = _noSelection; - } - - @override - SelectionResult dispatchSelectionEvent(SelectionEvent event) { - if (!_textWidget._state.widget.enabled) { - return SelectionResult.none; - } - - var result = SelectionResult.none; - switch (event.type) { - case SelectionEventType.startEdgeUpdate: - case SelectionEventType.endEdgeUpdate: - final renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height); - final point = globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition); - final adjustedPoint = SelectionUtils.adjustDragOffset(renderObjectRect, point); - if (event.type == SelectionEventType.startEdgeUpdate) { - _start = adjustedPoint; - } else { - _end = adjustedPoint; - } - result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point); - break; - case SelectionEventType.clear: - _start = _end = null; - case SelectionEventType.selectAll: - _start = Offset.zero; - _end = Offset.infinite; - case SelectionEventType.selectWord: - _selectFragment(globalToLocal((event as SelectWordSelectionEvent).globalPosition)); - _notifySelectionChange(); - return SelectionResult.none; - case SelectionEventType.granularlyExtendSelection: - result = SelectionResult.end; - final extendSelectionEvent = event as GranularlyExtendSelectionEvent; - // Initialize the offset it there is no ongoing selection. - if (_start == null || _end == null) { - if (extendSelectionEvent.forward) { - _start = _end = Offset.zero; - } else { - _start = _end = Offset.infinite; - } - } - // Move the corresponding selection edge. - final newOffset = extendSelectionEvent.forward ? Offset.infinite : Offset.zero; - if (extendSelectionEvent.isEnd) { - if (newOffset == _end) { - result = extendSelectionEvent.forward ? SelectionResult.next : SelectionResult.previous; - } - _end = newOffset; - } else { - if (newOffset == _start) { - result = extendSelectionEvent.forward ? SelectionResult.next : SelectionResult.previous; - } - _start = newOffset; - } - case SelectionEventType.directionallyExtendSelection: - result = SelectionResult.end; - final extendSelectionEvent = event as DirectionallyExtendSelectionEvent; - // Convert to local coordinates. - final horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx; - final Offset newOffset; - final bool forward; - switch (extendSelectionEvent.direction) { - case SelectionExtendDirection.backward: - case SelectionExtendDirection.previousLine: - forward = false; - // Initialize the offset it there is no ongoing selection. - if (_start == null || _end == null) { - _start = _end = Offset.infinite; - } - // Move the corresponding selection edge. - if (extendSelectionEvent.direction == SelectionExtendDirection.previousLine || horizontalBaseLine < 0) { - newOffset = Offset.zero; - } else { - newOffset = Offset.infinite; - } - case SelectionExtendDirection.nextLine: - case SelectionExtendDirection.forward: - forward = true; - // Initialize the offset it there is no ongoing selection. - if (_start == null || _end == null) { - _start = _end = Offset.zero; - } - // Move the corresponding selection edge. - if (extendSelectionEvent.direction == SelectionExtendDirection.nextLine || - horizontalBaseLine > size.width) { - newOffset = Offset.infinite; - } else { - newOffset = Offset.zero; - } - } - if (extendSelectionEvent.isEnd) { - if (newOffset == _end) { - result = forward ? SelectionResult.next : SelectionResult.previous; - } - _end = newOffset; - } else { - if (newOffset == _start) { - result = forward ? SelectionResult.next : SelectionResult.previous; - } - _start = newOffset; - } - // FIXME: #156/#157 handle new SelectionEventType.selectParagraph (currently only in master channel) - default: // case SelectionEventType.selectParagraph: - _start = _end = null; - } - _updateGeometry(); - return result; - } - - @override - SelectedContent? getSelectedContent() => - value.hasSelection && _selectedText != null ? SelectedContent(plainText: _selectedText!) : null; - - @override - SelectedContentRange? getSelection() { - if (_selectedRanges.ranges.isEmpty) return null; - return SelectedContentRange( - startOffset: _selectedRanges.ranges.first.start, - endOffset: _selectedRanges.ranges.last.end, - ); - } - - @override - int get contentLength => _selectedRanges.pageText.fullText.length; - - LayerLink? _startHandle; - LayerLink? _endHandle; - - @override - void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { - if (_startHandle == startHandle && _endHandle == endHandle) { - return; - } - - _startHandle = startHandle; - _endHandle = endHandle; - // FIXME: pushHandleLayers sometimes called after dispose... - if (debugDisposed != true) { - markNeedsPaint(); - } - } - - @override - void paint(PaintingContext context, Offset offset) { - super.paint(context, offset); - - final scale = _sizeOnSelection != null ? size.width / _sizeOnSelection!.width : 1.0; - if (PdfPageTextOverlay.isDebug) { - for (int i = 0; i < _fragments.length; i++) { - final f = _fragments[i]; - final rect = f.bounds.toRect(page: _page, scaledPageSize: size); - context.canvas.drawRect( - rect.shift(offset), - Paint() - ..style = PaintingStyle.stroke - ..color = Colors.red - ..strokeWidth = 1, - ); - } - - if (_selectedRect != null) { - context.canvas.drawRect( - (_selectedRect! * scale).shift(offset), - Paint() - ..style = PaintingStyle.fill - ..color = Colors.blue.withAlpha(100), - ); - } - } - - if (!_geometry.value.hasSelection) { - return; - } - - for (final rect in _geometry.value.selectionRects) { - context.canvas.drawRect( - (rect * scale).shift(offset), - Paint() - ..style = PaintingStyle.fill - ..color = _selectionColor, - ); - } - - if (_startHandle != null) { - context.pushLayer( - LeaderLayer(link: _startHandle!, offset: offset + (value.startSelectionPoint!.localPosition * scale)) - ..applyTransform(null, Matrix4.diagonal3Values(scale, scale, 1.0)), - (context, offset) {}, - Offset.zero, - ); - } - if (_endHandle != null) { - context.pushLayer( - LeaderLayer(link: _endHandle!, offset: offset + (value.endSelectionPoint!.localPosition * scale)) - ..applyTransform(null, Matrix4.diagonal3Values(scale, scale, 1.0)), - (context, offset) {}, - Offset.zero, - ); - } - - // if (size != _sizeOnSelection) { - // Future.microtask( - // () { - // final sp = _geometry.value.startSelectionPoint; - // final ep = _geometry.value.endSelectionPoint; - // if (sp == null || ep == null) return; - // _sizeOnSelection = size; - // _selectedRect = _selectedRect! * scale; - // _geometry.value = _geometry.value.copyWith( - // startSelectionPoint: SelectionPoint( - // handleType: sp.handleType, - // lineHeight: sp.lineHeight * scale, - // localPosition: sp.localPosition * scale), - // endSelectionPoint: SelectionPoint( - // handleType: ep.handleType, - // lineHeight: ep.lineHeight * scale, - // localPosition: ep.localPosition * scale), - // selectionRects: - // _geometry.value.selectionRects.map((r) => r * scale).toList(), - // ); - // markNeedsPaint(); - // }, - // ); - // return; - // } - } -} diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index 4bb04edc..a91e727b 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -20,11 +20,11 @@ class PdfTextSearcher extends Listenable { Timer? _searchTextTimer; // timer to start search int _searchSession = 0; // current search session - List _matches = const []; + List _matches = const []; List _matchesPageStartIndices = const []; Pattern? _lastSearchPattern; int? _currentIndex; - PdfTextRangeWithFragments? _currentMatch; + PdfPageTextRange? _currentMatch; int? _searchingPageNumber; int? _totalPageCount; bool _isSearching = false; @@ -34,7 +34,7 @@ class PdfTextSearcher extends Listenable { int? get currentIndex => _currentIndex; /// Get the current matches. - List get matches => _matches; + List get matches => _matches; /// Whether there are any matches or not (so far). bool get hasMatches => _currentIndex != null && matches.isNotEmpty; @@ -151,7 +151,7 @@ class PdfTextSearcher extends Listenable { bool goToFirstMatch, ) async { await controller?.useDocument((document) async { - final textMatches = []; + final textMatches = []; final textMatchesPageStartIndex = []; bool first = true; _isSearching = true; @@ -219,7 +219,7 @@ class PdfTextSearcher extends Listenable { } /// Go to the given match. - Future goToMatch(PdfTextRangeWithFragments match) async { + Future goToMatch(PdfPageTextRange match) async { _currentMatch = match; _currentIndex = _matches.indexOf(match); await controller?.ensureVisible( diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8ce698d5..4cc68617 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -4,10 +4,10 @@ import 'dart:collection'; import 'dart:math'; import 'dart:ui' as ui; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; @@ -205,7 +205,7 @@ class PdfViewer extends StatefulWidget { State createState() => _PdfViewerState(); } -class _PdfViewerState extends State with SingleTickerProviderStateMixin { +class _PdfViewerState extends State with SingleTickerProviderStateMixin implements PdfTextSelectionDelegate { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; @@ -225,19 +225,32 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final List _zoomStops = [1.0]; - final _pageImages = {}; - final _pageImageRenderingTimers = {}; - final _pageImagesPartial = {}; - final _cancellationTokens = >{}; - final _pageImagePartialRenderingRequests = {}; + final _imageCache = _PdfPageImageCache(); + final _magnifierImageCache = _PdfPageImageCache(); late final _canvasLinkPainter = _CanvasLinkPainter(this); // Changes to the stream rebuilds the viewer final _updateStream = BehaviorSubject(); - final _selectables = SplayTreeMap(); - Timer? _selectionChangedThrottleTimer; + final _textCache = {}; + final _textSelection = SplayTreeMap(); + Timer? _textSelectionChangedThrottleTimer; + final double _hitTestMargin = 3.0; + + /// The starting/ending point of the text selection. + _TextSelectionPoint? _selA, _selB; + Offset? _textSelectAnchor; + + /// [_textSelA] is the rectangle of the first character in the selected paragraph and + PdfTextSelectionAnchor? _textSelA; + + /// [_textSelB] is the rectangle of the last character in the selected paragraph. + PdfTextSelectionAnchor? _textSelB; + + _TextSelectionPartMoving _selPartMoving = _TextSelectionPartMoving.none; + + Offset? _showTextSelectionContextMenuAt; Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; @@ -264,7 +277,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (oldWidget?.documentRef == widget.documentRef) { if (widget.params.doChangesRequireReload(oldWidget?.params)) { if (widget.params.annotationRenderingMode != oldWidget?.params.annotationRenderingMode) { - _releaseAllImages(); + _imageCache.releaseAllImages(); + _magnifierImageCache.releaseAllImages(); } } return; @@ -279,34 +293,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _onDocumentChanged(); } - void _releaseAllImages() { - for (final timer in _pageImageRenderingTimers.values) { - timer.cancel(); - } - _pageImageRenderingTimers.clear(); - for (final request in _pageImagePartialRenderingRequests.values) { - request.cancel(); - } - _pageImagePartialRenderingRequests.clear(); - for (final image in _pageImages.values) { - image.image.dispose(); - } - _pageImages.clear(); - for (final image in _pageImagesPartial.values) { - image.image.dispose(); - } - _pageImagesPartial.clear(); - } - void _onDocumentChanged() async { _layout = null; - _documentSubscription?.cancel(); _documentSubscription = null; - _selectionChangedThrottleTimer?.cancel(); + _textSelectionChangedThrottleTimer?.cancel(); _stopInteraction(); - _releaseAllImages(); + _imageCache.releaseAllImages(); + _magnifierImageCache.releaseAllImages(); _canvasLinkPainter.resetAll(); + _textCache.clear(); + _clearTextSelections(invalidate: false); _pageNumber = null; _gotoTargetPageNumber = null; _initialized = false; @@ -370,12 +367,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override void dispose() { _documentSubscription?.cancel(); - _selectionChangedThrottleTimer?.cancel(); + _textSelectionChangedThrottleTimer?.cancel(); _interactionEndedTimer?.cancel(); - _cancelAllPendingRenderings(); + _imageCache.cancelAllPendingRenderings(); + _magnifierImageCache.cancelAllPendingRenderings(); _animController.dispose(); widget.documentRef.resolveListenable().removeListener(_onDocumentChanged); - _releaseAllImages(); + _imageCache.releaseAllImages(); + _magnifierImageCache.releaseAllImages(); _canvasLinkPainter.resetAll(); _txController.removeListener(_onMatrixChanged); _controller?._attach(null); @@ -388,7 +387,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onDocumentEvent(PdfDocumentEvent event) { if (event is PdfDocumentPageStatusChangedEvent) { for (final page in event.pages) { - _removeCacheImagesForPage(page.pageNumber); + _imageCache.removeCacheImagesForPage(page.pageNumber); + _magnifierImageCache.removeCacheImagesForPage(page.pageNumber); } _invalidate(); } @@ -414,9 +414,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix child: widget.params.loadingBannerBuilder?.call(context, listenable.bytesDownloaded, listenable.totalBytes), ); } - Widget selectableRegionInjector(Widget child) => - widget.params.selectableRegionInjector?.call(context, child) ?? - (widget.params.enableTextSelection ? SelectionArea(child: child) : child); return Container( color: widget.params.backgroundColor, @@ -426,42 +423,68 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix child: StreamBuilder( stream: _updateStream, builder: (context, snapshot) { - return selectableRegionInjector( - LayoutBuilder( - builder: (context, constraints) { - _updateLayout(Size(constraints.maxWidth, constraints.maxHeight)); - return Stack( - children: [ - iv.InteractiveViewer( - transformationController: _txController, - constrained: false, - boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), - maxScale: widget.params.maxScale, - minScale: minScale, - panAxis: widget.params.panAxis, - panEnabled: widget.params.panEnabled, - scaleEnabled: widget.params.scaleEnabled, - onInteractionEnd: _onInteractionEnd, - onInteractionStart: _onInteractionStart, - onInteractionUpdate: widget.params.onInteractionUpdate, - interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, - onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, - // PDF pages - child: CustomPaint( - foregroundPainter: _CustomPainter.fromFunctions(_customPaint), - size: _layout!.documentSize, + return LayoutBuilder( + builder: (context, constraints) { + final isCopyTextEnabled = _document!.permissions?.allowsCopying != false; + final enableSwipeToSelectText = + widget.params.textSelectionParams?.textSelectionTriggeredBySwipe ?? + shouldTextSelectionTriggeredBySwipe; + final viewSize = Size(constraints.maxWidth, constraints.maxHeight); + _updateLayout(viewSize); + return Stack( + children: [ + iv.InteractiveViewer( + transformationController: _txController, + constrained: false, + boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), + maxScale: widget.params.maxScale, + minScale: minScale, + panAxis: widget.params.panAxis, + panEnabled: widget.params.panEnabled, + scaleEnabled: widget.params.scaleEnabled, + onInteractionEnd: _onInteractionEnd, + onInteractionStart: _onInteractionStart, + onInteractionUpdate: widget.params.onInteractionUpdate, + interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, + onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + // PDF pages + child: MouseRegion( + cursor: SystemMouseCursors.move, + hitTestBehavior: HitTestBehavior.deferToChild, + child: MouseRegion( + cursor: SystemMouseCursors.text, + hitTestBehavior: HitTestBehavior.deferToChild, + child: GestureDetector( + onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, + onDoubleTapDown: widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, + onLongPressStart: widget.params.textSelectionParams?.textLongPress ?? _textLongPress, + onSecondaryTapUp: + widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, + onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, + onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, + onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, + child: CustomPaint( + foregroundPainter: _CustomPainter.fromFunctions( + _paintPages, + hitTestFunction: _hitTestForTextSelection, + ), + size: _layout!.documentSize, + ), + ), ), ), - if (_initialized) ..._buildPageOverlayWidgets(context), - if (_initialized && _canvasLinkPainter.isEnabled) - SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(_viewSize!)), - if (_initialized && widget.params.viewerOverlayBuilder != null) - ...widget.params.viewerOverlayBuilder!(context, _viewSize!, _canvasLinkPainter._handleLinkTap) - .map((e) => SelectionContainer.disabled(child: e)), - ], - ); - }, - ), + ), + if (_initialized) ..._buildPageOverlayWidgets(context), + if (_initialized && _canvasLinkPainter.isEnabled) + SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(viewSize)), + if (_initialized && widget.params.viewerOverlayBuilder != null) + ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap).map( + (e) => SelectionContainer.disabled(child: e), + ), + if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), + ], + ); + }, ); }, ), @@ -591,6 +614,16 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix case LogicalKeyboardKey.arrowRight: _goToManipulated((m) => m.translate(-widget.params.scrollByArrowKey, 0.0)); return true; + case LogicalKeyboardKey.keyA: + if (isCommandKeyPressed) { + selectAllText(); + return true; + } + case LogicalKeyboardKey.keyC: + if (isCommandKeyPressed) { + _copyTextSelection(); + return true; + } } return false; } @@ -765,20 +798,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; List _buildPageOverlayWidgets(BuildContext context) { - _selectables.clear(); - final renderBox = context.findRenderObject(); if (renderBox is! RenderBox) return []; final linkWidgets = []; - final textWidgets = []; final overlayWidgets = []; final targetRect = _getCacheExtentRect(); - final isTextSelectionEnabled = - (widget.params.enableTextSelection || - widget.params.selectableRegionInjector != null || - widget.params.perPageSelectableRegionInjector != null) && - _document!.permissions?.allowsCopying != false; for (int i = 0; i < _document!.pages.length; i++) { final rect = _layout!.pageLayouts[i]; @@ -802,7 +827,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix child: child, onPointerSignal: (event) { if (event is PointerScrollEvent) { - _onWheelDelta(event.scrollDelta); + _onWheelDelta(event); } }, ), @@ -811,31 +836,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - Widget perPageSelectableRegionInjector(Widget child) => - widget.params.perPageSelectableRegionInjector?.call(context, child, page, rectExternal) ?? child; - - if (isTextSelectionEnabled && _document!.permissions?.allowsCopying != false) { - textWidgets.add( - Positioned( - key: Key('#__pageTextOverlay__:${page.pageNumber}'), - left: rectExternal.left, - top: rectExternal.top, - width: rectExternal.width, - height: rectExternal.height, - child: perPageSelectableRegionInjector( - PdfPageTextOverlay( - selectables: _selectables, - enabled: !_isInteractionGoingOn, - page: page, - pageRect: rectExternal, - onTextSelectionChange: _onSelectionChange, - selectionColor: DefaultSelectionStyle.of(context).selectionColor!, - ), - ), - ), - ); - } - final overlay = widget.params.pageOverlaysBuilder?.call(context, rectExternal, page); if (overlay != null && overlay.isNotEmpty) { overlayWidgets.add( @@ -852,40 +852,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - Widget selectableRegionInjector(Widget child) => child; - - return [ - if (textWidgets.isNotEmpty) - selectableRegionInjector( - Listener( - behavior: HitTestBehavior.translucent, - // FIXME: Selectable absorbs wheel events. - onPointerSignal: (event) { - if (event is PointerScrollEvent) { - _onWheelDelta(event.scrollDelta); - } - }, - child: Stack(children: textWidgets), - ), - ), - ...linkWidgets, - ...overlayWidgets, - ]; - } - - void _clearAllTextSelections() { - for (final s in _selectables.values) { - s.dispatchSelectionEvent(const ClearSelectionEvent()); - } + return [...linkWidgets, ...overlayWidgets]; } - void _onSelectionChange(PdfTextRanges selection) { - _selectionChangedThrottleTimer?.cancel(); - _selectionChangedThrottleTimer = Timer(const Duration(milliseconds: 300), () { - if (!mounted || !_selectables.containsKey(selection.pageNumber)) return; - widget.params.onTextSelectionChange?.call( - _selectables.values.map((s) => s.selectedRanges).where((s) => s.isNotEmpty).toList(), - ); + void _onSelectionChange() { + _textSelectionChangedThrottleTimer?.cancel(); + _textSelectionChangedThrottleTimer = Timer(const Duration(milliseconds: 300), () { + if (!mounted) return; + widget.params.textSelectionParams?.onTextSelectionChange?.call(this); }); } @@ -905,34 +879,29 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return Rect.fromPoints(renderBox.globalToLocal(tl), renderBox.globalToLocal(br)); } - void _addCancellationToken(int pageNumber, PdfPageRenderCancellationToken token) { - var tokens = _cancellationTokens.putIfAbsent(pageNumber, () => []); - tokens.add(token); - } - - void _cancelPendingRenderings(int pageNumber) { - final tokens = _cancellationTokens[pageNumber]; - if (tokens != null) { - for (final token in tokens) { - token.cancel(); - } - tokens.clear(); - } - } - - void _cancelAllPendingRenderings() { - for (final pageNumber in _cancellationTokens.keys) { - _cancelPendingRenderings(pageNumber); - } - _cancellationTokens.clear(); - } - /// [_CustomPainter] calls the function to paint PDF pages. - void _customPaint(ui.Canvas canvas, ui.Size size) { + void _paintPages(ui.Canvas canvas, ui.Size size) { if (!_initialized) return; - final targetRect = _getCacheExtentRect(); - final scale = MediaQuery.of(context).devicePixelRatio * _currentZoom; + _paintPagesCustom( + canvas, + cache: _imageCache, + maxImageCacheBytes: widget.params.maxImageBytesCachedOnMemory, + targetRect: _getCacheExtentRect(), + resolutionMultiplier: MediaQuery.of(context).devicePixelRatio, + filterQuality: FilterQuality.low, + ); + } + void _paintPagesCustom( + ui.Canvas canvas, { + required _PdfPageImageCache cache, + required int maxImageCacheBytes, + required Rect targetRect, + double resolutionMultiplier = 1.0, + bool previewEnabled = true, + FilterQuality filterQuality = FilterQuality.high, + }) { + final scale = _currentZoom * resolutionMultiplier; final unusedPageList = []; final dropShadowPaint = widget.params.pageDropShadow?.toPaint()?..style = PaintingStyle.fill; @@ -941,18 +910,18 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final intersection = rect.intersect(targetRect); if (intersection.isEmpty) { final page = _document!.pages[i]; - _cancelPendingRenderings(page.pageNumber); - if (_pageImages.containsKey(i + 1)) { + cache.cancelPendingRenderings(page.pageNumber); + if (cache.pageImages.containsKey(i + 1)) { unusedPageList.add(i + 1); } continue; } final page = _document!.pages[i]; - final realSize = _pageImages[page.pageNumber]; - final partial = _pageImagesPartial[page.pageNumber]; + final previewImage = cache.pageImages[page.pageNumber]; + final partial = cache.pageImagesPartial[page.pageNumber]; - final scaleLimit = + final previewScaleLimit = widget.params.getPageRenderingScale?.call( context, page, @@ -974,12 +943,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - if (realSize != null) { + if (previewEnabled && previewImage != null) { canvas.drawImageRect( - realSize.image, - Rect.fromLTWH(0, 0, realSize.image.width.toDouble(), realSize.image.height.toDouble()), + previewImage.image, + Rect.fromLTWH(0, 0, previewImage.image.width.toDouble(), previewImage.image.height.toDouble()), rect, - Paint()..filterQuality = FilterQuality.high, + Paint()..filterQuality = filterQuality, ); } else { canvas.drawRect( @@ -990,24 +959,41 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - if (realSize == null || realSize.scale != scaleLimit) { - _requestPageImageCached(page, scaleLimit); + if (previewEnabled && (previewImage == null || previewImage.scale != previewScaleLimit)) { + _requestPagePreviewImageCached(cache, page, previewScaleLimit); } final pageScale = scale * max(rect.width / page.width, rect.height / page.height); - if (pageScale > scaleLimit) { - _requestPartialImage(page, scale); + if (pageScale > previewScaleLimit) { + _requestRealSizePartialImage(cache, page, pageScale); } - if (pageScale > scaleLimit && partial != null) { + if (pageScale > previewScaleLimit && partial != null) { canvas.drawImageRect( partial.image, Rect.fromLTWH(0, 0, partial.image.width.toDouble(), partial.image.height.toDouble()), partial.rect, - Paint()..filterQuality = FilterQuality.high, + Paint()..filterQuality = filterQuality, ); } + final selectionColor = + Theme.of(context).textSelectionTheme.selectionColor ?? DefaultSelectionStyle.of(context).selectionColor!; + final text = _getCachedTextOrDelayLoadText(page.pageNumber); + if (text != null) { + final selectionInPage = _textSelection[page.pageNumber]; + if (selectionInPage != null) { + for (final r in selectionInPage.enumerateFragmentBoundingRects()) { + canvas.drawRect( + r.bounds.toRectInDocument(page: page, pageRect: rect), + Paint() + ..color = selectionColor + ..style = PaintingStyle.fill, + ); + } + } + } + if (_canvasLinkPainter.isEnabled) { _canvasLinkPainter.paintLinkHighlights(canvas, rect, page); } @@ -1022,16 +1008,73 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final currentPageNumber = _pageNumber; if (currentPageNumber != null && currentPageNumber > 0) { final currentPage = _document!.pages[currentPageNumber - 1]; - _removeCacheImagesIfCacheBytesExceedsLimit( + cache.removeCacheImagesIfCacheBytesExceedsLimit( unusedPageList, - widget.params.maxImageBytesCachedOnMemory, + maxImageCacheBytes, currentPage, + dist: + (pageNumber) => + (_layout!.pageLayouts[pageNumber - 1].center - + _layout!.pageLayouts[currentPage.pageNumber - 1].center) + .distanceSquared, ); } } } } + /// Loads text for the specified page number. + /// + /// If the text is not loaded yet, it will be loaded asynchronously + /// and [onTextLoaded] callback will be called when the text is loaded. + /// If [onTextLoaded] is not provided and [invalidate] is true, the widget will be rebuilt when the text is loaded. + PdfPageText? _getCachedTextOrDelayLoadText(int pageNumber, {void Function()? onTextLoaded, bool invalidate = true}) { + if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]; + if (onTextLoaded == null && invalidate) { + onTextLoaded = () { + if (mounted) { + setState(() {}); + } + }; + } + _loadTextAsync(pageNumber, onTextLoaded: onTextLoaded); + return null; + } + + Future _loadTextAsync(int pageNumber, {void Function()? onTextLoaded}) async { + if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]!; + return await synchronized(() async { + if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]!; + final page = _document!.pages[pageNumber - 1]; + final text = await page.loadText(); + _textCache[pageNumber] = text; + if (onTextLoaded != null) { + onTextLoaded(); + } + return text; + }); + } + + bool _hitTestForTextSelection(ui.Offset position) { + for (int i = 0; i < _document!.pages.length; i++) { + final pageRect = _layout!.pageLayouts[i]; + if (!pageRect.contains(position)) continue; + final page = _document!.pages[i]; + final text = _getCachedTextOrDelayLoadText( + page.pageNumber, + invalidate: false, + ); // the routine may be called multiple times, we can ignore the chance + if (text == null) continue; + for (final f in text.fragments) { + final rect = f.bounds.toRectInDocument(page: page, pageRect: pageRect).inflate(_hitTestMargin); + if (rect.contains(position)) { + return true; + } + } + } + return false; + } + PdfPageLayout _layoutPages(List pages, PdfViewerParams params) { final width = pages.fold(0.0, (w, p) => max(w, p.width)) + params.margin * 2; @@ -1049,33 +1092,40 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _invalidate() => _updateStream.add(_txController.value); - Future _requestPageImageCached(PdfPage page, double scale) async { + Future _requestPagePreviewImageCached(_PdfPageImageCache cache, PdfPage page, double scale) async { final width = page.width * scale; final height = page.height * scale; if (width < 1 || height < 1) return; // if this is the first time to render the page, render it immediately - if (!_pageImages.containsKey(page.pageNumber)) { - _cachePageImage(page, width, height, scale); + if (!cache.pageImages.containsKey(page.pageNumber)) { + _cachePagePreviewImage(cache, page, width, height, scale); return; } - _pageImageRenderingTimers[page.pageNumber]?.cancel(); + cache.pageImageRenderingTimers[page.pageNumber]?.cancel(); if (!mounted) return; - _pageImageRenderingTimers[page.pageNumber] = Timer( + cache.pageImageRenderingTimers[page.pageNumber] = Timer( _pageImageCachingDelay, - () => _cachePageImage(page, width, height, scale), + () => _cachePagePreviewImage(cache, page, width, height, scale), ); } - Future _cachePageImage(PdfPage page, double width, double height, double scale) async { + Future _cachePagePreviewImage( + _PdfPageImageCache cache, + PdfPage page, + double width, + double height, + double scale, + ) async { if (!mounted) return; - if (_pageImages[page.pageNumber]?.scale == scale) return; + if (cache.pageImages[page.pageNumber]?.scale == scale) return; final cancellationToken = page.createCancellationToken(); - _addCancellationToken(page.pageNumber, cancellationToken); + + cache.addCancellationToken(page.pageNumber, cancellationToken); await synchronized(() async { if (!mounted || cancellationToken.isCanceled) return; - if (_pageImages[page.pageNumber]?.scale == scale) return; + if (cache.pageImages[page.pageNumber]?.scale == scale) return; final img = await page.render( fullWidth: width, fullHeight: height, @@ -1095,24 +1145,24 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix newImage.dispose(); return; } - _pageImages[page.pageNumber]?.dispose(); - _pageImages[page.pageNumber] = newImage; + cache.pageImages[page.pageNumber]?.dispose(); + cache.pageImages[page.pageNumber] = newImage; img.dispose(); _invalidate(); }); } - Future _requestPartialImage(PdfPage page, double scale) async { - _pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); + Future _requestRealSizePartialImage(_PdfPageImageCache cache, PdfPage page, double scale) async { + cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); final cancellationToken = page.createCancellationToken(); - _pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( + cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(_partialImageLoadingDelay, () async { if (!mounted || cancellationToken.isCanceled) return; - final newImage = await _createPartialImage(page, scale, cancellationToken); - if (_pageImagesPartial[page.pageNumber] == newImage) return; - _pageImagesPartial.remove(page.pageNumber)?.dispose(); + final newImage = await _createRealSizePartialImage(cache, page, scale, cancellationToken); + if (cache.pageImagesPartial[page.pageNumber] == newImage) return; + cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); if (newImage != null) { - _pageImagesPartial[page.pageNumber] = newImage; + cache.pageImagesPartial[page.pageNumber] = newImage; } _invalidate(); }), @@ -1120,14 +1170,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - Future<_PdfImageWithScaleAndRect?> _createPartialImage( + Future<_PdfImageWithScaleAndRect?> _createRealSizePartialImage( + _PdfPageImageCache cache, PdfPage page, double scale, PdfPageRenderCancellationToken cancellationToken, ) async { final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; final rect = pageRect.intersect(_visibleRect); - final prev = _pageImagesPartial[page.pageNumber]; + final prev = cache.pageImagesPartial[page.pageNumber]; if (prev?.rect == rect && prev?.scale == scale) return prev; if (rect.width < 1 || rect.height < 1) return null; final inPageRect = rect.translate(-pageRect.left, -pageRect.top); @@ -1156,50 +1207,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return result; } - void _removeCacheImagesForPage(int pageNumber) { - final removed = _pageImages.remove(pageNumber); - if (removed != null) { - removed.image.dispose(); - } - final removedPartial = _pageImagesPartial.remove(pageNumber); - if (removedPartial != null) { - removedPartial.image.dispose(); - } - } - - void _removeCacheImagesIfCacheBytesExceedsLimit(List pageNumbers, int acceptableBytes, PdfPage currentPage) { - double dist(int pageNumber) { - return (_layout!.pageLayouts[pageNumber - 1].center - _layout!.pageLayouts[currentPage.pageNumber - 1].center) - .distanceSquared; - } - - pageNumbers.sort((a, b) => dist(b).compareTo(dist(a))); - int getBytesConsumed(ui.Image? image) => image == null ? 0 : (image.width * image.height * 4).toInt(); - int bytesConsumed = - _pageImages.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)) + - _pageImagesPartial.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)); - for (final key in pageNumbers) { - final removed = _pageImages.remove(key); - if (removed != null) { - bytesConsumed -= getBytesConsumed(removed.image); - removed.image.dispose(); - } - final removedPartial = _pageImagesPartial.remove(key); - if (removedPartial != null) { - bytesConsumed -= getBytesConsumed(removedPartial.image); - removedPartial.image.dispose(); - } - if (bytesConsumed <= acceptableBytes) { - break; - } - } - } - - void _onWheelDelta(Offset delta) { + void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); final m = _txController.value.clone(); - final dx = -delta.dx * widget.params.scrollByMouseWheel!; - final dy = -delta.dy * widget.params.scrollByMouseWheel!; + final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel!; + final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel!; if (widget.params.scrollHorizontallyByMouseWheel) { m.translate(dy, dx); } else { @@ -1572,6 +1584,670 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Offset? _documentToGlobal(Offset document) => _localToGlobal( document.scale(_currentZoom, _currentZoom).translate(_txController.value.xZoomed, _txController.value.yZoomed), ); + + void _textTap(TapDownDetails details) { + if (_isInteractionGoingOn) return; + _clearTextSelections(); + } + + void _textDoubleTap(TapDownDetails details) {} + + void _textLongPress(LongPressStartDetails details) { + if (_isInteractionGoingOn) return; + selectWord(details.localPosition); + } + + void _textSecondaryTapUp(TapUpDetails details) { + _showTextSelectionContextMenuAt = details.globalPosition; + _invalidate(); + } + + void _onTextPanStart(DragStartDetails details) { + if (_isInteractionGoingOn) return; + _selPartMoving = _TextSelectionPartMoving.free; + _selA = _findTextAndIndexForPoint(details.localPosition); + _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); + _selB = null; + _showTextSelectionContextMenuAt = null; + _updateTextSelection(); + } + + void _onTextPanUpdate(DragUpdateDetails details) { + _updateTextSelectRectTo(details.localPosition); + } + + void _onTextPanEnd(DragEndDetails details) { + _updateTextSelectRectTo(details.localPosition); + _selPartMoving = _TextSelectionPartMoving.none; + _invalidate(); + } + + void _updateTextSelectRectTo(Offset panTo) { + if (_selPartMoving != _TextSelectionPartMoving.free) return; + final to = _findTextAndIndexForPoint( + panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), + ); + if (to != null) { + _selB = to; + _updateTextSelection(); + } + } + + void _updateTextSelection({bool invalidate = true}) { + final a = _selA; + final b = _selB; + if (a == null || b == null) { + _textSelA = _textSelB = null; + _textSelection.clear(); + } else if (a.text.pageNumber == b.text.pageNumber) { + final page = _document!.pages[a.text.pageNumber - 1]; + final pageRect = _layout!.pageLayouts[a.text.pageNumber - 1]; + final range = a.text.getRangeFromAB(a.index, b.index); + _textSelA = PdfTextSelectionAnchor( + a.text.charRects[range.start].toRectInDocument(page: page, pageRect: pageRect), + range.firstFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.a, + a.text, + a.index, + ); + _textSelB = PdfTextSelectionAnchor( + a.text.charRects[range.end - 1].toRectInDocument(page: page, pageRect: pageRect), + range.lastFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.b, + a.text, + b.index, + ); + _textSelection.clear(); + _textSelection[page.pageNumber] = range; + } else { + final first = a.text.pageNumber < b.text.pageNumber ? a : b; + final second = a.text.pageNumber < b.text.pageNumber ? b : a; + final rangeA = PdfPageTextRange(pageText: first.text, start: first.index, end: first.text.charRects.length); + _textSelA = PdfTextSelectionAnchor( + first.text.charRects[first.index].toRectInDocument( + page: _document!.pages[first.text.pageNumber - 1], + pageRect: _layout!.pageLayouts[first.text.pageNumber - 1], + ), + rangeA.firstFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.a, + first.text, + first.index, + ); + final rangeB = PdfPageTextRange(pageText: second.text, start: 0, end: second.index + 1); + _textSelB = PdfTextSelectionAnchor( + second.text.charRects[second.index].toRectInDocument( + page: _document!.pages[second.text.pageNumber - 1], + pageRect: _layout!.pageLayouts[second.text.pageNumber - 1], + ), + rangeB.lastFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.b, + second.text, + second.index, + ); + _textSelection.clear(); + _textSelection[first.text.pageNumber] = rangeA; + _textSelection[second.text.pageNumber] = rangeB; + for (int i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { + final text = _getCachedTextOrDelayLoadText(i, onTextLoaded: () => _updateTextSelection()); + if (text == null) continue; + _textSelection[i] = PdfPageTextRange(pageText: text, start: 0, end: text.charRects.length); + } + } + + if (invalidate) { + _notifyTextSelectionChange(); + } + } + + /// [point] is in the document coordinates. + _TextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { + if (point == null) return null; + for (int pageIndex = 0; pageIndex < _document!.pages.length; pageIndex++) { + final pageRect = _layout!.pageLayouts[pageIndex]; + if (!pageRect.contains(point)) { + continue; + } + final page = _document!.pages[pageIndex]; + final text = _getCachedTextOrDelayLoadText(pageIndex + 1, onTextLoaded: () => _updateTextSelection()); + if (text == null) continue; + final pt = point.translate(-pageRect.left, -pageRect.top).toPdfPoint(page: page, scaledPageSize: pageRect.size); + double d2Min = double.infinity; + int? closestIndex; + for (int i = 0; i < text.charRects.length; i++) { + final charRect = text.charRects[i]; + if (charRect.containsPoint(pt)) { + return _TextSelectionPoint(text, i, point); + } + final d2 = charRect.distanceSquaredTo(pt); + if (d2 < d2Min) { + d2Min = d2; + closestIndex = i; + } + } + if (closestIndex != null && d2Min <= hitTestMargin * hitTestMargin) { + return _TextSelectionPoint(text, closestIndex, point); + } + } + return null; + } + + void _notifyTextSelectionChange() { + _onSelectionChange(); + _invalidate(); + } + + List _placeTextSelectionWidgets(BuildContext context, Size viewSize, bool isCopyTextEnabled) { + final renderBox = _renderBox; + if (renderBox == null || _textSelA == null || _textSelB == null || _textSelection.isEmpty) { + return []; + } + + final rectA = _documentToRenderBox(_textSelA!.rect, renderBox); + final rectB = _documentToRenderBox(_textSelB!.rect, renderBox); + if (rectA == null || rectB == null) { + return []; + } + + final enableSelectionHandles = widget.params.textSelectionParams?.enableSelectionHandles ?? isMobile; + + double? aLeft, aTop, aRight, aBottom; + double? bLeft, bTop, bRight; + Widget? anchorA, anchorB; + + if (enableSelectionHandles && _selPartMoving != _TextSelectionPartMoving.free) { + final builder = widget.params.textSelectionParams?.buildSelectionHandle ?? _buildDefaultSelectionHandle; + + if (_textSelA != null) { + switch (_textSelA!.direction) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + aRight = viewSize.width - rectA.left; + aBottom = viewSize.height - rectA.top; + case PdfTextDirection.rtl: + aLeft = rectA.right; + aBottom = viewSize.height - rectA.top; + case PdfTextDirection.vrtl: + aLeft = rectA.right; + aBottom = viewSize.height - rectA.top; + } + anchorA = builder(context, _textSelA!); + } + if (_textSelB != null) { + switch (_textSelB!.direction) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + bLeft = rectB.right; + bTop = rectB.bottom; + case PdfTextDirection.rtl: + bRight = viewSize.width - rectB.left; + bTop = rectB.bottom; + case PdfTextDirection.vrtl: + bRight = viewSize.width - rectB.left; + bTop = rectB.bottom; + } + anchorB = builder(context, _textSelB!); + } + } + + // Determines whether the widget is [Positioned] or [Align] to avoid unnecessary wrapping. + bool isPositionalWidget(Widget? widget) => widget != null && (widget is Positioned || widget is Align); + + Widget? layoutWidget(Widget? widget, {Offset? position, Size? size}) { + if (widget == null || isPositionalWidget(widget)) return widget; + if (position != null) return Positioned(left: position.dx, top: position.dy, child: widget); + + const safeArea = 60.0; + final selRect = rectA.expandToInclude(rectB).inflate(safeArea); + + return Positioned(left: 8, top: 8, child: widget); + } + + Widget? magnifier; + final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); + final magnifierEnabled = + (magnifierParams.enabled ?? isMobile) && + (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? (_currentZoom < magnifierParams.scale)); + if (magnifierEnabled) { + final textAnchor = switch (_selPartMoving) { + _TextSelectionPartMoving.a => _selA!.index < _selB!.index ? _textSelA : _textSelB, + _TextSelectionPartMoving.b => _selA!.index < _selB!.index ? _textSelB : _textSelA, + _ => null, + }; + final magCenter = textAnchor?.anchorPoint; + if (magCenter != null && textAnchor!.rect.height < magnifierParams.height * .7) { + final magnifierMain = _buildMagnifier(context, magCenter, magnifierParams); + final builder = magnifierParams.builder ?? _buildMagnifierDecoration; + magnifier = layoutWidget( + builder(context, magnifierParams, magnifierMain), + size: Size(magnifierParams.width, magnifierParams.height), + ); + } + } + + bool showContextMenu = false; + if (_showTextSelectionContextMenuAt != null) { + showContextMenu = true; + } else if (isMobile && _textSelA != null && _textSelB != null && _selPartMoving == _TextSelectionPartMoving.none) { + // Show context menu on mobile when selection is not moving. + showContextMenu = true; + } + + Widget? contextMenu; + if (showContextMenu) { + final position = + _showTextSelectionContextMenuAt != null ? renderBox.globalToLocal(_showTextSelectionContextMenuAt!) : null; + final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; + contextMenu = layoutWidget( + ctxMenuBuilder(context, _textSelA!, _textSelB!, this, position, () { + _showTextSelectionContextMenuAt = null; + _invalidate(); + }), + position: position, + ); + } + + return [ + if (anchorA != null) + Positioned( + left: aLeft, + top: aTop, + right: aRight, + bottom: aBottom, + child: MouseRegion( + cursor: SystemMouseCursors.move, + child: GestureDetector( + onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPartMoving.a, details), + onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPartMoving.a, details), + onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPartMoving.a, details), + child: anchorA, + ), + ), + ), + if (anchorB != null) + Positioned( + left: bLeft, + top: bTop, + right: bRight, + child: MouseRegion( + cursor: SystemMouseCursors.move, + child: GestureDetector( + onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPartMoving.b, details), + onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPartMoving.b, details), + onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPartMoving.b, details), + child: anchorB, + ), + ), + ), + if (magnifier != null) magnifier, + if (contextMenu != null) contextMenu, + ]; + } + + Widget? _buildDefaultSelectionHandle(BuildContext context, PdfTextSelectionAnchor anchor) { + return Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.black, width: 2), + ), + ); + } + + Widget _buildMagnifier(BuildContext context, Offset docPos, PdfViewerSelectionMagnifierParams magnifierParams) { + final magScale = magnifierParams.scale; + return LayoutBuilder( + builder: (context, constraints) { + return ClipRect( + clipBehavior: Clip.antiAlias, + child: CustomPaint( + painter: _CustomPainter.fromFunctions((canvas, size) { + canvas.save(); + canvas.scale(magScale); + canvas.translate(-docPos.dx + size.width / 2 / magScale, -docPos.dy + size.height / 2 / magScale); + _paintPagesCustom( + canvas, + cache: _magnifierImageCache, + maxImageCacheBytes: + widget.params.textSelectionParams?.magnifier?.maxImageBytesCachedOnMemory ?? + PdfViewerSelectionMagnifierParams.defaultMaxImageBytesCachedOnMemory, + targetRect: Rect.fromCenter( + center: docPos, + width: size.width / magScale, + height: size.height / magScale, + ), + resolutionMultiplier: magScale, + previewEnabled: false, + filterQuality: FilterQuality.low, + ); + canvas.restore(); + canvas.drawLine( + Offset(size.width / 2, size.height / 4), + Offset(size.width / 2, size.height * 3 / 4), + Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke, + ); + + if (magnifierParams.paintMagnifierContent != null) { + magnifierParams.paintMagnifierContent!(canvas, size, docPos, magnifierParams); + } else { + canvas.drawLine( + Offset(size.width / 4, size.height / 2), + Offset(size.width * 3 / 4, size.height / 2), + Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke, + ); + } + }), + size: Size(constraints.maxWidth, constraints.maxHeight), + ), + ); + }, + ); + } + + Widget _buildMagnifierDecoration(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget child) { + return Container( + width: params.width, + height: params.height, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], + ), + child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), + ); + } + + Widget? _buildTextSelectionContextMenu( + BuildContext context, + PdfTextSelectionAnchor a, + PdfTextSelectionAnchor b, + PdfTextSelectionDelegate textSelectionDelegate, + Offset? rightClickPosition, + void Function() dismissContextMenu, + ) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], + ), + padding: const EdgeInsets.all(2), + child: Column( + children: [ + RawMaterialButton( + visualDensity: VisualDensity.compact, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: + textSelectionDelegate.isCopyAllowed + ? () { + if (textSelectionDelegate.isCopyAllowed) { + textSelectionDelegate.copyTextSelection(); + } + } + : null, + child: Text( + 'Copy', + style: TextStyle(color: textSelectionDelegate.isCopyAllowed ? Colors.black : Colors.grey), + ), + ), + if (textSelectionDelegate.isCopyAllowed) + RawMaterialButton( + visualDensity: VisualDensity.compact, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + textSelectionDelegate.selectAllText(); + }, + child: const Text('Select All'), + ), + ], + ), + ); + } + + void _onSelectionHandlePanStart(_TextSelectionPartMoving handle, DragStartDetails details) { + if (_isInteractionGoingOn) return; + _selPartMoving = handle; + final position = _globalToDocument(details.globalPosition); + final anchor = Offset(_txController.value.x, _txController.value.y); + if (_selPartMoving == _TextSelectionPartMoving.a) { + _textSelectAnchor = anchor + _textSelA!.rect.topLeft - position!; + final a = _findTextAndIndexForPoint(_textSelA!.rect.center); + if (a == null) return; + _selA = a; + } else if (_selPartMoving == _TextSelectionPartMoving.b) { + _textSelectAnchor = anchor + _textSelB!.rect.bottomRight - position!; + final b = _findTextAndIndexForPoint(_textSelB!.rect.center); + if (b == null) return; + _selB = b; + } else { + return; + } + _showTextSelectionContextMenuAt = null; + _updateTextSelection(); + } + + void _updateSelectionHandlesPan(Offset? panTo) { + if (panTo == null) return; + if (_selPartMoving == _TextSelectionPartMoving.a) { + final a = _findTextAndIndexForPoint( + panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), + ); + if (a == null) return; + _selA = a; + } else if (_selPartMoving == _TextSelectionPartMoving.b) { + final b = _findTextAndIndexForPoint( + panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), + ); + if (b == null) return; + _selB = b; + } else { + return; + } + _updateTextSelection(); + } + + void _onSelectionHandlePanUpdate(_TextSelectionPartMoving handle, DragUpdateDetails details) { + if (_isInteractionGoingOn) return; + _showTextSelectionContextMenuAt = null; + _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + } + + void _onSelectionHandlePanEnd(_TextSelectionPartMoving handle, DragEndDetails details) { + if (_isInteractionGoingOn) return; + _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + _selPartMoving = _TextSelectionPartMoving.none; + } + + void _clearTextSelections({bool invalidate = true}) { + _selA = _selB = null; + _textSelA = _textSelB = null; + _showTextSelectionContextMenuAt = null; + _updateTextSelection(invalidate: invalidate); + } + + @override + Future clearTextSelection() async => _clearTextSelections(); + + @override + String get selectedText => _textSelection.values.map((p) => p.text).join(); + + @override + List get selectedTextRange => _textSelection.values.toList(); + + @override + bool get isCopyAllowed => _document!.permissions?.allowsCopying != false; + + @override + Future selectAllText() async { + if (_document!.pages.isEmpty && _layout != null) return; + final textSelection = SplayTreeMap(); + for (int i = 1; i <= _document!.pages.length; i++) { + final text = await _loadTextAsync(i); + textSelection[i] = PdfPageTextRange(pageText: text, start: 0, end: text.fullText.length); + } + _selA = _findTextAndIndexForPoint( + textSelection[1]!.pageText.charRects.first.center.toOffsetInDocument( + page: _document!.pages[0], + pageRect: _layout!.pageLayouts[0], + ), + ); + _selB = _findTextAndIndexForPoint( + textSelection[_document!.pages.length]!.pageText.charRects.last.center.toOffsetInDocument( + page: _document!.pages.last, + pageRect: _layout!.pageLayouts.last, + ), + ); + _showTextSelectionContextMenuAt = null; + _updateTextSelection(); + } + + @override + Future selectWord(Offset offset) async { + _textSelection.clear(); + for (int i = 0; i < _document!.pages.length; i++) { + final pageRect = _layout!.pageLayouts[i]; + if (!pageRect.contains(offset)) { + continue; + } + + final text = await _loadTextAsync(i + 1); + final page = _document!.pages[i]; + final point = offset + .translate(-pageRect.left, -pageRect.top) + .toPdfPoint(page: page, scaledPageSize: pageRect.size); + final f = text.fragments.firstWhereOrNull((f) => f.bounds.containsPoint(point)); + if (f == null) { + continue; + } + final range = _textSelection[i + 1] = PdfPageTextRange(pageText: text, start: f.index, end: f.end); + final selectionRect = f.bounds.toRectInDocument(page: page, pageRect: pageRect); + _selA = _TextSelectionPoint( + text, + f.index, + text.charRects[f.index].center.toOffsetInDocument(page: page, pageRect: pageRect), + ); + _selB = _TextSelectionPoint( + text, + f.end - 1, + text.charRects[f.end - 1].center.toOffsetInDocument(page: page, pageRect: pageRect), + ); + _textSelA = PdfTextSelectionAnchor( + selectionRect, + range.pageText.getFragmentForTextIndex(range.start)?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.a, + text, + _selA!.index, + ); + _textSelB = _textSelA!.copyWith(type: PdfTextSelectionAnchorType.b, index: _selB!.index); + _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); + break; + } + _notifyTextSelectionChange(); + } + + Future _copyTextSelection() async { + if (_document!.permissions?.allowsCopying == false) return false; + setClipboardData(selectedText); + return true; + } + + @override + Future copyTextSelection() async { + final result = await _copyTextSelection(); + _clearTextSelections(); + return result; + } +} + +class _PdfPageImageCache { + final pageImages = {}; + final pageImageRenderingTimers = {}; + final pageImagesPartial = {}; + final cancellationTokens = >{}; + final pageImagePartialRenderingRequests = {}; + + void addCancellationToken(int pageNumber, PdfPageRenderCancellationToken token) { + var tokens = cancellationTokens.putIfAbsent(pageNumber, () => []); + tokens.add(token); + } + + void releaseAllImages() { + for (final timer in pageImageRenderingTimers.values) { + timer.cancel(); + } + pageImageRenderingTimers.clear(); + for (final request in pageImagePartialRenderingRequests.values) { + request.cancel(); + } + pageImagePartialRenderingRequests.clear(); + for (final image in pageImages.values) { + image.image.dispose(); + } + pageImages.clear(); + for (final image in pageImagesPartial.values) { + image.image.dispose(); + } + pageImagesPartial.clear(); + } + + void cancelPendingRenderings(int pageNumber) { + final tokens = cancellationTokens[pageNumber]; + if (tokens != null) { + for (final token in tokens) { + token.cancel(); + } + tokens.clear(); + } + } + + void cancelAllPendingRenderings() { + for (final pageNumber in cancellationTokens.keys) { + cancelPendingRenderings(pageNumber); + } + cancellationTokens.clear(); + } + + void removeCacheImagesForPage(int pageNumber) { + final removed = pageImages.remove(pageNumber); + if (removed != null) { + removed.image.dispose(); + } + final removedPartial = pageImagesPartial.remove(pageNumber); + if (removedPartial != null) { + removedPartial.image.dispose(); + } + } + + void removeCacheImagesIfCacheBytesExceedsLimit( + List pageNumbers, + int acceptableBytes, + PdfPage currentPage, { + required double Function(int pageNumber) dist, + }) { + pageNumbers.sort((a, b) => dist(b).compareTo(dist(a))); + int getBytesConsumed(ui.Image? image) => image == null ? 0 : (image.width * image.height * 4).toInt(); + int bytesConsumed = + pageImages.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)) + + pageImagesPartial.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)); + for (final key in pageNumbers) { + final removed = pageImages.remove(key); + if (removed != null) { + bytesConsumed -= getBytesConsumed(removed.image); + removed.image.dispose(); + } + final removedPartial = pageImagesPartial.remove(key); + if (removedPartial != null) { + bytesConsumed -= getBytesConsumed(removedPartial.image); + removedPartial.image.dispose(); + } + if (bytesConsumed <= acceptableBytes) { + break; + } + } + } } class _PdfPartialImageRenderingRequest { @@ -1611,6 +2287,97 @@ class _PdfViewerTransformationController extends TransformationController { } } +/// What selection part is moving by mouse-dragging/finger-panning. +enum _TextSelectionPartMoving { none, free, a, b } + +@immutable +class _TextSelectionPoint { + const _TextSelectionPoint(this.text, this.index, this.point); + final PdfPageText text; + final int index; + final Offset point; + + @override + String toString() => '$_TextSelectionPoint(text: $text, index: $index, point: $point)'; +} + +/// Represents the anchor point of the text selection. +/// +/// It contains the rectangle of the anchor point, the text direction, and the type of the anchor (A or B). +@immutable +class PdfTextSelectionAnchor { + const PdfTextSelectionAnchor(this.rect, this.direction, this.type, this.page, this.index); + + /// The rectangle of the character, on which the anchor is associated to. + final Rect rect; + + /// The text direction of the anchored character. + final PdfTextDirection direction; + + /// The type of the anchor, either [PdfTextSelectionAnchorType.a] or [PdfTextSelectionAnchorType.b]. + final PdfTextSelectionAnchorType type; + + /// The page text on which the anchor is associated to. + final PdfPageText page; + + /// The index of the character in [page]. + /// + /// Please note that the index is always inclusive, even for the end anchor (B). + final int index; + + /// The point of the anchor in the document coordinates, which is an apex of [rect] depending on + /// the [direction] and [type]. + Offset get anchorPoint { + switch (direction) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + return type == PdfTextSelectionAnchorType.a ? rect.topLeft : rect.bottomRight; + case PdfTextDirection.rtl: + return type == PdfTextSelectionAnchorType.a ? rect.topRight : rect.bottomLeft; + case PdfTextDirection.vrtl: + return type == PdfTextSelectionAnchorType.a ? rect.topRight : rect.bottomLeft; + } + } + + /// Copies the current instance with the given parameters. + PdfTextSelectionAnchor copyWith({ + Rect? rect, + PdfTextDirection? direction, + PdfTextSelectionAnchorType? type, + PdfPageText? page, + int? index, + }) { + return PdfTextSelectionAnchor( + rect ?? this.rect, + direction ?? this.direction, + type ?? this.type, + page ?? this.page, + index ?? this.index, + ); + } + + @override + operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! PdfTextSelectionAnchor) return false; + return rect == other.rect && + direction == other.direction && + type == other.type && + page == other.page && + index == other.index; + } + + @override + int get hashCode { + return rect.hashCode ^ direction.hashCode ^ type.hashCode ^ page.hashCode ^ index.hashCode; + } +} + +/// Defines the type of the text selection anchor. +/// +/// It can be either [a] or [b], which represents the start and end of the selection respectively. +enum PdfTextSelectionAnchorType { a, b } + /// Defines page layout. class PdfPageLayout { PdfPageLayout({required this.pageLayouts, required this.documentSize}); @@ -1980,11 +2747,17 @@ class PdfViewerController extends ValueListenable { /// Provided to workaround certain widgets eating wheel events. Use with [Listener.onPointerSignal]. void handlePointerSignalEvent(PointerSignalEvent event) { if (event is PointerScrollEvent) { - _state._onWheelDelta(event.scrollDelta); + _state._onWheelDelta(event); } } + /// Invalidates the current Widget display state. + /// + /// Almost identical to `setState` but can be called outside the state. void invalidate() => _state._invalidate(); + + /// The text selection delegate. + PdfTextSelectionDelegate get textSelectionDelegate => _state; } /// [PdfViewerController.calcFitZoomMatrices] returns the list of this class. @@ -2076,13 +2849,20 @@ extension RectExt on Rect { /// Create a [CustomPainter] from a paint function. class _CustomPainter extends CustomPainter { /// Create a [CustomPainter] from a paint function. - const _CustomPainter.fromFunctions(this.paintFunction); + const _CustomPainter.fromFunctions(this.paintFunction, {this.hitTestFunction}); final void Function(ui.Canvas canvas, ui.Size size) paintFunction; + final bool Function(ui.Offset position)? hitTestFunction; @override void paint(ui.Canvas canvas, ui.Size size) => paintFunction(canvas, size); @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + + @override + bool hitTest(ui.Offset position) { + if (hitTestFunction == null) return false; + return hitTestFunction!(position); + } } Widget _defaultErrorBannerBuilder( @@ -2156,7 +2936,7 @@ class _CanvasLinkPainter { return true; } } - _state._clearAllTextSelections(); + _state._clearTextSelections(); return false; } @@ -2210,7 +2990,7 @@ class _CanvasLinkPainter { ..style = PaintingStyle.fill; for (final link in links) { for (final rect in link.rects) { - final rectLink = rect.toRectInPageRect(page: page, pageRect: pageRect); + final rectLink = rect.toRectInDocument(page: page, pageRect: pageRect); canvas.drawRect(rectLink, paint); } } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index ee58bd61..9af4a220 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -22,15 +22,21 @@ class PdfViewerParams { this.useAlternativeFitScaleAsMinScale = true, this.panAxis = PanAxis.free, this.boundaryMargin, - this.annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + this.annotationRenderingMode = + PdfAnnotationRenderingMode.annotationAndForms, this.limitRenderingCache = true, this.pageAnchor = PdfPageAnchor.top, this.pageAnchorEnd = PdfPageAnchor.bottom, this.onePassRenderingScaleThreshold = 200 / 72, - this.enableTextSelection = false, + this.textSelectionParams, this.matchTextColor, this.activeMatchTextColor, - this.pageDropShadow = const BoxShadow(color: Colors.black54, blurRadius: 4, spreadRadius: 2, offset: Offset(2, 2)), + this.pageDropShadow = const BoxShadow( + color: Colors.black54, + blurRadius: 4, + spreadRadius: 2, + offset: Offset(2, 2), + ), this.panEnabled = true, this.scaleEnabled = true, this.onInteractionEnd, @@ -60,9 +66,6 @@ class PdfViewerParams { this.linkWidgetBuilder, this.pagePaintCallbacks, this.pageBackgroundPaintCallbacks, - this.onTextSelectionChange, - this.selectableRegionInjector, - this.perPageSelectableRegionInjector, this.onKey, this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.forceReload = false, @@ -184,17 +187,8 @@ class PdfViewerParams { /// If you want more granular control for each page, use [getPageRenderingScale]. final double onePassRenderingScaleThreshold; - /// Enable text selection on pages. - /// - /// The default is false. - /// If it is true, the text selection is enabled by injecting [SelectionArea] - /// internally. - /// - /// Basically, you can enable text selection by setting one (or more) of the following parameters: - /// - [enableTextSelection] to enable [SelectionArea] on the viewer - /// - [selectableRegionInjector] to inject your own [SelectableRegion] on the viewer - /// - [perPageSelectableRegionInjector] to inject your own [SelectableRegion] on each page - final bool enableTextSelection; + /// Parameters for text selection. + final PdfTextSelectionParams? textSelectionParams; /// Color for text search match. /// @@ -365,7 +359,7 @@ class PdfViewerParams { /// Add overlays to the viewer. /// /// This function is to generate widgets on PDF viewer's overlay [Stack]. - /// The widgets can be layed out using layout widgets such as [Positioned] and [Align]. + /// The widgets can be laid out using layout widgets such as [Positioned] and [Align]. /// /// The most typical use case is to add scroll thumbs to the viewer. /// The following fragment illustrates how to add vertical and horizontal scroll thumbs: @@ -480,35 +474,6 @@ class PdfViewerParams { /// For the detail usage, see [PdfViewerPagePaintCallback]. final List? pageBackgroundPaintCallbacks; - /// Function to be notified when the text selection is changed. - final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; - - /// Function to inject [SelectionArea] or [SelectableRegion] to customize text selection. - /// - /// It can be also used to "remove" the text selection feature by returning the child widget as it is. - /// Furthermore, it can be used to customize the text selection feature by returning a custom widget. - /// - /// Basically, you can enable text selection by setting one (or more) of the following parameters: - /// - [enableTextSelection] to enable [SelectionArea] on the viewer - /// - [selectableRegionInjector] to inject your own [SelectableRegion] on the viewer - /// - [perPageSelectableRegionInjector] to inject your own [SelectableRegion] on each page - /// - /// You can even enable both of [selectableRegionInjector] and [perPageSelectableRegionInjector] at the same time. - final PdfSelectableRegionInjector? selectableRegionInjector; - - /// Function to inject [SelectionArea] or [SelectableRegion] to customize text selection on each page. - /// - /// It can be also used to "remove" the text selection feature by returning the child widget as it is. - /// Furthermore, it can be used to customize the text selection feature by returning a custom widget. - /// - /// Basically, you can enable text selection by setting one (or more) of the following parameters: - /// - [enableTextSelection] to enable [SelectionArea] on the viewer - /// - [selectableRegionInjector] to inject your own [SelectableRegion] on the viewer - /// - [perPageSelectableRegionInjector] to inject your own [SelectableRegion] on each page - /// - /// You can even enable both of [selectableRegionInjector] and [perPageSelectableRegionInjector] at the same time. - final PdfPerPageSelectableRegionInjector? perPageSelectableRegionInjector; - /// Function to handle key events. /// /// See [PdfViewerOnKeyCallback] for the details. @@ -535,23 +500,27 @@ class PdfViewerParams { other.backgroundColor != backgroundColor || other.maxScale != maxScale || other.minScale != minScale || - other.useAlternativeFitScaleAsMinScale != useAlternativeFitScaleAsMinScale || + other.useAlternativeFitScaleAsMinScale != + useAlternativeFitScaleAsMinScale || other.panAxis != panAxis || other.boundaryMargin != boundaryMargin || other.annotationRenderingMode != annotationRenderingMode || other.limitRenderingCache != limitRenderingCache || other.pageAnchor != pageAnchor || other.pageAnchorEnd != pageAnchorEnd || - other.onePassRenderingScaleThreshold != onePassRenderingScaleThreshold || - other.enableTextSelection != enableTextSelection || + other.onePassRenderingScaleThreshold != + onePassRenderingScaleThreshold || + other.textSelectionParams != textSelectionParams || other.matchTextColor != matchTextColor || other.activeMatchTextColor != activeMatchTextColor || other.pageDropShadow != pageDropShadow || other.panEnabled != panEnabled || other.scaleEnabled != scaleEnabled || - other.interactionEndFrictionCoefficient != interactionEndFrictionCoefficient || + other.interactionEndFrictionCoefficient != + interactionEndFrictionCoefficient || other.scrollByMouseWheel != scrollByMouseWheel || - other.scrollHorizontallyByMouseWheel != scrollHorizontallyByMouseWheel || + other.scrollHorizontallyByMouseWheel != + scrollHorizontallyByMouseWheel || other.enableKeyboardNavigation != enableKeyboardNavigation || other.scrollByArrowKey != scrollByArrowKey || other.horizontalCacheExtent != horizontalCacheExtent || @@ -567,15 +536,17 @@ class PdfViewerParams { other.backgroundColor == backgroundColor && other.maxScale == maxScale && other.minScale == minScale && - other.useAlternativeFitScaleAsMinScale == useAlternativeFitScaleAsMinScale && + other.useAlternativeFitScaleAsMinScale == + useAlternativeFitScaleAsMinScale && other.panAxis == panAxis && other.boundaryMargin == boundaryMargin && other.annotationRenderingMode == annotationRenderingMode && other.limitRenderingCache == limitRenderingCache && other.pageAnchor == pageAnchor && other.pageAnchorEnd == pageAnchorEnd && - other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && - other.enableTextSelection == enableTextSelection && + other.onePassRenderingScaleThreshold == + onePassRenderingScaleThreshold && + other.textSelectionParams == textSelectionParams && other.matchTextColor == matchTextColor && other.activeMatchTextColor == activeMatchTextColor && other.pageDropShadow == pageDropShadow && @@ -584,7 +555,8 @@ class PdfViewerParams { other.onInteractionEnd == onInteractionEnd && other.onInteractionStart == onInteractionStart && other.onInteractionUpdate == onInteractionUpdate && - other.interactionEndFrictionCoefficient == interactionEndFrictionCoefficient && + other.interactionEndFrictionCoefficient == + interactionEndFrictionCoefficient && other.onDocumentChanged == onDocumentChanged && other.calculateInitialPageNumber == calculateInitialPageNumber && other.calculateInitialZoom == calculateInitialZoom && @@ -594,7 +566,8 @@ class PdfViewerParams { other.onPageChanged == onPageChanged && other.getPageRenderingScale == getPageRenderingScale && other.scrollByMouseWheel == scrollByMouseWheel && - other.scrollHorizontallyByMouseWheel == scrollHorizontallyByMouseWheel && + other.scrollHorizontallyByMouseWheel == + scrollHorizontallyByMouseWheel && other.enableKeyboardNavigation == enableKeyboardNavigation && other.scrollByArrowKey == scrollByArrowKey && other.horizontalCacheExtent == horizontalCacheExtent && @@ -607,9 +580,6 @@ class PdfViewerParams { other.linkWidgetBuilder == linkWidgetBuilder && other.pagePaintCallbacks == pagePaintCallbacks && other.pageBackgroundPaintCallbacks == pageBackgroundPaintCallbacks && - other.onTextSelectionChange == onTextSelectionChange && - other.selectableRegionInjector == selectableRegionInjector && - other.perPageSelectableRegionInjector == perPageSelectableRegionInjector && other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && other.forceReload == forceReload; @@ -629,7 +599,7 @@ class PdfViewerParams { pageAnchor.hashCode ^ pageAnchorEnd.hashCode ^ onePassRenderingScaleThreshold.hashCode ^ - enableTextSelection.hashCode ^ + textSelectionParams.hashCode ^ matchTextColor.hashCode ^ activeMatchTextColor.hashCode ^ pageDropShadow.hashCode ^ @@ -661,15 +631,312 @@ class PdfViewerParams { linkWidgetBuilder.hashCode ^ pagePaintCallbacks.hashCode ^ pageBackgroundPaintCallbacks.hashCode ^ - onTextSelectionChange.hashCode ^ - selectableRegionInjector.hashCode ^ - perPageSelectableRegionInjector.hashCode ^ onKey.hashCode ^ keyHandlerParams.hashCode ^ forceReload.hashCode; } } +/// Parameters for text selection. +@immutable +class PdfTextSelectionParams { + const PdfTextSelectionParams({ + this.textSelectionTriggeredBySwipe, + this.enableSelectionHandles, + this.buildContextMenu, + this.buildSelectionHandle, + this.onTextSelectionChange, + this.textTap, + this.textDoubleTap, + this.textLongPress, + this.textSecondaryTapUp, + this.magnifier, + }); + + /// Whether text selection is triggered by swipe. + /// + /// null to determine the behavior based on the platform; enabled on Desktop/Web, disabled on Mobile. + final bool? textSelectionTriggeredBySwipe; + + /// Whether to show selection handles. + /// + /// null to determine the behavior based on the platform; enabled on Mobile, disabled on Desktop/Web. + final bool? enableSelectionHandles; + + /// Function to build context menu for text selection. + /// + /// - If the function returns null, no context menu is shown. + /// - If the function is null, the default context menu will be used. + final PdfViewerTextSelectionContextMenuBuilder? buildContextMenu; + + /// Function to build anchor handle for text selection. + /// + /// - If the function returns null, no anchor handle is shown. + /// - If the function is null, the default anchor handle will be used. + final PdfViewerTextSelectionAnchorHandleBuilder? buildSelectionHandle; + + /// Function to be notified when the text selection is changed. + final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; + + /// Function to call when the text is tapped. + /// + /// By default, tapping on the text resets the text selection. + /// You can set empty function to disable the default behavior. + final void Function(TapDownDetails details)? textTap; + + /// Function to call when the text is double tapped. + /// + /// By default, double tapping on the text do nothing. + final void Function(TapDownDetails details)? textDoubleTap; + + /// Function to call when the text is long pressed. + /// + /// By default, long pressing on the text selects the word under the pointer. + /// You can set empty function to disable the default behavior. + final void Function(LongPressStartDetails details)? textLongPress; + + /// Function to call when the text is secondary tapped (right-click). + /// + /// By default, secondary tap on the text open the context menu. + final void Function(TapUpDetails details)? textSecondaryTapUp; + + /// Parameters for the magnifier. + final PdfViewerSelectionMagnifierParams? magnifier; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfTextSelectionParams && + other.textSelectionTriggeredBySwipe == textSelectionTriggeredBySwipe && + other.buildContextMenu == buildContextMenu && + other.buildSelectionHandle == buildSelectionHandle && + other.onTextSelectionChange == onTextSelectionChange && + other.textTap == textTap && + other.textDoubleTap == textDoubleTap && + other.textLongPress == textLongPress && + other.textSecondaryTapUp == textSecondaryTapUp && + other.enableSelectionHandles == enableSelectionHandles && + other.magnifier == magnifier; + } + + @override + int get hashCode => + textSelectionTriggeredBySwipe.hashCode ^ + buildContextMenu.hashCode ^ + buildSelectionHandle.hashCode ^ + onTextSelectionChange.hashCode ^ + textTap.hashCode ^ + textDoubleTap.hashCode ^ + textLongPress.hashCode ^ + textSecondaryTapUp.hashCode ^ + enableSelectionHandles.hashCode ^ + magnifier.hashCode; +} + +/// Function to build the text selection context menu. +/// +/// [a], [b] are the text selection anchors that represent the selected text range. +/// +/// [textSelectionDelegate] provides access to the text selection actions such as copy and clear selection. +/// Please note that the function does not copy the text if [PdfTextSelectionDelegate.isCopyAllowed] is false and +/// use of [PdfTextSelectionDelegate.selectedText] is also restricted by the same condition. +/// +/// [rightClickPosition] is the position of the right-click in the document coordinates if available. +/// [dismissContextMenu] is the function to dismiss the context menu. +typedef PdfViewerTextSelectionContextMenuBuilder = + Widget? Function( + BuildContext context, + PdfTextSelectionAnchor a, + PdfTextSelectionAnchor b, + PdfTextSelectionDelegate textSelectionDelegate, + Offset? rightClickPosition, + void Function() dismissContextMenu, + ); + +/// Function to build the text selection anchor handle. +typedef PdfViewerTextSelectionAnchorHandleBuilder = + Widget? Function(BuildContext context, PdfTextSelectionAnchor anchor); + +/// Function to be notified when the text selection is changed. +/// +/// [textSelection] contains the selected text range on each page. +typedef PdfViewerTextSelectionChangeCallback = + void Function(PdfTextSelection textSelection); + +/// Text selection +abstract class PdfTextSelection { + /// Whether the copy action is allowed. + bool get isCopyAllowed; + + /// Get the selected text. + /// + /// Although the use of this property is not restricted by [isCopyAllowed] + /// but you have to ensure that your use of the text does not violate [isCopyAllowed] condition. + String get selectedText; + + /// Get the selected text range. + List get selectedTextRange; +} + +/// Delegate for text selection actions. +abstract class PdfTextSelectionDelegate implements PdfTextSelection { + /// Copy the selected text. + /// + /// Please note that the function does not copy the text if [isCopyAllowed] is false. + /// The function returns true if the copy action is successful. + Future copyTextSelection(); + + /// Clear the text selection. + /// + /// By clearing the text selection, the text context menu will be dismissed. + Future clearTextSelection(); + + /// Select all text. + /// + /// The function may take some time to complete if the document is large. + Future selectAllText(); + + /// Select a word at the given position. + /// + /// Please note that [position] is in document coordinates. + Future selectWord(Offset position); +} + +@immutable +class PdfViewerSelectionMagnifierParams { + const PdfViewerSelectionMagnifierParams({ + this.enabled, + this.width = 200.0, + this.height = 200.0, + this.scale = 4.0, + this.builder, + this.paintMagnifierContent, + this.shouldBeShown, + this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, + }); + + /// The default maximum image bytes cached on memory is 256 KB. + static const defaultMaxImageBytesCachedOnMemory = 256 * 1024; + + /// Whether the magnifier is enabled. + /// + /// If null, the magnifier is enabled by default on Mobile and disabled on Desktop/Web. + final bool? enabled; + + /// Width of the widget to build (Not the size of the magnifier content). + /// + /// The size is used to layout the magnifier widget. + final double width; + + /// Height of the widget to build (Not the size of the magnifier content). + /// + /// The size is used to layout the magnifier widget. + final double height; + + /// Scale (zoom ratio) of the magnifier content. + final double scale; + + /// Function to build the magnifier widget. + /// + /// The function can be used to decorate the magnifier widget with additional widgets such as [Container] or [Size]. + /// The widget built by the function should be the exact size of specified by [width] and [height]. + /// + /// If the function returns null, the magnifier is not shown. + /// If the function is null, the magnifier is shown with the default magnifier widget. + /// + /// If the function returns a widget of [Positioned] or [Align], the magnifier content is laid out as + /// specified. Otherwise, the widget is laid out automatically. + /// + /// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: + /// + /// ```dart + /// builder: (context, params, magnifierContent) { + /// return Container( + /// width: params.width, + /// height: params.height, + /// decoration: BoxDecoration( + /// borderRadius: BorderRadius.circular(16), + /// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], + /// ), + /// child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), + /// ); + /// } + /// ``` + /// + /// You can also use the function to build a [OverlayEntry] to show the magnifier as an overlay. In that case, + /// you should do everything by yourself and return null from the function. + /// + final PdfViewerMagnifierBuilder? builder; + + /// Function to paint the magnifier content. + final PdfViewerMagnifierContentPaintFunction? paintMagnifierContent; + + /// Function to determine whether the magnifier should be shown based on conditions such as zoom level. + /// + /// If [enabled] is false, this function is not called. + /// By default, the magnifier is shown if the zoom level is smaller than [scale]. + final PdfViewerMagnifierShouldBeShownFunction? shouldBeShown; + + /// The maximum number of image bytes to be cached on memory. + /// + /// The default is 256 * 1024 bytes (256 KB). + final int maxImageBytesCachedOnMemory; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfViewerSelectionMagnifierParams && + other.enabled == enabled && + other.width == width && + other.height == height && + other.scale == scale && + other.builder == builder && + other.paintMagnifierContent == paintMagnifierContent && + other.shouldBeShown == shouldBeShown && + other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; + } + + @override + int get hashCode => + enabled.hashCode ^ + width.hashCode ^ + height.hashCode ^ + scale.hashCode ^ + builder.hashCode ^ + paintMagnifierContent.hashCode ^ + shouldBeShown.hashCode ^ + maxImageBytesCachedOnMemory.hashCode; +} + +/// Function to build the magnifier widget. +typedef PdfViewerMagnifierBuilder = + Widget? Function( + BuildContext context, + PdfViewerSelectionMagnifierParams params, + Widget magnifierContent, + ); + +/// Function to paint the magnifier content. +/// +/// The function is called to paint the magnifier content on the canvas. +/// The [canvas] is the canvas to paint on, [size] is the size of the magnifier content, +/// and [center] is the center position of the magnifier in the document coordinates. +/// [params] is the magnifier parameters. +typedef PdfViewerMagnifierContentPaintFunction = + void Function( + Canvas canvas, + Size size, + Offset center, + PdfViewerSelectionMagnifierParams params, + ); + +/// Function to determine whether the magnifier should be shown or not. +typedef PdfViewerMagnifierShouldBeShownFunction = + bool Function( + PdfViewerController controller, + PdfViewerSelectionMagnifierParams params, + ); + /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); @@ -686,21 +953,36 @@ typedef PdfViewerCalculateInitialPageNumberFunction = /// - [fitZoom] is the zoom level to fit the "initial" page into the viewer. /// - [coverZoom] is the zoom level to cover the entire viewer with the "initial" page. typedef PdfViewerCalculateZoomFunction = - double? Function(PdfDocument document, PdfViewerController controller, double fitZoom, double coverZoom); + double? Function( + PdfDocument document, + PdfViewerController controller, + double fitZoom, + double coverZoom, + ); /// Function to guess the current page number based on the visible rectangle and page layouts. typedef PdfViewerCalculateCurrentPageNumberFunction = - int? Function(Rect visibleRect, List pageRects, PdfViewerController controller); + int? Function( + Rect visibleRect, + List pageRects, + PdfViewerController controller, + ); /// Function called when the viewer is ready. /// -typedef PdfViewerReadyCallback = void Function(PdfDocument document, PdfViewerController controller); +typedef PdfViewerReadyCallback = + void Function(PdfDocument document, PdfViewerController controller); /// Function to be called when the viewer view size is changed. /// /// [viewSize] is the new view size. /// [oldViewSize] is the previous view size. -typedef PdfViewerViewSizeChanged = void Function(Size viewSize, Size? oldViewSize, PdfViewerController controller); +typedef PdfViewerViewSizeChanged = + void Function( + Size viewSize, + Size? oldViewSize, + PdfViewerController controller, + ); /// Function called when the current page is changed. typedef PdfPageChangedCallback = void Function(int? pageNumber); @@ -712,14 +994,20 @@ typedef PdfPageChangedCallback = void Function(int? pageNumber); /// - [controller] can be used to get the current zoom by [PdfViewerController.currentZoom] /// - [estimatedScale] is the precalculated scale for the page typedef PdfViewerGetPageRenderingScale = - double? Function(BuildContext context, PdfPage page, PdfViewerController controller, double estimatedScale); + double? Function( + BuildContext context, + PdfPage page, + PdfViewerController controller, + double estimatedScale, + ); /// Function to customize the layout of the pages. /// /// - [pages] is the list of pages. /// This is just a copy of the first loaded page of the document. /// - [params] is the viewer parameters. -typedef PdfPageLayoutFunction = PdfPageLayout Function(List pages, PdfViewerParams params); +typedef PdfPageLayoutFunction = + PdfPageLayout Function(List pages, PdfViewerParams params); /// Function to normalize the matrix. /// @@ -729,14 +1017,23 @@ typedef PdfPageLayoutFunction = PdfPageLayout Function(List pages, PdfV /// /// If no actual matrix change is needed, just return the input matrix. typedef PdfMatrixNormalizeFunction = - Matrix4 Function(Matrix4 matrix, Size viewSize, PdfPageLayout layout, PdfViewerController? controller); + Matrix4 Function( + Matrix4 matrix, + Size viewSize, + PdfPageLayout layout, + PdfViewerController? controller, + ); /// Function to build viewer overlays. /// /// [size] is the size of the viewer widget. /// [handleLinkTap] is a function to handle link tap. For more details, see [PdfViewerParams.viewerOverlayBuilder]. typedef PdfViewerOverlaysBuilder = - List Function(BuildContext context, Size size, PdfViewerHandleLinkTap handleLinkTap); + List Function( + BuildContext context, + Size size, + PdfViewerHandleLinkTap handleLinkTap, + ); /// Function to handle link tap. /// @@ -749,31 +1046,30 @@ typedef PdfViewerHandleLinkTap = bool Function(Offset position); /// /// [pageRect] is the rectangle of the page in the viewer. /// [page] is the page. -typedef PdfPageOverlaysBuilder = List Function(BuildContext context, Rect pageRect, PdfPage page); +typedef PdfPageOverlaysBuilder = + List Function(BuildContext context, Rect pageRect, PdfPage page); /// Function to build loading banner. /// /// [bytesDownloaded] is the number of bytes downloaded so far. /// [totalBytes] is the total number of bytes to be downloaded if available. -typedef PdfViewerLoadingBannerBuilder = Widget Function(BuildContext context, int bytesDownloaded, int? totalBytes); +typedef PdfViewerLoadingBannerBuilder = + Widget Function(BuildContext context, int bytesDownloaded, int? totalBytes); /// Function to build loading error banner. typedef PdfViewerErrorBannerBuilder = - Widget Function(BuildContext context, Object error, StackTrace? stackTrace, PdfDocumentRef documentRef); + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + PdfDocumentRef documentRef, + ); /// Function to build link widget for [PdfLink]. /// /// [size] is the size of the link. -typedef PdfLinkWidgetBuilder = Widget? Function(BuildContext context, PdfLink link, Size size); - -/// Function to inject [SelectionArea] or [SelectableRegion] to customize text selection. -typedef PdfSelectableRegionInjector = Widget Function(BuildContext context, Widget child); - -/// Function to inject [SelectionArea] or [SelectableRegion] to customize text selection on each page. -/// -/// [pageRect] is the rectangle of the page in the viewer. -typedef PdfPerPageSelectableRegionInjector = - Widget Function(BuildContext context, Widget child, PdfPage page, Rect pageRect); +typedef PdfLinkWidgetBuilder = + Widget? Function(BuildContext context, PdfLink link, Size size); /// Function to paint things on page. /// @@ -787,15 +1083,11 @@ typedef PdfPerPageSelectableRegionInjector = /// ```dart /// PdfRect pdfRect = ...; /// canvas.drawRect( -/// pdfRect.toRectInPageRect(page: page, pageRect: pageRect), +/// pdfRect.toRectInDocument(page: page, pageRect: pageRect), /// Paint()..color = Colors.red); /// ``` -typedef PdfViewerPagePaintCallback = void Function(ui.Canvas canvas, Rect pageRect, PdfPage page); - -/// Function to be notified when the text selection is changed. -/// -/// [selections] contains the selected text ranges on each page. -typedef PdfViewerTextSelectionChangeCallback = void Function(List selections); +typedef PdfViewerPagePaintCallback = + void Function(ui.Canvas canvas, Rect pageRect, PdfPage page); /// When [PdfViewerController.goToPage] is called, the page is aligned to the specified anchor. /// @@ -861,7 +1153,7 @@ class PdfLinkHandlerParams { /// ..color = Colors.red.withOpacity(0.2) /// ..style = PaintingStyle.fill; /// for (final link in links) { - /// final rect = link.rect.toRectInPageRect(page: page, pageRect: pageRect); + /// final rect = link.rect.toRectInDocument(page: page, pageRect: pageRect); /// canvas.drawRect(rect, paint); /// } /// } @@ -885,12 +1177,21 @@ class PdfLinkHandlerParams { @override int get hashCode { - return onLinkTap.hashCode ^ linkColor.hashCode ^ customPainter.hashCode ^ enableAutoLinkDetection.hashCode; + return onLinkTap.hashCode ^ + linkColor.hashCode ^ + customPainter.hashCode ^ + enableAutoLinkDetection.hashCode; } } /// Custom painter for the page links. -typedef PdfLinkCustomPagePainter = void Function(ui.Canvas canvas, Rect pageRect, PdfPage page, List links); +typedef PdfLinkCustomPagePainter = + void Function( + ui.Canvas canvas, + Rect pageRect, + PdfPage page, + List links, + ); /// Function to handle key events. /// @@ -906,7 +1207,11 @@ typedef PdfLinkCustomPagePainter = void Function(ui.Canvas canvas, Rect pageRect /// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated /// by key repeat feature. typedef PdfViewerOnKeyCallback = - bool? Function(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress); + bool? Function( + PdfViewerKeyHandlerParams params, + LogicalKeyboardKey key, + bool isRealKeyPress, + ); /// Parameters for the built-in key handler. /// From da0a2833ed76980d72f20d092493a04fabd0e7c1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Jul 2025 00:49:38 +0900 Subject: [PATCH 190/663] WIP: reformat files --- packages/pdfrx/analysis_options.yaml | 45 +++ packages/pdfrx/example/viewer/lib/main.dart | 1 - .../example/viewer/lib/markers_view.dart | 2 +- .../example/viewer/lib/outline_view.dart | 1 - .../pdfrx/example/viewer/lib/search_view.dart | 1 - packages/pdfrx/lib/pdfrx.dart | 3 + packages/pdfrx/lib/src/pdf_document_ref.dart | 2 +- packages/pdfrx/lib/src/pdfrx_flutter.dart | 55 +--- .../pdfrx/lib/src/utils/native/native.dart | 6 +- packages/pdfrx/lib/src/utils/web/web.dart | 9 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 165 +++------- .../lib/src/widgets/pdf_error_widget.dart | 2 +- .../src/widgets/pdf_page_links_overlay.dart | 1 - .../lib/src/widgets/pdf_text_searcher.dart | 1 - .../pdfrx/lib/src/widgets/pdf_viewer.dart | 1 - .../lib/src/widgets/pdf_viewer_params.dart | 140 ++------- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 1 - packages/pdfrx/test/pdf_viewer_test.dart | 5 +- packages/pdfrx_engine/analysis_options.yaml | 41 ++- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 +- .../lib/src/native/http_cache_control.dart | 31 +- .../lib/src/native/pdf_file_cache.dart | 80 ++--- .../lib/src/native/pdfrx_pdfium.dart | 292 +++++------------- .../pdfrx_engine/lib/src/native/worker.dart | 21 +- packages/pdfrx_engine/lib/src/pdf_api.dart | 223 +++---------- .../lib/src/pdfrx_engine_dart.dart | 45 +-- .../pdfrx_engine/test/pdf_document_test.dart | 16 +- 27 files changed, 334 insertions(+), 859 deletions(-) create mode 100644 packages/pdfrx/analysis_options.yaml diff --git a/packages/pdfrx/analysis_options.yaml b/packages/pdfrx/analysis_options.yaml new file mode 100644 index 00000000..6be920c2 --- /dev/null +++ b/packages/pdfrx/analysis_options.yaml @@ -0,0 +1,45 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + prefer_single_quotes: true + comment_references: true + prefer_relative_imports: true + use_key_in_widget_constructors: true + avoid_return_types_on_setters: true + avoid_types_on_closure_parameters: true + eol_at_end_of_file: true + sort_child_properties_last: true + sort_unnamed_constructors_first: true + sort_constructors_first: true + always_put_required_named_parameters_first: true + invalid_runtime_check_with_js_interop_types: true + + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options + +analyzer: + +formatter: + page_width: 120 diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 0944b6e9..b9de556b 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -4,7 +4,6 @@ import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:url_launcher/url_launcher.dart'; import 'markers_view.dart'; diff --git a/packages/pdfrx/example/viewer/lib/markers_view.dart b/packages/pdfrx/example/viewer/lib/markers_view.dart index b1aac319..7f227735 100644 --- a/packages/pdfrx/example/viewer/lib/markers_view.dart +++ b/packages/pdfrx/example/viewer/lib/markers_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; class Marker { Marker(this.color, this.range); diff --git a/packages/pdfrx/example/viewer/lib/outline_view.dart b/packages/pdfrx/example/viewer/lib/outline_view.dart index dabcd32e..f7dbee1a 100644 --- a/packages/pdfrx/example/viewer/lib/outline_view.dart +++ b/packages/pdfrx/example/viewer/lib/outline_view.dart @@ -3,7 +3,6 @@ // import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; class OutlineView extends StatelessWidget { const OutlineView({ diff --git a/packages/pdfrx/example/viewer/lib/search_view.dart b/packages/pdfrx/example/viewer/lib/search_view.dart index 9ad7bb0e..94af9c7c 100644 --- a/packages/pdfrx/example/viewer/lib/search_view.dart +++ b/packages/pdfrx/example/viewer/lib/search_view.dart @@ -1,7 +1,6 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:synchronized/extension.dart'; // diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index 79a92990..d42339c6 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -1,3 +1,6 @@ +// for backward compatibility, we'd better export pdfrx_engine APIs as well +export 'package:pdfrx_engine/pdfrx_engine.dart'; + export 'src/pdf_document_ref.dart'; export 'src/pdfrx_flutter.dart'; export 'src/widgets/pdf_text_searcher.dart'; diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index 00f01f22..a0ab4892 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; import 'package:synchronized/extension.dart'; /// Callback function to notify download progress. diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 6bfaeeb5..941c4d9e 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; import 'utils/platform.dart'; @@ -42,13 +42,7 @@ extension PdfImageExt on PdfImage { /// Create [Image] from the rendered image. Future createImage() { final comp = Completer(); - decodeImageFromPixels( - pixels, - width, - height, - PixelFormat.bgra8888, - (image) => comp.complete(image), - ); + decodeImageFromPixels(pixels, width, height, PixelFormat.bgra8888, (image) => comp.complete(image)); return comp.future; } } @@ -60,8 +54,7 @@ extension PdfRectExt on PdfRect { /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = - scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return Rect.fromLTRB( rotated.left * scale, (page.height - rotated.top) * scale, @@ -72,21 +65,13 @@ extension PdfRectExt on PdfRect { /// Convert to [Rect] in Flutter coordinate using [pageRect] as the page's bounding rectangle. Rect toRectInDocument({required PdfPage page, required Rect pageRect}) => - toRect( - page: page, - scaledPageSize: pageRect.size, - ).translate(pageRect.left, pageRect.top); + toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); } extension RectPdfRectExt on Rect { /// Convert to [PdfRect] in PDF page coordinates. - PdfRect toPdfRect({ - required PdfPage page, - Size? scaledPageSize, - int? rotation, - }) { - final scale = - scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + PdfRect toPdfRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return PdfRect( left / scale, page.height - top / scale, @@ -101,39 +86,23 @@ extension PdfPointExt on PdfPoint { /// [page] is the page to convert the rectangle. /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. - Offset toOffset({ - required PdfPage page, - Size? scaledPageSize, - int? rotation, - }) { + Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); - final scale = - scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; + final scale = scaledPageSize == null ? 1.0 : scaledPageSize.height / page.height; return Offset(rotated.x * scale, (page.height - rotated.y) * scale); } Offset toOffsetInDocument({required PdfPage page, required Rect pageRect}) { final rotated = rotate(page.rotation.index, page); final scale = pageRect.height / page.height; - return Offset( - rotated.x * scale, - (page.height - rotated.y) * scale, - ).translate(pageRect.left, pageRect.top); + return Offset(rotated.x * scale, (page.height - rotated.y) * scale).translate(pageRect.left, pageRect.top); } } extension OffsetPdfPointExt on Offset { /// Convert to [PdfPoint] in PDF page coordinates. - PdfPoint toPdfPoint({ - required PdfPage page, - Size? scaledPageSize, - int? rotation, - }) { - final scale = - scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; - return PdfPoint( - dx * scale, - page.height - dy * scale, - ).rotateReverse(rotation ?? page.rotation.index, page); + PdfPoint toPdfPoint({required PdfPage page, Size? scaledPageSize, int? rotation}) { + final scale = scaledPageSize == null ? 1.0 : page.height / scaledPageSize.height; + return PdfPoint(dx * scale, page.height - dy * scale).rotateReverse(rotation ?? page.rotation.index, page); } } diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index d6878f14..3293a5e5 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,16 +1,14 @@ import 'dart:io'; import 'package:flutter/services.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; final isApple = Platform.isMacOS || Platform.isIOS; final isWindows = Platform.isWindows; /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => - isApple - ? HardwareKeyboard.instance.isMetaPressed - : HardwareKeyboard.instance.isControlPressed; + isApple ? HardwareKeyboard.instance.isMetaPressed : HardwareKeyboard.instance.isControlPressed; void setClipboardData(String text) { Clipboard.setData(ClipboardData(text: text)); diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 873a51ee..924ebfb3 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,5 +1,5 @@ import 'package:flutter/services.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; import 'package:web/web.dart' as web; import '../../wasm/pdfrx_wasm.dart'; @@ -8,9 +8,7 @@ final isApple = false; final isWindows = false; /// Key pressing state of ⌘ or Control depending on the platform. -bool get isCommandKeyPressed => - HardwareKeyboard.instance.isMetaPressed || - HardwareKeyboard.instance.isControlPressed; +bool get isCommandKeyPressed => HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isControlPressed; void setClipboardData(String text) { web.window.navigator.clipboard.writeText(text); @@ -24,5 +22,4 @@ final isMobile = false; bool get shouldTextSelectionTriggeredBySwipe => true; /// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. -final PdfDocumentFactory? pdfDocumentFactoryOverride = - PdfDocumentFactoryWasmImpl(); +final PdfDocumentFactory? pdfDocumentFactoryOverride = PdfDocumentFactoryWasmImpl(); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index fb6f4464..ca6c88e0 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -14,11 +14,7 @@ import 'package:web/web.dart' as web; extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { /// Sends a command to the worker and returns a promise @JS('sendCommand') - external JSPromise sendCommand([ - String command, - JSAny? parameters, - JSArray? transfer, - ]); + external JSPromise sendCommand([String command, JSAny? parameters, JSArray? transfer]); /// Registers a callback function and returns its ID @JS('registerCallback') @@ -47,14 +43,8 @@ class _PdfiumWasmCallback { } } -Future> _sendCommand( - String command, { - Map? parameters, -}) async { - final result = - await _pdfiumWasmCommunicator - .sendCommand(command, parameters?.jsify()) - .toDart; +Future> _sendCommand(String command, {Map? parameters}) async { + final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; return (result.dartify()) as Map; } @@ -79,9 +69,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (_initialized) return; Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); pdfiumWasmWorkerUrl = _getWorkerUrl(); - final moduleUrl = _resolveUrl( - Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath, - ); + final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); final script = web.document.createElement('script') as web.HTMLScriptElement ..type = 'text/javascript' @@ -92,9 +80,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { web.document.querySelector('head')!.appendChild(script); final completer = Completer(); final sub1 = script.onLoad.listen((_) => completer.complete()); - final sub2 = script.onError.listen( - (event) => completer.completeError(event), - ); + final sub2 = script.onError.listen((event) => completer.completeError(event)); try { await completer.future; } catch (e) { @@ -108,8 +94,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { await _sendCommand( 'init', parameters: { - if (Pdfrx.pdfiumWasmHeaders != null) - 'headers': Pdfrx.pdfiumWasmHeaders, + if (Pdfrx.pdfiumWasmHeaders != null) 'headers': Pdfrx.pdfiumWasmHeaders, 'withCredentials': Pdfrx.pdfiumWasmWithCredentials, }, ); @@ -118,21 +103,16 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } static String? _pdfiumWasmModulesUrlFromMetaTag() { - final meta = - web.document.querySelector('meta[name="pdfium-wasm-module-url"]') - as web.HTMLMetaElement?; + final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; return meta?.content; } /// Workaround for Cross-Origin-Embedder-Policy restriction on WASM enabled environments String _getWorkerUrl() { - final moduleUrl = _resolveUrl( - Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath, - ); + final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); final workerJsUrl = _resolveUrl('pdfium_worker.js', baseUrl: moduleUrl); final pdfiumWasmUrl = _resolveUrl('pdfium.wasm', baseUrl: moduleUrl); - final content = - 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; + final content = 'const pdfiumWasmUrl="$pdfiumWasmUrl";importScripts("$workerJsUrl");'; final blob = web.Blob( [content].jsify() as JSArray, web.BlobPropertyBag(type: 'application/javascript'), @@ -148,9 +128,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { /// 3. The current browser window's URL (`web.window.location.href`). static String _resolveUrl(String relativeUrl, {String? baseUrl}) { final baseHref = ui_web.BrowserPlatformLocation().getBaseHref(); - return Uri.parse( - baseUrl ?? baseHref ?? web.window.location.href, - ).resolveUri(Uri.parse(relativeUrl)).toString(); + return Uri.parse(baseUrl ?? baseHref ?? web.window.location.href).resolveUri(Uri.parse(relativeUrl)).toString(); } @override @@ -161,9 +139,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { bool useProgressiveLoading = false, }) async { if (Pdfrx.loadAsset == null) { - throw StateError( - 'Pdfrx.loadAsset is not set. Please set it to load assets.', - ); + throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); } final asset = await Pdfrx.loadAsset!(name); return await openData( @@ -178,8 +154,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { @override Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) - read, + required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -188,9 +163,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { - throw UnimplementedError( - 'PdfDocumentFactoryWasmImpl.openCustom is not implemented.', - ); + throw UnimplementedError('PdfDocumentFactoryWasmImpl.openCustom is not implemented.'); } @override @@ -205,11 +178,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => _sendCommand( 'loadDocumentFromData', - parameters: { - 'data': data, - 'password': password, - 'useProgressiveLoading': useProgressiveLoading, - }, + parameters: {'data': data, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), sourceName: sourceName ?? 'data', passwordProvider: passwordProvider, @@ -226,11 +195,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { }) => _openByFunc( (password) => _sendCommand( 'loadDocumentFromUrl', - parameters: { - 'url': filePath, - 'password': password, - 'useProgressiveLoading': useProgressiveLoading, - }, + parameters: {'url': filePath, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), sourceName: filePath, passwordProvider: passwordProvider, @@ -256,9 +221,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (progressCallback != null) { await _init(); progressCallbackReg = _PdfiumWasmCallback.register( - ((int bytesReceived, int bytesTotal) => - progressCallback(bytesReceived, bytesTotal)) - .toJS, + ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, ); } @@ -269,8 +232,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { 'url': uri.toString(), 'password': password, 'useProgressiveLoading': useProgressiveLoading, - if (progressCallbackReg != null) - 'progressCallbackId': progressCallbackReg.id, + if (progressCallbackReg != null) 'progressCallbackId': progressCallbackReg.id, 'preferRangeAccess': preferRangeAccess, if (headers != null) 'headers': headers, 'withCredentials': withCredentials, @@ -303,9 +265,7 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { } else { password = await passwordProvider?.call(); if (password == null) { - throw const PdfPasswordException( - 'No password supplied by PasswordProvider.', - ); + throw const PdfPasswordException('No password supplied by PasswordProvider.'); } } @@ -317,26 +277,17 @@ class PdfDocumentFactoryWasmImpl extends PdfDocumentFactory { if (errorCode == fpdfErrPassword) { continue; } - throw StateError( - 'Failed to open document: ${result['errorCodeStr']} ($errorCode)', - ); + throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); } - return _PdfDocumentWasm._( - result, - sourceName: sourceName, - disposeCallback: onDispose, - ); + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose); } } } class _PdfDocumentWasm extends PdfDocument { - _PdfDocumentWasm._( - this.document, { - required super.sourceName, - this.disposeCallback, - }) : permissions = parsePermissions(document) { + _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) + : permissions = parsePermissions(document) { pages = parsePages(this, document['pages'] as List); } @@ -366,16 +317,12 @@ class _PdfDocumentWasm extends PdfDocument { @override bool isIdenticalDocumentHandle(Object? other) { - return other is _PdfDocumentWasm && - other.document['docHandle'] == document['docHandle']; + return other is _PdfDocumentWasm && other.document['docHandle'] == document['docHandle']; } @override Future> loadOutline() async { - final result = await _sendCommand( - 'loadOutline', - parameters: {'docHandle': document['docHandle']}, - ); + final result = await _sendCommand('loadOutline', parameters: {'docHandle': document['docHandle']}); final outlineList = result['outline'] as List; return outlineList.map((node) => _nodeFromMap(node)).toList(); } @@ -384,10 +331,7 @@ class _PdfDocumentWasm extends PdfDocument { return PdfOutlineNode( title: node['title'], dest: _pdfDestFromMap(node['dest']), - children: - (node['children'] as List) - .map((child) => _nodeFromMap(child)) - .toList(), + children: (node['children'] as List).map((child) => _nodeFromMap(child)).toList(), ); } @@ -432,8 +376,7 @@ class _PdfDocumentWasm extends PdfDocument { static PdfPermissions? parsePermissions(Map document) { final perms = (document['permissions'] as num).toInt(); - final securityHandlerRevision = - (document['securityHandlerRevision'] as num).toInt(); + final securityHandlerRevision = (document['securityHandlerRevision'] as num).toInt(); if (perms >= 0 && securityHandlerRevision >= 0) { return PdfPermissions(perms, securityHandlerRevision); } else { @@ -441,10 +384,7 @@ class _PdfDocumentWasm extends PdfDocument { } } - static List parsePages( - _PdfDocumentWasm doc, - List pageList, - ) { + static List parsePages(_PdfDocumentWasm doc, List pageList) { return pageList .map( (page) => _PdfPageWasm( @@ -460,8 +400,7 @@ class _PdfDocumentWasm extends PdfDocument { } } -class _PdfPageRenderCancellationTokenWasm - extends PdfPageRenderCancellationToken { +class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { _PdfPageRenderCancellationTokenWasm(); bool _isCanceled = false; @@ -476,14 +415,8 @@ class _PdfPageRenderCancellationTokenWasm } class _PdfPageWasm extends PdfPage { - _PdfPageWasm( - this.document, - int pageIndex, - this.width, - this.height, - int rotation, - this.isLoaded, - ) : pageNumber = pageIndex + 1, + _PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) + : pageNumber = pageIndex + 1, rotation = PdfPageRotation.values[rotation]; @override @@ -495,10 +428,7 @@ class _PdfPageWasm extends PdfPage { final _PdfDocumentWasm document; @override - Future> loadLinks({ - bool compact = false, - bool enableAutoLinkDetection = true, - }) async { + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { final result = await _sendCommand( 'loadLinks', parameters: { @@ -514,12 +444,7 @@ class _PdfPageWasm extends PdfPage { final rects = (link['rects'] as List).map((r) { final rect = r as List; - return PdfRect( - rect[0] as double, - rect[1] as double, - rect[2] as double, - rect[3] as double, - ); + return PdfRect(rect[0] as double, rect[1] as double, rect[2] as double, rect[3] as double); }).toList(); final url = link['url']; if (url is String) { @@ -537,25 +462,14 @@ class _PdfPageWasm extends PdfPage { Future loadRawText() async { final result = await _sendCommand( 'loadRawText', - parameters: { - 'docHandle': document.document['docHandle'], - 'pageIndex': pageNumber - 1, - }, + parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); final charRectsAll = (result['charRects'] as List).map((rect) { final r = rect as List; - return PdfRect( - r[0] as double, - r[1] as double, - r[2] as double, - r[3] as double, - ); + return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); }).toList(); - return PdfPageRawText( - result['fullText'] as String, - UnmodifiableListView(charRectsAll), - ); + return PdfPageRawText(result['fullText'] as String, UnmodifiableListView(charRectsAll)); } @override @@ -582,8 +496,7 @@ class _PdfPageWasm extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = - PdfAnnotationRenderingMode.annotationAndForms, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { @@ -617,11 +530,7 @@ class _PdfPageWasm extends PdfPage { } class PdfImageWeb extends PdfImage { - PdfImageWeb({ - required this.width, - required this.height, - required this.pixels, - }); + PdfImageWeb({required this.width, required this.height, required this.pixels}); @override final int width; diff --git a/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart b/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart index b1565e6d..2f016df5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; +import 'package:pdfrx/pdfrx.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../utils/platform.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart index 2614f89f..0f207bf0 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index a91e727b..7459208d 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 4cc68617..0e12723c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:vector_math/vector_math_64.dart' as vec; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 9af4a220..33dcf5f5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -2,7 +2,6 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; @@ -22,8 +21,7 @@ class PdfViewerParams { this.useAlternativeFitScaleAsMinScale = true, this.panAxis = PanAxis.free, this.boundaryMargin, - this.annotationRenderingMode = - PdfAnnotationRenderingMode.annotationAndForms, + this.annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, this.limitRenderingCache = true, this.pageAnchor = PdfPageAnchor.top, this.pageAnchorEnd = PdfPageAnchor.bottom, @@ -31,12 +29,7 @@ class PdfViewerParams { this.textSelectionParams, this.matchTextColor, this.activeMatchTextColor, - this.pageDropShadow = const BoxShadow( - color: Colors.black54, - blurRadius: 4, - spreadRadius: 2, - offset: Offset(2, 2), - ), + this.pageDropShadow = const BoxShadow(color: Colors.black54, blurRadius: 4, spreadRadius: 2, offset: Offset(2, 2)), this.panEnabled = true, this.scaleEnabled = true, this.onInteractionEnd, @@ -500,27 +493,23 @@ class PdfViewerParams { other.backgroundColor != backgroundColor || other.maxScale != maxScale || other.minScale != minScale || - other.useAlternativeFitScaleAsMinScale != - useAlternativeFitScaleAsMinScale || + other.useAlternativeFitScaleAsMinScale != useAlternativeFitScaleAsMinScale || other.panAxis != panAxis || other.boundaryMargin != boundaryMargin || other.annotationRenderingMode != annotationRenderingMode || other.limitRenderingCache != limitRenderingCache || other.pageAnchor != pageAnchor || other.pageAnchorEnd != pageAnchorEnd || - other.onePassRenderingScaleThreshold != - onePassRenderingScaleThreshold || + other.onePassRenderingScaleThreshold != onePassRenderingScaleThreshold || other.textSelectionParams != textSelectionParams || other.matchTextColor != matchTextColor || other.activeMatchTextColor != activeMatchTextColor || other.pageDropShadow != pageDropShadow || other.panEnabled != panEnabled || other.scaleEnabled != scaleEnabled || - other.interactionEndFrictionCoefficient != - interactionEndFrictionCoefficient || + other.interactionEndFrictionCoefficient != interactionEndFrictionCoefficient || other.scrollByMouseWheel != scrollByMouseWheel || - other.scrollHorizontallyByMouseWheel != - scrollHorizontallyByMouseWheel || + other.scrollHorizontallyByMouseWheel != scrollHorizontallyByMouseWheel || other.enableKeyboardNavigation != enableKeyboardNavigation || other.scrollByArrowKey != scrollByArrowKey || other.horizontalCacheExtent != horizontalCacheExtent || @@ -536,16 +525,14 @@ class PdfViewerParams { other.backgroundColor == backgroundColor && other.maxScale == maxScale && other.minScale == minScale && - other.useAlternativeFitScaleAsMinScale == - useAlternativeFitScaleAsMinScale && + other.useAlternativeFitScaleAsMinScale == useAlternativeFitScaleAsMinScale && other.panAxis == panAxis && other.boundaryMargin == boundaryMargin && other.annotationRenderingMode == annotationRenderingMode && other.limitRenderingCache == limitRenderingCache && other.pageAnchor == pageAnchor && other.pageAnchorEnd == pageAnchorEnd && - other.onePassRenderingScaleThreshold == - onePassRenderingScaleThreshold && + other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && other.textSelectionParams == textSelectionParams && other.matchTextColor == matchTextColor && other.activeMatchTextColor == activeMatchTextColor && @@ -555,8 +542,7 @@ class PdfViewerParams { other.onInteractionEnd == onInteractionEnd && other.onInteractionStart == onInteractionStart && other.onInteractionUpdate == onInteractionUpdate && - other.interactionEndFrictionCoefficient == - interactionEndFrictionCoefficient && + other.interactionEndFrictionCoefficient == interactionEndFrictionCoefficient && other.onDocumentChanged == onDocumentChanged && other.calculateInitialPageNumber == calculateInitialPageNumber && other.calculateInitialZoom == calculateInitialZoom && @@ -566,8 +552,7 @@ class PdfViewerParams { other.onPageChanged == onPageChanged && other.getPageRenderingScale == getPageRenderingScale && other.scrollByMouseWheel == scrollByMouseWheel && - other.scrollHorizontallyByMouseWheel == - scrollHorizontallyByMouseWheel && + other.scrollHorizontallyByMouseWheel == scrollHorizontallyByMouseWheel && other.enableKeyboardNavigation == enableKeyboardNavigation && other.scrollByArrowKey == scrollByArrowKey && other.horizontalCacheExtent == horizontalCacheExtent && @@ -760,8 +745,7 @@ typedef PdfViewerTextSelectionAnchorHandleBuilder = /// Function to be notified when the text selection is changed. /// /// [textSelection] contains the selected text range on each page. -typedef PdfViewerTextSelectionChangeCallback = - void Function(PdfTextSelection textSelection); +typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection textSelection); /// Text selection abstract class PdfTextSelection { @@ -910,11 +894,7 @@ class PdfViewerSelectionMagnifierParams { /// Function to build the magnifier widget. typedef PdfViewerMagnifierBuilder = - Widget? Function( - BuildContext context, - PdfViewerSelectionMagnifierParams params, - Widget magnifierContent, - ); + Widget? Function(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget magnifierContent); /// Function to paint the magnifier content. /// @@ -923,19 +903,11 @@ typedef PdfViewerMagnifierBuilder = /// and [center] is the center position of the magnifier in the document coordinates. /// [params] is the magnifier parameters. typedef PdfViewerMagnifierContentPaintFunction = - void Function( - Canvas canvas, - Size size, - Offset center, - PdfViewerSelectionMagnifierParams params, - ); + void Function(Canvas canvas, Size size, Offset center, PdfViewerSelectionMagnifierParams params); /// Function to determine whether the magnifier should be shown or not. typedef PdfViewerMagnifierShouldBeShownFunction = - bool Function( - PdfViewerController controller, - PdfViewerSelectionMagnifierParams params, - ); + bool Function(PdfViewerController controller, PdfViewerSelectionMagnifierParams params); /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); @@ -953,36 +925,21 @@ typedef PdfViewerCalculateInitialPageNumberFunction = /// - [fitZoom] is the zoom level to fit the "initial" page into the viewer. /// - [coverZoom] is the zoom level to cover the entire viewer with the "initial" page. typedef PdfViewerCalculateZoomFunction = - double? Function( - PdfDocument document, - PdfViewerController controller, - double fitZoom, - double coverZoom, - ); + double? Function(PdfDocument document, PdfViewerController controller, double fitZoom, double coverZoom); /// Function to guess the current page number based on the visible rectangle and page layouts. typedef PdfViewerCalculateCurrentPageNumberFunction = - int? Function( - Rect visibleRect, - List pageRects, - PdfViewerController controller, - ); + int? Function(Rect visibleRect, List pageRects, PdfViewerController controller); /// Function called when the viewer is ready. /// -typedef PdfViewerReadyCallback = - void Function(PdfDocument document, PdfViewerController controller); +typedef PdfViewerReadyCallback = void Function(PdfDocument document, PdfViewerController controller); /// Function to be called when the viewer view size is changed. /// /// [viewSize] is the new view size. /// [oldViewSize] is the previous view size. -typedef PdfViewerViewSizeChanged = - void Function( - Size viewSize, - Size? oldViewSize, - PdfViewerController controller, - ); +typedef PdfViewerViewSizeChanged = void Function(Size viewSize, Size? oldViewSize, PdfViewerController controller); /// Function called when the current page is changed. typedef PdfPageChangedCallback = void Function(int? pageNumber); @@ -994,20 +951,14 @@ typedef PdfPageChangedCallback = void Function(int? pageNumber); /// - [controller] can be used to get the current zoom by [PdfViewerController.currentZoom] /// - [estimatedScale] is the precalculated scale for the page typedef PdfViewerGetPageRenderingScale = - double? Function( - BuildContext context, - PdfPage page, - PdfViewerController controller, - double estimatedScale, - ); + double? Function(BuildContext context, PdfPage page, PdfViewerController controller, double estimatedScale); /// Function to customize the layout of the pages. /// /// - [pages] is the list of pages. /// This is just a copy of the first loaded page of the document. /// - [params] is the viewer parameters. -typedef PdfPageLayoutFunction = - PdfPageLayout Function(List pages, PdfViewerParams params); +typedef PdfPageLayoutFunction = PdfPageLayout Function(List pages, PdfViewerParams params); /// Function to normalize the matrix. /// @@ -1017,23 +968,14 @@ typedef PdfPageLayoutFunction = /// /// If no actual matrix change is needed, just return the input matrix. typedef PdfMatrixNormalizeFunction = - Matrix4 Function( - Matrix4 matrix, - Size viewSize, - PdfPageLayout layout, - PdfViewerController? controller, - ); + Matrix4 Function(Matrix4 matrix, Size viewSize, PdfPageLayout layout, PdfViewerController? controller); /// Function to build viewer overlays. /// /// [size] is the size of the viewer widget. /// [handleLinkTap] is a function to handle link tap. For more details, see [PdfViewerParams.viewerOverlayBuilder]. typedef PdfViewerOverlaysBuilder = - List Function( - BuildContext context, - Size size, - PdfViewerHandleLinkTap handleLinkTap, - ); + List Function(BuildContext context, Size size, PdfViewerHandleLinkTap handleLinkTap); /// Function to handle link tap. /// @@ -1046,30 +988,22 @@ typedef PdfViewerHandleLinkTap = bool Function(Offset position); /// /// [pageRect] is the rectangle of the page in the viewer. /// [page] is the page. -typedef PdfPageOverlaysBuilder = - List Function(BuildContext context, Rect pageRect, PdfPage page); +typedef PdfPageOverlaysBuilder = List Function(BuildContext context, Rect pageRect, PdfPage page); /// Function to build loading banner. /// /// [bytesDownloaded] is the number of bytes downloaded so far. /// [totalBytes] is the total number of bytes to be downloaded if available. -typedef PdfViewerLoadingBannerBuilder = - Widget Function(BuildContext context, int bytesDownloaded, int? totalBytes); +typedef PdfViewerLoadingBannerBuilder = Widget Function(BuildContext context, int bytesDownloaded, int? totalBytes); /// Function to build loading error banner. typedef PdfViewerErrorBannerBuilder = - Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, - PdfDocumentRef documentRef, - ); + Widget Function(BuildContext context, Object error, StackTrace? stackTrace, PdfDocumentRef documentRef); /// Function to build link widget for [PdfLink]. /// /// [size] is the size of the link. -typedef PdfLinkWidgetBuilder = - Widget? Function(BuildContext context, PdfLink link, Size size); +typedef PdfLinkWidgetBuilder = Widget? Function(BuildContext context, PdfLink link, Size size); /// Function to paint things on page. /// @@ -1086,8 +1020,7 @@ typedef PdfLinkWidgetBuilder = /// pdfRect.toRectInDocument(page: page, pageRect: pageRect), /// Paint()..color = Colors.red); /// ``` -typedef PdfViewerPagePaintCallback = - void Function(ui.Canvas canvas, Rect pageRect, PdfPage page); +typedef PdfViewerPagePaintCallback = void Function(ui.Canvas canvas, Rect pageRect, PdfPage page); /// When [PdfViewerController.goToPage] is called, the page is aligned to the specified anchor. /// @@ -1177,21 +1110,12 @@ class PdfLinkHandlerParams { @override int get hashCode { - return onLinkTap.hashCode ^ - linkColor.hashCode ^ - customPainter.hashCode ^ - enableAutoLinkDetection.hashCode; + return onLinkTap.hashCode ^ linkColor.hashCode ^ customPainter.hashCode ^ enableAutoLinkDetection.hashCode; } } /// Custom painter for the page links. -typedef PdfLinkCustomPagePainter = - void Function( - ui.Canvas canvas, - Rect pageRect, - PdfPage page, - List links, - ); +typedef PdfLinkCustomPagePainter = void Function(ui.Canvas canvas, Rect pageRect, PdfPage page, List links); /// Function to handle key events. /// @@ -1207,11 +1131,7 @@ typedef PdfLinkCustomPagePainter = /// [isRealKeyPress] is true if the key event is the actual key press event. It is false if the key event is generated /// by key repeat feature. typedef PdfViewerOnKeyCallback = - bool? Function( - PdfViewerKeyHandlerParams params, - LogicalKeyboardKey key, - bool isRealKeyPress, - ); + bool? Function(PdfViewerKeyHandlerParams params, LogicalKeyboardKey key, bool isRealKeyPress); /// Parameters for the built-in key handler. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index ba279e18..0db691d5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; import '../../pdfrx.dart'; diff --git a/packages/pdfrx/test/pdf_viewer_test.dart b/packages/pdfrx/test/pdf_viewer_test.dart index c63d3d3e..8204b028 100644 --- a/packages/pdfrx/test/pdf_viewer_test.dart +++ b/packages/pdfrx/test/pdf_viewer_test.dart @@ -5,7 +5,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; final testPdfFile = File('example/viewer/assets/hello.pdf'); final binding = TestWidgetsFlutterBinding.ensureInitialized(); @@ -24,9 +23,7 @@ void main() { await tester.pumpWidget( MaterialApp( // FIXME: Just a workaround for "A RenderFlex overflowed..." - home: SingleChildScrollView( - child: PdfViewer.uri(Uri.parse('https://example.com/hello.pdf')), - ), + home: SingleChildScrollView(child: PdfViewer.uri(Uri.parse('https://example.com/hello.pdf'))), ), ); diff --git a/packages/pdfrx_engine/analysis_options.yaml b/packages/pdfrx_engine/analysis_options.yaml index dee8927a..940ed13a 100644 --- a/packages/pdfrx_engine/analysis_options.yaml +++ b/packages/pdfrx_engine/analysis_options.yaml @@ -13,18 +13,37 @@ include: package:lints/recommended.yaml -# Uncomment the following section to specify additional rules. +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + prefer_single_quotes: true + comment_references: true + prefer_relative_imports: true + use_key_in_widget_constructors: true + avoid_return_types_on_setters: true + avoid_types_on_closure_parameters: true + eol_at_end_of_file: true + sort_child_properties_last: true + sort_unnamed_constructors_first: true + sort_constructors_first: true + always_put_required_named_parameters_first: true + invalid_runtime_check_with_js_interop_types: true -# linter: -# rules: -# - camel_case_types -# analyzer: -# exclude: -# - path/to/excluded/files/** +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints +analyzer: -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options +formatter: + page_width: 120 diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 48b8cfce..cfbdfd8b 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -26,8 +26,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { @override Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) - read, + required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, diff --git a/packages/pdfrx_engine/lib/src/native/http_cache_control.dart b/packages/pdfrx_engine/lib/src/native/http_cache_control.dart index 1bedc7e7..232e8cfb 100644 --- a/packages/pdfrx_engine/lib/src/native/http_cache_control.dart +++ b/packages/pdfrx_engine/lib/src/native/http_cache_control.dart @@ -107,22 +107,21 @@ class HttpCacheControlState { maxAge = maxAgeForNoStore.inSeconds; } return HttpCacheControlState( - cacheControl: - cacheControl != null - ? HttpCacheControl.fromDirectives( - noCache: noCache, - mustRevalidate: cacheControl.contains('must-revalidate'), - noStore: noStore, - private: cacheControl.contains('private'), - public: cacheControl.contains('public'), - mustUnderstand: cacheControl.contains('must-understand'), - noTransform: cacheControl.contains('no-transform'), - immutable: cacheControl.contains('immutable'), - staleWhileRevalidate: cacheControl.contains('stale-while-revalidate'), - staleIfError: cacheControl.contains('stale-if-error'), - maxAge: maxAge, - ) - : const HttpCacheControl(directives: 0), + cacheControl: cacheControl != null + ? HttpCacheControl.fromDirectives( + noCache: noCache, + mustRevalidate: cacheControl.contains('must-revalidate'), + noStore: noStore, + private: cacheControl.contains('private'), + public: cacheControl.contains('public'), + mustUnderstand: cacheControl.contains('must-understand'), + noTransform: cacheControl.contains('no-transform'), + immutable: cacheControl.contains('immutable'), + staleWhileRevalidate: cacheControl.contains('stale-while-revalidate'), + staleIfError: cacheControl.contains('stale-if-error'), + maxAge: maxAge, + ) + : const HttpCacheControl(directives: 0), date: date, expires: expires, etag: etag, diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 43fba9ab..27079087 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -76,9 +76,7 @@ class PdfFileCache { Future close() async { final raf = _raf; if (raf != null) { - _rafFinalizer.detach( - this, - ); // Detach from finalizer since we are closing explicitly + _rafFinalizer.detach(this); // Detach from finalizer since we are closing explicitly _raf = null; await raf.close(); } @@ -92,12 +90,7 @@ class PdfFileCache { } } - Future _read( - List buffer, - int bufferPosition, - int position, - int size, - ) async { + Future _read(List buffer, int bufferPosition, int position, int size) async { await _ensureFileOpen(); await _raf!.setPosition(position); await _raf!.readInto(buffer, bufferPosition, bufferPosition + size); @@ -114,15 +107,10 @@ class PdfFileCache { return await _raf!.length(); } - Future read( - List buffer, - int bufferPosition, - int position, - int size, - ) => _read(buffer, bufferPosition, _headerSize! + position, size); + Future read(List buffer, int bufferPosition, int position, int size) => + _read(buffer, bufferPosition, _headerSize! + position, size); - Future write(int position, List bytes) => - _write(_headerSize! + position, bytes); + Future write(int position, List bytes) => _write(_headerSize! + position, bytes); bool isCached(int block) => _cacheState[block >> 3] & (1 << (block & 7)) != 0; @@ -170,9 +158,7 @@ class PdfFileCache { if (dataStrSize != 0) { final dataStrBytes = Uint8List(dataStrSize); await _read(dataStrBytes, 0, header1Size, dataStrBytes.length); - _cacheControlState = HttpCacheControlState.parseDataStr( - utf8.decode(dataStrBytes), - ); + _cacheControlState = HttpCacheControlState.parseDataStr(utf8.decode(dataStrBytes)); } else { _cacheControlState = HttpCacheControlState.empty; } @@ -213,10 +199,7 @@ class PdfFileCache { return true; } - Future initializeWithFileSize( - int fileSize, { - required bool truncateExistingContent, - }) async { + Future initializeWithFileSize(int fileSize, {required bool truncateExistingContent}) async { if (truncateExistingContent) { await invalidateCache(); } @@ -250,18 +233,14 @@ class PdfFileCache { await _write(header1Size, dataStrEncoded); } - Future setCacheControlState( - HttpCacheControlState cacheControlState, - ) async { + Future setCacheControlState(HttpCacheControlState cacheControlState) async { _cacheControlState = cacheControlState; await _saveCacheControlState(); } static Future getCacheFilePathForUri(Uri uri) async { if (Pdfrx.getCacheDirectory == null) { - throw StateError( - 'Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.', - ); + throw StateError('Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.'); } final cacheDir = await Pdfrx.getCacheDirectory!(); final fnHash = sha1 @@ -311,9 +290,7 @@ Future pdfDocumentFromUri( }) async { progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); - final httpClientWrapper = _HttpClientWrapper( - Pdfrx.createHttpClient ?? () => http.Client(), - ); + final httpClientWrapper = _HttpClientWrapper(Pdfrx.createHttpClient ?? () => http.Client()); try { if (!cache.isInitialized) { @@ -336,8 +313,7 @@ Future pdfDocumentFromUri( } } else { // Check if the file is fresh (no-need-to-reload). - if (cache.cacheControlState.cacheControl.mustRevalidate && - cache.cacheControlState.isFresh(now: DateTime.now())) { + if (cache.cacheControlState.cacheControl.mustRevalidate && cache.cacheControlState.isFresh(now: DateTime.now())) { // cache is valid; no need to download. } else { final result = await _downloadBlock( @@ -374,14 +350,7 @@ Future pdfDocumentFromUri( final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { - await _downloadBlock( - httpClientWrapper, - uri, - cache, - progressCallback, - blockId, - headers: headers, - ); + await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers); } final readEnd = min(p + size, (blockId + 1) * cache.blockSize); final sizeToRead = readEnd - p; @@ -444,15 +413,12 @@ Future<_DownloadResult> _downloadBlock( final request = http.Request('GET', uri) ..headers.addAll({ if (useRangeAccess) 'Range': 'bytes=$blockOffset-${end - 1}', - if (addCacheControlHeaders) - ...cache.cacheControlState.getHeadersForFetch(), + if (addCacheControlHeaders) ...cache.cacheControlState.getHeadersForFetch(), if (headers != null) ...headers, }); late final http.StreamedResponse response; try { - response = await httpClientWrapper.client - .send(request) - .timeout(Duration(seconds: 5)); + response = await httpClientWrapper.client.send(request).timeout(Duration(seconds: 5)); } on TimeoutException { httpClientWrapper.reset(); rethrow; @@ -465,9 +431,7 @@ Future<_DownloadResult> _downloadBlock( } if (response.statusCode != 200 && response.statusCode != 206) { - throw PdfException( - 'Failed to download PDF file: ${response.statusCode} ${response.reasonPhrase}', - ); + throw PdfException('Failed to download PDF file: ${response.statusCode} ${response.reasonPhrase}'); } if (addCacheControlHeaders) { @@ -485,14 +449,9 @@ Future<_DownloadResult> _downloadBlock( isFullDownload = true; } if (!cache.isInitialized) { - await cache.initializeWithFileSize( - fileSize ?? 0, - truncateExistingContent: true, - ); + await cache.initializeWithFileSize(fileSize ?? 0, truncateExistingContent: true); } - await cache.setCacheControlState( - HttpCacheControlState.fromHeaders(response.headers), - ); + await cache.setCacheControlState(HttpCacheControlState.fromHeaders(response.headers)); var offset = blockOffset; var cachedBytesSoFar = cache.cachedBytes; @@ -505,10 +464,7 @@ Future<_DownloadResult> _downloadBlock( if (isFullDownload) { fileSize ??= cachedBytesSoFar; - await cache.initializeWithFileSize( - fileSize, - truncateExistingContent: false, - ); + await cache.initializeWithFileSize(fileSize, truncateExistingContent: false); await cache.setCached(0, lastBlock: cache.totalBlocks - 1); } else { await cache.setCached(blockId, lastBlock: blockId + blockCount - 1); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 0f561909..390ae012 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -44,15 +44,11 @@ bool _initialized = false; void _init() { if (_initialized) return; using((arena) { - final config = arena.allocate( - sizeOf(), - ); + final config = arena.allocate(sizeOf()); config.ref.version = 2; if (Pdfrx.fontPaths.isNotEmpty) { - final fontPathArray = arena.allocate>( - sizeOf>() * (Pdfrx.fontPaths.length + 1), - ); + final fontPathArray = arena.allocate>(sizeOf>() * (Pdfrx.fontPaths.length + 1)); for (int i = 0; i < Pdfrx.fontPaths.length; i++) { fontPathArray[i] = Pdfrx.fontPaths[i].toUtf8(arena); } @@ -82,9 +78,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { bool useProgressiveLoading = false, }) async { if (Pdfrx.loadAsset == null) { - throw StateError( - 'Pdfrx.loadAsset is not set. Please set it to load assets.', - ); + throw StateError('Pdfrx.loadAsset is not set. Please set it to load assets.'); } final asset = await Pdfrx.loadAsset!(name); return await _openData( @@ -126,14 +120,10 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { }) { _init(); return _openByFunc( - (password) async => - (await backgroundWorker).computeWithArena((arena, params) { - final doc = pdfium.FPDF_LoadDocument( - params.filePath.toUtf8(arena), - params.password?.toUtf8(arena) ?? nullptr, - ); - return doc.address; - }, (filePath: filePath, password: password)), + (password) async => (await backgroundWorker).computeWithArena((arena, params) { + final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); + return doc.address; + }, (filePath: filePath, password: password)), sourceName: filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -173,8 +163,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { @override Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) - read, + required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -225,9 +214,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { return _openByFunc( (password) async => (await backgroundWorker).computeWithArena( (arena, params) => pdfium.FPDF_LoadCustomDocument( - Pointer.fromAddress( - params.fileAccess, - ), + Pointer.fromAddress(params.fileAccess), params.password?.toUtf8(arena) ?? nullptr, ).address, (fileAccess: fa.fileAccess.address, password: password), @@ -295,9 +282,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { } else { password = await passwordProvider?.call(); if (password == null) { - throw const PdfPasswordException( - 'No password supplied by PasswordProvider.', - ); + throw const PdfPasswordException('No password supplied by PasswordProvider.'); } } final doc = await openPdfDocument(password); @@ -312,16 +297,13 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { if (_isPasswordError()) { continue; } - throw PdfException( - 'Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).', - ); + throw PdfException('Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).'); } } } extension _FpdfUtf8StringExt on String { - Pointer toUtf8(Arena arena) => - Pointer.fromAddress(toNativeUtf8(allocator: arena).address); + Pointer toUtf8(Arena arena) => Pointer.fromAddress(toNativeUtf8(allocator: arena).address); } class _PdfDocumentPdfium extends PdfDocument { @@ -369,12 +351,9 @@ class _PdfDocumentPdfium extends PdfDocument { pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; try { final permissions = pdfium.FPDF_GetDocPermissions(doc); - final securityHandlerRevision = - pdfium.FPDF_GetSecurityHandlerRevision(doc); + final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); - formInfo = calloc.allocate( - sizeOf(), - ); + formInfo = calloc.allocate(sizeOf()); formInfo.ref.version = 1; formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); return ( @@ -398,12 +377,8 @@ class _PdfDocumentPdfium extends PdfDocument { permissions: result.securityHandlerRevision != -1 ? PdfPermissions(result.permissions, result.securityHandlerRevision) : null, - formHandle: pdfium_bindings.FPDF_FORMHANDLE.fromAddress( - result.formHandle, - ), - formInfo: Pointer.fromAddress( - result.formInfo, - ), + formHandle: pdfium_bindings.FPDF_FORMHANDLE.fromAddress(result.formHandle), + formInfo: Pointer.fromAddress(result.formInfo), disposeCallback: disposeCallback, ); final pages = await pdfDoc._loadPagesInLimitedTime( @@ -439,19 +414,10 @@ class _PdfDocumentPdfium extends PdfDocument { if (isDisposed) return; _pages = List.unmodifiable(loaded.pages); - subject.add( - PdfDocumentPageStatusChangedEvent( - this, - _pages.sublist(firstUnloadedPageIndex), - ), - ); + subject.add(PdfDocumentPageStatusChangedEvent(this, _pages.sublist(firstUnloadedPageIndex))); if (onPageLoadProgress != null) { - final result = await onPageLoadProgress( - loaded.pageCountLoadedTotal, - loaded.pages.length, - data, - ); + final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); if (result == false) { // If the callback returns false, stop loading pages return; @@ -464,8 +430,7 @@ class _PdfDocumentPdfium extends PdfDocument { } /// Loads pages in the document in a time-limited manner. - Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> - _loadPagesInLimitedTime({ + Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ List<_PdfPagePdfium> pagesLoadedSoFar = const [], int? maxPageCountToLoadAdditionally, Duration? timeout, @@ -473,21 +438,13 @@ class _PdfDocumentPdfium extends PdfDocument { try { final results = await (await backgroundWorker).compute( (params) { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.docAddress, - ); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); return using((arena) { final pageCount = pdfium.FPDF_GetPageCount(doc); final end = maxPageCountToLoadAdditionally == null ? pageCount - : min( - pageCount, - params.pagesCountLoadedSoFar + - params.maxPageCountToLoadAdditionally!, - ); - final t = params.timeoutUs != null - ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) - : null; + : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); + final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; final pages = <({double width, double height, int rotation})>[]; for (int i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); @@ -565,29 +522,15 @@ class _PdfDocumentPdfium extends PdfDocument { if (!isDisposed) { isDisposed = true; subject.close(); - await (await backgroundWorker).compute( - (params) { - final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress( - params.formHandle, - ); - final formInfo = - Pointer.fromAddress( - params.formInfo, - ); - pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); - calloc.free(formInfo); + await (await backgroundWorker).compute((params) { + final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle); + final formInfo = Pointer.fromAddress(params.formInfo); + pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); + calloc.free(formInfo); - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.document, - ); - pdfium.FPDF_CloseDocument(doc); - }, - ( - formHandle: formHandle.address, - formInfo: formInfo.address, - document: document.address, - ), - ); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + pdfium.FPDF_CloseDocument(doc); + }, (formHandle: formHandle.address, formInfo: formInfo.address, document: document.address)); disposeCallback?.call(); } @@ -598,14 +541,8 @@ class _PdfDocumentPdfium extends PdfDocument { ? [] : await (await backgroundWorker).compute( (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.document, - ); - return _getOutlineNodeSiblings( - pdfium.FPDFBookmark_GetFirstChild(document, nullptr), - document, - arena, - ); + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); }), (document: document.address), ); @@ -623,16 +560,8 @@ class _PdfDocumentPdfium extends PdfDocument { siblings.add( PdfOutlineNode( title: titleBuf.cast().toDartString(), - dest: _pdfDestFromDest( - pdfium.FPDFBookmark_GetDest(document, bookmark), - document, - arena, - ), - children: _getOutlineNodeSiblings( - pdfium.FPDFBookmark_GetFirstChild(document, bookmark), - document, - arena, - ), + dest: _pdfDestFromDest(pdfium.FPDFBookmark_GetDest(document, bookmark), document, arena), + children: _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, bookmark), document, arena), ), ); bookmark = pdfium.FPDFBookmark_GetNextSibling(document, bookmark); @@ -675,13 +604,11 @@ class _PdfPagePdfium extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = - PdfAnnotationRenderingMode.annotationAndForms, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { - if (cancellationToken != null && - cancellationToken is! PdfPageRenderCancellationTokenPdfium) { + if (cancellationToken != null && cancellationToken is! PdfPageRenderCancellationTokenPdfium) { throw ArgumentError( 'cancellationToken must be created by PdfPage.createCancellationToken().', 'cancellationToken', @@ -715,29 +642,16 @@ class _PdfPagePdfium extends PdfPage { params.width * rgbaSize, ); if (bmp == nullptr) { - throw PdfException( - 'FPDFBitmap_CreateEx(${params.width}, ${params.height}) failed.', - ); + throw PdfException('FPDFBitmap_CreateEx(${params.width}, ${params.height}) failed.'); } pdfium_bindings.FPDF_PAGE page = nullptr; try { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.document, - ); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); if (page == nullptr) { - throw PdfException( - 'FPDF_LoadPage(${params.pageNumber}) failed.', - ); + throw PdfException('FPDF_LoadPage(${params.pageNumber}) failed.'); } - pdfium.FPDFBitmap_FillRect( - bmp, - 0, - 0, - params.width, - params.height, - params.backgroundColor!, - ); + pdfium.FPDFBitmap_FillRect(bmp, 0, 0, params.width, params.height, params.backgroundColor!); pdfium.FPDF_RenderPageBitmap( bmp, @@ -748,19 +662,15 @@ class _PdfPagePdfium extends PdfPage { params.fullHeight, 0, params.flags | - (params.annotationRenderingMode != - PdfAnnotationRenderingMode.none + (params.annotationRenderingMode != PdfAnnotationRenderingMode.none ? pdfium_bindings.FPDF_ANNOT : 0), ); if (params.formHandle != 0 && - params.annotationRenderingMode == - PdfAnnotationRenderingMode.annotationAndForms) { + params.annotationRenderingMode == PdfAnnotationRenderingMode.annotationAndForms) { pdfium.FPDF_FFLDraw( - pdfium_bindings.FPDF_FORMHANDLE.fromAddress( - params.formHandle, - ), + pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle), bmp, page, -params.x, @@ -803,11 +713,7 @@ class _PdfPagePdfium extends PdfPage { final resultBuffer = buffer; buffer = nullptr; - return _PdfImagePdfium._( - width: width, - height: height, - buffer: resultBuffer, - ); + return _PdfImagePdfium._(width: width, height: height, buffer: resultBuffer); } catch (e) { return null; } finally { @@ -817,8 +723,7 @@ class _PdfPagePdfium extends PdfPage { } @override - PdfPageRenderCancellationTokenPdfium createCancellationToken() => - PdfPageRenderCancellationTokenPdfium(this); + PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this); @override Future loadRawText() async { @@ -860,10 +765,7 @@ class _PdfPagePdfium extends PdfPage { } @override - Future> loadLinks({ - bool compact = false, - bool enableAutoLinkDetection = true, - }) async { + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { final links = await _loadAnnotLinks(); if (enableAutoLinkDetection) { links.addAll(await _loadWebLinks()); @@ -883,9 +785,7 @@ class _PdfPagePdfium extends PdfPage { pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; try { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.document, - ); + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); textPage = pdfium.FPDFText_LoadPage(page); if (textPage == nullptr) return []; @@ -895,28 +795,20 @@ class _PdfPagePdfium extends PdfPage { final doubleSize = sizeOf(); return using((arena) { final rectBuffer = arena.allocate(4 * doubleSize); - return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), ( - index, - ) { - final rects = List.generate( - pdfium.FPDFLink_CountRects(linkPage, index), - (rectIndex) { - pdfium.FPDFLink_GetRect( - linkPage, - index, - rectIndex, - rectBuffer, - rectBuffer.offset(doubleSize), - rectBuffer.offset(doubleSize * 2), - rectBuffer.offset(doubleSize * 3), - ); - return _rectFromLTRBBuffer(rectBuffer); - }, - ); - return PdfLink( - rects, - url: Uri.tryParse(_getLinkUrl(linkPage, index, arena)), - ); + return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), (index) { + final rects = List.generate(pdfium.FPDFLink_CountRects(linkPage, index), (rectIndex) { + pdfium.FPDFLink_GetRect( + linkPage, + index, + rectIndex, + rectBuffer, + rectBuffer.offset(doubleSize), + rectBuffer.offset(doubleSize * 2), + rectBuffer.offset(doubleSize * 3), + ); + return _rectFromLTRBBuffer(rectBuffer); + }); + return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); }); }); } finally { @@ -926,15 +818,9 @@ class _PdfPagePdfium extends PdfPage { } }, (document: document.document.address, pageNumber: pageNumber)); - static String _getLinkUrl( - pdfium_bindings.FPDF_PAGELINK linkPage, - int linkIndex, - Arena arena, - ) { + static String _getLinkUrl(pdfium_bindings.FPDF_PAGELINK linkPage, int linkIndex, Arena arena) { final urlLength = pdfium.FPDFLink_GetURL(linkPage, linkIndex, nullptr, 0); - final urlBuffer = arena.allocate( - urlLength * sizeOf(), - ); + final urlBuffer = arena.allocate(urlLength * sizeOf()); pdfium.FPDFLink_GetURL(linkPage, linkIndex, urlBuffer, urlLength); return urlBuffer.cast().toDartString(); } @@ -943,15 +829,11 @@ class _PdfPagePdfium extends PdfPage { ? [] : await (await backgroundWorker).compute( (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress( - params.document, - ); + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); try { final count = pdfium.FPDFPage_GetAnnotCount(page); - final rectf = arena.allocate( - sizeOf(), - ); + final rectf = arena.allocate(sizeOf()); final links = []; for (int i = 0; i < count; i++) { final annot = pdfium.FPDFPage_GetAnnot(page, i); @@ -965,11 +847,7 @@ class _PdfPagePdfium extends PdfPage { ); final dest = _processAnnotDest(annot, document, arena); if (dest != nullptr) { - links.add( - PdfLink([ - rect, - ], dest: _pdfDestFromDest(dest, document, arena)), - ); + links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena))); } else { final uri = _processAnnotLink(annot, document, arena); if (uri != null) { @@ -1019,12 +897,7 @@ class _PdfPagePdfium extends PdfPage { case pdfium_bindings.PDFACTION_URI: final size = pdfium.FPDFAction_GetURIPath(document, action, nullptr, 0); final buffer = arena.allocate(size); - pdfium.FPDFAction_GetURIPath( - document, - action, - buffer.cast(), - size, - ); + pdfium.FPDFAction_GetURIPath(document, action, buffer.cast(), size); try { final String newBuffer = buffer.toDartString(); return Uri.tryParse(newBuffer); @@ -1037,8 +910,7 @@ class _PdfPagePdfium extends PdfPage { } } -class PdfPageRenderCancellationTokenPdfium - extends PdfPageRenderCancellationToken { +class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { PdfPageRenderCancellationTokenPdfium(this.page); final PdfPage page; Pointer? _cancelFlag; @@ -1075,11 +947,7 @@ class _PdfImagePdfium extends PdfImage { final Pointer _buffer; - _PdfImagePdfium._({ - required this.width, - required this.height, - required Pointer buffer, - }) : _buffer = buffer; + _PdfImagePdfium._({required this.width, required this.height, required Pointer buffer}) : _buffer = buffer; @override void dispose() { @@ -1087,32 +955,20 @@ class _PdfImagePdfium extends PdfImage { } } -PdfRect _rectFromLTRBBuffer(Pointer buffer) => - PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); +PdfRect _rectFromLTRBBuffer(Pointer buffer) => PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); extension _PointerExt on Pointer { - Pointer offset(int offsetInBytes) => - Pointer.fromAddress(address + offsetInBytes); + Pointer offset(int offsetInBytes) => Pointer.fromAddress(address + offsetInBytes); } -PdfDest? _pdfDestFromDest( - pdfium_bindings.FPDF_DEST dest, - pdfium_bindings.FPDF_DOCUMENT document, - Arena arena, -) { +PdfDest? _pdfDestFromDest(pdfium_bindings.FPDF_DEST dest, pdfium_bindings.FPDF_DOCUMENT document, Arena arena) { if (dest == nullptr) return null; final pul = arena.allocate(sizeOf()); - final values = arena.allocate( - sizeOf() * 4, - ); + final values = arena.allocate(sizeOf() * 4); final pageIndex = pdfium.FPDFDest_GetDestPageIndex(document, dest); final type = pdfium.FPDFDest_GetView(dest, pul, values); if (type != 0) { - return PdfDest( - pageIndex + 1, - PdfDestCommand.values[type], - List.generate(pul.value, (index) => values[index]), - ); + return PdfDest(pageIndex + 1, PdfDestCommand.values[type], List.generate(pul.value, (index) => values[index])); } return null; } diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index c69173d0..d2a5fb3d 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -17,15 +17,8 @@ class BackgroundWorker { static Future create({String? debugName}) async { final receivePort = ReceivePort(); - await Isolate.spawn( - _workerEntry, - receivePort.sendPort, - debugName: debugName, - ); - final worker = BackgroundWorker._( - receivePort, - await receivePort.first as SendPort, - ); + await Isolate.spawn(_workerEntry, receivePort.sendPort, debugName: debugName); + final worker = BackgroundWorker._(receivePort, await receivePort.first as SendPort); // propagate the pdfium module path to the worker worker.compute((params) { @@ -50,10 +43,7 @@ class BackgroundWorker { }); } - Future compute( - PdfrxComputeCallback callback, - M message, - ) async { + Future compute(PdfrxComputeCallback callback, M message) async { if (_isDisposed) { throw StateError('Worker is already disposed'); } @@ -63,10 +53,7 @@ class BackgroundWorker { } /// [compute] wrapper that also provides [Arena] for temporary memory allocation. - Future computeWithArena( - R Function(Arena arena, M message) callback, - M message, - ) => + Future computeWithArena(R Function(Arena arena, M message) callback, M message) => compute((message) => using((arena) => callback(arena, message)), message); void dispose() { diff --git a/packages/pdfrx_engine/lib/src/pdf_api.dart b/packages/pdfrx_engine/lib/src/pdf_api.dart index 924712b3..e1e87820 100644 --- a/packages/pdfrx_engine/lib/src/pdf_api.dart +++ b/packages/pdfrx_engine/lib/src/pdf_api.dart @@ -8,8 +8,7 @@ import 'package:http/http.dart' as http; import 'package:pdfrx_engine/src/utils/unmodifiable_list.dart'; import 'package:vector_math/vector_math_64.dart' hide Colors; -import './mock/pdfrx_mock.dart' - if (dart.library.io) './native/pdfrx_pdfium.dart'; +import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. @@ -91,8 +90,7 @@ abstract class PdfDocumentFactory { }); Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) - read, + required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -118,8 +116,7 @@ abstract class PdfDocumentFactory { /// /// [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]); +typedef PdfDownloadProgressCallback = void Function(int downloadedBytes, [int? totalBytes]); /// Function to provide password for encrypted PDF. /// @@ -258,8 +255,7 @@ abstract class PdfDocument { /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. static Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) - read, + required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, required String sourceName, PdfPasswordProvider? passwordProvider, @@ -347,8 +343,7 @@ abstract class PdfDocument { bool isIdenticalDocumentHandle(Object? other); } -typedef PdfPageLoadingCallback = - FutureOr Function(int currentPageNumber, int totalPageCount, T? data); +typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); /// PDF document event types. enum PdfDocumentEventType { pageStatusChanged } @@ -436,8 +431,7 @@ abstract class PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, - PdfAnnotationRenderingMode annotationRenderingMode = - PdfAnnotationRenderingMode.annotationAndForms, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }); @@ -452,12 +446,7 @@ abstract class PdfPage { Future loadText() async { final raw = await _loadFormattedText(); if (raw == null) { - return PdfPageText( - pageNumber: pageNumber, - fullText: '', - charRects: [], - fragments: [], - ); + return PdfPageText(pageNumber: pageNumber, fullText: '', charRects: [], fragments: []); } final inputCharRects = raw.charRects; final inputFullText = raw.fullText; @@ -476,11 +465,7 @@ abstract class PdfPage { PdfTextDirection getLineDirection(int start, int end) { if (start == end || start + 1 == end) return PdfTextDirection.unknown; - return vector2direction( - inputCharRects[start].center.differenceTo( - inputCharRects[end - 1].center, - ), - ); + return vector2direction(inputCharRects[start].center.differenceTo(inputCharRects[end - 1].center)); } void addWord( @@ -501,32 +486,11 @@ abstract class PdfPage { switch (dir) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: - outputCharRects.add( - PdfRect( - a.right, - bounds.top, - a.right < b.left ? b.left : a.right, - bounds.bottom, - ), - ); + outputCharRects.add(PdfRect(a.right, bounds.top, a.right < b.left ? b.left : a.right, bounds.bottom)); case PdfTextDirection.rtl: - outputCharRects.add( - PdfRect( - b.right, - bounds.top, - b.right < a.left ? a.left : b.right, - bounds.bottom, - ), - ); + outputCharRects.add(PdfRect(b.right, bounds.top, b.right < a.left ? a.left : b.right, bounds.bottom)); case PdfTextDirection.vrtl: - outputCharRects.add( - PdfRect( - bounds.left, - a.bottom, - bounds.right, - a.bottom > b.top ? b.top : a.bottom, - ), - ); + outputCharRects.add(PdfRect(bounds.left, a.bottom, bounds.right, a.bottom > b.top ? b.top : a.bottom)); } outputText.write(' '); } @@ -536,27 +500,11 @@ abstract class PdfPage { switch (dir) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: - outputCharRects.add( - PdfRect( - bounds.right, - bounds.top, - bounds.right, - bounds.bottom, - ), - ); + outputCharRects.add(PdfRect(bounds.right, bounds.top, bounds.right, bounds.bottom)); case PdfTextDirection.rtl: - outputCharRects.add( - PdfRect(bounds.left, bounds.top, bounds.left, bounds.bottom), - ); + outputCharRects.add(PdfRect(bounds.left, bounds.top, bounds.left, bounds.bottom)); case PdfTextDirection.vrtl: - outputCharRects.add( - PdfRect( - bounds.left, - bounds.bottom, - bounds.right, - bounds.bottom, - ), - ); + outputCharRects.add(PdfRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom)); } outputText.write('\n'); } @@ -568,22 +516,17 @@ abstract class PdfPage { case PdfTextDirection.unknown: for (int i = wordStart; i < wordEnd; i++) { final r = inputCharRects[i]; - outputCharRects.add( - PdfRect(r.left, bounds.top, r.right, bounds.bottom), - ); + outputCharRects.add(PdfRect(r.left, bounds.top, r.right, bounds.bottom)); } case PdfTextDirection.vrtl: for (int i = wordStart; i < wordEnd; i++) { final r = inputCharRects[i]; - outputCharRects.add( - PdfRect(bounds.left, r.top, bounds.right, r.bottom), - ); + outputCharRects.add(PdfRect(bounds.left, r.top, bounds.right, r.bottom)); } } outputText.write(inputFullText.substring(wordStart, wordEnd)); } - if (outputText.length > pos) - fragmentsTmp.add((length: outputText.length - pos, direction: dir)); + if (outputText.length > pos) fragmentsTmp.add((length: outputText.length - pos, direction: dir)); } } @@ -613,10 +556,7 @@ abstract class PdfPage { return cur.center.differenceTo(next.center); } - List<({int start, int end, PdfTextDirection dir})> splitLine( - int start, - int end, - ) { + List<({int start, int end, PdfTextDirection dir})> splitLine(int start, int end) { final list = <({int start, int end, PdfTextDirection dir})>[]; final lineThreshold = 1.5; // radians final last = end - 1; @@ -625,11 +565,7 @@ abstract class PdfPage { for (int next = start + 1; next < last;) { final nextVec = charVec(next, curVec); if (curVec.angleTo(nextVec) > lineThreshold) { - list.add(( - start: curStart, - end: next + 1, - dir: vector2direction(curVec), - )); + list.add((start: curStart, end: next + 1, dir: vector2direction(curVec))); curStart = next + 1; if (next + 2 == end) break; curVec = charVec(next + 1, nextVec); @@ -651,10 +587,7 @@ abstract class PdfPage { if (segments.length >= 2) { for (int i = 0; i < segments.length; i++) { final seg = segments[i]; - final bounds = inputCharRects.boundingRect( - start: seg.start, - end: seg.end, - ); + final bounds = inputCharRects.boundingRect(start: seg.start, end: seg.end); addWords(seg.start, seg.end, seg.dir, bounds); if (i + 1 == segments.length && newLineEnd != null) { addWord(seg.end, newLineEnd, seg.dir, bounds, isNewLine: true); @@ -675,9 +608,7 @@ abstract class PdfPage { handleLine(lineStart, match.start, newLineEnd: match.end); } else { final lastRect = outputCharRects.last; - outputCharRects.add( - PdfRect(lastRect.left, lastRect.top, lastRect.left, lastRect.bottom), - ); + outputCharRects.add(PdfRect(lastRect.left, lastRect.top, lastRect.left, lastRect.bottom)); outputText.write('\n'); } lineStart = match.end; @@ -699,11 +630,7 @@ abstract class PdfPage { final length = fragmentsTmp[i].length; final direction = fragmentsTmp[i].direction; final end = start + length; - final fragmentRects = UnmodifiableSublist( - outputCharRects, - start: start, - end: end, - ); + final fragmentRects = UnmodifiableSublist(outputCharRects, start: start, end: end); fragments.add( PdfPageTextFragment( pageText: text, @@ -750,9 +677,7 @@ abstract class PdfPage { final rect = raw.charRects[lineStart]; final nextRect = raw.charRects[match.end]; final nextCenterX = nextRect.center.x; - if (rect.left < nextCenterX && - nextCenterX < rect.right && - rect.top > nextRect.top) { + if (rect.left < nextCenterX && nextCenterX < rect.right && rect.top > nextRect.top) { // The line is vertical, and the line-feed is virtual continue; } @@ -779,10 +704,7 @@ abstract class PdfPage { /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. /// The default is true. - Future> loadLinks({ - bool compact = false, - bool enableAutoLinkDetection = true, - }); + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); } /// PDF's raw text and its associated character bounding boxes. @@ -931,10 +853,7 @@ class PdfPageText { charRects: const [], direction: PdfTextDirection.unknown, ); - final index = fragments.lowerBound( - searchIndex, - (a, b) => a.index - b.index, - ); + final index = fragments.lowerBound(searchIndex, (a, b) => a.index - b.index); if (index > fragments.length) { return -1; // range error } @@ -968,10 +887,7 @@ class PdfPageText { /// /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Stream allMatches( - Pattern pattern, { - bool caseInsensitive = true, - }) async* { + Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { final String text; if (pattern is RegExp) { caseInsensitive = pattern.isCaseSensitive; @@ -985,11 +901,7 @@ class PdfPageText { final matches = pattern.allMatches(text); for (final match in matches) { if (match.start == match.end) continue; - final m = PdfPageTextRange( - pageText: this, - start: match.start, - end: match.end, - ); + final m = PdfPageTextRange(pageText: this, start: match.start, end: match.end); yield m; } } @@ -1002,9 +914,7 @@ class PdfPageText { final min = a < b ? a : b; final max = a < b ? b : a; if (min < 0 || max > fullText.length) { - throw RangeError( - 'Indices out of range: $min, $max for fullText length ${fullText.length}.', - ); + throw RangeError('Indices out of range: $min, $max for fullText length ${fullText.length}.'); } return PdfPageTextRange(pageText: this, start: min, end: max + 1); } @@ -1072,11 +982,7 @@ class PdfPageTextRange { /// Create a [PdfPageTextRange]. /// /// [start] is inclusive and [end] is exclusive. - const PdfPageTextRange({ - required this.pageText, - required this.start, - required this.end, - }); + const PdfPageTextRange({required this.pageText, required this.start, required this.end}); /// The page text the text range are associated with. final PdfPageText pageText; @@ -1128,11 +1034,7 @@ class PdfPageTextRange { for (int i = fStart; i <= fEnd; i++) { final f = pageText.fragments[i]; if (f.end <= start || end <= f.index) continue; - yield PdfTextFragmentBoundingRect( - f, - max(start - f.index, 0), - min(end - f.index, f.length), - ); + yield PdfTextFragmentBoundingRect(f, max(start - f.index, 0), min(end - f.index, f.length)); } } } @@ -1145,14 +1047,8 @@ class PdfPageTextRange { /// The unit is normally in points (1/72 inch). class PdfRect { const PdfRect(this.left, this.top, this.right, this.bottom) - : assert( - left <= right, - 'Left coordinate must be less than or equal to right coordinate.', - ), - assert( - top >= bottom, - 'Top coordinate must be greater than or equal to bottom coordinate.', - ); + : assert(left <= right, 'Left coordinate must be less than or equal to right coordinate.'), + assert(top >= bottom, 'Top coordinate must be greater than or equal to bottom coordinate.'); /// Left coordinate. final double left; @@ -1205,14 +1101,10 @@ class PdfRect { /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). bool containsXy(double x, double y, {double margin = 0}) => - x >= left - margin && - x <= right + margin && - y >= bottom - margin && - y <= top + margin; + x >= left - margin && x <= right + margin && y >= bottom - margin && y <= top + margin; /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool containsPoint(PdfPoint offset, {double margin = 0}) => - containsXy(offset.x, offset.y, margin: margin); + bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); double distanceSquaredTo(PdfPoint point) { if (containsPoint(point)) { @@ -1245,12 +1137,7 @@ class PdfRect { case 1: return PdfRect(bottom, width - left, top, width - right); case 2: - return PdfRect( - width - right, - height - bottom, - width - left, - height - top, - ); + return PdfRect(width - right, height - bottom, width - left, height - top); case 3: return PdfRect(height - top, right, height - bottom, left); default: @@ -1269,12 +1156,7 @@ class PdfRect { case 1: return PdfRect(width - top, right, width - bottom, left); case 2: - return PdfRect( - width - right, - height - bottom, - width - left, - height - top, - ); + return PdfRect(width - right, height - bottom, width - left, height - top); case 3: return PdfRect(bottom, height - left, top, height - right); default: @@ -1282,23 +1164,17 @@ class PdfRect { } } - PdfRect inflate(double dx, double dy) => - PdfRect(left - dx, top + dy, right + dx, bottom - dy); + PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is PdfRect && - other.left == left && - other.top == top && - other.right == right && - other.bottom == bottom; + return other is PdfRect && other.left == left && other.top == top && other.right == right && other.bottom == bottom; } @override - int get hashCode => - left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; + int get hashCode => left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; @override String toString() { @@ -1358,8 +1234,7 @@ class PdfTextFragmentBoundingRect { final int eif; /// Rectangle in PDF page coordinates. - PdfRect get bounds => - fragment.pageText.charRects.boundingRect(start: start, end: end); + PdfRect get bounds => fragment.pageText.charRects.boundingRect(start: start, end: end); /// Start index of the text range in page's full text. int get start => fragment.index + sif; @@ -1385,17 +1260,14 @@ class PdfDest { final List? params; @override - String toString() => - 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; + String toString() => 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; /// Compact the destination. /// /// The method is used to compact the destination to reduce memory usage. /// [params] is typically growable and also modifiable. The method ensures that [params] is unmodifiable. PdfDest compact() { - return params == null - ? this - : PdfDest(pageNumber, command, List.unmodifiable(params!)); + return params == null ? this : PdfDest(pageNumber, command, List.unmodifiable(params!)); } } @@ -1419,10 +1291,7 @@ enum PdfDestCommand { /// Parse the command name to [PdfDestCommand]. factory PdfDestCommand.parse(String name) { final nameLow = name.toLowerCase(); - return PdfDestCommand.values.firstWhere( - (e) => e.name == nameLow, - orElse: () => PdfDestCommand.unknown, - ); + return PdfDestCommand.values.firstWhere((e) => e.name == nameLow, orElse: () => PdfDestCommand.unknown); } } @@ -1463,11 +1332,7 @@ class PdfLink { /// /// See [PdfDocument.loadOutline]. class PdfOutlineNode { - const PdfOutlineNode({ - required this.title, - required this.dest, - required this.children, - }); + const PdfOutlineNode({required this.title, required this.dest, required this.children}); /// Outline node title. final String title; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart index e38c5c91..c6a6d46b 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart @@ -15,30 +15,21 @@ const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; /// [pdfiumRelease] is the release of pdfium to download if not already present. /// /// The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. -Future pdfrxEngineDartInitialize({ - String? tmpPath, - String? pdfiumRelease = pdfrxCurrentPdfiumRelease, -}) async { +Future pdfrxEngineDartInitialize({String? tmpPath, String? pdfiumRelease = pdfrxCurrentPdfiumRelease}) async { if (_isInitialized) return; Pdfrx.loadAsset ??= (name) async { - throw UnimplementedError( - 'By default, Pdfrx.loadAsset is not implemented for Dart.', - ); + throw UnimplementedError('By default, Pdfrx.loadAsset is not implemented for Dart.'); }; final tmpDir = Directory.systemTemp; Pdfrx.getCacheDirectory ??= () => tmpDir.path; - final pdfiumPath = Directory( - Platform.environment['PDFIUM_PATH'] ?? "${tmpDir.path}/pdfrx.cache/pdfium", - ); + final pdfiumPath = Directory(Platform.environment['PDFIUM_PATH'] ?? "${tmpDir.path}/pdfrx.cache/pdfium"); Pdfrx.pdfiumModulePath ??= pdfiumPath.path; if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath( - pdfiumPath.path, - ); + Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath(pdfiumPath.path); } _isInitialized = true; @@ -58,31 +49,13 @@ Future downloadAndGetPdfiumModulePath( final platform = pa[1]!; final arch = pa[2]!; if (platform == 'windows' && arch == 'x64') { - return await _downloadPdfium( - tmpPath, - 'win', - arch, - 'bin/pdfium.dll', - pdfiumRelease, - ); + return await _downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); } if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await _downloadPdfium( - tmpPath, - platform, - arch, - 'lib/libpdfium.so', - pdfiumRelease, - ); + return await _downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); } if (platform == 'macos') { - return await _downloadPdfium( - tmpPath, - 'mac', - arch, - 'lib/libpdfium.dylib', - pdfiumRelease, - ); + return await _downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); } else { throw Exception('Unsupported platform: $platform-$arch'); } @@ -106,9 +79,7 @@ Future _downloadPdfium( if (tgz.statusCode != 200) { throw Exception('Failed to download pdfium: $uri'); } - final archive = TarDecoder().decodeBytes( - GZipDecoder().decodeBytes(tgz.bodyBytes), - ); + final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); try { await tmpDir.delete(recursive: true); } catch (_) {} diff --git a/packages/pdfrx_engine/test/pdf_document_test.dart b/packages/pdfrx_engine/test/pdf_document_test.dart index 3380f10e..c0e24b55 100644 --- a/packages/pdfrx_engine/test/pdf_document_test.dart +++ b/packages/pdfrx_engine/test/pdf_document_test.dart @@ -12,22 +12,14 @@ final testPdfFile = File('../pdfrx/example/viewer/assets/hello.pdf'); void main() { setUp(() => pdfrxEngineDartInitialize(tmpPath: tmpRoot.path)); - test( - 'PdfDocument.openFile', - () async => - await testDocument(await PdfDocument.openFile(testPdfFile.path)), - ); + test('PdfDocument.openFile', () async => await testDocument(await PdfDocument.openFile(testPdfFile.path))); test('PdfDocument.openData', () async { final data = await testPdfFile.readAsBytes(); await testDocument(await PdfDocument.openData(data)); }); test('PdfDocument.openUri', () async { - Pdfrx.createHttpClient = () => MockClient( - (request) async => - http.Response.bytes(await testPdfFile.readAsBytes(), 200), - ); - await testDocument( - await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf')), - ); + Pdfrx.createHttpClient = () => + MockClient((request) async => http.Response.bytes(await testPdfFile.readAsBytes(), 200)); + await testDocument(await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf'))); }); } From 44c9ff5ad612770643b92a8fe92f01781304775a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Jul 2025 00:52:30 +0900 Subject: [PATCH 191/663] pdfrx_engine 0.1.2 --- packages/pdfrx_engine/CHANGELOG.md | 10 +++++++++- packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index effe43c8..d2ec2c8f 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,11 @@ -## 1.0.0 +## 0.1.2 + +- Introduces new PDF text extraction API. + +## 0.1.1 + +- Minor fixes. + +## 0.1.0 - Initial version. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 52db99fd..73552477 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 84f8d76b95964900408cbc918c893419b12e189d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Jul 2025 02:06:21 +0900 Subject: [PATCH 192/663] pdfrx_engine 0.1.3 --- packages/pdfrx/example/viewer/pubspec.lock | 26 +++++- packages/pdfrx/lib/src/pdf_document_ref.dart | 3 +- packages/pdfrx/lib/src/pdfrx_flutter.dart | 10 ++- .../pdfrx/lib/src/utils/native/native.dart | 5 +- packages/pdfrx/lib/src/utils/web/web.dart | 6 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 2 - .../lib/src/widgets/pdf_error_widget.dart | 2 +- .../lib/src/widgets/pdf_text_searcher.dart | 2 +- .../lib/src/widgets/pdf_viewer_params.dart | 6 +- packages/pdfrx/test/pdf_viewer_test.dart | 4 +- packages/pdfrx_engine/CHANGELOG.md | 5 ++ packages/pdfrx_engine/README.md | 13 ++- packages/pdfrx_engine/example/README.md | 32 +++++++ packages/pdfrx_engine/example/pdf2image.dart | 69 +++++++++++++++ packages/pdfrx_engine/lib/pdfrx_engine.dart | 5 +- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 2 +- .../lib/src/native/pdf_file_cache.dart | 7 +- .../lib/src/native/pdfrx_pdfium.dart | 2 +- .../pdfrx_engine/lib/src/native/worker.dart | 2 +- .../lib/src/pdfium_downloader.dart | 66 ++++++++++++++ .../lib/src/{pdf_api.dart => pdfrx_api.dart} | 14 +-- packages/pdfrx_engine/lib/src/pdfrx_dart.dart | 18 ++++ .../lib/src/pdfrx_engine_dart.dart | 88 ------------------- .../lib/src/pdfrx_initialize_dart.dart | 40 +++++++++ packages/pdfrx_engine/pubspec.yaml | 4 +- .../pdfrx_engine/test/pdf_document_test.dart | 2 +- 26 files changed, 307 insertions(+), 128 deletions(-) create mode 100644 packages/pdfrx_engine/example/README.md create mode 100644 packages/pdfrx_engine/example/pdf2image.dart create mode 100644 packages/pdfrx_engine/lib/src/pdfium_downloader.dart rename packages/pdfrx_engine/lib/src/{pdf_api.dart => pdfrx_api.dart} (99%) create mode 100644 packages/pdfrx_engine/lib/src/pdfrx_dart.dart delete mode 100644 packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart create mode 100644 packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index de0adeda..71fb5cb5 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -205,6 +205,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" intl: dependency: transitive description: @@ -346,7 +354,15 @@ packages: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.1" + version: "0.1.3" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -544,6 +560,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index a0ab4892..cec05536 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -1,9 +1,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:pdfrx/pdfrx.dart'; import 'package:synchronized/extension.dart'; +import '../pdfrx.dart'; + /// Callback function to notify download progress. /// /// [downloadedBytes] is the number of bytes downloaded so far. diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 941c4d9e..a7f73088 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -3,8 +3,8 @@ import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:pdfrx/pdfrx.dart'; +import '../pdfrx.dart'; import 'utils/platform.dart'; bool _isInitialized = false; @@ -14,6 +14,8 @@ bool _isInitialized = false; /// This function actually sets up the following functions: /// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. /// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. +/// +/// For Dart (non-Flutter) programs, you should call [pdfrxInitialize] instead. void pdfrxFlutterInitialize() { if (_isInitialized) return; @@ -40,6 +42,8 @@ extension PdfPageExt on PdfPage { extension PdfImageExt on PdfImage { /// Create [Image] from the rendered image. + /// + /// The returned [Image] must be disposed of when no longer needed. Future createImage() { final comp = Completer(); decodeImageFromPixels(pixels, width, height, PixelFormat.bgra8888, (image) => comp.complete(image)); @@ -50,7 +54,7 @@ extension PdfImageExt on PdfImage { extension PdfRectExt on PdfRect { /// Convert to [Rect] in Flutter coordinate. /// [page] is the page to convert the rectangle. - /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. + /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage].size is used. /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); @@ -84,7 +88,7 @@ extension RectPdfRectExt on Rect { extension PdfPointExt on PdfPoint { /// Convert to [Offset] in Flutter coordinate. /// [page] is the page to convert the rectangle. - /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage.size] is used. + /// [scaledPageSize] is the scaled page size to scale the rectangle. If not specified, [PdfPage].size is used. /// [rotation] is the rotation of the page. If not specified, [PdfPage.rotation] is used. Offset toOffset({required PdfPage page, Size? scaledPageSize, int? rotation}) { final rotated = rotate(rotation ?? page.rotation.index, page); diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 3293a5e5..44b4279c 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,7 +1,8 @@ import 'dart:io'; import 'package:flutter/services.dart'; -import 'package:pdfrx/pdfrx.dart'; + +import '../../../pdfrx.dart'; final isApple = Platform.isMacOS || Platform.isIOS; final isWindows = Platform.isWindows; @@ -25,4 +26,4 @@ bool get shouldTextSelectionTriggeredBySwipe { } /// Override for the [PdfDocumentFactory] for native platforms; it is null. -final PdfDocumentFactory? pdfDocumentFactoryOverride = null; +PdfDocumentFactory? get pdfDocumentFactoryOverride => null; diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 924ebfb3..c767e615 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart'; -import 'package:pdfrx/pdfrx.dart'; import 'package:web/web.dart' as web; +import '../../../pdfrx.dart'; import '../../wasm/pdfrx_wasm.dart'; final isApple = false; @@ -22,4 +22,6 @@ final isMobile = false; bool get shouldTextSelectionTriggeredBySwipe => true; /// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. -final PdfDocumentFactory? pdfDocumentFactoryOverride = PdfDocumentFactoryWasmImpl(); +PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; + +final _factoryWasm = PdfDocumentFactoryWasmImpl(); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index ca6c88e0..39b2efca 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -49,8 +49,6 @@ Future> _sendCommand(String command, {Map pdfrxEngineDartInitialize()); + // and pdfrxInitialize is a better way to initialize the library. + setUp(() => pdfrxInitialize()); Pdfrx.createHttpClient = () => MockClient((request) async { return http.Response.bytes(await testPdfFile.readAsBytes(), 200); diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index d2ec2c8f..3cb0d7dc 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.3 + +- Add an example for converting PDF pages to images. +- Add PdfImage.createImageNF() extension method to create Image (of image package) from PdfImage. + ## 0.1.2 - Introduces new PDF text extraction API. diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 94ec9219..c6fa1f34 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -22,19 +22,24 @@ This package is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutt The following fragment illustrates how to use the PDF engine to load and render a PDF file: ```dart +import 'dart:io'; +import 'package:image/image.dart' as img; import 'package:pdfrx_engine/pdfrx_engine.dart'; void main() async { + await pdfrxInitialize(); + final document = await PdfDocument.openFile('test.pdf'); - final page = document.pages[0]; - final image = await page.render( + final page = document.pages[0]; // first page + final pageImage = await page.render( width: page.width * 200 / 72, height: page.height * 200 / 72, ); - image.dispose(); + final image = pageImage!.createImageNF(); + await File('output.png').writeAsBytes(img.encodePng(image)); + pageImage.dispose(); document.close(); } - ``` ## PDF API diff --git a/packages/pdfrx_engine/example/README.md b/packages/pdfrx_engine/example/README.md new file mode 100644 index 00000000..ea1a7f12 --- /dev/null +++ b/packages/pdfrx_engine/example/README.md @@ -0,0 +1,32 @@ +# pdfrx_engine Examples + +This directory contains examples demonstrating how to use pdfrx_engine. + +## pdf2image.dart + +Converts a PDF file to PNG images (one image per page). + +### Usage + +```bash +dart pub get +dart run pdf2image.dart [output_dir] +``` + +### Example + +```bash +dart run pdf2image.dart document.pdf ./output +``` + +This will: +- Read `document.pdf` +- Create PNG images for each page +- Save them as `page_1.png`, `page_2.png`, etc. in the `./output` directory + +### Features + +- Renders pages at 2x scale for better quality +- Uses white background for transparent areas +- Creates output directory if it doesn't exist +- Uses the `createImageNF` extension method for image conversion \ No newline at end of file diff --git a/packages/pdfrx_engine/example/pdf2image.dart b/packages/pdfrx_engine/example/pdf2image.dart new file mode 100644 index 00000000..91ed9b5d --- /dev/null +++ b/packages/pdfrx_engine/example/pdf2image.dart @@ -0,0 +1,69 @@ +import 'dart:io'; + +import 'package:image/image.dart' as img; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +Future main(List args) async { + if (args.isEmpty) { + print('Usage: dart pdf2image.dart [output_dir]'); + print('Example: dart pdf2image.dart document.pdf ./output'); + return 1; + } + + final pdfFile = args[0]; + final outputDir = args.length > 1 ? args[1] : './output'; + + // Create output directory if it doesn't exist + final outputDirectory = Directory(outputDir); + if (!outputDirectory.existsSync()) { + outputDirectory.createSync(recursive: true); + } + + print('Converting PDF: $pdfFile'); + print('Output directory: $outputDir'); + + try { + await pdfrxInitialize(); + + // Open the PDF document + final document = await PdfDocument.openFile(pdfFile); + + print('PDF opened successfully. Pages: ${document.pages.length}'); + + // Process each page + for (int i = 0; i < document.pages.length; i++) { + final pageNumber = i + 1; + print('Processing page $pageNumber/${document.pages.length}...'); + + final page = document.pages[i]; + + // Render at 200 DPI + const scale = 200.0 / 72; + final pageImage = await page.render(fullWidth: page.width * scale, fullHeight: page.height * scale); + if (pageImage == null) { + print('Failed to render page $pageNumber'); + continue; + } + + // Convert to image format using the createImageNF extension + final image = pageImage.createImageNF(); + + pageImage.dispose(); + + // Save as PNG + final outputFile = File('$outputDir/page_$pageNumber.png'); + await outputFile.writeAsBytes(img.encodePng(image)); + print('Saved: ${outputFile.path}'); + } + + // Clean up + document.dispose(); + + print('\nConversion completed successfully!'); + print('Output files saved in: $outputDir'); + return 0; + } catch (e) { + print('Error: $e'); + return 1; + } +} diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 6fb27fcf..82abe669 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -1,4 +1,5 @@ library; -export 'src/pdf_api.dart'; -export 'src/pdfrx_engine_dart.dart'; +export 'src/pdfrx_api.dart'; +export 'src/pdfrx_dart.dart'; +export 'src/pdfrx_initialize_dart.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index cfbdfd8b..f3b27918 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -12,7 +12,7 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { Future unimplemented() { throw UnimplementedError( 'PdfDocumentFactory.instance is not initialized. ' - 'Please call pdfrxFlutterInitialize() or explicitly set PdfDocumentFactory.instance.', + 'Please call pdfrxInitialize()/pdfrxFlutterInitialize() or explicitly set PdfDocumentFactory.instance.', ); } diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 27079087..7e1633da 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -9,7 +9,8 @@ import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:synchronized/extension.dart'; -import '../pdf_api.dart'; +import '../pdfrx_api.dart'; +import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; final _rafFinalizer = Finalizer((raf) { @@ -28,8 +29,8 @@ final _rafFinalizer = Finalizer((raf) { /// /// The cache directory used by this class is obtained using [Pdfrx.getCacheDirectory]. /// -/// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. -/// For Dart only, you can set this function to load assets from your own asset management system. +/// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. +/// For Dart only, call [pdfrxInitialize] or explicitly set [Pdfrx.getCacheDirectory]. class PdfFileCache { PdfFileCache(this.file); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 390ae012..987024f8 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -8,7 +8,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:rxdart/rxdart.dart'; -import '../pdf_api.dart'; +import '../pdfrx_api.dart'; import 'pdf_file_cache.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index d2a5fb3d..25439c5e 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -4,7 +4,7 @@ import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import '../pdf_api.dart'; +import '../pdfrx_api.dart'; typedef PdfrxComputeCallback = FutureOr Function(M message); diff --git a/packages/pdfrx_engine/lib/src/pdfium_downloader.dart b/packages/pdfrx_engine/lib/src/pdfium_downloader.dart new file mode 100644 index 00000000..375fadae --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfium_downloader.dart @@ -0,0 +1,66 @@ +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:http/http.dart' as http; + +/// PdfiumDownloader is a utility class to download the Pdfium module for various platforms. +class PdfiumDownloader { + PdfiumDownloader._(); + + /// The release of pdfium to download. + static const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; + + /// Downloads the pdfium module for the current platform and architecture. + /// + /// Currently, the following platforms are supported: + /// - Windows x64 + /// - Linux x64, arm64 + /// - macOS x64, arm64 + /// + /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. + static Future downloadAndGetPdfiumModulePath( + String tmpPath, { + String? pdfiumRelease = pdfrxCurrentPdfiumRelease, + }) async { + final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; + final platform = pa[1]!; + final arch = pa[2]!; + if (platform == 'windows' && arch == 'x64') { + return await _downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); + } + if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { + return await _downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); + } + if (platform == 'macos') { + return await _downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); + } else { + throw Exception('Unsupported platform: $platform-$arch'); + } + } + + /// Downloads the pdfium module for the given platform and architecture. + static Future _downloadPdfium( + String tmpRoot, + String platform, + String arch, + String modulePath, + String? pdfiumRelease, + ) async { + final tmpDir = Directory('$tmpRoot/$platform-$arch'); + final targetPath = '${tmpDir.path}/$modulePath'; + if (await File(targetPath).exists()) return targetPath; + + final uri = + 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; + final tgz = await http.Client().get(Uri.parse(uri)); + if (tgz.statusCode != 200) { + throw Exception('Failed to download pdfium: $uri'); + } + final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); + try { + await tmpDir.delete(recursive: true); + } catch (_) {} + await extractArchiveToDisk(archive, tmpDir.path); + return targetPath; + } +} diff --git a/packages/pdfrx_engine/lib/src/pdf_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart similarity index 99% rename from packages/pdfrx_engine/lib/src/pdf_api.dart rename to packages/pdfrx_engine/lib/src/pdfrx_api.dart index e1e87820..9ecfe1e3 100644 --- a/packages/pdfrx_engine/lib/src/pdf_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -5,10 +5,10 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; -import 'package:pdfrx_engine/src/utils/unmodifiable_list.dart'; import 'package:vector_math/vector_math_64.dart' hide Colors; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; +import 'utils/unmodifiable_list.dart'; /// Class to provide Pdfrx's configuration. /// The parameters should be set before calling any Pdfrx's functions. @@ -49,7 +49,7 @@ class Pdfrx { /// This function is used to load PDF files from assets. /// It is used to isolate pdfrx API implementation from Flutter framework. /// - /// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. + /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. /// For Dart only, you can set this function to load assets from your own asset management system. static Future Function(String name)? loadAsset; @@ -57,7 +57,7 @@ class Pdfrx { /// /// You can override the default cache directory by setting this variable. /// - /// For Flutter, [pdfrxFlutterInitialize] should be called explicitly or implicitly before using this class. + /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. /// For Dart only, you can set this function to obtain the cache directory from your own file system. static FutureOr Function()? getCacheDirectory; } @@ -392,7 +392,7 @@ abstract class PdfPage { /// Whether the page is really loaded or not. /// - /// If the value is false, the page's [width], [height], [size], and [rotation] are just guessed values and + /// If the value is false, the page's [width], [height], and [rotation] are just guessed values and /// will be updated when the page is really loaded. bool get isLoaded; @@ -405,7 +405,7 @@ abstract class PdfPage { /// - If [fullWidth], [fullHeight] are not specified, [PdfPage.width] and [PdfPage.height] are used (it means rendered at 72-dpi). /// [backgroundColor] is `AARRGGBB` integer color notation used to fill the background of the page. If no color is specified, 0xffffffff (white) is used. /// - [annotationRenderingMode] controls to render annotations or not. The default is [PdfAnnotationRenderingMode.annotationAndForms]. - /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderingFlags.none]. + /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderFlags.none]. /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. /// /// The following figure illustrates what each parameter means: @@ -839,8 +839,8 @@ class PdfPageText { /// Find text fragment index for the specified text index. /// /// If the specified text index is out of range, it returns -1; - /// only the exception is [textIndex] is equal to [fullText.length], - /// which means the end of the text and it returns [fragments.length]. + /// only the exception is [textIndex] is equal to [fullText].length, + /// which means the end of the text and it returns [fragments].length. int getFragmentIndexForTextIndex(int textIndex) { if (textIndex == fullText.length) { return fragments.length; // the end of the text diff --git a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart new file mode 100644 index 00000000..695ca51a --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart @@ -0,0 +1,18 @@ +import 'package:image/image.dart'; + +import '../pdfrx_engine.dart'; + +extension PdfImageDartExt on PdfImage { + /// Create [Image] (of [image package](https://pub.dev/packages/image)) from the rendered image. + /// + /// **NF**: This method does not require Flutter and can be used in pure Dart applications. + Image createImageNF() { + return Image.fromBytes( + width: width, + height: height, + bytes: pixels.buffer, + numChannels: 4, + order: ChannelOrder.bgra, + ); + } +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart deleted file mode 100644 index c6a6d46b..00000000 --- a/packages/pdfrx_engine/lib/src/pdfrx_engine_dart.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:io'; - -import 'package:archive/archive_io.dart'; -import 'package:http/http.dart' as http; -import 'package:pdfrx_engine/pdfrx_engine.dart'; - -bool _isInitialized = false; - -/// The release of pdfium to download. -const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; - -/// Initializes the Pdfrx library for Dart. -/// -/// [tmpPath] is the path to the temporary directory for caching. -/// [pdfiumRelease] is the release of pdfium to download if not already present. -/// -/// The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. -Future pdfrxEngineDartInitialize({String? tmpPath, String? pdfiumRelease = pdfrxCurrentPdfiumRelease}) async { - if (_isInitialized) return; - - Pdfrx.loadAsset ??= (name) async { - throw UnimplementedError('By default, Pdfrx.loadAsset is not implemented for Dart.'); - }; - - final tmpDir = Directory.systemTemp; - Pdfrx.getCacheDirectory ??= () => tmpDir.path; - final pdfiumPath = Directory(Platform.environment['PDFIUM_PATH'] ?? "${tmpDir.path}/pdfrx.cache/pdfium"); - Pdfrx.pdfiumModulePath ??= pdfiumPath.path; - - if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { - pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await downloadAndGetPdfiumModulePath(pdfiumPath.path); - } - - _isInitialized = true; -} - -/// Downloads the pdfium module for the current platform and architecture. -/// -/// Currently, the following platforms are supported: -/// - Windows x64 -/// - Linux x64, arm64 -/// - macOS x64, arm64 -Future downloadAndGetPdfiumModulePath( - String tmpPath, { - String? pdfiumRelease = pdfrxCurrentPdfiumRelease, -}) async { - final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; - final platform = pa[1]!; - final arch = pa[2]!; - if (platform == 'windows' && arch == 'x64') { - return await _downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); - } - if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await _downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); - } - if (platform == 'macos') { - return await _downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); - } else { - throw Exception('Unsupported platform: $platform-$arch'); - } -} - -/// Downloads the pdfium module for the given platform and architecture. -Future _downloadPdfium( - String tmpRoot, - String platform, - String arch, - String modulePath, - String? pdfiumRelease, -) async { - final tmpDir = Directory('$tmpRoot/$platform-$arch'); - final targetPath = '${tmpDir.path}/$modulePath'; - if (await File(targetPath).exists()) return targetPath; - - final uri = - 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; - final tgz = await http.Client().get(Uri.parse(uri)); - if (tgz.statusCode != 200) { - throw Exception('Failed to download pdfium: $uri'); - } - final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); - try { - await tmpDir.delete(recursive: true); - } catch (_) {} - await extractArchiveToDisk(archive, tmpDir.path); - return targetPath; -} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart new file mode 100644 index 00000000..046fb396 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +import '../pdfrx_engine.dart'; +import 'pdfium_downloader.dart'; + +bool _isInitialized = false; + +/// Initializes the Pdfrx library for Dart. +/// +/// This function sets up the following: +/// +/// - [Pdfrx.getCacheDirectory] is set to return the system temporary directory. +/// - [Pdfrx.pdfiumModulePath] is configured to point to the pdfium module. +/// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. +/// - If Pdfium module is not found, it will be downloaded from the internet. +/// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). +/// +/// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. +Future pdfrxInitialize({ + String? tmpPath, + String? pdfiumRelease = PdfiumDownloader.pdfrxCurrentPdfiumRelease, +}) async { + if (_isInitialized) return; + + Pdfrx.loadAsset ??= (name) async { + throw UnimplementedError('By default, Pdfrx.loadAsset is not implemented for Dart.'); + }; + + final tmpDir = Directory.systemTemp; + Pdfrx.getCacheDirectory ??= () => tmpDir.path; + final pdfiumPath = Directory(Platform.environment['PDFIUM_PATH'] ?? '${tmpDir.path}/pdfrx.cache/pdfium'); + Pdfrx.pdfiumModulePath ??= pdfiumPath.path; + + if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { + pdfiumPath.createSync(recursive: true); + Pdfrx.pdfiumModulePath = await PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); + } + + _isInitialized = true; +} diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 73552477..9d02d9a5 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.2 +version: 0.1.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -18,8 +18,8 @@ dependencies: rxdart: synchronized: ^3.3.1 vector_math: ^2.1.4 - web: ^1.1.1 archive: ^4.0.7 + image: ^4.5.4 dev_dependencies: ffigen: ^19.0.0 diff --git a/packages/pdfrx_engine/test/pdf_document_test.dart b/packages/pdfrx_engine/test/pdf_document_test.dart index c0e24b55..287e5c05 100644 --- a/packages/pdfrx_engine/test/pdf_document_test.dart +++ b/packages/pdfrx_engine/test/pdf_document_test.dart @@ -10,7 +10,7 @@ import 'utils.dart'; final testPdfFile = File('../pdfrx/example/viewer/assets/hello.pdf'); void main() { - setUp(() => pdfrxEngineDartInitialize(tmpPath: tmpRoot.path)); + setUp(() => pdfrxInitialize(tmpPath: tmpRoot.path)); test('PdfDocument.openFile', () async => await testDocument(await PdfDocument.openFile(testPdfFile.path))); test('PdfDocument.openData', () async { From 02d785cd54b6d988b953e1c81d36ce535cdf1884 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Jul 2025 02:14:15 +0900 Subject: [PATCH 193/663] pdfrrx_engine 0.1.4 (minor updates) --- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_engine/CHANGELOG.md | 4 +++ packages/pdfrx_engine/example/README.md | 32 ------------------- .../example/{pdf2image.dart => main.dart} | 0 packages/pdfrx_engine/pubspec.yaml | 4 +-- 5 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 packages/pdfrx_engine/example/README.md rename packages/pdfrx_engine/example/{pdf2image.dart => main.dart} (100%) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 2cf702de..c4668725 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -2,7 +2,7 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. version: 1.3.2 homepage: https://github.com/espresso3389/pdfrx -repository: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues screenshots: - description: 'Viewer example on Web' diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 3cb0d7dc..c0336844 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4 + +- Minor updates. + ## 0.1.3 - Add an example for converting PDF pages to images. diff --git a/packages/pdfrx_engine/example/README.md b/packages/pdfrx_engine/example/README.md deleted file mode 100644 index ea1a7f12..00000000 --- a/packages/pdfrx_engine/example/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# pdfrx_engine Examples - -This directory contains examples demonstrating how to use pdfrx_engine. - -## pdf2image.dart - -Converts a PDF file to PNG images (one image per page). - -### Usage - -```bash -dart pub get -dart run pdf2image.dart [output_dir] -``` - -### Example - -```bash -dart run pdf2image.dart document.pdf ./output -``` - -This will: -- Read `document.pdf` -- Create PNG images for each page -- Save them as `page_1.png`, `page_2.png`, etc. in the `./output` directory - -### Features - -- Renders pages at 2x scale for better quality -- Uses white background for transparent areas -- Creates output directory if it doesn't exist -- Uses the `createImageNF` extension method for image conversion \ No newline at end of file diff --git a/packages/pdfrx_engine/example/pdf2image.dart b/packages/pdfrx_engine/example/main.dart similarity index 100% rename from packages/pdfrx_engine/example/pdf2image.dart rename to packages/pdfrx_engine/example/main.dart diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 9d02d9a5..9285322e 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,8 +1,8 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.3 +version: 0.1.4 homepage: https://github.com/espresso3389/pdfrx -repository: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues environment: From c1624144b1831ace81c26eafa13e99339a56d446 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 14 Jul 2025 15:47:23 +0900 Subject: [PATCH 194/663] Text selection anchor/magnifier/context menu --- packages/pdfrx/example/viewer/lib/main.dart | 1 + packages/pdfrx/example/viewer/pubspec.lock | 4 +- packages/pdfrx/example/viewer/pubspec.yaml | 2 - packages/pdfrx/lib/src/pdf_document_ref.dart | 13 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 680 ++++++++++++++---- .../lib/src/widgets/pdf_viewer_params.dart | 201 ++++-- .../lib/src/native/pdfrx_pdfium.dart | 4 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 94 ++- 8 files changed, 809 insertions(+), 190 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index b9de556b..831a8e03 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -319,6 +319,7 @@ class _MainPageState extends State with WidgetsBindingObserver { pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, textSelectionParams: PdfTextSelectionParams( enableSelectionHandles: true, + showContextMenuOnSelectionHandle: true, magnifier: PdfViewerSelectionMagnifierParams(enabled: true), onTextSelectionChange: (textSelection) { textSelections = textSelection.selectedTextRange; diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 71fb5cb5..52ddf831 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -349,12 +349,12 @@ packages: source: path version: "1.3.2" pdfrx_engine: - dependency: "direct main" + dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.3" + version: "0.1.4" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 814391bd..fff04372 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -14,8 +14,6 @@ dependencies: pdfrx: path: ../../ - pdfrx_engine: - path: ../../../pdfrx_engine cupertino_icons: ^1.0.8 rxdart: ^0.28.0 diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index cec05536..cd179eaf 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -510,4 +510,17 @@ class PdfDownloadReport { final int downloaded; final int total; final Duration elapsedTime; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfDownloadReport && + other.downloaded == downloaded && + other.total == total && + other.elapsedTime == elapsedTime; + } + + @override + int get hashCode => downloaded.hashCode ^ total.hashCode ^ elapsedTime.hashCode; } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 0e12723c..09e76f68 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -234,7 +234,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final _textCache = {}; final _textSelection = SplayTreeMap(); - Timer? _textSelectionChangedThrottleTimer; + Timer? _textSelectionChangedDebounceTimer; final double _hitTestMargin = 3.0; /// The starting/ending point of the text selection. @@ -247,7 +247,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// [_textSelB] is the rectangle of the last character in the selected paragraph. PdfTextSelectionAnchor? _textSelB; - _TextSelectionPartMoving _selPartMoving = _TextSelectionPartMoving.none; + _TextSelectionPart _selPartMoving = _TextSelectionPart.none; + + _TextSelectionPart _selPartLastMoved = _TextSelectionPart.none; Offset? _showTextSelectionContextMenuAt; @@ -296,7 +298,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _layout = null; _documentSubscription?.cancel(); _documentSubscription = null; - _textSelectionChangedThrottleTimer?.cancel(); + _textSelectionChangedDebounceTimer?.cancel(); _stopInteraction(); _imageCache.releaseAllImages(); _magnifierImageCache.releaseAllImages(); @@ -334,18 +336,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _loadDelayed(); } - /// How long to wait before loading the trailing pages after the initial page load. - /// - /// This is to ensure that the initial page is displayed quickly, and the trailing pages are loaded in the background. - static const _trailingPageLoadingDelay = Duration(milliseconds: kIsWeb ? 200 : 100); - - /// How long to wait before loading the page image. - static const _pageImageCachingDelay = Duration(milliseconds: kIsWeb ? 100 : 20); - static const _partialImageLoadingDelay = Duration(milliseconds: kIsWeb ? 200 : 50); - Future _loadDelayed() async { // To make the page image loading more smooth, delay the loading of pages - await Future.delayed(_trailingPageLoadingDelay); + await Future.delayed(widget.params.behaviorControlParams.trailingPageLoadingDelay); final stopwatch = Stopwatch()..start(); await _document?.loadPagesProgressively((pageNumber, totalPageCount, document) { @@ -366,7 +359,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override void dispose() { _documentSubscription?.cancel(); - _textSelectionChangedThrottleTimer?.cancel(); + _textSelectionChangedDebounceTimer?.cancel(); _interactionEndedTimer?.cancel(); _imageCache.cancelAllPendingRenderings(); _magnifierImageCache.cancelAllPendingRenderings(); @@ -855,8 +848,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } void _onSelectionChange() { - _textSelectionChangedThrottleTimer?.cancel(); - _textSelectionChangedThrottleTimer = Timer(const Duration(milliseconds: 300), () { + _textSelectionChangedDebounceTimer?.cancel(); + _textSelectionChangedDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (!mounted) return; widget.params.textSelectionParams?.onTextSelectionChange?.call(this); }); @@ -887,6 +880,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix maxImageCacheBytes: widget.params.maxImageBytesCachedOnMemory, targetRect: _getCacheExtentRect(), resolutionMultiplier: MediaQuery.of(context).devicePixelRatio, + enableLowResolutionPagePreview: widget.params.behaviorControlParams.enableLowResolutionPagePreview, filterQuality: FilterQuality.low, ); } @@ -897,7 +891,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix required int maxImageCacheBytes, required Rect targetRect, double resolutionMultiplier = 1.0, - bool previewEnabled = true, + bool enableLowResolutionPagePreview = true, FilterQuality filterQuality = FilterQuality.high, }) { final scale = _currentZoom * resolutionMultiplier; @@ -942,7 +936,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - if (previewEnabled && previewImage != null) { + if (enableLowResolutionPagePreview && previewImage != null) { canvas.drawImageRect( previewImage.image, Rect.fromLTWH(0, 0, previewImage.image.width.toDouble(), previewImage.image.height.toDouble()), @@ -958,22 +952,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - if (previewEnabled && (previewImage == null || previewImage.scale != previewScaleLimit)) { + if (enableLowResolutionPagePreview && (previewImage == null || previewImage.scale != previewScaleLimit)) { _requestPagePreviewImageCached(cache, page, previewScaleLimit); } final pageScale = scale * max(rect.width / page.width, rect.height / page.height); - if (pageScale > previewScaleLimit) { + if (!enableLowResolutionPagePreview || pageScale > previewScaleLimit) { _requestRealSizePartialImage(cache, page, pageScale); } - if (pageScale > previewScaleLimit && partial != null) { - canvas.drawImageRect( - partial.image, - Rect.fromLTWH(0, 0, partial.image.width.toDouble(), partial.image.height.toDouble()), - partial.rect, - Paint()..filterQuality = filterQuality, - ); + if ((!enableLowResolutionPagePreview || pageScale > previewScaleLimit) && partial != null) { + partial.draw(canvas, filterQuality); } final selectionColor = @@ -1105,7 +1094,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix cache.pageImageRenderingTimers[page.pageNumber]?.cancel(); if (!mounted) return; cache.pageImageRenderingTimers[page.pageNumber] = Timer( - _pageImageCachingDelay, + widget.params.behaviorControlParams.pageImageCachingDelay, () => _cachePagePreviewImage(cache, page, width, height, scale), ); } @@ -1155,15 +1144,18 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( - Timer(_partialImageLoadingDelay, () async { - if (!mounted || cancellationToken.isCanceled) return; - final newImage = await _createRealSizePartialImage(cache, page, scale, cancellationToken); - if (cache.pageImagesPartial[page.pageNumber] == newImage) return; - cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); - if (newImage != null) { - cache.pageImagesPartial[page.pageNumber] = newImage; - } - _invalidate(); + Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { + await synchronized(() async { + if (!mounted || cancellationToken.isCanceled) return; + final oldImage = cache.pageImagesPartial[page.pageNumber]; + final newImage = await _createRealSizePartialImage(cache, page, scale, oldImage, cancellationToken); + if (oldImage == newImage) return; + if (newImage != null) { + cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); + cache.pageImagesPartial[page.pageNumber] = newImage; + _invalidate(); + } + }); }), cancellationToken, ); @@ -1173,6 +1165,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _PdfPageImageCache cache, PdfPage page, double scale, + _PdfImageWithScaleAndRect? oldImage, PdfPageRenderCancellationToken cancellationToken, ) async { final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; @@ -1180,30 +1173,85 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final prev = cache.pageImagesPartial[page.pageNumber]; if (prev?.rect == rect && prev?.scale == scale) return prev; if (rect.width < 1 || rect.height < 1) return null; - final inPageRect = rect.translate(-pageRect.left, -pageRect.top); - if (!mounted || cancellationToken.isCanceled) return null; - final img = await page.render( - x: (inPageRect.left * scale).toInt(), - y: (inPageRect.top * scale).toInt(), - width: (inPageRect.width * scale).toInt(), - height: (inPageRect.height * scale).toInt(), - fullWidth: pageRect.width * scale, - fullHeight: pageRect.height * scale, - backgroundColor: 0xffffffff, - annotationRenderingMode: widget.params.annotationRenderingMode, - flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, - cancellationToken: cancellationToken, - ); - if (img == null) return null; - if (!mounted || cancellationToken.isCanceled) { + Future<_PdfImageWithScaleAndRect?> renderRect(Rect rect, [int? x, int? y, int? width, int? height]) async { + final inPageRect = rect.translate(-pageRect.left, -pageRect.top); + x ??= (inPageRect.left * scale).toInt(); + y ??= (inPageRect.top * scale).toInt(); + width ??= (inPageRect.width * scale).toInt(); + height ??= (inPageRect.height * scale).toInt(); + if (width < 1 || height < 1) return null; + final img = await page.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: pageRect.width * scale, + fullHeight: pageRect.height * scale, + backgroundColor: 0xffffffff, + annotationRenderingMode: widget.params.annotationRenderingMode, + flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, + cancellationToken: cancellationToken, + ); + if (img == null) { + throw PdfException('Canceled'); + } + if (!mounted || cancellationToken.isCanceled) { + img.dispose(); + throw PdfException('Canceled'); + } + final result = _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y); img.dispose(); - return null; + return result; + } + + // experimental differential rendering + if (widget.params.behaviorControlParams.enableDifferentialRendering && + oldImage != null && + oldImage.scale == scale) { + if (rect == oldImage.rect) { + return oldImage; // no need to render again + } + if (oldImage.rect.overlaps(rect)) { + final inPageRect = rect.translate(-pageRect.left, -pageRect.top); + final x = (inPageRect.left * scale).toInt(); + final y = (inPageRect.top * scale).toInt(); + final width = (inPageRect.width * scale).toInt(); + final height = (inPageRect.height * scale).toInt(); + _PdfImageWithScaleAndRect? upper, lower, left, right; + try { + upper = await renderRect(rect, x, y, width, oldImage.top - y); + lower = await renderRect(rect, x, oldImage.bottom, width, y + height - oldImage.bottom); + left = await renderRect(rect, x, oldImage.top, oldImage.left - x, oldImage.height); + right = await renderRect(rect, oldImage.right, oldImage.top, x + width - oldImage.right, oldImage.height); + + final recorder = ui.PictureRecorder(); + final canvas = ui.Canvas(recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble())); + oldImage.drawNoScale(canvas, x, y); + upper?.drawNoScale(canvas, x, y); + lower?.drawNoScale(canvas, x, y); + left?.drawNoScale(canvas, x, y); + right?.drawNoScale(canvas, x, y); + final picture = recorder.endRecording(); + final image = await picture.toImage(width, height); + picture.dispose(); + return _PdfImageWithScaleAndRect(image, scale, rect, x, y); + } catch (e) { + return null; // canceled + } finally { + upper?.dispose(); + lower?.dispose(); + left?.dispose(); + right?.dispose(); + } + } + } + try { + return await renderRect(rect); + } catch (e) { + return null; // canceled } - final result = _PdfImageWithScaleAndRect(await img.createImage(), scale, rect); - img.dispose(); - return result; } void _onWheelDelta(PointerScrollEvent event) { @@ -1603,7 +1651,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onTextPanStart(DragStartDetails details) { if (_isInteractionGoingOn) return; - _selPartMoving = _TextSelectionPartMoving.free; + _selPartMoving = _TextSelectionPart.free; _selA = _findTextAndIndexForPoint(details.localPosition); _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); _selB = null; @@ -1617,12 +1665,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onTextPanEnd(DragEndDetails details) { _updateTextSelectRectTo(details.localPosition); - _selPartMoving = _TextSelectionPartMoving.none; + _selPartMoving = _TextSelectionPart.none; _invalidate(); } void _updateTextSelectRectTo(Offset panTo) { - if (_selPartMoving != _TextSelectionPartMoving.free) return; + if (_selPartMoving != _TextSelectionPart.free) return; final to = _findTextAndIndexForPoint( panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), ); @@ -1735,6 +1783,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _invalidate(); } + Rect? _anchorARect; + Rect? _anchorBRect; + Rect? _magnifierRect; + Rect? _contextMenuRect; + _TextSelectionPart _hoverOn = _TextSelectionPart.none; + List _placeTextSelectionWidgets(BuildContext context, Size viewSize, bool isCopyTextEnabled) { final renderBox = _renderBox; if (renderBox == null || _textSelA == null || _textSelB == null || _textSelection.isEmpty) { @@ -1753,7 +1807,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix double? bLeft, bTop, bRight; Widget? anchorA, anchorB; - if (enableSelectionHandles && _selPartMoving != _TextSelectionPartMoving.free) { + if (enableSelectionHandles && _selPartMoving != _TextSelectionPart.free) { final builder = widget.params.textSelectionParams?.buildSelectionHandle ?? _buildDefaultSelectionHandle; if (_textSelA != null) { @@ -1769,7 +1823,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix aLeft = rectA.right; aBottom = viewSize.height - rectA.top; } - anchorA = builder(context, _textSelA!); + anchorA = builder( + context, + _textSelA!, + _selPartMoving == _TextSelectionPart.a + ? PdfViewerTextSelectionAnchorHandleState.dragging + : _hoverOn == _TextSelectionPart.a + ? PdfViewerTextSelectionAnchorHandleState.hover + : PdfViewerTextSelectionAnchorHandleState.normal, + ); } if (_textSelB != null) { switch (_textSelB!.direction) { @@ -1784,21 +1846,92 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix bRight = viewSize.width - rectB.left; bTop = rectB.bottom; } - anchorB = builder(context, _textSelB!); + anchorB = builder( + context, + _textSelB!, + _selPartMoving == _TextSelectionPart.b + ? PdfViewerTextSelectionAnchorHandleState.dragging + : _hoverOn == _TextSelectionPart.b + ? PdfViewerTextSelectionAnchorHandleState.hover + : PdfViewerTextSelectionAnchorHandleState.normal, + ); } + } else { + _anchorARect = _anchorBRect = null; } + bool abOrder() { + final v = _selA!.text.pageNumber - _selB!.text.pageNumber; + if (v != 0) return v < 0; + return _selA!.index < _selB!.index; + } + + final textAnchorMoving = switch (_selPartMoving) { + _TextSelectionPart.a => abOrder() ? _TextSelectionPart.a : _TextSelectionPart.b, + _TextSelectionPart.b => abOrder() ? _TextSelectionPart.b : _TextSelectionPart.a, + _ => _selPartMoving, + }; + // Determines whether the widget is [Positioned] or [Align] to avoid unnecessary wrapping. bool isPositionalWidget(Widget? widget) => widget != null && (widget is Positioned || widget is Align); - Widget? layoutWidget(Widget? widget, {Offset? position, Size? size}) { - if (widget == null || isPositionalWidget(widget)) return widget; - if (position != null) return Positioned(left: position.dx, top: position.dy, child: widget); + const margin = 8.0; - const safeArea = 60.0; - final selRect = rectA.expandToInclude(rectB).inflate(safeArea); - - return Positioned(left: 8, top: 8, child: widget); + Offset? calcPosition(Rect? rect, _TextSelectionPart part) { + if (rect == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { + return null; + } + final textAnchor = part == _TextSelectionPart.a ? _textSelA : _textSelB; + if (textAnchor == null) return null; + + late double left, top; + final width = rect.width; + final height = rect.height; + final rect0 = (part == _TextSelectionPart.a ? rectA : rectB); + final rect1 = (part == _TextSelectionPart.a ? _anchorARect : _anchorBRect); + final pt = rect0.center; + final rectTop = rect1 == null ? rect0.top : min(rect0.top, rect1.top); + final rectBottom = rect1 == null ? rect0.bottom : max(rect0.bottom, rect1.bottom); + final rectLeft = rect1 == null ? rect0.left : min(rect0.left, rect1.left); + final rectRight = rect1 == null ? rect0.right : max(rect0.right, rect1.right); + switch (textAnchor.direction) { + case PdfTextDirection.ltr: + case PdfTextDirection.rtl: + case PdfTextDirection.unknown: + left = pt.dx - width / 2; + if (left < margin) { + left = margin; + } else if (left + width + margin > viewSize.width) { + // If the anchor is too close to the right, place the magnifier to the left of it. + left = viewSize.width - width - margin; + } + top = rectTop - height - margin; + if (top < margin) { + // If the anchor is too close to the top, place the magnifier below it. + top = rectBottom + margin; + } + break; + case PdfTextDirection.vrtl: + if (part == _TextSelectionPart.a) { + left = rectRight + margin; + if (left + width + margin > viewSize.width) { + left = rectLeft - width - margin; + } + } else { + left = rectLeft - width - margin; + if (left < margin) { + left = rectRight + margin; + } + } + top = pt.dy - height / 2; + if (top < margin) { + top = margin; + } else if (top + height + margin > viewSize.height) { + // If the anchor is too close to the bottom, place the magnifier above it. + top = viewSize.height - height - margin; + } + } + return Offset(left, top); } Widget? magnifier; @@ -1806,43 +1939,100 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final magnifierEnabled = (magnifierParams.enabled ?? isMobile) && (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? (_currentZoom < magnifierParams.scale)); - if (magnifierEnabled) { - final textAnchor = switch (_selPartMoving) { - _TextSelectionPartMoving.a => _selA!.index < _selB!.index ? _textSelA : _textSelB, - _TextSelectionPartMoving.b => _selA!.index < _selB!.index ? _textSelB : _textSelA, - _ => null, - }; + if (magnifierEnabled && (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b)) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA : _textSelB; final magCenter = textAnchor?.anchorPoint; - if (magCenter != null && textAnchor!.rect.height < magnifierParams.height * .7) { + if (magCenter != null && textAnchor!.rect.height < magnifierParams.size.height * .7) { final magnifierMain = _buildMagnifier(context, magCenter, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = layoutWidget( - builder(context, magnifierParams, magnifierMain), - size: Size(magnifierParams.width, magnifierParams.height), - ); + magnifier = builder(context, magnifierParams, magnifierMain); + if (magnifier != null && !isPositionalWidget(magnifier)) { + final offset = calcPosition(_magnifierRect, textAnchorMoving) ?? Offset.zero; + magnifier = Positioned( + left: offset.dx, + top: offset.dy, + child: _WidgetSizeSniffer( + key: Key('magnifier'), + child: magnifier, + onSizeChanged: (rect) { + _magnifierRect = rect.toLocal(context); + _invalidate(); + }, + ), + ); + } } + } else { + _magnifierRect = null; } + final showContextMenuOnSelectionHandle = + widget.params.textSelectionParams?.showContextMenuOnSelectionHandle ?? isMobile; bool showContextMenu = false; if (_showTextSelectionContextMenuAt != null) { showContextMenu = true; - } else if (isMobile && _textSelA != null && _textSelB != null && _selPartMoving == _TextSelectionPartMoving.none) { + } else if (showContextMenuOnSelectionHandle && + _textSelA != null && + _textSelB != null && + _selPartMoving == _TextSelectionPart.none) { // Show context menu on mobile when selection is not moving. showContextMenu = true; } + Offset normalizedContextMenuPosition(Offset pos) { + if (_contextMenuRect == null) return pos; + var left = pos.dx; + var top = pos.dy; + if (left + _contextMenuRect!.width + margin > viewSize.width) { + left = viewSize.width - _contextMenuRect!.width - margin; + } + if (left < margin) { + left = margin; + } + if (top + _contextMenuRect!.height + margin > viewSize.height) { + top = viewSize.height - _contextMenuRect!.height - margin; + } + if (top < margin) { + top = margin; + } + return Offset(left, top); + } + Widget? contextMenu; - if (showContextMenu) { - final position = - _showTextSelectionContextMenuAt != null ? renderBox.globalToLocal(_showTextSelectionContextMenuAt!) : null; + if (showContextMenu && + _selPartMoving == _TextSelectionPart.none && + (_selPartLastMoved == _TextSelectionPart.a || _selPartLastMoved == _TextSelectionPart.b) && + isCopyTextEnabled) { + final offset = + _showTextSelectionContextMenuAt != null + ? normalizedContextMenuPosition(renderBox.globalToLocal(_showTextSelectionContextMenuAt!)) + : (calcPosition(_contextMenuRect, _selPartLastMoved) ?? Offset.zero); + Offset.zero; final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; - contextMenu = layoutWidget( - ctxMenuBuilder(context, _textSelA!, _textSelB!, this, position, () { - _showTextSelectionContextMenuAt = null; - _invalidate(); - }), - position: position, - ); + contextMenu = ctxMenuBuilder(context, _textSelA!, _textSelB!, this, () { + _showTextSelectionContextMenuAt = null; + _invalidate(); + }); + if (contextMenu != null && !isPositionalWidget(contextMenu)) { + contextMenu = Positioned( + left: offset.dx, + top: offset.dy, + child: _WidgetSizeSniffer( + key: Key('contextMenu'), + child: contextMenu, + onSizeChanged: (rect) { + _contextMenuRect = rect.toLocal(context); + _invalidate(); + }, + ), + ); + } else { + _contextMenuRect = null; + } + } + + if (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) { + _selPartLastMoved = textAnchorMoving; } return [ @@ -1853,12 +2043,22 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix right: aRight, bottom: aBottom, child: MouseRegion( - cursor: SystemMouseCursors.move, + cursor: _selPartMoving == _TextSelectionPart.a ? SystemMouseCursors.none : SystemMouseCursors.move, + onEnter: (details) => _onSelectionHandleEnter(_TextSelectionPart.a, details), + onExit: (details) => _onSelectionHandleExit(_TextSelectionPart.a, details), + onHover: (details) => _onSelectionHandleHover(_TextSelectionPart.a, details), child: GestureDetector( - onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPartMoving.a, details), - onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPartMoving.a, details), - onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPartMoving.a, details), - child: anchorA, + onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPart.a, details), + onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPart.a, details), + onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPart.a, details), + child: _WidgetSizeSniffer( + key: Key('anchorA'), + child: anchorA, + onSizeChanged: (rect) { + _anchorARect = rect.toLocal(context); + _invalidate(); + }, + ), ), ), ), @@ -1868,12 +2068,23 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix top: bTop, right: bRight, child: MouseRegion( - cursor: SystemMouseCursors.move, + cursor: _selPartMoving == _TextSelectionPart.b ? SystemMouseCursors.none : SystemMouseCursors.move, + onEnter: (details) => _onSelectionHandleEnter(_TextSelectionPart.b, details), + onExit: (details) => _onSelectionHandleExit(_TextSelectionPart.b, details), + onHover: (details) => _onSelectionHandleHover(_TextSelectionPart.b, details), + child: GestureDetector( - onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPartMoving.b, details), - onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPartMoving.b, details), - onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPartMoving.b, details), - child: anchorB, + onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPart.b, details), + onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPart.b, details), + onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPart.b, details), + child: _WidgetSizeSniffer( + key: Key('anchorB'), + child: anchorB, + onSizeChanged: (rect) { + _anchorBRect = rect.toLocal(context); + _invalidate(); + }, + ), ), ), ), @@ -1882,20 +2093,98 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ]; } - Widget? _buildDefaultSelectionHandle(BuildContext context, PdfTextSelectionAnchor anchor) { - return Container( - width: 30, - height: 30, - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.black, width: 2), - ), + Widget _buildHandle(BuildContext context, Path path, PdfViewerTextSelectionAnchorHandleState state) { + final baseColor = + Theme.of(context).textSelectionTheme.selectionColor ?? DefaultSelectionStyle.of(context).selectionColor!; + final (selectionColor, shadow) = switch (state) { + PdfViewerTextSelectionAnchorHandleState.normal => (baseColor.withOpacity(.7), true), + PdfViewerTextSelectionAnchorHandleState.dragging => (baseColor.withOpacity(1), false), + PdfViewerTextSelectionAnchorHandleState.hover => (baseColor.withOpacity(1), true), + }; + return CustomPaint( + painter: _CustomPainter.fromFunctions((canvas, size) { + if (shadow) { + canvas.drawShadow(path, Colors.black, 4, true); + } + canvas.drawPath( + path, + Paint() + ..color = selectionColor + ..style = PaintingStyle.fill, + ); + }, hitTestFunction: (position) => position.dx >= 0 && position.dx <= 30 && position.dy >= 0 && position.dy <= 30), + size: Size(30, 30), ); } + Widget? _buildDefaultSelectionHandle( + BuildContext context, + PdfTextSelectionAnchor anchor, + PdfViewerTextSelectionAnchorHandleState state, + ) { + switch (anchor.direction) { + case PdfTextDirection.ltr: + if (anchor.type == PdfTextSelectionAnchorType.a) { + return _buildHandle( + context, + Path() + ..moveTo(30, 0) + ..lineTo(30, 30) + ..lineTo(0, 30) + ..close(), + state, + ); + } else { + return _buildHandle( + context, + Path() + ..moveTo(0, 0) + ..lineTo(30, 0) + ..lineTo(0, 30) + ..close(), + state, + ); + } + case PdfTextDirection.rtl: + case PdfTextDirection.vrtl: + if (anchor.type == PdfTextSelectionAnchorType.a) { + return _buildHandle( + context, + Path() + ..moveTo(0, 30) + ..lineTo(30, 30) + ..lineTo(0, 0) + ..close(), + state, + ); + } else { + return _buildHandle( + context, + Path() + ..moveTo(0, 0) + ..lineTo(30, 0) + ..lineTo(30, 30) + ..close(), + state, + ); + } + + case PdfTextDirection.unknown: + return _buildHandle( + context, + Path() + ..moveTo(0, 0) + ..lineTo(30, 0) + ..lineTo(30, 30) + ..lineTo(0, 30) + ..close(), + state, + ); + } + } + Widget _buildMagnifier(BuildContext context, Offset docPos, PdfViewerSelectionMagnifierParams magnifierParams) { - final magScale = magnifierParams.scale; + final magScale = magnifierParams.scale * MediaQuery.of(context).devicePixelRatio; return LayoutBuilder( builder: (context, constraints) { return ClipRect( @@ -1917,8 +2206,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix height: size.height / magScale, ), resolutionMultiplier: magScale, - previewEnabled: false, - filterQuality: FilterQuality.low, + enableLowResolutionPagePreview: false, + filterQuality: FilterQuality.high, ); canvas.restore(); canvas.drawLine( @@ -1950,8 +2239,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget _buildMagnifierDecoration(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget child) { return Container( - width: params.width, - height: params.height, + width: params.size.width, + height: params.size.height, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], @@ -1965,7 +2254,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfTextSelectionAnchor a, PdfTextSelectionAnchor b, PdfTextSelectionDelegate textSelectionDelegate, - Offset? rightClickPosition, void Function() dismissContextMenu, ) { return Container( @@ -1975,6 +2263,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ), padding: const EdgeInsets.all(2), child: Column( + mainAxisSize: MainAxisSize.min, children: [ RawMaterialButton( visualDensity: VisualDensity.compact, @@ -2006,17 +2295,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - void _onSelectionHandlePanStart(_TextSelectionPartMoving handle, DragStartDetails details) { + void _onSelectionHandlePanStart(_TextSelectionPart handle, DragStartDetails details) { if (_isInteractionGoingOn) return; _selPartMoving = handle; final position = _globalToDocument(details.globalPosition); final anchor = Offset(_txController.value.x, _txController.value.y); - if (_selPartMoving == _TextSelectionPartMoving.a) { + if (_selPartMoving == _TextSelectionPart.a) { _textSelectAnchor = anchor + _textSelA!.rect.topLeft - position!; final a = _findTextAndIndexForPoint(_textSelA!.rect.center); if (a == null) return; _selA = a; - } else if (_selPartMoving == _TextSelectionPartMoving.b) { + } else if (_selPartMoving == _TextSelectionPart.b) { _textSelectAnchor = anchor + _textSelB!.rect.bottomRight - position!; final b = _findTextAndIndexForPoint(_textSelB!.rect.center); if (b == null) return; @@ -2028,36 +2317,61 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _updateTextSelection(); } - void _updateSelectionHandlesPan(Offset? panTo) { - if (panTo == null) return; - if (_selPartMoving == _TextSelectionPartMoving.a) { + bool _updateSelectionHandlesPan(Offset? panTo) { + if (panTo == null) { + return false; + } + if (_selPartMoving == _TextSelectionPart.a) { final a = _findTextAndIndexForPoint( panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), ); - if (a == null) return; + if (a == null) { + return false; + } _selA = a; - } else if (_selPartMoving == _TextSelectionPartMoving.b) { + } else if (_selPartMoving == _TextSelectionPart.b) { final b = _findTextAndIndexForPoint( panTo + _textSelectAnchor! - Offset(_txController.value.x, _txController.value.y), ); - if (b == null) return; + if (b == null) { + return false; + } _selB = b; } else { - return; + return false; } _updateTextSelection(); + return true; } - void _onSelectionHandlePanUpdate(_TextSelectionPartMoving handle, DragUpdateDetails details) { + void _onSelectionHandlePanUpdate(_TextSelectionPart handle, DragUpdateDetails details) { if (_isInteractionGoingOn) return; _showTextSelectionContextMenuAt = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); } - void _onSelectionHandlePanEnd(_TextSelectionPartMoving handle, DragEndDetails details) { + void _onSelectionHandlePanEnd(_TextSelectionPart handle, DragEndDetails details) { if (_isInteractionGoingOn) return; - _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); - _selPartMoving = _TextSelectionPartMoving.none; + final result = _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + _selPartMoving = _TextSelectionPart.none; + if (!result) { + _updateTextSelection(); + } + } + + void _onSelectionHandleEnter(_TextSelectionPart handle, PointerEnterEvent details) { + _hoverOn = handle; + _invalidate(); + } + + void _onSelectionHandleExit(_TextSelectionPart handle, PointerExitEvent details) { + _hoverOn = _TextSelectionPart.none; + _invalidate(); + } + + void _onSelectionHandleHover(_TextSelectionPart handle, PointerHoverEvent details) { + _hoverOn = handle; + _invalidate(); } void _clearTextSelections({bool invalidate = true}) { @@ -2216,7 +2530,7 @@ class _PdfPageImageCache { } final removedPartial = pageImagesPartial.remove(pageNumber); if (removedPartial != null) { - removedPartial.image.dispose(); + synchronized(() => removedPartial.image.dispose()); } } @@ -2240,7 +2554,7 @@ class _PdfPageImageCache { final removedPartial = pageImagesPartial.remove(key); if (removedPartial != null) { bytesConsumed -= getBytesConsumed(removedPartial.image); - removedPartial.image.dispose(); + synchronized(() => removedPartial.image.dispose()); } if (bytesConsumed <= acceptableBytes) { break; @@ -2271,8 +2585,32 @@ class _PdfImageWithScale { } class _PdfImageWithScaleAndRect extends _PdfImageWithScale { - _PdfImageWithScaleAndRect(super.image, super.scale, this.rect); + _PdfImageWithScaleAndRect(super.image, super.scale, this.rect, this.left, this.top); final Rect rect; + final int left; + final int top; + + int get width => image.width; + int get height => image.height; + int get bottom => top + height; + int get right => left + width; + + void draw(Canvas canvas, [FilterQuality filterQuality = FilterQuality.low]) { + canvas.drawImageRect( + image, + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), + rect, + Paint()..filterQuality = filterQuality, + ); + } + + void drawNoScale(Canvas canvas, int x, int y, [FilterQuality filterQuality = FilterQuality.low]) { + canvas.drawImage( + image, + Offset((left - x).toDouble(), (top - y).toDouble()), + Paint()..filterQuality = filterQuality, + ); + } } class _PdfViewerTransformationController extends TransformationController { @@ -2287,7 +2625,7 @@ class _PdfViewerTransformationController extends TransformationController { } /// What selection part is moving by mouse-dragging/finger-panning. -enum _TextSelectionPartMoving { none, free, a, b } +enum _TextSelectionPart { none, free, a, b } @immutable class _TextSelectionPoint { @@ -2403,6 +2741,16 @@ class PdfPageHitTestResult { /// The offset in the PDF page coordinates; the origin is at the bottom-left corner. final PdfPoint offset; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageHitTestResult && other.page == page && other.offset == offset; + } + + @override + int get hashCode => page.hashCode ^ offset.hashCode; } /// Controls associated [PdfViewer]. @@ -2775,6 +3123,19 @@ class PdfPageFitInfo { @override String toString() => 'PdfPageFitInfo(pageNumber=$pageNumber, visibleAreaRatio=$visibleAreaRatio, matrix=$matrix)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageFitInfo && + other.pageNumber == pageNumber && + other.matrix == matrix && + other.visibleAreaRatio == visibleAreaRatio; + } + + @override + int get hashCode => pageNumber.hashCode ^ matrix.hashCode ^ visibleAreaRatio.hashCode; } extension PdfMatrix4Ext on Matrix4 { @@ -3077,3 +3438,52 @@ class _PdfViewerKeyHandlerState extends State<_PdfViewerKeyHandler> { ); } } + +class _WidgetSizeSniffer extends StatefulWidget { + const _WidgetSizeSniffer({required this.child, this.onSizeChanged, super.key}); + + final Widget child; + final FutureOr Function(_GlobalRect rect)? onSizeChanged; + + @override + State<_WidgetSizeSniffer> createState() => _WidgetSizeSnifferState(); +} + +class _GlobalRect { + const _GlobalRect(this.globalRect); + + final Rect globalRect; + + Rect? toLocal(BuildContext context) { + final renderBox = context.findRenderObject(); + if (renderBox is RenderBox) { + return Rect.fromPoints( + renderBox.globalToLocal(globalRect.topLeft), + renderBox.globalToLocal(globalRect.bottomRight), + ); + } + return null; + } +} + +class _WidgetSizeSnifferState extends State<_WidgetSizeSniffer> { + Rect? _rect; + + @override + Widget build(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!mounted) return; + final r = context.findRenderObject(); + if (r is! RenderBox) return; + final rect = Rect.fromPoints(r.localToGlobal(Offset.zero), r.localToGlobal(Offset(r.size.width, r.size.height))); + if (_rect != rect) { + _rect = rect; + await widget.onSizeChanged?.call(_GlobalRect(_rect!)); + if (mounted) { + setState(() {}); + } + } + }); + return Offstage(offstage: _rect == null, child: widget.child); + } +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index fa14b9be..8376a499 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1,5 +1,6 @@ import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -61,6 +62,7 @@ class PdfViewerParams { this.pageBackgroundPaintCallbacks, this.onKey, this.keyHandlerParams = const PdfViewerKeyHandlerParams(), + this.behaviorControlParams = const PdfViewerBehaviorControlParams(), this.forceReload = false, }); @@ -475,6 +477,8 @@ class PdfViewerParams { /// Parameters to customize key handling. final PdfViewerKeyHandlerParams keyHandlerParams; + final PdfViewerBehaviorControlParams behaviorControlParams; + /// Force reload the viewer. /// /// Normally whether to reload the viewer is determined by the changes of the parameters but @@ -567,6 +571,7 @@ class PdfViewerParams { other.pageBackgroundPaintCallbacks == pageBackgroundPaintCallbacks && other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && + other.behaviorControlParams == behaviorControlParams && other.forceReload == forceReload; } @@ -618,6 +623,7 @@ class PdfViewerParams { pageBackgroundPaintCallbacks.hashCode ^ onKey.hashCode ^ keyHandlerParams.hashCode ^ + behaviorControlParams.hashCode ^ forceReload.hashCode; } } @@ -628,6 +634,7 @@ class PdfTextSelectionParams { const PdfTextSelectionParams({ this.textSelectionTriggeredBySwipe, this.enableSelectionHandles, + this.showContextMenuOnSelectionHandle, this.buildContextMenu, this.buildSelectionHandle, this.onTextSelectionChange, @@ -648,6 +655,13 @@ class PdfTextSelectionParams { /// null to determine the behavior based on the platform; enabled on Mobile, disabled on Desktop/Web. final bool? enableSelectionHandles; + /// Whether to show context menu on selection handle. + /// + /// Normally, on desktop and web, the context menu is shown on right-click. + /// If this is true, the context menu is shown on selection handle. + /// null to determine the behavior based on the platform; enabled on Mobile, disabled on Desktop/Web. + final bool? showContextMenuOnSelectionHandle; + /// Function to build context menu for text selection. /// /// - If the function returns null, no context menu is shown. @@ -701,6 +715,7 @@ class PdfTextSelectionParams { other.textLongPress == textLongPress && other.textSecondaryTapUp == textSecondaryTapUp && other.enableSelectionHandles == enableSelectionHandles && + other.showContextMenuOnSelectionHandle == showContextMenuOnSelectionHandle && other.magnifier == magnifier; } @@ -715,6 +730,7 @@ class PdfTextSelectionParams { textLongPress.hashCode ^ textSecondaryTapUp.hashCode ^ enableSelectionHandles.hashCode ^ + showContextMenuOnSelectionHandle.hashCode ^ magnifier.hashCode; } @@ -726,21 +742,76 @@ class PdfTextSelectionParams { /// Please note that the function does not copy the text if [PdfTextSelectionDelegate.isCopyAllowed] is false and /// use of [PdfTextSelectionDelegate.selectedText] is also restricted by the same condition. /// -/// [rightClickPosition] is the position of the right-click in the document coordinates if available. /// [dismissContextMenu] is the function to dismiss the context menu. +/// +/// The following fragment is a simple example to build a context menu with copy and select all actions: +/// +/// ```dart +/// Widget? _buildTextSelectionContextMenu( +/// BuildContext context, +/// PdfTextSelectionAnchor a, +/// PdfTextSelectionAnchor b, +/// PdfTextSelectionDelegate textSelectionDelegate, +/// void Function() dismissContextMenu, +/// ) { +/// return Container( +/// decoration: BoxDecoration( +/// color: Colors.white, +/// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], +/// ), +/// padding: const EdgeInsets.all(2), +/// child: Column( +/// mainAxisSize: MainAxisSize.min, +/// children: [ +/// RawMaterialButton( +/// visualDensity: VisualDensity.compact, +/// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, +/// onPressed: +/// textSelectionDelegate.isCopyAllowed +/// ? () { +/// if (textSelectionDelegate.isCopyAllowed) { +/// textSelectionDelegate.copyTextSelection(); +/// } +/// } +/// : null, +/// child: Text( +/// 'Copy', +/// style: TextStyle(color: textSelectionDelegate.isCopyAllowed ? Colors.black : Colors.grey), +/// ), +/// ), +/// if (textSelectionDelegate.isCopyAllowed) +/// RawMaterialButton( +/// visualDensity: VisualDensity.compact, +/// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, +/// onPressed: () { +/// textSelectionDelegate.selectAllText(); +/// }, +/// child: const Text('Select All'), +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` typedef PdfViewerTextSelectionContextMenuBuilder = Widget? Function( BuildContext context, PdfTextSelectionAnchor a, PdfTextSelectionAnchor b, PdfTextSelectionDelegate textSelectionDelegate, - Offset? rightClickPosition, void Function() dismissContextMenu, ); +/// State of the text selection anchor handle. +enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } + /// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = - Widget? Function(BuildContext context, PdfTextSelectionAnchor anchor); + Widget? Function( + BuildContext context, + PdfTextSelectionAnchor anchor, + PdfViewerTextSelectionAnchorHandleState state, + ); /// Function to be notified when the text selection is changed. /// @@ -790,8 +861,7 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { class PdfViewerSelectionMagnifierParams { const PdfViewerSelectionMagnifierParams({ this.enabled, - this.width = 200.0, - this.height = 200.0, + this.size = const Size(150.0, 150.0), this.scale = 4.0, this.builder, this.paintMagnifierContent, @@ -807,49 +877,13 @@ class PdfViewerSelectionMagnifierParams { /// If null, the magnifier is enabled by default on Mobile and disabled on Desktop/Web. final bool? enabled; - /// Width of the widget to build (Not the size of the magnifier content). - /// - /// The size is used to layout the magnifier widget. - final double width; - - /// Height of the widget to build (Not the size of the magnifier content). - /// - /// The size is used to layout the magnifier widget. - final double height; + /// Size of the magnifier content. + final Size size; /// Scale (zoom ratio) of the magnifier content. final double scale; /// Function to build the magnifier widget. - /// - /// The function can be used to decorate the magnifier widget with additional widgets such as [Container] or [Size]. - /// The widget built by the function should be the exact size of specified by [width] and [height]. - /// - /// If the function returns null, the magnifier is not shown. - /// If the function is null, the magnifier is shown with the default magnifier widget. - /// - /// If the function returns a widget of [Positioned] or [Align], the magnifier content is laid out as - /// specified. Otherwise, the widget is laid out automatically. - /// - /// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: - /// - /// ```dart - /// builder: (context, params, magnifierContent) { - /// return Container( - /// width: params.width, - /// height: params.height, - /// decoration: BoxDecoration( - /// borderRadius: BorderRadius.circular(16), - /// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], - /// ), - /// child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), - /// ); - /// } - /// ``` - /// - /// You can also use the function to build a [OverlayEntry] to show the magnifier as an overlay. In that case, - /// you should do everything by yourself and return null from the function. - /// final PdfViewerMagnifierBuilder? builder; /// Function to paint the magnifier content. @@ -869,10 +903,10 @@ class PdfViewerSelectionMagnifierParams { @override bool operator ==(Object other) { if (identical(this, other)) return true; + return other is PdfViewerSelectionMagnifierParams && other.enabled == enabled && - other.width == width && - other.height == height && + other.size == size && other.scale == scale && other.builder == builder && other.paintMagnifierContent == paintMagnifierContent && @@ -883,8 +917,7 @@ class PdfViewerSelectionMagnifierParams { @override int get hashCode => enabled.hashCode ^ - width.hashCode ^ - height.hashCode ^ + size.hashCode ^ scale.hashCode ^ builder.hashCode ^ paintMagnifierContent.hashCode ^ @@ -893,6 +926,30 @@ class PdfViewerSelectionMagnifierParams { } /// Function to build the magnifier widget. +/// +/// The function can be used to decorate the magnifier widget with additional widgets such as [Container] or [Size]. +/// +/// If the function returns null, the magnifier is not shown. +/// If the function is null, the magnifier is shown with the default magnifier widget. +/// +/// If the function returns a widget of [Positioned] or [Align], the magnifier content is laid out as +/// specified. Otherwise, the widget is laid out automatically. +/// +/// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: +/// +/// ```dart +/// builder: (context, params, magnifierContent) { +/// return Container( +/// width: params.size.width, +/// height: params.size.height, +/// decoration: BoxDecoration( +/// borderRadius: BorderRadius.circular(16), +/// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], +/// ), +/// child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), +/// ); +/// } +/// ``` typedef PdfViewerMagnifierBuilder = Widget? Function(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget magnifierContent); @@ -1178,3 +1235,53 @@ class PdfViewerKeyHandlerParams { focusNode.hashCode ^ parentNode.hashCode; } + +/// Parameters to customize the behavior of the PDF viewer. +/// +/// These parameters are to tune the behavior/performance of the PDF viewer. +class PdfViewerBehaviorControlParams { + const PdfViewerBehaviorControlParams({ + this.trailingPageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 100), + this.enableLowResolutionPagePreview = true, + this.pageImageCachingDelay = const Duration(milliseconds: kIsWeb ? 100 : 20), + this.partialImageLoadingDelay = const Duration(milliseconds: 0), + this.enableDifferentialRendering = false, + }); + + /// How long to wait before loading the trailing pages after the initial page load. + /// + /// This is to ensure that the initial page is displayed quickly, and the trailing pages are loaded in the background. + final Duration trailingPageLoadingDelay; + + /// Whether to enable low resolution page preview. + final bool enableLowResolutionPagePreview; + + /// How long to wait before loading the page image. + final Duration pageImageCachingDelay; + + /// How long to wait before loading the partial real size image. + final Duration partialImageLoadingDelay; + + /// Whether to enable differential rendering (experimental). + final bool enableDifferentialRendering; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfViewerBehaviorControlParams && + other.trailingPageLoadingDelay == trailingPageLoadingDelay && + other.enableLowResolutionPagePreview == enableLowResolutionPagePreview && + other.pageImageCachingDelay == pageImageCachingDelay && + other.partialImageLoadingDelay == partialImageLoadingDelay && + other.enableDifferentialRendering == enableDifferentialRendering; + } + + @override + int get hashCode => + trailingPageLoadingDelay.hashCode ^ + enableLowResolutionPagePreview.hashCode ^ + pageImageCachingDelay.hashCode ^ + partialImageLoadingDelay.hashCode ^ + enableDifferentialRendering.hashCode; +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 987024f8..319c3251 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -444,7 +444,7 @@ class _PdfDocumentPdfium extends PdfDocument { final end = maxPageCountToLoadAdditionally == null ? pageCount : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); - final t = params.timeoutUs != null ? DateTime.now().add(Duration(microseconds: params.timeoutUs!)) : null; + final t = params.timeoutUs != null ? (Stopwatch()..start()) : null; final pages = <({double width, double height, int rotation})>[]; for (int i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); @@ -457,7 +457,7 @@ class _PdfDocumentPdfium extends PdfDocument { } finally { pdfium.FPDF_ClosePage(page); } - if (t != null && DateTime.now().isAfter(t)) { + if (t != null && t.elapsedMicroseconds > params.timeoutUs!) { break; } } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 9ecfe1e3..6fff03c7 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -716,6 +716,16 @@ class PdfPageRawText { /// Bounds corresponding to characters in the full text. final List charRects; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageRawText && other.fullText == fullText && _listEquals(other.charRects, charRects); + } + + @override + int get hashCode => fullText.hashCode ^ charRects.hashCode; } /// Page rotation. @@ -789,9 +799,19 @@ class PdfPermissions { /// Determine whether the PDF file allows modifying annotations, form fields, and their associated bool get allowsModifyAnnotations => (permissions & 32) != 0; -} -enum PdfImageFormat { rgba8888, bgra8888 } + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPermissions && + other.permissions == permissions && + other.securityHandlerRevision == securityHandlerRevision; + } + + @override + int get hashCode => permissions.hashCode ^ securityHandlerRevision.hashCode; +} /// Image rendered from PDF page. /// @@ -918,6 +938,20 @@ class PdfPageText { } return PdfPageTextRange(pageText: this, start: min, end: max + 1); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageText && + other.pageNumber == pageNumber && + other.fullText == fullText && + _listEquals(other.charRects, charRects) && + _listEquals(other.fragments, fragments); + } + + @override + int get hashCode => pageNumber.hashCode ^ fullText.hashCode ^ charRects.hashCode ^ fragments.hashCode; } /// Text direction in PDF page. @@ -1037,6 +1071,16 @@ class PdfPageTextRange { yield PdfTextFragmentBoundingRect(f, max(start - f.index, 0), min(end - f.index, f.length)); } } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageTextRange && other.pageText == pageText && other.start == start && other.end == end; + } + + @override + int get hashCode => pageText.hashCode ^ start.hashCode ^ end.hashCode; } /// Rectangle in PDF page coordinates. @@ -1244,6 +1288,16 @@ class PdfTextFragmentBoundingRect { /// Text direction of the text range. PdfTextDirection get direction => fragment.direction; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfTextFragmentBoundingRect && other.fragment == fragment && other.sif == sif && other.eif == eif; + } + + @override + int get hashCode => fragment.hashCode ^ sif.hashCode ^ eif.hashCode; } /// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. @@ -1269,6 +1323,19 @@ class PdfDest { PdfDest compact() { return params == null ? this : PdfDest(pageNumber, command, List.unmodifiable(params!)); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfDest && + other.pageNumber == pageNumber && + other.command == command && + _listEquals(other.params, params); + } + + @override + int get hashCode => pageNumber.hashCode ^ command.hashCode ^ params.hashCode; } /// [PDF 32000-1:2008, 12.3.2.2 Explicit Destinations, Table 151](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) @@ -1326,6 +1393,16 @@ class PdfLink { String toString() { return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects}'; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfLink && other.url == url && other.dest == dest && _listEquals(other.rects, rects); + } + + @override + int get hashCode => url.hashCode ^ dest.hashCode ^ rects.hashCode; } /// Outline (a.k.a. Bookmark) node in PDF document. @@ -1342,6 +1419,19 @@ class PdfOutlineNode { /// Outline child nodes. final List children; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfOutlineNode && + other.title == title && + other.dest == dest && + _listEquals(other.children, children); + } + + @override + int get hashCode => title.hashCode ^ dest.hashCode ^ children.hashCode; } class PdfException implements Exception { From a5684ad249ba6c29825840471e00b6877776c0cd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 16 Jul 2025 04:23:40 +0900 Subject: [PATCH 195/663] WIP --- .github/workflows/build-test.yml | 41 ++++++- .github/workflows/github-pages.yml | 6 + .vscode/launch.json | 7 ++ packages/pdfrx/CHANGELOG.md | 12 ++ packages/pdfrx/README.md | 2 +- packages/pdfrx/example/viewer/lib/main.dart | 3 - packages/pdfrx/example/viewer/pubspec.lock | 4 +- .../pdfrx/lib/src/utils/native/native.dart | 13 +- packages/pdfrx/lib/src/utils/web/web.dart | 7 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 112 ++++++------------ .../lib/src/widgets/pdf_viewer_params.dart | 18 +-- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_engine/CHANGELOG.md | 4 + packages/pdfrx_engine/pubspec.yaml | 2 +- 14 files changed, 136 insertions(+), 97 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 690ff57f..deee5d6b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -71,6 +71,12 @@ jobs: ~/flutter/bin/flutter doctor -v yes | ~/flutter/bin/flutter doctor --android-licenses + - name: Install melos + run: ~/flutter/bin/dart pub global activate melos + + - name: Bootstrap monorepo + run: ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get @@ -94,6 +100,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get @@ -117,6 +128,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get @@ -145,6 +161,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get @@ -173,6 +194,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get @@ -197,6 +223,11 @@ jobs: C:\flutter\bin\flutter.bat channel stable C:\flutter\bin\flutter.bat doctor -v + - name: Melos setup + run: | + C:\flutter\bin\dart pub global activate melos + C:\flutter\bin\dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat pub get @@ -221,7 +252,10 @@ jobs: # C:\flutter\bin\flutter.bat config --no-enable-android # C:\flutter\bin\flutter.bat channel stable # C:\flutter\bin\flutter.bat doctor -v - + # - name: Melos setup + # run: | + # C:\flutter\bin\dart pub global activate melos + # C:\flutter\bin\dart pub global run melos:melos bootstrap # - name: Install dependencies # working-directory: packages/pdfrx/example/viewer # run: C:\flutter\bin\flutter.bat pub get @@ -245,6 +279,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 93d29d1f..834acdc1 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -26,6 +26,12 @@ jobs: flutter --version FLUTTER_VERSION=$(flutter --version | head -n 1 | awk '{print $2}') echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV + + - name: Melos setup + run: | + ~/flutter/bin/dart pub global activate melos + ~/flutter/bin/dart pub global run melos:melos bootstrap + - name: Check pdfrx version run: | PDFRX_VERSION=$(awk -F": " '/^version:/ {print $2}' pubspec.yaml) diff --git a/.vscode/launch.json b/.vscode/launch.json index db264ac1..f80aebf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,6 +16,13 @@ "type": "dart", "program": "packages/pdfrx/example/viewer/lib/main.dart", "args": ["-d", "chrome", "--wasm"] + }, + { + "name": "viewer example (WASM/Web Server)", + "request": "launch", + "type": "dart", + "program": "packages/pdfrx/example/viewer/lib/main.dart", + "args": ["-d", "web-server", "--wasm"] } ] } diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index f74cce2d..0d32aeab 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.0.0-beta1 + +This is a major release that introduces significant architectural changes and new features. + +- BREAKING CHANGE: Extracted PDF rendering engine into a separate `pdfrx_engine` package that is platform-agnostic +- NEW FEATURE: Text selection support with native platform UI including: + - Selection handles with drag support + - Magnifier/loupe for precise text selection + - Context menu with copy functionality +- IMPROVED: Better separation of concerns between rendering engine and Flutter UI layer +- IMPROVED: Platform-specific implementations are now more maintainable + # 1.3.2 - NEW FEATURE: Added `useProgressiveLoading` parameter for all `PdfViewer` constructors to enable progressive page loading diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index be7ef739..acb0b7fa 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -59,7 +59,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.3.2 + pdfrx: ^2.0.0-beta1 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 831a8e03..bb3a6212 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -318,9 +318,6 @@ class _MainPageState extends State with WidgetsBindingObserver { pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, textSelectionParams: PdfTextSelectionParams( - enableSelectionHandles: true, - showContextMenuOnSelectionHandle: true, - magnifier: PdfViewerSelectionMagnifierParams(enabled: true), onTextSelectionChange: (textSelection) { textSelections = textSelection.selectedTextRange; }, diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 52ddf831..edd59240 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,14 +347,14 @@ packages: path: "../.." relative: true source: path - version: "1.3.2" + version: "2.0.0-beta1" pdfrx_engine: dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.4" + version: "0.1.5" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 44b4279c..507ee21c 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -19,11 +19,22 @@ void setClipboardData(String text) { final isMobile = Platform.isAndroid || Platform.isIOS || Platform.isFuchsia; /// Whether text selection should be triggered by swipe gestures or not. - bool get shouldTextSelectionTriggeredBySwipe { if (isMobile) return false; return true; } +/// Whether to show text selection handles. +bool get shouldShowTextSelectionHandles { + if (isMobile) return true; + return false; +} + +/// Whether to show text selection magnifier. +bool get shouldShowTextSelectionMagnifier { + if (isMobile) return true; + return false; +} + /// Override for the [PdfDocumentFactory] for native platforms; it is null. PdfDocumentFactory? get pdfDocumentFactoryOverride => null; diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index c767e615..4ca836da 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -18,8 +18,13 @@ void setClipboardData(String text) { final isMobile = false; /// Whether text selection should be triggered by swipe gestures or not. +bool get shouldTextSelectionTriggeredBySwipe => false; -bool get shouldTextSelectionTriggeredBySwipe => true; +/// Whether to show text selection handles. +bool get shouldShowTextSelectionHandles => true; + +/// Whether to show text selection magnifier. +bool get shouldShowTextSelectionMagnifier => true; /// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 09e76f68..21e24a02 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -878,7 +878,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix canvas, cache: _imageCache, maxImageCacheBytes: widget.params.maxImageBytesCachedOnMemory, - targetRect: _getCacheExtentRect(), + targetRect: _visibleRect, + cacheTargetRect: _getCacheExtentRect(), resolutionMultiplier: MediaQuery.of(context).devicePixelRatio, enableLowResolutionPagePreview: widget.params.behaviorControlParams.enableLowResolutionPagePreview, filterQuality: FilterQuality.low, @@ -890,6 +891,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix required _PdfPageImageCache cache, required int maxImageCacheBytes, required Rect targetRect, + Rect? cacheTargetRect, double resolutionMultiplier = 1.0, bool enableLowResolutionPagePreview = true, FilterQuality filterQuality = FilterQuality.high, @@ -897,10 +899,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final scale = _currentZoom * resolutionMultiplier; final unusedPageList = []; final dropShadowPaint = widget.params.pageDropShadow?.toPaint()?..style = PaintingStyle.fill; + cacheTargetRect ??= targetRect; for (int i = 0; i < _document!.pages.length; i++) { final rect = _layout!.pageLayouts[i]; - final intersection = rect.intersect(targetRect); + final intersection = rect.intersect(cacheTargetRect); if (intersection.isEmpty) { final page = _document!.pages[i]; cache.cancelPendingRenderings(page.pageNumber); @@ -958,7 +961,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final pageScale = scale * max(rect.width / page.width, rect.height / page.height); if (!enableLowResolutionPagePreview || pageScale > previewScaleLimit) { - _requestRealSizePartialImage(cache, page, pageScale); + _requestRealSizePartialImage(cache, page, pageScale, targetRect); } if ((!enableLowResolutionPagePreview || pageScale > previewScaleLimit) && partial != null) { @@ -1111,7 +1114,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final cancellationToken = page.createCancellationToken(); cache.addCancellationToken(page.pageNumber, cancellationToken); - await synchronized(() async { + await cache.synchronized(() async { if (!mounted || cancellationToken.isCanceled) return; if (cache.pageImages[page.pageNumber]?.scale == scale) return; final img = await page.render( @@ -1140,22 +1143,21 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix }); } - Future _requestRealSizePartialImage(_PdfPageImageCache cache, PdfPage page, double scale) async { + Future _requestRealSizePartialImage( + _PdfPageImageCache cache, + PdfPage page, + double scale, + Rect targetRect, + ) async { cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { - await synchronized(() async { - if (!mounted || cancellationToken.isCanceled) return; - final oldImage = cache.pageImagesPartial[page.pageNumber]; - final newImage = await _createRealSizePartialImage(cache, page, scale, oldImage, cancellationToken); - if (oldImage == newImage) return; - if (newImage != null) { - cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); - cache.pageImagesPartial[page.pageNumber] = newImage; - _invalidate(); - } - }); + if (!mounted || cancellationToken.isCanceled) return; + final newImage = await _createRealSizePartialImage(cache, page, scale, targetRect, cancellationToken); + if (newImage != null) { + _invalidate(); + } }), cancellationToken, ); @@ -1165,15 +1167,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _PdfPageImageCache cache, PdfPage page, double scale, - _PdfImageWithScaleAndRect? oldImage, + Rect targetRect, PdfPageRenderCancellationToken cancellationToken, ) async { + if (!mounted || cancellationToken.isCanceled) return null; final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; - final rect = pageRect.intersect(_visibleRect); + final rect = pageRect.intersect(targetRect); final prev = cache.pageImagesPartial[page.pageNumber]; if (prev?.rect == rect && prev?.scale == scale) return prev; if (rect.width < 1 || rect.height < 1) return null; - if (!mounted || cancellationToken.isCanceled) return null; Future<_PdfImageWithScaleAndRect?> renderRect(Rect rect, [int? x, int? y, int? width, int? height]) async { final inPageRect = rect.translate(-pageRect.left, -pageRect.top); @@ -1206,52 +1208,16 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return result; } - // experimental differential rendering - if (widget.params.behaviorControlParams.enableDifferentialRendering && - oldImage != null && - oldImage.scale == scale) { - if (rect == oldImage.rect) { - return oldImage; // no need to render again - } - if (oldImage.rect.overlaps(rect)) { - final inPageRect = rect.translate(-pageRect.left, -pageRect.top); - final x = (inPageRect.left * scale).toInt(); - final y = (inPageRect.top * scale).toInt(); - final width = (inPageRect.width * scale).toInt(); - final height = (inPageRect.height * scale).toInt(); - _PdfImageWithScaleAndRect? upper, lower, left, right; - try { - upper = await renderRect(rect, x, y, width, oldImage.top - y); - lower = await renderRect(rect, x, oldImage.bottom, width, y + height - oldImage.bottom); - left = await renderRect(rect, x, oldImage.top, oldImage.left - x, oldImage.height); - right = await renderRect(rect, oldImage.right, oldImage.top, x + width - oldImage.right, oldImage.height); - - final recorder = ui.PictureRecorder(); - final canvas = ui.Canvas(recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble())); - oldImage.drawNoScale(canvas, x, y); - upper?.drawNoScale(canvas, x, y); - lower?.drawNoScale(canvas, x, y); - left?.drawNoScale(canvas, x, y); - right?.drawNoScale(canvas, x, y); - final picture = recorder.endRecording(); - final image = await picture.toImage(width, height); - picture.dispose(); - return _PdfImageWithScaleAndRect(image, scale, rect, x, y); - } catch (e) { - return null; // canceled - } finally { - upper?.dispose(); - lower?.dispose(); - left?.dispose(); - right?.dispose(); - } - } - } try { - return await renderRect(rect); + final newImage = await renderRect(rect); + if (newImage != null) { + cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); + cache.pageImagesPartial[page.pageNumber] = newImage; + } } catch (e) { return null; // canceled } + return null; } void _onWheelDelta(PointerScrollEvent event) { @@ -1801,7 +1767,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return []; } - final enableSelectionHandles = widget.params.textSelectionParams?.enableSelectionHandles ?? isMobile; + final enableSelectionHandles = + widget.params.textSelectionParams?.enableSelectionHandles ?? shouldShowTextSelectionHandles; double? aLeft, aTop, aRight, aBottom; double? bLeft, bTop, bRight; @@ -1875,9 +1842,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix // Determines whether the widget is [Positioned] or [Align] to avoid unnecessary wrapping. bool isPositionalWidget(Widget? widget) => widget != null && (widget is Positioned || widget is Align); - const margin = 8.0; + const defMargin = 8.0; - Offset? calcPosition(Rect? rect, _TextSelectionPart part) { + Offset? calcPosition(Rect? rect, _TextSelectionPart part, {double margin = defMargin, double? marginOnBottom}) { if (rect == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { return null; } @@ -1908,7 +1875,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix top = rectTop - height - margin; if (top < margin) { // If the anchor is too close to the top, place the magnifier below it. - top = rectBottom + margin; + top = rectBottom + (marginOnBottom ?? margin); } break; case PdfTextDirection.vrtl: @@ -1937,7 +1904,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget? magnifier; final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = - (magnifierParams.enabled ?? isMobile) && + (magnifierParams.enabled ?? shouldShowTextSelectionMagnifier) && (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? (_currentZoom < magnifierParams.scale)); if (magnifierEnabled && (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b)) { final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA : _textSelB; @@ -1947,7 +1914,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final builder = magnifierParams.builder ?? _buildMagnifierDecoration; magnifier = builder(context, magnifierParams, magnifierMain); if (magnifier != null && !isPositionalWidget(magnifier)) { - final offset = calcPosition(_magnifierRect, textAnchorMoving) ?? Offset.zero; + final offset = calcPosition(_magnifierRect, textAnchorMoving, marginOnBottom: 80) ?? Offset.zero; magnifier = Positioned( left: offset.dx, top: offset.dy, @@ -1979,7 +1946,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix showContextMenu = true; } - Offset normalizedContextMenuPosition(Offset pos) { + Offset normalizedContextMenuPosition(Offset pos, {double margin = defMargin}) { if (_contextMenuRect == null) return pos; var left = pos.dx; var top = pos.dy; @@ -2207,7 +2174,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ), resolutionMultiplier: magScale, enableLowResolutionPagePreview: false, - filterQuality: FilterQuality.high, + filterQuality: FilterQuality.low, ); canvas.restore(); canvas.drawLine( @@ -2528,10 +2495,7 @@ class _PdfPageImageCache { if (removed != null) { removed.image.dispose(); } - final removedPartial = pageImagesPartial.remove(pageNumber); - if (removedPartial != null) { - synchronized(() => removedPartial.image.dispose()); - } + pageImagesPartial.remove(pageNumber)?.dispose(); } void removeCacheImagesIfCacheBytesExceedsLimit( @@ -2554,7 +2518,7 @@ class _PdfPageImageCache { final removedPartial = pageImagesPartial.remove(key); if (removedPartial != null) { bytesConsumed -= getBytesConsumed(removedPartial.image); - synchronized(() => removedPartial.image.dispose()); + removedPartial.dispose(); } if (bytesConsumed <= acceptableBytes) { break; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 8376a499..3c704648 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -647,12 +647,12 @@ class PdfTextSelectionParams { /// Whether text selection is triggered by swipe. /// - /// null to determine the behavior based on the platform; enabled on Desktop/Web, disabled on Mobile. + /// null to determine the behavior based on the platform; enabled on desktop, disabled on mobile/Web. final bool? textSelectionTriggeredBySwipe; /// Whether to show selection handles. /// - /// null to determine the behavior based on the platform; enabled on Mobile, disabled on Desktop/Web. + /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop/. final bool? enableSelectionHandles; /// Whether to show context menu on selection handle. @@ -874,7 +874,7 @@ class PdfViewerSelectionMagnifierParams { /// Whether the magnifier is enabled. /// - /// If null, the magnifier is enabled by default on Mobile and disabled on Desktop/Web. + /// If null, the magnifier is enabled by default on mobile/web and disabled on desktop. final bool? enabled; /// Size of the magnifier content. @@ -1244,8 +1244,7 @@ class PdfViewerBehaviorControlParams { this.trailingPageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 100), this.enableLowResolutionPagePreview = true, this.pageImageCachingDelay = const Duration(milliseconds: kIsWeb ? 100 : 20), - this.partialImageLoadingDelay = const Duration(milliseconds: 0), - this.enableDifferentialRendering = false, + this.partialImageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 0), }); /// How long to wait before loading the trailing pages after the initial page load. @@ -1262,9 +1261,6 @@ class PdfViewerBehaviorControlParams { /// How long to wait before loading the partial real size image. final Duration partialImageLoadingDelay; - /// Whether to enable differential rendering (experimental). - final bool enableDifferentialRendering; - @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1273,8 +1269,7 @@ class PdfViewerBehaviorControlParams { other.trailingPageLoadingDelay == trailingPageLoadingDelay && other.enableLowResolutionPagePreview == enableLowResolutionPagePreview && other.pageImageCachingDelay == pageImageCachingDelay && - other.partialImageLoadingDelay == partialImageLoadingDelay && - other.enableDifferentialRendering == enableDifferentialRendering; + other.partialImageLoadingDelay == partialImageLoadingDelay; } @override @@ -1282,6 +1277,5 @@ class PdfViewerBehaviorControlParams { trailingPageLoadingDelay.hashCode ^ enableLowResolutionPagePreview.hashCode ^ pageImageCachingDelay.hashCode ^ - partialImageLoadingDelay.hashCode ^ - enableDifferentialRendering.hashCode; + partialImageLoadingDelay.hashCode; } diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index c4668725..793ce750 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.2 +version: 2.0.0-beta1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index c0336844..66b04ce7 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.5 + +- New text extraction API. + ## 0.1.4 - Minor updates. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 9285322e..719a2182 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.4 +version: 0.1.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 2c787dc58961f6f3e16d334a8d74e0432bee294b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 16 Jul 2025 20:09:15 +0900 Subject: [PATCH 196/663] Updates for #417 --- bin/remove_wasm_modules.dart | 70 +++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/bin/remove_wasm_modules.dart b/bin/remove_wasm_modules.dart index b2d5a2c8..15c8c1ea 100644 --- a/bin/remove_wasm_modules.dart +++ b/bin/remove_wasm_modules.dart @@ -7,7 +7,21 @@ import 'package:path/path.dart' as path; Future main(List args) async { try { - final projectRoot = args.isEmpty ? '.' : args.first; + // Parse arguments + bool revert = false; + String projectRoot = '.'; + + for (final arg in args) { + if (arg == '--revert' || arg == '-r') { + revert = true; + } else if (!arg.startsWith('-')) { + projectRoot = arg; + } else if (arg == '--help' || arg == '-h') { + _printUsage(); + return 0; + } + } + final pubspecLock = File(path.join(projectRoot, 'pubspec.lock')); if (!pubspecLock.existsSync()) { print('No pubspec.lock found in $projectRoot'); @@ -18,21 +32,25 @@ Future main(List args) async { final pdfrxWasmPackage = deps.allDependencies.firstWhere((p) => p.name == 'pdfrx'); print('Found: ${pdfrxWasmPackage.name} ${pdfrxWasmPackage.version}: ${pdfrxWasmPackage.pubspecYamlPath}'); - final modulesDir = Directory(path.join(path.dirname(pdfrxWasmPackage.pubspecYamlPath!), 'assets')); - if (!modulesDir.existsSync()) { - print('Not found: $modulesDir'); + final pubspecPath = pdfrxWasmPackage.pubspecYamlPath!; + final pubspecFile = File(pubspecPath); + + if (!pubspecFile.existsSync()) { + print('pubspec.yaml not found at: $pubspecPath'); return 3; } - for (final file in modulesDir.listSync()) { - if (file is File) { - print('Deleting: ${file.path}'); - try { - file.deleteSync(); - } catch (e) { - print('Error deleting file: ${file.path}, error: $e'); - } - } + // Read the pubspec.yaml content + var pubspecYaml = pubspecFile.readAsStringSync(); + final modifiedYaml = + revert + ? pubspecYaml.replaceAll('# - assets/', '- assets/') + : pubspecYaml.replaceAll('- assets/', '# - assets/'); + if (modifiedYaml == pubspecYaml) { + print('No changes needed.'); + } else { + pubspecFile.writeAsStringSync(modifiedYaml); + print('Successfully ${revert ? "reverted" : "modified"}.'); } return 0; } catch (e, s) { @@ -40,3 +58,29 @@ Future main(List args) async { return 1; } } + +void _printUsage() { + print(''' +Usage: dart run bin/remove_wasm_modules.dart [options] [project_root] + +This tool comments out the '- assets/' line in pdfrx's pubspec.yaml to exclude +WASM modules from the package, reducing the package size. + +Options: + -r, --revert Revert the changes (uncomment the assets line) + -h, --help Show this help message + +Arguments: + project_root Path to the project root (default: current directory) + +Examples: + # Comment out assets line + dart run bin/remove_wasm_modules.dart + + # Revert changes (uncomment assets line) + dart run bin/remove_wasm_modules.dart --revert + + # Specify a different project root + dart run bin/remove_wasm_modules.dart ../my_project +'''); +} From c8b560343d6b35607079d52309f4017f8b741baa Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 16 Jul 2025 20:20:22 +0900 Subject: [PATCH 197/663] Release v1.3.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 5 +++++ README.md | 6 ++++++ example/viewer/pubspec.lock | 2 +- pubspec.yaml | 5 ++--- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f74cce2d..a77a9faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.3.3 + +- NEW FEATURE: Updated `bin/remove_wasm_modules.dart` to comment out assets line in pubspec.yaml instead of deleting files + - Added `--revert` option to restore commented assets line in `bin/remove_wasm_modules.dart` + # 1.3.2 - NEW FEATURE: Added `useProgressiveLoading` parameter for all `PdfViewer` constructors to enable progressive page loading diff --git a/README.md b/README.md index a7ac4e47..c4764eaf 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ dart run pdfrx:remove_wasm_modules flutter build ... ``` +The command comments out the `- assets/` line in pdfrx's pubspec.yaml. To restore the WASM modules, use: + +```bash +dart run pdfrx:remove_wasm_modules --revert +``` + ## Customizations/Features You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 19035c51..79aa797e 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.3.2" + version: "1.3.3" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 05f3ff81..7aca90c6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.2 +version: 1.3.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -77,5 +77,4 @@ ffigen: name: "pdfium" comments: style: any - length: full - + length: full \ No newline at end of file From 09a9ff3c5b4dceef70ad4c15468d80422ee22868 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 17 Jul 2025 01:29:54 +0900 Subject: [PATCH 198/663] WIP --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 254 +++++++++--------- .../lib/src/widgets/pdf_viewer_params.dart | 95 ++++--- 2 files changed, 195 insertions(+), 154 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 21e24a02..09e81355 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -880,7 +880,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix maxImageCacheBytes: widget.params.maxImageBytesCachedOnMemory, targetRect: _visibleRect, cacheTargetRect: _getCacheExtentRect(), - resolutionMultiplier: MediaQuery.of(context).devicePixelRatio, + scale: _currentZoom * MediaQuery.of(context).devicePixelRatio, enableLowResolutionPagePreview: widget.params.behaviorControlParams.enableLowResolutionPagePreview, filterQuality: FilterQuality.low, ); @@ -890,13 +890,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ui.Canvas canvas, { required _PdfPageImageCache cache, required int maxImageCacheBytes, + required double scale, required Rect targetRect, Rect? cacheTargetRect, - double resolutionMultiplier = 1.0, bool enableLowResolutionPagePreview = true, FilterQuality filterQuality = FilterQuality.high, }) { - final scale = _currentZoom * resolutionMultiplier; final unusedPageList = []; final dropShadowPaint = widget.params.pageDropShadow?.toPaint()?..style = PaintingStyle.fill; cacheTargetRect ??= targetRect; @@ -917,14 +916,22 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final previewImage = cache.pageImages[page.pageNumber]; final partial = cache.pageImagesPartial[page.pageNumber]; - final previewScaleLimit = - widget.params.getPageRenderingScale?.call( - context, - page, - _controller!, - widget.params.onePassRenderingScaleThreshold, - ) ?? - widget.params.onePassRenderingScaleThreshold; + final getPageRenderingScale = + widget.params.getPageRenderingScale ?? + (context, page, controller, estimatedScale) { + final max = widget.params.onePassRenderingSizeThreshold; + if (page.width > max || page.height > max) { + return min(max / page.width, max / page.height); + } + return estimatedScale; + }; + + final previewScaleLimit = getPageRenderingScale( + context, + page, + _controller!, + widget.params.onePassRenderingScaleThreshold, + ); if (dropShadowPaint != null) { final offset = widget.params.pageDropShadow!.offset; @@ -1117,29 +1124,27 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix await cache.synchronized(() async { if (!mounted || cancellationToken.isCanceled) return; if (cache.pageImages[page.pageNumber]?.scale == scale) return; - final img = await page.render( - fullWidth: width, - fullHeight: height, - backgroundColor: 0xffffffff, - annotationRenderingMode: widget.params.annotationRenderingMode, - flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, - cancellationToken: cancellationToken, - ); - if (img == null) return; - if (!mounted || cancellationToken.isCanceled) { - img.dispose(); - return; - } - final newImage = _PdfImageWithScale(await img.createImage(), scale); - if (!mounted || cancellationToken.isCanceled) { - img.dispose(); - newImage.dispose(); - return; + PdfImage? img; + try { + img = await page.render( + fullWidth: width, + fullHeight: height, + backgroundColor: 0xffffffff, + annotationRenderingMode: widget.params.annotationRenderingMode, + flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, + cancellationToken: cancellationToken, + ); + if (img == null || !mounted || cancellationToken.isCanceled) return; + + final newImage = _PdfImageWithScale(await img.createImage(), scale); + cache.pageImages[page.pageNumber]?.dispose(); + cache.pageImages[page.pageNumber] = newImage; + _invalidate(); + } catch (e) { + return; // ignore error + } finally { + img?.dispose(); } - cache.pageImages[page.pageNumber]?.dispose(); - cache.pageImages[page.pageNumber] = newImage; - img.dispose(); - _invalidate(); }); } @@ -1150,12 +1155,21 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Rect targetRect, ) async { cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); + + final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; + final rect = pageRect.intersect(targetRect); + final prev = cache.pageImagesPartial[page.pageNumber]; + if (prev?.rect == rect && prev?.scale == scale) return; + if (rect.width < 1 || rect.height < 1) return; + final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { if (!mounted || cancellationToken.isCanceled) return; - final newImage = await _createRealSizePartialImage(cache, page, scale, targetRect, cancellationToken); + final newImage = await _createRealSizePartialImage(cache, page, scale, rect, cancellationToken); if (newImage != null) { + cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); + cache.pageImagesPartial[page.pageNumber] = newImage; _invalidate(); } }), @@ -1167,24 +1181,21 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _PdfPageImageCache cache, PdfPage page, double scale, - Rect targetRect, + Rect rect, PdfPageRenderCancellationToken cancellationToken, ) async { if (!mounted || cancellationToken.isCanceled) return null; final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; - final rect = pageRect.intersect(targetRect); - final prev = cache.pageImagesPartial[page.pageNumber]; - if (prev?.rect == rect && prev?.scale == scale) return prev; - if (rect.width < 1 || rect.height < 1) return null; - - Future<_PdfImageWithScaleAndRect?> renderRect(Rect rect, [int? x, int? y, int? width, int? height]) async { - final inPageRect = rect.translate(-pageRect.left, -pageRect.top); - x ??= (inPageRect.left * scale).toInt(); - y ??= (inPageRect.top * scale).toInt(); - width ??= (inPageRect.width * scale).toInt(); - height ??= (inPageRect.height * scale).toInt(); - if (width < 1 || height < 1) return null; - final img = await page.render( + final inPageRect = rect.translate(-pageRect.left, -pageRect.top); + final x = (inPageRect.left * scale).toInt(); + final y = (inPageRect.top * scale).toInt(); + final width = (inPageRect.width * scale).toInt(); + final height = (inPageRect.height * scale).toInt(); + if (width < 1 || height < 1) return null; + + PdfImage? img; + try { + img = await page.render( x: x, y: y, width: width, @@ -1196,28 +1207,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, ); - if (img == null) { - throw PdfException('Canceled'); - } - if (!mounted || cancellationToken.isCanceled) { - img.dispose(); - throw PdfException('Canceled'); - } - final result = _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y); - img.dispose(); - return result; - } - - try { - final newImage = await renderRect(rect); - if (newImage != null) { - cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); - cache.pageImagesPartial[page.pageNumber] = newImage; - } + if (img == null || !mounted || cancellationToken.isCanceled) return null; + return _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y); } catch (e) { - return null; // canceled + return null; // ignore error + } finally { + img?.dispose(); } - return null; } void _onWheelDelta(PointerScrollEvent event) { @@ -1844,16 +1840,20 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix const defMargin = 8.0; - Offset? calcPosition(Rect? rect, _TextSelectionPart part, {double margin = defMargin, double? marginOnBottom}) { - if (rect == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { + Offset? calcPosition( + Size? widgetSize, + _TextSelectionPart part, { + double margin = defMargin, + double? marginOnTop, + double? marginOnBottom, + }) { + if (widgetSize == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { return null; } final textAnchor = part == _TextSelectionPart.a ? _textSelA : _textSelB; if (textAnchor == null) return null; late double left, top; - final width = rect.width; - final height = rect.height; final rect0 = (part == _TextSelectionPart.a ? rectA : rectB); final rect1 = (part == _TextSelectionPart.a ? _anchorARect : _anchorBRect); final pt = rect0.center; @@ -1865,14 +1865,14 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix case PdfTextDirection.ltr: case PdfTextDirection.rtl: case PdfTextDirection.unknown: - left = pt.dx - width / 2; + left = pt.dx - widgetSize.width / 2 + margin; if (left < margin) { left = margin; - } else if (left + width + margin > viewSize.width) { + } else if (left + widgetSize.width + margin > viewSize.width) { // If the anchor is too close to the right, place the magnifier to the left of it. - left = viewSize.width - width - margin; + left = viewSize.width - widgetSize.width - margin; } - top = rectTop - height - margin; + top = rectTop - widgetSize.height - (marginOnTop ?? margin); if (top < margin) { // If the anchor is too close to the top, place the magnifier below it. top = rectBottom + (marginOnBottom ?? margin); @@ -1881,21 +1881,21 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix case PdfTextDirection.vrtl: if (part == _TextSelectionPart.a) { left = rectRight + margin; - if (left + width + margin > viewSize.width) { - left = rectLeft - width - margin; + if (left + widgetSize.width + margin > viewSize.width) { + left = rectLeft - widgetSize.width - margin; } } else { - left = rectLeft - width - margin; + left = rectLeft - widgetSize.width - margin; if (left < margin) { left = rectRight + margin; } } - top = pt.dy - height / 2; + top = pt.dy - widgetSize.height / 2; if (top < margin) { top = margin; - } else if (top + height + margin > viewSize.height) { + } else if (top + widgetSize.height + margin > viewSize.height) { // If the anchor is too close to the bottom, place the magnifier above it. - top = viewSize.height - height - margin; + top = viewSize.height - widgetSize.height - margin; } } return Offset(left, top); @@ -1905,16 +1905,20 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = (magnifierParams.enabled ?? shouldShowTextSelectionMagnifier) && - (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? (_currentZoom < magnifierParams.scale)); + (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? true); if (magnifierEnabled && (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b)) { final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA : _textSelB; final magCenter = textAnchor?.anchorPoint; - if (magCenter != null && textAnchor!.rect.height < magnifierParams.size.height * .7) { - final magnifierMain = _buildMagnifier(context, magCenter, magnifierParams); + final charSize = textAnchor!.rect.size * _currentZoom; + final h = textAnchor.direction == PdfTextDirection.vrtl ? charSize.width : charSize.height; + if (magCenter != null && h < magnifierParams.magnifierSizeThreshold) { + final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)(textAnchor, magnifierParams); + final magnifierMain = _buildMagnifier(context, magRect, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = builder(context, magnifierParams, magnifierMain); + magnifier = builder(context, magnifierParams, magnifierMain, magRect.size); if (magnifier != null && !isPositionalWidget(magnifier)) { - final offset = calcPosition(_magnifierRect, textAnchorMoving, marginOnBottom: 80) ?? Offset.zero; + final offset = + calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; magnifier = Positioned( left: offset.dx, top: offset.dy, @@ -1973,7 +1977,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final offset = _showTextSelectionContextMenuAt != null ? normalizedContextMenuPosition(renderBox.globalToLocal(_showTextSelectionContextMenuAt!)) - : (calcPosition(_contextMenuRect, _selPartLastMoved) ?? Offset.zero); + : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); Offset.zero; final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; contextMenu = ctxMenuBuilder(context, _textSelA!, _textSelB!, this, () { @@ -2150,52 +2154,48 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } } - Widget _buildMagnifier(BuildContext context, Offset docPos, PdfViewerSelectionMagnifierParams magnifierParams) { - final magScale = magnifierParams.scale * MediaQuery.of(context).devicePixelRatio; + /// Calculate the rectangle shown in the magnifier for the given text anchor. + Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { + final c = textAnchor.page.charRects[textAnchor.index]; + return switch (textAnchor.direction) { + PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => Rect.fromLTRB( + textAnchor.rect.left - c.height * 2, + textAnchor.rect.top - c.height * .2, + textAnchor.rect.right + c.height * 2, + textAnchor.rect.bottom + c.height * .2, + ), + PdfTextDirection.vrtl => Rect.fromLTRB( + textAnchor.rect.left - c.width * .2, + textAnchor.rect.top - c.width * 2, + textAnchor.rect.right + c.width * .2, + textAnchor.rect.bottom + c.width * 2, + ), + }; + } + + Widget _buildMagnifier(BuildContext context, Rect rectToDraw, PdfViewerSelectionMagnifierParams magnifierParams) { return LayoutBuilder( builder: (context, constraints) { return ClipRect( clipBehavior: Clip.antiAlias, child: CustomPaint( painter: _CustomPainter.fromFunctions((canvas, size) { + final magScale = max(size.width / rectToDraw.width, size.height / rectToDraw.height); canvas.save(); canvas.scale(magScale); - canvas.translate(-docPos.dx + size.width / 2 / magScale, -docPos.dy + size.height / 2 / magScale); + canvas.translate(-rectToDraw.left, -rectToDraw.top); _paintPagesCustom( canvas, cache: _magnifierImageCache, maxImageCacheBytes: widget.params.textSelectionParams?.magnifier?.maxImageBytesCachedOnMemory ?? PdfViewerSelectionMagnifierParams.defaultMaxImageBytesCachedOnMemory, - targetRect: Rect.fromCenter( - center: docPos, - width: size.width / magScale, - height: size.height / magScale, - ), - resolutionMultiplier: magScale, + targetRect: rectToDraw, + scale: magScale * MediaQuery.of(context).devicePixelRatio, enableLowResolutionPagePreview: false, filterQuality: FilterQuality.low, ); canvas.restore(); - canvas.drawLine( - Offset(size.width / 2, size.height / 4), - Offset(size.width / 2, size.height * 3 / 4), - Paint() - ..color = Colors.blue - ..style = PaintingStyle.stroke, - ); - - if (magnifierParams.paintMagnifierContent != null) { - magnifierParams.paintMagnifierContent!(canvas, size, docPos, magnifierParams); - } else { - canvas.drawLine( - Offset(size.width / 4, size.height / 2), - Offset(size.width * 3 / 4, size.height / 2), - Paint() - ..color = Colors.blue - ..style = PaintingStyle.stroke, - ); - } }), size: Size(constraints.maxWidth, constraints.maxHeight), ), @@ -2204,15 +2204,22 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ); } - Widget _buildMagnifierDecoration(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget child) { + Widget _buildMagnifierDecoration( + BuildContext context, + PdfViewerSelectionMagnifierParams params, + Widget child, + Size childSize, + ) { + final scale = 80 / min(childSize.width, childSize.height); return Container( - width: params.size.width, - height: params.size.height, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(30), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], ), - child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), + child: ClipRRect( + borderRadius: BorderRadius.circular(30), + child: SizedBox(width: childSize.width * scale, height: childSize.height * scale, child: child), + ), ); } @@ -2543,6 +2550,9 @@ class _PdfImageWithScale { final ui.Image image; final double scale; + int get width => image.width; + int get height => image.height; + void dispose() { image.dispose(); } @@ -2554,8 +2564,6 @@ class _PdfImageWithScaleAndRect extends _PdfImageWithScale { final int left; final int top; - int get width => image.width; - int get height => image.height; int get bottom => top + height; int get right => left + width; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 3c704648..d0fdce61 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -27,6 +27,7 @@ class PdfViewerParams { this.pageAnchor = PdfPageAnchor.top, this.pageAnchorEnd = PdfPageAnchor.bottom, this.onePassRenderingScaleThreshold = 200 / 72, + this.onePassRenderingSizeThreshold = 2000, this.textSelectionParams, this.matchTextColor, this.activeMatchTextColor, @@ -182,6 +183,11 @@ class PdfViewerParams { /// If you want more granular control for each page, use [getPageRenderingScale]. final double onePassRenderingScaleThreshold; + /// If a page is too large, the page is rendered with the size which fits within the threshold size (in pixels). + /// + /// The default is 2000, which implies the maximum size of the page is 2000 pixels in width or height. + final double onePassRenderingSizeThreshold; + /// Parameters for text selection. final PdfTextSelectionParams? textSelectionParams; @@ -505,6 +511,7 @@ class PdfViewerParams { other.pageAnchor != pageAnchor || other.pageAnchorEnd != pageAnchorEnd || other.onePassRenderingScaleThreshold != onePassRenderingScaleThreshold || + other.onePassRenderingSizeThreshold != onePassRenderingSizeThreshold || other.textSelectionParams != textSelectionParams || other.matchTextColor != matchTextColor || other.activeMatchTextColor != activeMatchTextColor || @@ -537,6 +544,7 @@ class PdfViewerParams { other.pageAnchor == pageAnchor && other.pageAnchorEnd == pageAnchorEnd && other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && + other.onePassRenderingSizeThreshold == onePassRenderingSizeThreshold && other.textSelectionParams == textSelectionParams && other.matchTextColor == matchTextColor && other.activeMatchTextColor == activeMatchTextColor && @@ -589,6 +597,7 @@ class PdfViewerParams { pageAnchor.hashCode ^ pageAnchorEnd.hashCode ^ onePassRenderingScaleThreshold.hashCode ^ + onePassRenderingSizeThreshold.hashCode ^ textSelectionParams.hashCode ^ matchTextColor.hashCode ^ activeMatchTextColor.hashCode ^ @@ -857,14 +866,14 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { Future selectWord(Offset position); } +/// Parameters for the text selection magnifier. @immutable class PdfViewerSelectionMagnifierParams { const PdfViewerSelectionMagnifierParams({ this.enabled, - this.size = const Size(150.0, 150.0), - this.scale = 4.0, + this.magnifierSizeThreshold = 72, + this.getMagnifierRectForAnchor, this.builder, - this.paintMagnifierContent, this.shouldBeShown, this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, }); @@ -877,18 +886,17 @@ class PdfViewerSelectionMagnifierParams { /// If null, the magnifier is enabled by default on mobile/web and disabled on desktop. final bool? enabled; - /// Size of the magnifier content. - final Size size; + /// If the character size (in pt.) is smaller than this value, the magnifier will be shown. + /// + /// The default is 72 pt. + final double magnifierSizeThreshold; - /// Scale (zoom ratio) of the magnifier content. - final double scale; + /// Function to get the magnifier rectangle for the anchor. + final PdfViewerGetMagnifierRectForAnchor? getMagnifierRectForAnchor; /// Function to build the magnifier widget. final PdfViewerMagnifierBuilder? builder; - /// Function to paint the magnifier content. - final PdfViewerMagnifierContentPaintFunction? paintMagnifierContent; - /// Function to determine whether the magnifier should be shown based on conditions such as zoom level. /// /// If [enabled] is false, this function is not called. @@ -906,10 +914,9 @@ class PdfViewerSelectionMagnifierParams { return other is PdfViewerSelectionMagnifierParams && other.enabled == enabled && - other.size == size && - other.scale == scale && + other.magnifierSizeThreshold == magnifierSizeThreshold && + other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && - other.paintMagnifierContent == paintMagnifierContent && other.shouldBeShown == shouldBeShown && other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; } @@ -917,14 +924,39 @@ class PdfViewerSelectionMagnifierParams { @override int get hashCode => enabled.hashCode ^ - size.hashCode ^ - scale.hashCode ^ + magnifierSizeThreshold.hashCode ^ + getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ - paintMagnifierContent.hashCode ^ shouldBeShown.hashCode ^ maxImageBytesCachedOnMemory.hashCode; } +/// Function to get the magnifier rectangle for the anchor. +/// +/// The following fragment illustrates how to get the magnifier rectangle for the anchor: +/// +///```dart +/// getMagnifierRectForAnchor: (textAnchor, params) { +/// final c = textAnchor.page.charRects[textAnchor.index]; +/// return switch (textAnchor.direction) { +/// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => Rect.fromLTRB( +/// textAnchor.rect.left - c.height * 2, +/// textAnchor.rect.top - c.height * .2, +/// textAnchor.rect.right + c.height * 2, +/// textAnchor.rect.bottom + c.height * .2, +/// ), +/// PdfTextDirection.vrtl => Rect.fromLTRB( +/// textAnchor.rect.left - c.width * .2, +/// textAnchor.rect.top - c.width * 2, +/// textAnchor.rect.right + c.width * .2, +/// textAnchor.rect.bottom + c.width * 2, +/// ), +/// }; +/// } +///``` +typedef PdfViewerGetMagnifierRectForAnchor = + Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params); + /// Function to build the magnifier widget. /// /// The function can be used to decorate the magnifier widget with additional widgets such as [Container] or [Size]. @@ -938,29 +970,30 @@ class PdfViewerSelectionMagnifierParams { /// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: /// /// ```dart -/// builder: (context, params, magnifierContent) { +/// builder: (context, params, magnifierContent, magnifierContentSize) { +/// final scale = 80 / min(magnifierContentSize.width, magnifierContentSize.height); /// return Container( -/// width: params.size.width, -/// height: params.size.height, /// decoration: BoxDecoration( /// borderRadius: BorderRadius.circular(16), /// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], /// ), -/// child: ClipRRect(borderRadius: BorderRadius.circular(15), child: child), +/// child: ClipRRect(borderRadius: BorderRadius.circular(15), +/// child: SizedBox( +/// width: magnifierContentSize.width * scale, +/// height: magnifierContentSize.height * scale, +/// child: magnifierContent +/// ), +/// ), /// ); /// } /// ``` typedef PdfViewerMagnifierBuilder = - Widget? Function(BuildContext context, PdfViewerSelectionMagnifierParams params, Widget magnifierContent); - -/// Function to paint the magnifier content. -/// -/// The function is called to paint the magnifier content on the canvas. -/// The [canvas] is the canvas to paint on, [size] is the size of the magnifier content, -/// and [center] is the center position of the magnifier in the document coordinates. -/// [params] is the magnifier parameters. -typedef PdfViewerMagnifierContentPaintFunction = - void Function(Canvas canvas, Size size, Offset center, PdfViewerSelectionMagnifierParams params); + Widget? Function( + BuildContext context, + PdfViewerSelectionMagnifierParams params, + Widget magnifierContent, + Size magnifierContentSize, + ); /// Function to determine whether the magnifier should be shown or not. typedef PdfViewerMagnifierShouldBeShownFunction = @@ -1008,7 +1041,7 @@ typedef PdfPageChangedCallback = void Function(int? pageNumber); /// - [controller] can be used to get the current zoom by [PdfViewerController.currentZoom] /// - [estimatedScale] is the precalculated scale for the page typedef PdfViewerGetPageRenderingScale = - double? Function(BuildContext context, PdfPage page, PdfViewerController controller, double estimatedScale); + double Function(BuildContext context, PdfPage page, PdfViewerController controller, double estimatedScale); /// Function to customize the layout of the pages. /// From c8361076b5b57b5724b10926c40c5398f674ef05 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 17 Jul 2025 16:56:13 +0900 Subject: [PATCH 199/663] #419: adding progressive loading support on PdfDocumentViewBuilder (disabled by default) --- lib/src/widgets/pdf_widgets.dart | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/pdf_widgets.dart b/lib/src/widgets/pdf_widgets.dart index 3872e0e2..8b400299 100644 --- a/lib/src/widgets/pdf_widgets.dart +++ b/lib/src/widgets/pdf_widgets.dart @@ -46,11 +46,13 @@ class PdfDocumentViewBuilder extends StatefulWidget { super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, bool autoDispose = true, }) : documentRef = PdfDocumentRefAsset( assetName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, autoDispose: autoDispose, ); @@ -60,11 +62,14 @@ class PdfDocumentViewBuilder extends StatefulWidget { super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + bool autoDispose = true, }) : documentRef = PdfDocumentRefFile( filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, autoDispose: autoDispose, ); @@ -74,6 +79,7 @@ class PdfDocumentViewBuilder extends StatefulWidget { super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, bool autoDispose = true, bool preferRangeAccess = false, Map? headers, @@ -82,6 +88,7 @@ class PdfDocumentViewBuilder extends StatefulWidget { uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, autoDispose: autoDispose, preferRangeAccess: preferRangeAccess, headers: headers, @@ -119,14 +126,23 @@ class _PdfDocumentViewBuilderState extends State { void _onDocumentChanged() { if (mounted) { + widget.documentRef.resolveListenable().useDocument((document) { + document.loadPagesProgressively((_, _, _) { + if (mounted) { + setState(() {}); + return true; + } else { + return false; + } + }); + }); setState(() {}); } } @override Widget build(BuildContext context) { - final listenable = widget.documentRef.resolveListenable(); - return widget.builder(context, listenable.document); + return widget.builder(context, widget.documentRef.resolveListenable().document); } } From e755ef6261a752b50fe9231c9393ae1cd37f69a4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 17 Jul 2025 17:18:49 +0900 Subject: [PATCH 200/663] Release v1.3.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 4 ++++ README.md | 2 +- example/viewer/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a77a9faa..5dba6599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.3.4 + +- FIXED: `PdfDocumentViewBuilder` did not properly handle progressive loading ([#419](https://github.com/espresso3389/pdfrx/pull/419)) + # 1.3.3 - NEW FEATURE: Updated `bin/remove_wasm_modules.dart` to comment out assets line in pubspec.yaml instead of deleting files diff --git a/README.md b/README.md index c4764eaf..3bb9690d 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^1.3.2 + pdfrx: ^1.3.4 ``` ### Note for Windows diff --git a/example/viewer/pubspec.lock b/example/viewer/pubspec.lock index 79aa797e..014ecea4 100644 --- a/example/viewer/pubspec.lock +++ b/example/viewer/pubspec.lock @@ -331,7 +331,7 @@ packages: path: "../.." relative: true source: path - version: "1.3.3" + version: "1.3.4" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7aca90c6..bdb8d1da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 1.3.3 +version: 1.3.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From aa5cd6152d8d4ab86258bf1b01741433fabcc46a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 17 Jul 2025 22:39:59 +0900 Subject: [PATCH 201/663] wheel-to-scroll should be conditioned by zoom ratio --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 09e81355..c199621e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1219,8 +1219,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); final m = _txController.value.clone(); - final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel!; - final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel!; + final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; + final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; if (widget.params.scrollHorizontallyByMouseWheel) { m.translate(dy, dx); } else { From cd741f1ce501080ebe2e04830183c829669e2d06 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 17 Jul 2025 23:28:03 +0900 Subject: [PATCH 202/663] pdfrx text extraction API change --- packages/pdfrx/assets/pdfium_worker.js | 36 +++++++++++--- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 15 ++++-- .../lib/src/widgets/pdf_text_searcher.dart | 2 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 36 +++++++++----- .../lib/src/native/pdfrx_pdfium.dart | 34 +++++++++++-- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 49 +++++++++++-------- 6 files changed, 122 insertions(+), 50 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index f86dc195..90ba3d43 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -909,21 +909,42 @@ function _memset(ptr, value, num) { /** * * @param {{pageIndex: number, docHandle: number}} params - * @returns {{fullText: string, charRects: number[][], fragments: number[]}} + * @returns {{fullText: string}} */ -function loadRawText(params) { +function loadText(params) { const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); - if (textPage == null) return { fullText: '', charRects: [], fragments: [] }; + if (textPage == null) return { fullText: '' }; + + const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); + let fullText = ''; + + for (let i = 0; i < count; i++) { + fullText += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); + } + + Pdfium.wasmExports.FPDFText_ClosePage(textPage); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + return { fullText }; +} + +/** + * + * @param {{pageIndex: number, docHandle: number}} params + * @returns {{charRects: number[][]}} + */ +function loadTextCharRects(params) { + const { pageIndex, docHandle } = params; + const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); + const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); + if (textPage == null) return { charRects: [] }; const rectBuffer = Pdfium.wasmExports.malloc(8 * 4); // double[4] const rect = new Float64Array(Pdfium.memory.buffer, rectBuffer, 4); const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); - let fullText = ''; let charRects = []; for (let i = 0; i < count; i++) { - fullText += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); Pdfium.wasmExports.FPDFText_GetCharBox( textPage, i, @@ -938,7 +959,7 @@ function loadRawText(params) { Pdfium.wasmExports.FPDFText_ClosePage(textPage); Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - return { fullText, charRects }; + return { charRects }; } /** @@ -1124,7 +1145,8 @@ const functions = { loadPage, closePage, renderPage, - loadRawText, + loadText, + loadTextCharRects, loadLinks, }; diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 39b2efca..d3038ac3 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -457,9 +457,18 @@ class _PdfPageWasm extends PdfPage { } @override - Future loadRawText() async { + Future loadText() async { final result = await _sendCommand( - 'loadRawText', + 'loadText', + parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, + ); + return result['fullText'] as String; + } + + @override + Future> loadTextCharRects() async { + final result = await _sendCommand( + 'loadTextCharRects', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); final charRectsAll = @@ -467,7 +476,7 @@ class _PdfPageWasm extends PdfPage { final r = rect as List; return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); }).toList(); - return PdfPageRawText(result['fullText'] as String, UnmodifiableListView(charRectsAll)); + return UnmodifiableListView(charRectsAll); } @override diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index 228a0367..efbddcf6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -187,7 +187,7 @@ class PdfTextSearcher extends Listenable { final cached = _cachedText[pageNumber]; if (cached != null) return cached; return await controller?.useDocument((document) async { - return _cachedText[pageNumber] ??= await document.pages[pageNumber - 1].loadText(); + return _cachedText[pageNumber] ??= await document.pages[pageNumber - 1].loadStructuredText(); }); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c199621e..18d51394 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1044,7 +1044,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return await synchronized(() async { if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]!; final page = _document!.pages[pageNumber - 1]; - final text = await page.loadText(); + final text = await page.loadStructuredText(); _textCache[pageNumber] = text; if (onTextLoaded != null) { onTextLoaded(); @@ -2371,22 +2371,32 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Future selectAllText() async { if (_document!.pages.isEmpty && _layout != null) return; final textSelection = SplayTreeMap(); + PdfPageText? first, last; for (int i = 1; i <= _document!.pages.length; i++) { final text = await _loadTextAsync(i); + if (text.fullText.isEmpty) continue; textSelection[i] = PdfPageTextRange(pageText: text, start: 0, end: text.fullText.length); + first ??= text; + last = text; } - _selA = _findTextAndIndexForPoint( - textSelection[1]!.pageText.charRects.first.center.toOffsetInDocument( - page: _document!.pages[0], - pageRect: _layout!.pageLayouts[0], - ), - ); - _selB = _findTextAndIndexForPoint( - textSelection[_document!.pages.length]!.pageText.charRects.last.center.toOffsetInDocument( - page: _document!.pages.last, - pageRect: _layout!.pageLayouts.last, - ), - ); + + if (first != null && last != null) { + _selA = _findTextAndIndexForPoint( + first.charRects.first.center.toOffsetInDocument( + page: _document!.pages[first.pageNumber - 1], + pageRect: _layout!.pageLayouts[first.pageNumber - 1], + ), + ); + _selB = _findTextAndIndexForPoint( + last.charRects.last.center.toOffsetInDocument( + page: _document!.pages[last.pageNumber - 1], + pageRect: _layout!.pageLayouts[last.pageNumber - 1], + ), + ); + } else { + _selA = _selB = null; + } + _textSelA = _textSelB = null; _showTextSelectionContextMenuAt = null; _updateTextSelection(); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 319c3251..4d90e606 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -726,9 +726,35 @@ class _PdfPagePdfium extends PdfPage { PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this); @override - Future loadRawText() async { + Future loadText() async { if (document.isDisposed) { - return null; + throw StateError('Cannot load text from disposed document.'); + } + return await (await backgroundWorker).compute( + (params) => using((arena) { + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); + final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); + final textPage = pdfium.FPDFText_LoadPage(page); + try { + final charCount = pdfium.FPDFText_CountChars(textPage); + final sb = StringBuffer(); + for (int i = 0; i < charCount; i++) { + sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); + } + return sb.toString(); + } finally { + pdfium.FPDFText_ClosePage(textPage); + pdfium.FPDF_ClosePage(page); + } + }), + (docHandle: document.document.address, pageNumber: pageNumber), + ); + } + + @override + Future> loadTextCharRects() async { + if (document.isDisposed) { + throw StateError('Cannot load text from disposed document.'); } return await (await backgroundWorker).compute( (params) => using((arena) { @@ -741,9 +767,7 @@ class _PdfPagePdfium extends PdfPage { try { final charCount = pdfium.FPDFText_CountChars(textPage); final charRects = []; - final sb = StringBuffer(); for (int i = 0; i < charCount; i++) { - sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); pdfium.FPDFText_GetCharBox( textPage, i, @@ -754,7 +778,7 @@ class _PdfPagePdfium extends PdfPage { ); charRects.add(_rectFromLTRBBuffer(rectBuffer)); } - return PdfPageRawText(sb.toString(), charRects); + return charRects; } finally { pdfium.FPDFText_ClosePage(textPage); pdfium.FPDF_ClosePage(page); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 6fff03c7..ff37bff9 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -439,11 +439,11 @@ abstract class PdfPage { /// Create [PdfPageRenderCancellationToken] to cancel the rendering process. PdfPageRenderCancellationToken createCancellationToken(); - static final reSpaces = RegExp(r'(\s+)', unicode: true); - static final reNewLine = RegExp(r'\r?\n', unicode: true); + static final _reSpaces = RegExp(r'(\s+)', unicode: true); + static final _reNewLine = RegExp(r'\r?\n', unicode: true); /// Load text. - Future loadText() async { + Future loadStructuredText() async { final raw = await _loadFormattedText(); if (raw == null) { return PdfPageText(pageNumber: pageNumber, fullText: '', charRects: [], fragments: []); @@ -532,7 +532,7 @@ abstract class PdfPage { int addWords(int start, int end, PdfTextDirection dir, PdfRect bounds) { final firstIndex = fragmentsTmp.length; - final matches = reSpaces.allMatches(inputFullText.substring(start, end)); + final matches = _reSpaces.allMatches(inputFullText.substring(start, end)); int wordStart = start; for (final match in matches) { final spaceStart = start + match.start; @@ -603,7 +603,7 @@ abstract class PdfPage { } int lineStart = 0; - for (final match in reNewLine.allMatches(inputFullText)) { + for (final match in _reNewLine.allMatches(inputFullText)) { if (lineStart < match.start) { handleLine(lineStart, match.start, newLineEnd: match.end); } else { @@ -648,23 +648,21 @@ abstract class PdfPage { } Future _loadFormattedText() async { - final raw = await loadRawText(); - if (raw == null) { - return null; - } + final inputFullText = await loadText(); + final inputCharRects = await loadTextCharRects(); final fullText = StringBuffer(); final charRects = []; // Process the whole text - final lnMatches = reNewLine.allMatches(raw.fullText).toList(); + final lnMatches = _reNewLine.allMatches(inputFullText).toList(); int lineStart = 0; int prevEnd = 0; for (int i = 0; i < lnMatches.length; i++) { lineStart = prevEnd; final match = lnMatches[i]; - fullText.write(raw.fullText.substring(lineStart, match.start)); - charRects.addAll(raw.charRects.sublist(lineStart, match.start)); + fullText.write(inputFullText.substring(lineStart, match.start)); + charRects.addAll(inputCharRects.sublist(lineStart, match.start)); prevEnd = match.end; // Microsoft Word sometimes outputs vertical text like this: "縦\n書\nき\nの\nテ\nキ\nス\nト\nで\nす\n。\n" @@ -674,8 +672,8 @@ abstract class PdfPage { final len = match.start - lineStart; final nextLen = next.start - match.end; if (len == 1 && nextLen == 1) { - final rect = raw.charRects[lineStart]; - final nextRect = raw.charRects[match.end]; + final rect = inputCharRects[lineStart]; + final nextRect = inputCharRects[match.end]; final nextCenterX = nextRect.center.x; if (rect.left < nextCenterX && nextCenterX < rect.right && rect.top > nextRect.top) { // The line is vertical, and the line-feed is virtual @@ -683,18 +681,27 @@ abstract class PdfPage { } } } - fullText.write(raw.fullText.substring(match.start, match.end)); - charRects.addAll(raw.charRects.sublist(match.start, match.end)); + fullText.write(inputFullText.substring(match.start, match.end)); + charRects.addAll(inputCharRects.sublist(match.start, match.end)); } - if (prevEnd < raw.fullText.length) { - fullText.write(raw.fullText.substring(prevEnd)); - charRects.addAll(raw.charRects.sublist(prevEnd)); + if (prevEnd < inputFullText.length) { + fullText.write(inputFullText.substring(prevEnd)); + charRects.addAll(inputCharRects.sublist(prevEnd)); } return PdfPageRawText(fullText.toString(), charRects); } - /// Load raw text and its associated character bounding boxes. - Future loadRawText(); + /// Load plain text for the page. + /// + /// For text with character bounding boxes, use [loadStructuredText]. + Future loadText(); + + /// Load character bounding boxes for the page text. + /// + /// Each [PdfRect] corresponds to a character in the text loaded by [loadText]. + /// + /// For text with character bounding boxes, use [loadStructuredText]. + Future> loadTextCharRects(); /// Load links. /// From 3a5d98ff8d7d6ce072b69128d4c360ea6b553d57 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 02:56:13 +0900 Subject: [PATCH 203/663] WIP --- .../{ => internals}/pdf_error_widget.dart | 4 +- .../internals/pdf_viewer_key_handler.dart | 91 ++++++++ .../internals/widget_size_sniffer.dart | 56 +++++ .../pdfrx/lib/src/widgets/pdf_viewer.dart | 217 ++++-------------- .../lib/src/widgets/pdf_viewer_params.dart | 4 +- 5 files changed, 202 insertions(+), 170 deletions(-) rename packages/pdfrx/lib/src/widgets/{ => internals}/pdf_error_widget.dart (98%) create mode 100644 packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart create mode 100644 packages/pdfrx/lib/src/widgets/internals/widget_size_sniffer.dart diff --git a/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart similarity index 98% rename from packages/pdfrx/lib/src/widgets/pdf_error_widget.dart rename to packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart index 11844ba9..1be2d128 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_error_widget.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart @@ -2,8 +2,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import '../../pdfrx.dart'; -import '../utils/platform.dart'; +import '../../../pdfrx.dart'; +import '../../utils/platform.dart'; /// Show error widget when pdf viewer failed to load pdf. Widget pdfErrorWidget(BuildContext context, Object error, {StackTrace? stackTrace, bool bannerWarning = true}) { diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart new file mode 100644 index 00000000..8dadaaf1 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -0,0 +1,91 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../../pdfrx.dart'; + +/// A widget that handles key events for the PDF viewer. +class PdfViewerKeyHandler extends StatefulWidget { + const PdfViewerKeyHandler({required this.child, required this.onKeyRepeat, required this.params, super.key}); + + /// Called on every key repeat. + /// + /// See [PdfViewerOnKeyCallback] for the parameters. + final bool Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; + final PdfViewerKeyHandlerParams params; + final Widget child; + + @override + State createState() => _PdfViewerKeyHandlerState(); +} + +class _PdfViewerKeyHandlerState extends State { + Timer? _timer; + LogicalKeyboardKey? _currentKey; + + void _startRepeating(FocusNode node, LogicalKeyboardKey key) { + _currentKey = key; + + // Initial delay before starting to repeat + _timer = Timer(widget.params.initialDelay, () { + // Start repeating at the specified interval + _timer = Timer.periodic(widget.params.repeatInterval, (_) { + widget.onKeyRepeat(widget.params, _currentKey!, false); + }); + }); + } + + void _stopRepeating() { + _timer?.cancel(); + _timer = null; + _currentKey = null; + } + + @override + void dispose() { + _stopRepeating(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: widget.params.focusNode, + parentNode: widget.params.parentNode, + autofocus: widget.params.autofocus, + canRequestFocus: widget.params.canRequestFocus, + onKeyEvent: (node, event) { + if (event is KeyDownEvent) { + // Key pressed down + if (_currentKey == null) { + if (widget.onKeyRepeat(widget.params, event.logicalKey, true)) { + _startRepeating(node, event.logicalKey); + return KeyEventResult.handled; + } + } + } else if (event is KeyUpEvent) { + // Key released + if (_currentKey == event.logicalKey) { + _stopRepeating(); + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (context) { + final focusNode = Focus.of(context); + return ListenableBuilder( + listenable: focusNode, + builder: (context, _) { + return widget.child; + }, + ); + }, + ), + ); + } +} diff --git a/packages/pdfrx/lib/src/widgets/internals/widget_size_sniffer.dart b/packages/pdfrx/lib/src/widgets/internals/widget_size_sniffer.dart new file mode 100644 index 00000000..9939c958 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/internals/widget_size_sniffer.dart @@ -0,0 +1,56 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; + +/// A widget that sniffs the size of its child and calls the callback when the size changes. +class WidgetSizeSniffer extends StatefulWidget { + const WidgetSizeSniffer({required this.child, this.onSizeChanged, super.key}); + + final Widget child; + final FutureOr Function(GlobalRect rect)? onSizeChanged; + + @override + State createState() => _WidgetSizeSnifferState(); +} + +class _WidgetSizeSnifferState extends State { + Rect? _rect; + + @override + Widget build(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!mounted) return; + final r = context.findRenderObject(); + if (r is! RenderBox) return; + final rect = Rect.fromPoints(r.localToGlobal(Offset.zero), r.localToGlobal(Offset(r.size.width, r.size.height))); + if (_rect != rect) { + _rect = rect; + await widget.onSizeChanged?.call(GlobalRect(_rect!)); + if (mounted) { + setState(() {}); + } + } + }); + return Offstage(offstage: _rect == null, child: widget.child); + } +} + +/// A class to hold the global rectangle and provide a method to convert it to local coordinates. +class GlobalRect { + const GlobalRect(this.globalRect); + + final Rect globalRect; + + Rect? toLocal(BuildContext context) { + final renderBox = context.findRenderObject(); + if (renderBox is RenderBox) { + return Rect.fromPoints( + renderBox.globalToLocal(globalRect.topLeft), + renderBox.globalToLocal(globalRect.bottomRight), + ); + } + return null; + } +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 18d51394..ea3fad0a 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -16,7 +16,9 @@ import 'package:vector_math/vector_math_64.dart' as vec; import '../../pdfrx.dart'; import '../utils/platform.dart'; import 'interactive_viewer.dart' as iv; -import 'pdf_error_widget.dart'; +import 'internals/pdf_error_widget.dart'; +import 'internals/pdf_viewer_key_handler.dart'; +import 'internals/widget_size_sniffer.dart'; import 'pdf_page_links_overlay.dart'; /// A widget to display PDF document. @@ -409,7 +411,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return Container( color: widget.params.backgroundColor, - child: _PdfViewerKeyHandler( + child: PdfViewerKeyHandler( onKeyRepeat: _onKey, params: widget.params.keyHandlerParams, child: StreamBuilder( @@ -467,11 +469,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ), ), if (_initialized) ..._buildPageOverlayWidgets(context), - if (_initialized && _canvasLinkPainter.isEnabled) - SelectionContainer.disabled(child: _canvasLinkPainter.linkHandlingOverlay(viewSize)), + if (_initialized && _canvasLinkPainter.isEnabled) _canvasLinkPainter.linkHandlingOverlay(viewSize), if (_initialized && widget.params.viewerOverlayBuilder != null) ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap).map( - (e) => SelectionContainer.disabled(child: e), + (e) => e, ), if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), ], @@ -807,23 +808,21 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (rectExternal != null) { if (widget.params.linkHandlerParams == null && widget.params.linkWidgetBuilder != null) { linkWidgets.add( - SelectionContainer.disabled( - child: PdfPageLinksOverlay( - key: Key('#__pageLinks__:${page.pageNumber}'), - page: page, - pageRect: rectExternal, - params: widget.params, - // FIXME: workaround for link widget eats wheel events. - wrapperBuilder: - (child) => Listener( - child: child, - onPointerSignal: (event) { - if (event is PointerScrollEvent) { - _onWheelDelta(event); - } - }, - ), - ), + PdfPageLinksOverlay( + key: Key('#__pageLinks__:${page.pageNumber}'), + page: page, + pageRect: rectExternal, + params: widget.params, + // FIXME: workaround for link widget eats wheel events. + wrapperBuilder: + (child) => Listener( + child: child, + onPointerSignal: (event) { + if (event is PointerScrollEvent) { + _onWheelDelta(event); + } + }, + ), ), ); } @@ -837,7 +836,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix top: rectExternal.top, width: rectExternal.width, height: rectExternal.height, - child: SelectionContainer.disabled(child: Stack(children: overlay)), + child: Stack(children: overlay), ), ); } @@ -968,10 +967,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final pageScale = scale * max(rect.width / page.width, rect.height / page.height); if (!enableLowResolutionPagePreview || pageScale > previewScaleLimit) { - _requestRealSizePartialImage(cache, page, pageScale, targetRect); + _requestRealSizePartialImage(cache, page, pageScale, targetRect, 50); } if ((!enableLowResolutionPagePreview || pageScale > previewScaleLimit) && partial != null) { + canvas.drawRect( + partial.rect.intersect(rect), + Paint() + ..color = Colors.white + ..style = PaintingStyle.fill, + ); + partial.draw(canvas, filterQuality); } @@ -1153,20 +1159,28 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfPage page, double scale, Rect targetRect, + double margin, ) async { - cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); - final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; final rect = pageRect.intersect(targetRect); final prev = cache.pageImagesPartial[page.pageNumber]; - if (prev?.rect == rect && prev?.scale == scale) return; + if (prev?.requestedRect == rect && prev?.scale == scale) return; if (rect.width < 1 || rect.height < 1) return; + cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); + final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { if (!mounted || cancellationToken.isCanceled) return; - final newImage = await _createRealSizePartialImage(cache, page, scale, rect, cancellationToken); + final newImage = await _createRealSizePartialImage( + cache, + page, + scale, + rect.inflate(margin), + rect, + cancellationToken, + ); if (newImage != null) { cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); cache.pageImagesPartial[page.pageNumber] = newImage; @@ -1182,6 +1196,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfPage page, double scale, Rect rect, + Rect requestedRect, PdfPageRenderCancellationToken cancellationToken, ) async { if (!mounted || cancellationToken.isCanceled) return null; @@ -1202,13 +1217,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix height: height, fullWidth: pageRect.width * scale, fullHeight: pageRect.height * scale, - backgroundColor: 0xffffffff, + backgroundColor: 0, annotationRenderingMode: widget.params.annotationRenderingMode, flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, cancellationToken: cancellationToken, ); if (img == null || !mounted || cancellationToken.isCanceled) return null; - return _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y); + return _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y, requestedRect); } catch (e) { return null; // ignore error } finally { @@ -1922,7 +1937,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix magnifier = Positioned( left: offset.dx, top: offset.dy, - child: _WidgetSizeSniffer( + child: WidgetSizeSniffer( key: Key('magnifier'), child: magnifier, onSizeChanged: (rect) { @@ -1988,7 +2003,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix contextMenu = Positioned( left: offset.dx, top: offset.dy, - child: _WidgetSizeSniffer( + child: WidgetSizeSniffer( key: Key('contextMenu'), child: contextMenu, onSizeChanged: (rect) { @@ -2022,7 +2037,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPart.a, details), onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPart.a, details), onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPart.a, details), - child: _WidgetSizeSniffer( + child: WidgetSizeSniffer( key: Key('anchorA'), child: anchorA, onSizeChanged: (rect) { @@ -2048,7 +2063,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix onPanStart: (details) => _onSelectionHandlePanStart(_TextSelectionPart.b, details), onPanUpdate: (details) => _onSelectionHandlePanUpdate(_TextSelectionPart.b, details), onPanEnd: (details) => _onSelectionHandlePanEnd(_TextSelectionPart.b, details), - child: _WidgetSizeSniffer( + child: WidgetSizeSniffer( key: Key('anchorB'), child: anchorB, onSizeChanged: (rect) { @@ -2569,10 +2584,11 @@ class _PdfImageWithScale { } class _PdfImageWithScaleAndRect extends _PdfImageWithScale { - _PdfImageWithScaleAndRect(super.image, super.scale, this.rect, this.left, this.top); + _PdfImageWithScaleAndRect(super.image, super.scale, this.rect, this.left, this.top, this.requestedRect); final Rect rect; final int left; final int top; + final Rect requestedRect; int get bottom => top + height; int get right => left + width; @@ -3338,134 +3354,3 @@ class _CanvasLinkPainter { } } } - -class _PdfViewerKeyHandler extends StatefulWidget { - const _PdfViewerKeyHandler({required this.child, required this.onKeyRepeat, required this.params}); - - /// Called on every key repeat. - /// - /// See [PdfViewerOnKeyCallback] for the parameters. - final bool Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; - final PdfViewerKeyHandlerParams params; - final Widget child; - - @override - _PdfViewerKeyHandlerState createState() => _PdfViewerKeyHandlerState(); -} - -class _PdfViewerKeyHandlerState extends State<_PdfViewerKeyHandler> { - Timer? _timer; - LogicalKeyboardKey? _currentKey; - - void _startRepeating(FocusNode node, LogicalKeyboardKey key) { - _currentKey = key; - - // Initial delay before starting to repeat - _timer = Timer(widget.params.initialDelay, () { - // Start repeating at the specified interval - _timer = Timer.periodic(widget.params.repeatInterval, (_) { - widget.onKeyRepeat(widget.params, _currentKey!, false); - }); - }); - } - - void _stopRepeating() { - _timer?.cancel(); - _timer = null; - _currentKey = null; - } - - @override - void dispose() { - _stopRepeating(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Focus( - focusNode: widget.params.focusNode, - parentNode: widget.params.parentNode, - autofocus: widget.params.autofocus, - canRequestFocus: widget.params.canRequestFocus, - onKeyEvent: (node, event) { - if (event is KeyDownEvent) { - // Key pressed down - if (_currentKey == null) { - if (widget.onKeyRepeat(widget.params, event.logicalKey, true)) { - _startRepeating(node, event.logicalKey); - return KeyEventResult.handled; - } - } - } else if (event is KeyUpEvent) { - // Key released - if (_currentKey == event.logicalKey) { - _stopRepeating(); - return KeyEventResult.handled; - } - } - return KeyEventResult.ignored; - }, - child: Builder( - builder: (context) { - final focusNode = Focus.of(context); - return ListenableBuilder( - listenable: focusNode, - builder: (context, _) { - return widget.child; - }, - ); - }, - ), - ); - } -} - -class _WidgetSizeSniffer extends StatefulWidget { - const _WidgetSizeSniffer({required this.child, this.onSizeChanged, super.key}); - - final Widget child; - final FutureOr Function(_GlobalRect rect)? onSizeChanged; - - @override - State<_WidgetSizeSniffer> createState() => _WidgetSizeSnifferState(); -} - -class _GlobalRect { - const _GlobalRect(this.globalRect); - - final Rect globalRect; - - Rect? toLocal(BuildContext context) { - final renderBox = context.findRenderObject(); - if (renderBox is RenderBox) { - return Rect.fromPoints( - renderBox.globalToLocal(globalRect.topLeft), - renderBox.globalToLocal(globalRect.bottomRight), - ); - } - return null; - } -} - -class _WidgetSizeSnifferState extends State<_WidgetSizeSniffer> { - Rect? _rect; - - @override - Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (!mounted) return; - final r = context.findRenderObject(); - if (r is! RenderBox) return; - final rect = Rect.fromPoints(r.localToGlobal(Offset.zero), r.localToGlobal(Offset(r.size.width, r.size.height))); - if (_rect != rect) { - _rect = rect; - await widget.onSizeChanged?.call(_GlobalRect(_rect!)); - if (mounted) { - setState(() {}); - } - } - }); - return Offstage(offstage: _rect == null, child: widget.child); - } -} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index d0fdce61..b3e5deb8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1276,8 +1276,8 @@ class PdfViewerBehaviorControlParams { const PdfViewerBehaviorControlParams({ this.trailingPageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 100), this.enableLowResolutionPagePreview = true, - this.pageImageCachingDelay = const Duration(milliseconds: kIsWeb ? 100 : 20), - this.partialImageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 0), + this.pageImageCachingDelay = const Duration(milliseconds: kIsWeb ? 20 : 20), + this.partialImageLoadingDelay = const Duration(milliseconds: kIsWeb ? 0 : 0), }); /// How long to wait before loading the trailing pages after the initial page load. From fdb6018101545e951573bdee51f1b85868c4a550 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 04:34:07 +0900 Subject: [PATCH 204/663] WIP: improved text selection --- packages/pdfrx/example/viewer/lib/main.dart | 4 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 95 +++++++++++++------ .../lib/src/widgets/pdf_viewer_params.dart | 4 +- 3 files changed, 70 insertions(+), 33 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index bb3a6212..fbbcca60 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -318,8 +318,8 @@ class _MainPageState extends State with WidgetsBindingObserver { pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, textSelectionParams: PdfTextSelectionParams( - onTextSelectionChange: (textSelection) { - textSelections = textSelection.selectedTextRange; + onTextSelectionChange: (textSelection) async { + textSelections = await textSelection.getSelectedTextRange(); }, ), useAlternativeFitScaleAsMinScale: false, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index ea3fad0a..809473b1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1,6 +1,5 @@ // ignore_for_file: public_member_api_docs import 'dart:async'; -import 'dart:collection'; import 'dart:math'; import 'dart:ui' as ui; @@ -235,7 +234,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final _updateStream = BehaviorSubject(); final _textCache = {}; - final _textSelection = SplayTreeMap(); Timer? _textSelectionChangedDebounceTimer; final double _hitTestMargin = 3.0; @@ -985,7 +983,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Theme.of(context).textSelectionTheme.selectionColor ?? DefaultSelectionStyle.of(context).selectionColor!; final text = _getCachedTextOrDelayLoadText(page.pageNumber); if (text != null) { - final selectionInPage = _textSelection[page.pageNumber]; + final selectionInPage = _loadTextSelectionForPageNumber(page.pageNumber); if (selectionInPage != null) { for (final r in selectionInPage.enumerateFragmentBoundingRects()) { canvas.drawRect( @@ -1035,11 +1033,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfPageText? _getCachedTextOrDelayLoadText(int pageNumber, {void Function()? onTextLoaded, bool invalidate = true}) { if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]; if (onTextLoaded == null && invalidate) { - onTextLoaded = () { - if (mounted) { - setState(() {}); - } - }; + onTextLoaded = _invalidate; } _loadTextAsync(pageNumber, onTextLoaded: onTextLoaded); return null; @@ -1662,7 +1656,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final b = _selB; if (a == null || b == null) { _textSelA = _textSelB = null; - _textSelection.clear(); } else if (a.text.pageNumber == b.text.pageNumber) { final page = _document!.pages[a.text.pageNumber - 1]; final pageRect = _layout!.pageLayouts[a.text.pageNumber - 1]; @@ -1681,8 +1674,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix a.text, b.index, ); - _textSelection.clear(); - _textSelection[page.pageNumber] = range; } else { final first = a.text.pageNumber < b.text.pageNumber ? a : b; final second = a.text.pageNumber < b.text.pageNumber ? b : a; @@ -1708,14 +1699,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix second.text, second.index, ); - _textSelection.clear(); - _textSelection[first.text.pageNumber] = rangeA; - _textSelection[second.text.pageNumber] = rangeB; - for (int i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { - final text = _getCachedTextOrDelayLoadText(i, onTextLoaded: () => _updateTextSelection()); - if (text == null) continue; - _textSelection[i] = PdfPageTextRange(pageText: text, start: 0, end: text.charRects.length); - } } if (invalidate) { @@ -1768,7 +1751,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix List _placeTextSelectionWidgets(BuildContext context, Size viewSize, bool isCopyTextEnabled) { final renderBox = _renderBox; - if (renderBox == null || _textSelA == null || _textSelB == null || _textSelection.isEmpty) { + if (renderBox == null || _textSelA == null || _textSelB == null) { return []; } @@ -2373,11 +2356,61 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override Future clearTextSelection() async => _clearTextSelections(); + PdfPageTextRange? _loadTextSelectionForPageNumber(int pageNumber) { + final a = _selA; + final b = _selB; + if (a == null || b == null) { + return null; + } + final first = a.text.pageNumber < b.text.pageNumber ? a : b; + final second = a.text.pageNumber < b.text.pageNumber ? b : a; + if (first.text.pageNumber == second.text.pageNumber && first.text.pageNumber == pageNumber) { + return a.text.getRangeFromAB(a.index, b.index); + } + if (first.text.pageNumber == pageNumber) { + return PdfPageTextRange(pageText: first.text, start: a.index, end: first.text.charRects.length); + } + if (second.text.pageNumber == pageNumber) { + return PdfPageTextRange(pageText: second.text, start: 0, end: b.index + 1); + } + if (first.text.pageNumber < pageNumber && pageNumber < second.text.pageNumber) { + final text = _getCachedTextOrDelayLoadText(pageNumber, onTextLoaded: () => _invalidate()); + if (text == null) return null; + return PdfPageTextRange(pageText: text, start: 0, end: text.fullText.length); + } + return null; + } + @override - String get selectedText => _textSelection.values.map((p) => p.text).join(); + Future> getSelectedTextRange() async { + final a = _selA; + final b = _selB; + if (a == null || b == null) { + return []; + } + final first = a.text.pageNumber < b.text.pageNumber ? a : b; + final second = a.text.pageNumber < b.text.pageNumber ? b : a; + if (first.text.pageNumber == second.text.pageNumber) { + return [a.text.getRangeFromAB(a.index, b.index)]; + } + final selections = [a.text.getRangeFromAB(a.index, a.text.charRects.length - 1)]; + + for (int i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { + final text = await _loadTextAsync(i); + if (text.fullText.isEmpty) continue; + selections.add(text.getRangeFromAB(0, text.charRects.length - 1)); + } + + selections.add(second.text.getRangeFromAB(0, b.index)); + return selections; + } @override - List get selectedTextRange => _textSelection.values.toList(); + Future getSelectedText() async { + final selections = await getSelectedTextRange(); + if (selections.isEmpty) return ''; + return selections.map((e) => e.text).join(); + } @override bool get isCopyAllowed => _document!.permissions?.allowsCopying != false; @@ -2385,14 +2418,19 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override Future selectAllText() async { if (_document!.pages.isEmpty && _layout != null) return; - final textSelection = SplayTreeMap(); - PdfPageText? first, last; + PdfPageText? first; for (int i = 1; i <= _document!.pages.length; i++) { final text = await _loadTextAsync(i); if (text.fullText.isEmpty) continue; - textSelection[i] = PdfPageTextRange(pageText: text, start: 0, end: text.fullText.length); - first ??= text; + first = text; + break; + } + PdfPageText? last; + for (int i = _document!.pages.length; i >= 1; i--) { + final text = await _loadTextAsync(i); + if (text.fullText.isEmpty) continue; last = text; + break; } if (first != null && last != null) { @@ -2418,7 +2456,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override Future selectWord(Offset offset) async { - _textSelection.clear(); for (int i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(offset)) { @@ -2434,7 +2471,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (f == null) { continue; } - final range = _textSelection[i + 1] = PdfPageTextRange(pageText: text, start: f.index, end: f.end); + final range = PdfPageTextRange(pageText: text, start: f.index, end: f.end); final selectionRect = f.bounds.toRectInDocument(page: page, pageRect: pageRect); _selA = _TextSelectionPoint( text, @@ -2462,7 +2499,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Future _copyTextSelection() async { if (_document!.permissions?.allowsCopying == false) return false; - setClipboardData(selectedText); + setClipboardData(await getSelectedText()); return true; } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index b3e5deb8..fbf62434 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -836,10 +836,10 @@ abstract class PdfTextSelection { /// /// Although the use of this property is not restricted by [isCopyAllowed] /// but you have to ensure that your use of the text does not violate [isCopyAllowed] condition. - String get selectedText; + Future getSelectedText(); /// Get the selected text range. - List get selectedTextRange; + Future> getSelectedTextRange(); } /// Delegate for text selection actions. From 1db1f6566b0e0f32b47f9456ee5278c0a2c8b6a4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 10:36:37 +0900 Subject: [PATCH 205/663] WIP: internal premulitplied-BGRA support --- .vscode/launch.json | 8 +++--- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 16 +++++++++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 26 ++++++++++++++++--- .../lib/src/widgets/pdf_viewer_params.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 16 +++++++++++- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 3 +++ 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f80aebf8..36054036 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,24 +5,24 @@ "version": "0.2.0", "configurations": [ { - "name": "viewer example", + "name": "Example", "request": "launch", "type": "dart", "program": "packages/pdfrx/example/viewer/lib/main.dart" }, { - "name": "viewer example (WASM)", + "name": "WASM", "request": "launch", "type": "dart", "program": "packages/pdfrx/example/viewer/lib/main.dart", "args": ["-d", "chrome", "--wasm"] }, { - "name": "viewer example (WASM/Web Server)", + "name": "WASM-Server", "request": "launch", "type": "dart", "program": "packages/pdfrx/example/viewer/lib/main.dart", - "args": ["-d", "web-server", "--wasm"] + "args": ["-d", "web-server", "--wasm", "--web-port", "8080", "--web-hostname", "127.0.0.1"] } ] } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index d3038ac3..bb91511d 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -526,12 +526,26 @@ class _PdfPageWasm extends PdfPage { 'fullHeight': fullHeight, 'backgroundColor': backgroundColor, 'annotationRenderingMode': annotationRenderingMode.index, - 'flags': flags, + 'flags': flags & 0xffff, // Ensure flags are within 16-bit range 'formHandle': document.document['formHandle'], }, ); final bb = result['imageData'] as ByteBuffer; final pixels = Uint8List.view(bb.asByteData().buffer, 0, bb.lengthInBytes); + + if (flags & PdfPageRenderFlags.premultipliedAlpha != 0) { + final count = width * height; + for (int i = 0; i < count; i++) { + final b = pixels[i * 4]; + final g = pixels[i * 4 + 1]; + final r = pixels[i * 4 + 2]; + final a = pixels[i * 4 + 3]; + pixels[i * 4] = b * a ~/ 255; + pixels[i * 4 + 1] = g * a ~/ 255; + pixels[i * 4 + 2] = r * a ~/ 255; + } + } + return PdfImageWeb(width: width, height: height, pixels: pixels); } } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 809473b1..7ac4f5bd 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1148,6 +1148,11 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix }); } + /// If [margin] is not 0, the image may be rendered with out-of-page margins and they should be transparent; + /// this requires the rendered image should be real BGRA image and the alpha channel should be premultiplied. + /// Because Pdfium binary currently does not support rendering with premultiplied alpha, + /// we need to convert independent alpha to premultiplied alpha in the Flutter side. Because of this, + /// on Flutter Web, if the code is running on JS, it may degrade the performance a bit. WASM compilation seems vital. Future _requestRealSizePartialImage( _PdfPageImageCache cache, PdfPage page, @@ -1163,6 +1168,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); + final renderRect = rect.inflate(margin); + bool needPremultipliedAlpha = + (renderRect.left < 0 || + renderRect.top < 0 || + renderRect.right > pageRect.width || + renderRect.bottom > pageRect.height); + final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { @@ -1171,8 +1183,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix cache, page, scale, - rect.inflate(margin), + renderRect, rect, + needPremultipliedAlpha, cancellationToken, ); if (newImage != null) { @@ -1191,6 +1204,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix double scale, Rect rect, Rect requestedRect, + bool needPremultipliedAlpha, PdfPageRenderCancellationToken cancellationToken, ) async { if (!mounted || cancellationToken.isCanceled) return null; @@ -1202,6 +1216,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final height = (inPageRect.height * scale).toInt(); if (width < 1 || height < 1) return null; + int flags = 0; + if (widget.params.limitRenderingCache) flags |= PdfPageRenderFlags.limitedImageCache; + if (needPremultipliedAlpha) flags |= PdfPageRenderFlags.premultipliedAlpha; + PdfImage? img; try { img = await page.render( @@ -1211,9 +1229,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix height: height, fullWidth: pageRect.width * scale, fullHeight: pageRect.height * scale, - backgroundColor: 0, + backgroundColor: needPremultipliedAlpha ? 0 : 0xffffffff, annotationRenderingMode: widget.params.annotationRenderingMode, - flags: widget.params.limitRenderingCache ? PdfPageRenderFlags.limitedImageCache : PdfPageRenderFlags.none, + flags: flags, cancellationToken: cancellationToken, ); if (img == null || !mounted || cancellationToken.isCanceled) return null; @@ -2190,7 +2208,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfViewerSelectionMagnifierParams.defaultMaxImageBytesCachedOnMemory, targetRect: rectToDraw, scale: magScale * MediaQuery.of(context).devicePixelRatio, - enableLowResolutionPagePreview: false, + enableLowResolutionPagePreview: true, filterQuality: FilterQuality.low, ); canvas.restore(); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index fbf62434..ce0972b4 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1277,7 +1277,7 @@ class PdfViewerBehaviorControlParams { this.trailingPageLoadingDelay = const Duration(milliseconds: kIsWeb ? 200 : 100), this.enableLowResolutionPagePreview = true, this.pageImageCachingDelay = const Duration(milliseconds: kIsWeb ? 20 : 20), - this.partialImageLoadingDelay = const Duration(milliseconds: kIsWeb ? 0 : 0), + this.partialImageLoadingDelay = const Duration(milliseconds: kIsWeb ? 100 : 0), }); /// How long to wait before loading the trailing pages after the initial page load. diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 4d90e606..673c94a9 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -699,7 +699,7 @@ class _PdfPagePdfium extends PdfPage { fullHeight: fullHeight!.toInt(), backgroundColor: backgroundColor, annotationRenderingMode: annotationRenderingMode, - flags: flags, + flags: flags & 0xffff, // Ensure flags are within 16-bit range formHandle: document.formHandle.address, formInfo: document.formInfo.address, cancelFlag: cancelFlag.address, @@ -713,6 +713,20 @@ class _PdfPagePdfium extends PdfPage { final resultBuffer = buffer; buffer = nullptr; + + if (flags & PdfPageRenderFlags.premultipliedAlpha != 0) { + final count = width * height; + for (int i = 0; i < count; i++) { + final b = resultBuffer[i * rgbaSize]; + final g = resultBuffer[i * rgbaSize + 1]; + final r = resultBuffer[i * rgbaSize + 2]; + final a = resultBuffer[i * rgbaSize + 3]; + resultBuffer[i * rgbaSize] = b * a ~/ 255; + resultBuffer[i * rgbaSize + 1] = g * a ~/ 255; + resultBuffer[i * rgbaSize + 2] = r * a ~/ 255; + } + } + return _PdfImagePdfium._(width: width, height: height, buffer: resultBuffer); } catch (e) { return null; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index ff37bff9..fce68893 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -774,6 +774,9 @@ abstract class PdfPageRenderFlags { /// `FPDF_RENDER_NO_SMOOTHPATH` flag. static const noSmoothPath = 0x4000; + + /// Output image is in premultiplied alpha format. + static const premultipliedAlpha = 0x80000000; } /// Token to try to cancel the rendering process. From 3e6c38f7d77e0f8e7b2c1f0b2656d797cfce750a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 11:02:26 +0900 Subject: [PATCH 206/663] Release pdfrx_engine v0.1.6 and pdfrx v2.0.0-beta2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 117466f3..3333fe02 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.0.0-beta2 + +- IMPROVED: Text selection API changed to async with `getSelectedTextRange()` method +- IMPROVED: Internal stability and performance improvements +- Updated to use pdfrx_engine 0.1.6 with premultiplied alpha support + # 2.0.0-beta1 This is a major release that introduces significant architectural changes and new features. diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index fbe290cf..b1e02e52 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.0-beta1 + pdfrx: ^2.0.0-beta2 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 793ce750..b2741e22 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta1 +version: 2.0.0-beta2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: + pdfrx_engine: ^0.1.6 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 66b04ce7..b6fd4549 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.6 + +- Add premultiplied alpha support with new flag `PdfPageRenderFlags.premultipliedAlpha` + ## 0.1.5 - New text extraction API. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 719a2182..1cc557c5 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.5 +version: 0.1.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From ec9845251b929b53b79a41b0ef9ea4b52ed952e8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 20:10:11 +0900 Subject: [PATCH 207/663] WIP WIP --- .gitignore | 2 + .../CODE_OF_CONDUCT.md => CODE_OF_CONDUCT.md | 0 LICENSE | 11 +++ packages/pdfrx/.gitignore | 4 +- packages/pdfrx/example/viewer/pubspec.lock | 4 +- .../example/viewer/pubspec_overrides.yaml | 6 -- .../pdfrx/lib/src/utils/native/native.dart | 35 ++++----- packages/pdfrx/lib/src/utils/web/web.dart | 28 ++++--- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 14 ++-- .../lib/src/widgets/pdf_viewer_params.dart | 16 ++-- packages/pdfrx_engine/CODE_OF_CONDUCT.md | 76 ------------------- 11 files changed, 66 insertions(+), 130 deletions(-) rename packages/pdfrx/CODE_OF_CONDUCT.md => CODE_OF_CONDUCT.md (100%) create mode 100644 LICENSE delete mode 100644 packages/pdfrx/example/viewer/pubspec_overrides.yaml delete mode 100644 packages/pdfrx_engine/CODE_OF_CONDUCT.md diff --git a/.gitignore b/.gitignore index db39b04a..2be28b81 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ migrate_working_dir/ .dart_tool/ .claude/ + +pubspec_overrides.yaml diff --git a/packages/pdfrx/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md similarity index 100% rename from packages/pdfrx/CODE_OF_CONDUCT.md rename to CODE_OF_CONDUCT.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..575416cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ + +The MIT License (MIT) +=============== + +Copyright (c) 2018 @espresso3389 (Takashi Kawasaki) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pdfrx/.gitignore b/packages/pdfrx/.gitignore index 94e202b0..86f70671 100644 --- a/packages/pdfrx/.gitignore +++ b/packages/pdfrx/.gitignore @@ -30,8 +30,8 @@ migrate_working_dir/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -/pubspec_overrides.yaml +pubspec.lock +pubspec_overrides.yaml **/doc/api/ .dart_tool/ diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index edd59240..56b52129 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,14 +347,14 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta1" + version: "2.0.0-beta2" pdfrx_engine: dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.5" + version: "0.1.6" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/example/viewer/pubspec_overrides.yaml b/packages/pdfrx/example/viewer/pubspec_overrides.yaml deleted file mode 100644 index 8ea2f941..00000000 --- a/packages/pdfrx/example/viewer/pubspec_overrides.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# melos_managed_dependency_overrides: pdfrx,pdfrx_engine -dependency_overrides: - pdfrx: - path: ../.. - pdfrx_engine: - path: ../../../pdfrx_engine diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 507ee21c..5c2dad20 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -7,34 +7,31 @@ import '../../../pdfrx.dart'; final isApple = Platform.isMacOS || Platform.isIOS; final isWindows = Platform.isWindows; +/// Whether the current platform is mobile (Android, iOS, or Fuchsia). +final isMobile = Platform.isAndroid || Platform.isIOS || Platform.isFuchsia; + /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => isApple ? HardwareKeyboard.instance.isMetaPressed : HardwareKeyboard.instance.isControlPressed; +/// Sets the clipboard data with the provided text. void setClipboardData(String text) { Clipboard.setData(ClipboardData(text: text)); } -/// Whether the current platform is mobile (Android, iOS, or Fuchsia). -final isMobile = Platform.isAndroid || Platform.isIOS || Platform.isFuchsia; +/// Override for the [PdfDocumentFactory] for native platforms; it is null. +PdfDocumentFactory? get pdfDocumentFactoryOverride => null; -/// Whether text selection should be triggered by swipe gestures or not. -bool get shouldTextSelectionTriggeredBySwipe { - if (isMobile) return false; - return true; -} +abstract class PlatformBehaviorDefaults { + /// Whether text selection should be triggered by swipe gestures or not. + static bool get shouldTextSelectionTriggeredBySwipe => !isMobile; -/// Whether to show text selection handles. -bool get shouldShowTextSelectionHandles { - if (isMobile) return true; - return false; -} + /// Whether to show text selection handles. + static bool get shouldShowTextSelectionHandles => isMobile; -/// Whether to show text selection magnifier. -bool get shouldShowTextSelectionMagnifier { - if (isMobile) return true; - return false; -} + /// Whether to automatically show context menu on text selection. + static bool get showContextMenuAutomatically => isMobile; -/// Override for the [PdfDocumentFactory] for native platforms; it is null. -PdfDocumentFactory? get pdfDocumentFactoryOverride => null; + /// Whether to show text selection magnifier. + static bool get shouldShowTextSelectionMagnifier => isMobile; +} diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 4ca836da..8d6d445f 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -7,26 +7,32 @@ import '../../wasm/pdfrx_wasm.dart'; final isApple = false; final isWindows = false; +/// Whether the current platform is mobile (Android, iOS, or Fuchsia). +final isMobile = false; + /// Key pressing state of ⌘ or Control depending on the platform. bool get isCommandKeyPressed => HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isControlPressed; +/// Sets the clipboard data with the provided text. void setClipboardData(String text) { web.window.navigator.clipboard.writeText(text); } -/// Whether the current platform is mobile (Android, iOS, or Fuchsia). -final isMobile = false; +/// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. +PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; -/// Whether text selection should be triggered by swipe gestures or not. -bool get shouldTextSelectionTriggeredBySwipe => false; +final _factoryWasm = PdfDocumentFactoryWasmImpl(); -/// Whether to show text selection handles. -bool get shouldShowTextSelectionHandles => true; +abstract class PlatformBehaviorDefaults { + /// Whether text selection should be triggered by swipe gestures or not. + static bool get shouldTextSelectionTriggeredBySwipe => false; -/// Whether to show text selection magnifier. -bool get shouldShowTextSelectionMagnifier => true; + /// Whether to show text selection handles. + static bool get shouldShowTextSelectionHandles => true; -/// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. -PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; + /// Whether to automatically show context menu on text selection. + static bool get showContextMenuAutomatically => true; -final _factoryWasm = PdfDocumentFactoryWasmImpl(); + /// Whether to show text selection magnifier. + static bool get shouldShowTextSelectionMagnifier => true; +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 7ac4f5bd..6c981633 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -420,7 +420,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final isCopyTextEnabled = _document!.permissions?.allowsCopying != false; final enableSwipeToSelectText = widget.params.textSelectionParams?.textSelectionTriggeredBySwipe ?? - shouldTextSelectionTriggeredBySwipe; + PlatformBehaviorDefaults.shouldTextSelectionTriggeredBySwipe; final viewSize = Size(constraints.maxWidth, constraints.maxHeight); _updateLayout(viewSize); return Stack( @@ -1780,7 +1780,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } final enableSelectionHandles = - widget.params.textSelectionParams?.enableSelectionHandles ?? shouldShowTextSelectionHandles; + widget.params.textSelectionParams?.enableSelectionHandles ?? + PlatformBehaviorDefaults.shouldShowTextSelectionHandles; double? aLeft, aTop, aRight, aBottom; double? bLeft, bTop, bRight; @@ -1920,7 +1921,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget? magnifier; final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = - (magnifierParams.enabled ?? shouldShowTextSelectionMagnifier) && + (magnifierParams.enabled ?? PlatformBehaviorDefaults.shouldShowTextSelectionMagnifier) && (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? true); if (magnifierEnabled && (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b)) { final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA : _textSelB; @@ -1953,12 +1954,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _magnifierRect = null; } - final showContextMenuOnSelectionHandle = - widget.params.textSelectionParams?.showContextMenuOnSelectionHandle ?? isMobile; + final showContextMenuAutomatically = + widget.params.textSelectionParams?.showContextMenuAutomatically ?? + PlatformBehaviorDefaults.showContextMenuAutomatically; bool showContextMenu = false; if (_showTextSelectionContextMenuAt != null) { showContextMenu = true; - } else if (showContextMenuOnSelectionHandle && + } else if (showContextMenuAutomatically && _textSelA != null && _textSelB != null && _selPartMoving == _TextSelectionPart.none) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index ce0972b4..579dc64f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -643,7 +643,7 @@ class PdfTextSelectionParams { const PdfTextSelectionParams({ this.textSelectionTriggeredBySwipe, this.enableSelectionHandles, - this.showContextMenuOnSelectionHandle, + this.showContextMenuAutomatically, this.buildContextMenu, this.buildSelectionHandle, this.onTextSelectionChange, @@ -661,15 +661,15 @@ class PdfTextSelectionParams { /// Whether to show selection handles. /// - /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop/. + /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop. final bool? enableSelectionHandles; - /// Whether to show context menu on selection handle. + /// Whether to automatically show context menu on text selection. /// - /// Normally, on desktop and web, the context menu is shown on right-click. + /// Normally, on desktop, the context menu is shown on right-click. /// If this is true, the context menu is shown on selection handle. - /// null to determine the behavior based on the platform; enabled on Mobile, disabled on Desktop/Web. - final bool? showContextMenuOnSelectionHandle; + /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop. + final bool? showContextMenuAutomatically; /// Function to build context menu for text selection. /// @@ -724,7 +724,7 @@ class PdfTextSelectionParams { other.textLongPress == textLongPress && other.textSecondaryTapUp == textSecondaryTapUp && other.enableSelectionHandles == enableSelectionHandles && - other.showContextMenuOnSelectionHandle == showContextMenuOnSelectionHandle && + other.showContextMenuAutomatically == showContextMenuAutomatically && other.magnifier == magnifier; } @@ -739,7 +739,7 @@ class PdfTextSelectionParams { textLongPress.hashCode ^ textSecondaryTapUp.hashCode ^ enableSelectionHandles.hashCode ^ - showContextMenuOnSelectionHandle.hashCode ^ + showContextMenuAutomatically.hashCode ^ magnifier.hashCode; } diff --git a/packages/pdfrx_engine/CODE_OF_CONDUCT.md b/packages/pdfrx_engine/CODE_OF_CONDUCT.md deleted file mode 100644 index 7a7ac839..00000000 --- a/packages/pdfrx_engine/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at espresso3389@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see - From 36662d19783a0ee41ba3566c1ae101514e967eef Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 18 Jul 2025 22:01:04 +0900 Subject: [PATCH 208/663] Release pdfrx v2.0.0-beta3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed showContextMenuOnSelectionHandle to showContextMenuAutomatically for better clarity - Refactored platform-specific defaults into PlatformBehaviorDefaults class - Improved monorepo structure with shared analysis_options.yaml 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 3333fe02..4e8115fd 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.0-beta3 + +- BREAKING CHANGE: Renamed `showContextMenuOnSelectionHandle` to `showContextMenuAutomatically` in `PdfTextSelectionParams` for better clarity + # 2.0.0-beta2 - IMPROVED: Text selection API changed to async with `getSelectedTextRange()` method diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b1e02e52..126712b7 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.0-beta2 + pdfrx: ^2.0.0-beta3 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 56b52129..9b44112d 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta2" + version: "2.0.0-beta3" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b2741e22..539389ca 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta2 +version: 2.0.0-beta3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7bd9678bd7a68c517300829921a56294f4a52a71 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 00:58:28 +0900 Subject: [PATCH 209/663] withOpacity -> withValue(alpha) --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 6 +++--- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6c981633..d748c6aa 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2086,9 +2086,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final baseColor = Theme.of(context).textSelectionTheme.selectionColor ?? DefaultSelectionStyle.of(context).selectionColor!; final (selectionColor, shadow) = switch (state) { - PdfViewerTextSelectionAnchorHandleState.normal => (baseColor.withOpacity(.7), true), - PdfViewerTextSelectionAnchorHandleState.dragging => (baseColor.withOpacity(1), false), - PdfViewerTextSelectionAnchorHandleState.hover => (baseColor.withOpacity(1), true), + PdfViewerTextSelectionAnchorHandleState.normal => (baseColor.withValues(alpha: .7), true), + PdfViewerTextSelectionAnchorHandleState.dragging => (baseColor.withValues(alpha: 1), false), + PdfViewerTextSelectionAnchorHandleState.hover => (baseColor.withValues(alpha: 1), true), }; return CustomPaint( painter: _CustomPainter.fromFunctions((canvas, size) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 579dc64f..cb340774 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -193,12 +193,12 @@ class PdfViewerParams { /// Color for text search match. /// - /// If null, the default color is `Colors.yellow.withOpacity(0.5)`. + /// If null, the default color is `Colors.yellow.withValue(alpha: 0.5)`. final Color? matchTextColor; /// Color for active text search match. /// - /// If null, the default color is `Colors.orange.withOpacity(0.5)`. + /// If null, the default color is `Colors.orange.withValue(alpha: 0.5)`. final Color? activeMatchTextColor; /// Drop shadow for the page. @@ -1160,7 +1160,7 @@ class PdfLinkHandlerParams { /// The functions should return true if it processes the link; otherwise, it should return false. final void Function(PdfLink link) onLinkTap; - /// Color for the link. If null, the default color is `Colors.blue.withOpacity(0.2)`. + /// Color for the link. If null, the default color is `Colors.blue.withValue(alpha: 0.2)`. /// /// To fully customize the link appearance, use [customPainter]. final Color? linkColor; @@ -1173,7 +1173,7 @@ class PdfLinkHandlerParams { /// ```dart /// customPainter: (canvas, pageRect, page, links) { /// final paint = Paint() - /// ..color = Colors.red.withOpacity(0.2) + /// ..color = Colors.red.withValue(alpha: 0.2) /// ..style = PaintingStyle.fill; /// for (final link in links) { /// final rect = link.rect.toRectInDocument(page: page, pageRect: pageRect); From 5dc4e6e4ee584cf695f9b4d8e6759c6702841bf9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 02:41:06 +0900 Subject: [PATCH 210/663] WIP: condition context menu position on select all --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 80 +++++++++++-------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index d748c6aa..21659b14 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -251,7 +251,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _TextSelectionPart _selPartLastMoved = _TextSelectionPart.none; - Offset? _showTextSelectionContextMenuAt; + bool _isSelectingAllText = false; + + Offset? _textSelectionContextMenuGlobalPosition; Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; @@ -1634,17 +1636,18 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } void _textSecondaryTapUp(TapUpDetails details) { - _showTextSelectionContextMenuAt = details.globalPosition; + _textSelectionContextMenuGlobalPosition = details.globalPosition; _invalidate(); } void _onTextPanStart(DragStartDetails details) { if (_isInteractionGoingOn) return; _selPartMoving = _TextSelectionPart.free; + _isSelectingAllText = false; + _textSelectionContextMenuGlobalPosition = null; _selA = _findTextAndIndexForPoint(details.localPosition); _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); _selB = null; - _showTextSelectionContextMenuAt = null; _updateTextSelection(); } @@ -1655,6 +1658,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onTextPanEnd(DragEndDetails details) { _updateTextSelectRectTo(details.localPosition); _selPartMoving = _TextSelectionPart.none; + _isSelectingAllText = false; _invalidate(); } @@ -1857,6 +1861,25 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix const defMargin = 8.0; + Offset normalizeWidgetPosition(Offset pos, Size? widgetSize, {double margin = defMargin}) { + if (widgetSize == null) return pos; + var left = pos.dx; + var top = pos.dy; + if (left + widgetSize.width + margin > viewSize.width) { + left = viewSize.width - widgetSize.width - margin; + } + if (left < margin) { + left = margin; + } + if (top + widgetSize.height + margin > viewSize.height) { + top = viewSize.height - widgetSize.height - margin; + } + if (top < margin) { + top = margin; + } + return Offset(left, top); + } + Offset? calcPosition( Size? widgetSize, _TextSelectionPart part, { @@ -1915,7 +1938,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix top = viewSize.height - widgetSize.height - margin; } } - return Offset(left, top); + return normalizeWidgetPosition(Offset(left, top), widgetSize, margin: margin); } Widget? magnifier; @@ -1958,7 +1981,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix widget.params.textSelectionParams?.showContextMenuAutomatically ?? PlatformBehaviorDefaults.showContextMenuAutomatically; bool showContextMenu = false; - if (_showTextSelectionContextMenuAt != null) { + if (_textSelectionContextMenuGlobalPosition != null) { showContextMenu = true; } else if (showContextMenuAutomatically && _textSelA != null && @@ -1968,38 +1991,24 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix showContextMenu = true; } - Offset normalizedContextMenuPosition(Offset pos, {double margin = defMargin}) { - if (_contextMenuRect == null) return pos; - var left = pos.dx; - var top = pos.dy; - if (left + _contextMenuRect!.width + margin > viewSize.width) { - left = viewSize.width - _contextMenuRect!.width - margin; - } - if (left < margin) { - left = margin; - } - if (top + _contextMenuRect!.height + margin > viewSize.height) { - top = viewSize.height - _contextMenuRect!.height - margin; - } - if (top < margin) { - top = margin; - } - return Offset(left, top); - } - Widget? contextMenu; if (showContextMenu && _selPartMoving == _TextSelectionPart.none && - (_selPartLastMoved == _TextSelectionPart.a || _selPartLastMoved == _TextSelectionPart.b) && + (_textSelectionContextMenuGlobalPosition != null || + _selPartLastMoved == _TextSelectionPart.a || + _selPartLastMoved == _TextSelectionPart.b) && isCopyTextEnabled) { final offset = - _showTextSelectionContextMenuAt != null - ? normalizedContextMenuPosition(renderBox.globalToLocal(_showTextSelectionContextMenuAt!)) + _textSelectionContextMenuGlobalPosition != null + ? normalizeWidgetPosition( + renderBox.globalToLocal(_textSelectionContextMenuGlobalPosition!), + _contextMenuRect?.size, + ) : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); Offset.zero; final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; contextMenu = ctxMenuBuilder(context, _textSelA!, _textSelB!, this, () { - _showTextSelectionContextMenuAt = null; + _textSelectionContextMenuGlobalPosition = null; _invalidate(); }); if (contextMenu != null && !isPositionalWidget(contextMenu)) { @@ -2290,6 +2299,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onSelectionHandlePanStart(_TextSelectionPart handle, DragStartDetails details) { if (_isInteractionGoingOn) return; _selPartMoving = handle; + _isSelectingAllText = false; final position = _globalToDocument(details.globalPosition); final anchor = Offset(_txController.value.x, _txController.value.y); if (_selPartMoving == _TextSelectionPart.a) { @@ -2305,7 +2315,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } else { return; } - _showTextSelectionContextMenuAt = null; _updateTextSelection(); } @@ -2338,7 +2347,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onSelectionHandlePanUpdate(_TextSelectionPart handle, DragUpdateDetails details) { if (_isInteractionGoingOn) return; - _showTextSelectionContextMenuAt = null; + _textSelectionContextMenuGlobalPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); } @@ -2346,6 +2355,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (_isInteractionGoingOn) return; final result = _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); _selPartMoving = _TextSelectionPart.none; + _isSelectingAllText = false; if (!result) { _updateTextSelection(); } @@ -2369,7 +2379,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _clearTextSelections({bool invalidate = true}) { _selA = _selB = null; _textSelA = _textSelB = null; - _showTextSelectionContextMenuAt = null; + _textSelectionContextMenuGlobalPosition = null; + _isSelectingAllText = false; _updateTextSelection(invalidate: invalidate); } @@ -2470,7 +2481,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _selA = _selB = null; } _textSelA = _textSelB = null; - _showTextSelectionContextMenuAt = null; + _textSelectionContextMenuGlobalPosition = _documentToGlobal(_centerPosition); + _selPartLastMoved = _TextSelectionPart.none; + _isSelectingAllText = true; _updateTextSelection(); } @@ -2514,6 +2527,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); break; } + + _selPartLastMoved = _TextSelectionPart.a; + _isSelectingAllText = false; _notifyTextSelectionChange(); } From 69b64e317ea89a18f64422833c9da79dee702fdd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 02:50:40 +0900 Subject: [PATCH 211/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 3 +++ .../lib/src/widgets/pdf_viewer_params.dart | 3 +++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 17 +++++++++++------ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 21659b14..f82b8448 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2446,6 +2446,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override bool get isCopyAllowed => _document!.permissions?.allowsCopying != false; + @override + bool get isSelectingAllText => _isSelectingAllText; + @override Future selectAllText() async { if (_document!.pages.isEmpty && _layout != null) return; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index cb340774..3edaeaae 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -832,6 +832,9 @@ abstract class PdfTextSelection { /// Whether the copy action is allowed. bool get isCopyAllowed; + /// Whether the viewer is currently selecting all text. + bool get isSelectingAllText; + /// Get the selected text. /// /// Although the use of this property is not restricted by [isCopyAllowed] diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index fce68893..89174dcb 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1043,16 +1043,20 @@ class PdfPageTextRange { /// The composed text of the text range. String get text => pageText.fullText.substring(start, end); + /// The bounding rectangle of the text range in PDF page coordinates. PdfRect get bounds => pageText.charRects.boundingRect(start: start, end: end); - int get firstFragmentIndex { - return pageText.getFragmentIndexForTextIndex(start); - } + /// Get the first text fragment index corresponding to the text range. + /// + /// It can be used with [PdfPageText.fragments] to get the first text fragment in the range. + int get firstFragmentIndex => pageText.getFragmentIndexForTextIndex(start); - int get lastFragmentIndex { - return pageText.getFragmentIndexForTextIndex(end - 1); - } + /// Get the last text fragment index corresponding to the text range. + /// + /// It can be used with [PdfPageText.fragments] to get the last text fragment in the range. + int get lastFragmentIndex => pageText.getFragmentIndexForTextIndex(end - 1); + /// Get the first text fragment in the range. PdfPageTextFragment? get firstFragment { final index = firstFragmentIndex; if (index < 0 || index >= pageText.fragments.length) { @@ -1061,6 +1065,7 @@ class PdfPageTextRange { return pageText.fragments[index]; } + /// Get the last text fragment in the range. PdfPageTextFragment? get lastFragment { final index = lastFragmentIndex; if (index < 0 || index >= pageText.fragments.length) { From 1421582c1d597fdf4b3dbcaee9ce94c8638f386b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 03:40:53 +0900 Subject: [PATCH 212/663] WIP --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 2 +- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 15 ++++++++++----- .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index bb91511d..7ea1b5c5 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -533,7 +533,7 @@ class _PdfPageWasm extends PdfPage { final bb = result['imageData'] as ByteBuffer; final pixels = Uint8List.view(bb.asByteData().buffer, 0, bb.lengthInBytes); - if (flags & PdfPageRenderFlags.premultipliedAlpha != 0) { + if ((flags & PdfPageRenderFlags.premultipliedAlpha) != 0) { final count = width * height; for (int i = 0; i < count; i++) { final b = pixels[i * 4]; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f82b8448..23ac1eb2 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -967,7 +967,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final pageScale = scale * max(rect.width / page.width, rect.height / page.height); if (!enableLowResolutionPagePreview || pageScale > previewScaleLimit) { - _requestRealSizePartialImage(cache, page, pageScale, targetRect, 50); + _requestRealSizePartialImage(cache, page, pageScale, targetRect, 0); } if ((!enableLowResolutionPagePreview || pageScale > previewScaleLimit) && partial != null) { @@ -1180,6 +1180,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { + debugPrint('Requesting real size partial image for page ${page.pageNumber} at rect $rect'); if (!mounted || cancellationToken.isCanceled) return; final newImage = await _createRealSizePartialImage( cache, @@ -2282,13 +2283,17 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix style: TextStyle(color: textSelectionDelegate.isCopyAllowed ? Colors.black : Colors.grey), ), ), - if (textSelectionDelegate.isCopyAllowed) + + if (!textSelectionDelegate.isSelectingAllText) RawMaterialButton( visualDensity: VisualDensity.compact, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () { - textSelectionDelegate.selectAllText(); - }, + onPressed: + textSelectionDelegate.isSelectingAllText + ? null + : () { + textSelectionDelegate.selectAllText(); + }, child: const Text('Select All'), ), ], diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 673c94a9..5e5af234 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -714,7 +714,7 @@ class _PdfPagePdfium extends PdfPage { final resultBuffer = buffer; buffer = nullptr; - if (flags & PdfPageRenderFlags.premultipliedAlpha != 0) { + if ((flags & PdfPageRenderFlags.premultipliedAlpha) != 0) { final count = width * height; for (int i = 0; i < count; i++) { final b = resultBuffer[i * rgbaSize]; From 4f4661ed739f78b4d1a4cab24b77363311afb451 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 03:48:45 +0900 Subject: [PATCH 213/663] Release pdfrx v2.0.0-beta4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 4e8115fd..9c9901ea 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.0.0-beta4 + +- IMPROVED: Enhanced text selection behavior with better Select All functionality +- FIXED: Corrected operator precedence issue with premultiplied alpha flag check in WASM implementation +- IMPROVED: Context menu position handling when selecting all text +- Updated deprecated `withOpacity` to `withValue(alpha:)` for better forward compatibility + # 2.0.0-beta3 - BREAKING CHANGE: Renamed `showContextMenuOnSelectionHandle` to `showContextMenuAutomatically` in `PdfTextSelectionParams` for better clarity diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 9b44112d..65440cbc 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta3" + version: "2.0.0-beta4" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 539389ca..161e0f86 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta3 +version: 2.0.0-beta4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From afbf485b392124d9a79f413fdd0e4d85b1cbc29a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 04:22:41 +0900 Subject: [PATCH 214/663] WIP --- packages/pdfrx/assets/pdfium_worker.js | 20 +++++++-- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 2 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 45 +++---------------- 3 files changed, 23 insertions(+), 44 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 90ba3d43..b858400c 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -864,7 +864,9 @@ function renderPage(params) { const FPDF_ANNOT = 1; const PdfAnnotationRenderingMode_none = 0; const PdfAnnotationRenderingMode_annotationAndForms = 2; + const premultipliedAlpha = 0x80000000; + const pdfiumFlags = (flags & 0xffff) | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0); Pdfium.wasmExports.FPDF_RenderPageBitmap( bitmap, pageHandle, @@ -873,16 +875,26 @@ function renderPage(params) { fullWidth, fullHeight, 0, - flags | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0) + pdfiumFlags ); if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, flags); } - + const src = new Uint8Array(Pdfium.memory.buffer, bufferPtr, bufferSize); let copiedBuffer = new ArrayBuffer(bufferSize); - let b = new Uint8Array(copiedBuffer); - b.set(new Uint8Array(Pdfium.memory.buffer, bufferPtr, bufferSize)); + let dest = new Uint8Array(copiedBuffer); + if (flags & premultipliedAlpha) { + for (let i = 0; i < src.length; i += 4) { + const a = src[i + 3]; + dest[i] = (src[i] * a + 128) >> 8; + dest[i + 1] = (src[i + 1] * a + 128) >> 8; + dest[i + 2] = (src[i + 2] * a + 128) >> 8; + dest[i + 3] = a; + } + } else { + dest.set(src); + } return { result: { diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 7ea1b5c5..0d35a5b2 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -526,7 +526,7 @@ class _PdfPageWasm extends PdfPage { 'fullHeight': fullHeight, 'backgroundColor': backgroundColor, 'annotationRenderingMode': annotationRenderingMode.index, - 'flags': flags & 0xffff, // Ensure flags are within 16-bit range + 'flags': flags, 'formHandle': document.document['formHandle'], }, ); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 23ac1eb2..5bc851a1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -967,17 +967,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix final pageScale = scale * max(rect.width / page.width, rect.height / page.height); if (!enableLowResolutionPagePreview || pageScale > previewScaleLimit) { - _requestRealSizePartialImage(cache, page, pageScale, targetRect, 0); + _requestRealSizePartialImage(cache, page, pageScale, targetRect); } if ((!enableLowResolutionPagePreview || pageScale > previewScaleLimit) && partial != null) { - canvas.drawRect( - partial.rect.intersect(rect), - Paint() - ..color = Colors.white - ..style = PaintingStyle.fill, - ); - partial.draw(canvas, filterQuality); } @@ -1150,47 +1143,25 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix }); } - /// If [margin] is not 0, the image may be rendered with out-of-page margins and they should be transparent; - /// this requires the rendered image should be real BGRA image and the alpha channel should be premultiplied. - /// Because Pdfium binary currently does not support rendering with premultiplied alpha, - /// we need to convert independent alpha to premultiplied alpha in the Flutter side. Because of this, - /// on Flutter Web, if the code is running on JS, it may degrade the performance a bit. WASM compilation seems vital. Future _requestRealSizePartialImage( _PdfPageImageCache cache, PdfPage page, double scale, Rect targetRect, - double margin, ) async { final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; final rect = pageRect.intersect(targetRect); final prev = cache.pageImagesPartial[page.pageNumber]; - if (prev?.requestedRect == rect && prev?.scale == scale) return; + if (prev?.rect == rect && prev?.scale == scale) return; if (rect.width < 1 || rect.height < 1) return; cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); - final renderRect = rect.inflate(margin); - bool needPremultipliedAlpha = - (renderRect.left < 0 || - renderRect.top < 0 || - renderRect.right > pageRect.width || - renderRect.bottom > pageRect.height); - final cancellationToken = page.createCancellationToken(); cache.pageImagePartialRenderingRequests[page.pageNumber] = _PdfPartialImageRenderingRequest( Timer(widget.params.behaviorControlParams.partialImageLoadingDelay, () async { - debugPrint('Requesting real size partial image for page ${page.pageNumber} at rect $rect'); if (!mounted || cancellationToken.isCanceled) return; - final newImage = await _createRealSizePartialImage( - cache, - page, - scale, - renderRect, - rect, - needPremultipliedAlpha, - cancellationToken, - ); + final newImage = await _createRealSizePartialImage(cache, page, scale, rect, cancellationToken); if (newImage != null) { cache.pageImagesPartial.remove(page.pageNumber)?.dispose(); cache.pageImagesPartial[page.pageNumber] = newImage; @@ -1206,8 +1177,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix PdfPage page, double scale, Rect rect, - Rect requestedRect, - bool needPremultipliedAlpha, PdfPageRenderCancellationToken cancellationToken, ) async { if (!mounted || cancellationToken.isCanceled) return null; @@ -1221,7 +1190,6 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix int flags = 0; if (widget.params.limitRenderingCache) flags |= PdfPageRenderFlags.limitedImageCache; - if (needPremultipliedAlpha) flags |= PdfPageRenderFlags.premultipliedAlpha; PdfImage? img; try { @@ -1232,13 +1200,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix height: height, fullWidth: pageRect.width * scale, fullHeight: pageRect.height * scale, - backgroundColor: needPremultipliedAlpha ? 0 : 0xffffffff, + backgroundColor: 0xffffffff, annotationRenderingMode: widget.params.annotationRenderingMode, flags: flags, cancellationToken: cancellationToken, ); if (img == null || !mounted || cancellationToken.isCanceled) return null; - return _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y, requestedRect); + return _PdfImageWithScaleAndRect(await img.createImage(), scale, rect, x, y); } catch (e) { return null; // ignore error } finally { @@ -2665,11 +2633,10 @@ class _PdfImageWithScale { } class _PdfImageWithScaleAndRect extends _PdfImageWithScale { - _PdfImageWithScaleAndRect(super.image, super.scale, this.rect, this.left, this.top, this.requestedRect); + _PdfImageWithScaleAndRect(super.image, super.scale, this.rect, this.left, this.top); final Rect rect; final int left; final int top; - final Rect requestedRect; int get bottom => top + height; int get right => left + width; From c387eab7a12535539d1ba9aba57b6bdea11b42a0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 04:26:45 +0900 Subject: [PATCH 215/663] Release pdfrx v2.0.0-beta5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 9c9901ea..ff7fe6e4 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.0-beta5 + +- Minor fixes and improvements + # 2.0.0-beta4 - IMPROVED: Enhanced text selection behavior with better Select All functionality diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 65440cbc..a9078c09 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta4" + version: "2.0.0-beta5" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 161e0f86..931140dd 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta4 +version: 2.0.0-beta5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 20c6eaec8d3d91711a46d89075b038436df8c8b8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 19 Jul 2025 18:39:17 +0900 Subject: [PATCH 216/663] WIP --- packages/pdfrx/example/viewer/lib/main.dart | 4 + packages/pdfrx/lib/src/pdfrx_flutter.dart | 30 +++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 8 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 113 ++++++++++++------ .../lib/src/widgets/pdf_viewer_params.dart | 64 ++++++---- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 33 +++-- .../lib/src/native/pdfrx_pdfium.dart | 4 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 42 +++++-- 8 files changed, 208 insertions(+), 90 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index fbbcca60..ba58c252 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -322,6 +322,9 @@ class _MainPageState extends State with WidgetsBindingObserver { textSelections = await textSelection.getSelectedTextRange(); }, ), + keyHandlerParams: PdfViewerKeyHandlerParams( + autofocus: true, + ), useAlternativeFitScaleAsMinScale: false, maxScale: 8, onViewSizeChanged: (viewSize, oldViewSize, controller) { @@ -438,6 +441,7 @@ class _MainPageState extends State with WidgetsBindingObserver { onViewerReady: (document, controller) async { outline.value = await document.loadOutline(); textSearcher.value = PdfTextSearcher(controller)..addListener(_update); + controller.requestFocus(); }, ), ); diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index a7f73088..e00c93e7 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; @@ -32,6 +33,35 @@ void pdfrxFlutterInitialize() { return dir.path; }; + // Checking pdfium.wasm availability for Web and debug builds. + if (kDebugMode) { + () async { + try { + await Pdfrx.loadAsset!('packages/pdfrx/assets/pdfium.wasm'); + if (!kIsWeb) { + debugPrint( + '*********************\n' + '⚠️\u001b[37;41;1mDEBUG TIME WARNING: The app is bundling PDFium WASM module (about 4MB) as a part of the app.\u001b[0m\n' + 'For production use (not for Web/Debug), you\'d better remove the PDFium WASM module.\n' + 'See https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\n' + '*********************\n', + ); + } + } catch (e) { + if (kIsWeb) { + debugPrint( + '*********************\n' + '⚠️\u001b[37;41;1mDEBUG TIME WARNING: The app is running on Web, but the PDFium WASM module is not bundled with the app.\u001b[0m\n' + 'Make sure to include the PDFium WASM module in your web project.\n' + 'If you explicitly set Pdfrx.pdfiumWasmModulesUrl, you can ignore this warning.\n' + 'See https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\n' + '*********************\n', + ); + } + } + }(); + } + _isInitialized = true; } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 0d35a5b2..83c7b80d 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -334,8 +334,8 @@ class _PdfDocumentWasm extends PdfDocument { } @override - Future loadPagesProgressively( - PdfPageLoadingCallback? onPageLoadProgress, { + Future loadPagesProgressively({ + PdfPageLoadingCallback? onPageLoadProgress, T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { @@ -358,7 +358,9 @@ class _PdfDocumentWasm extends PdfDocument { pages[page.pageNumber - 1] = page; // Update the existing page } - subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); + if (!subject.isClosed) { + subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); + } if (onPageLoadProgress != null) { if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 5bc851a1..67bb6b74 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -258,6 +258,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; + BuildContext? _contextForFocusNode; + @override void initState() { super.initState(); @@ -286,11 +288,10 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } return; } else { - final oldListenable = oldWidget?.documentRef.resolveListenable(); - oldListenable?.removeListener(_onDocumentChanged); - final listenable = widget.documentRef.resolveListenable(); - listenable.addListener(_onDocumentChanged); - listenable.load(); + oldWidget?.documentRef.resolveListenable().removeListener(_onDocumentChanged); + widget.documentRef.resolveListenable() + ..addListener(_onDocumentChanged) + ..load(); } _onDocumentChanged(); @@ -343,13 +344,16 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix await Future.delayed(widget.params.behaviorControlParams.trailingPageLoadingDelay); final stopwatch = Stopwatch()..start(); - await _document?.loadPagesProgressively((pageNumber, totalPageCount, document) { - if (document == _document && mounted) { - debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); - return true; - } - return false; - }, data: _document); + await _document?.loadPagesProgressively( + onPageLoadProgress: (pageNumber, totalPageCount, document) { + if (document == _document && mounted) { + debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); + return true; + } + return false; + }, + data: _document, + ); } void _notifyOnDocumentChanged() { @@ -417,6 +421,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix child: StreamBuilder( stream: _updateStream, builder: (context, snapshot) { + _contextForFocusNode = context; return LayoutBuilder( builder: (context, constraints) { final isCopyTextEnabled = _document!.permissions?.allowsCopying != false; @@ -559,6 +564,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onInteractionStart(ScaleStartDetails details) { _startInteraction(); + _requestFocus(); widget.params.onInteractionStart?.call(details); } @@ -1592,16 +1598,28 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix document.scale(_currentZoom, _currentZoom).translate(_txController.value.xZoomed, _txController.value.yZoomed), ); + FocusNode? _getFocusNode() { + return _contextForFocusNode != null ? Focus.of(_contextForFocusNode!) : null; + } + + void _requestFocus() { + _getFocusNode()?.requestFocus(); + } + void _textTap(TapDownDetails details) { if (_isInteractionGoingOn) return; _clearTextSelections(); + _requestFocus(); } - void _textDoubleTap(TapDownDetails details) {} + void _textDoubleTap(TapDownDetails details) { + _requestFocus(); + } void _textLongPress(LongPressStartDetails details) { if (_isInteractionGoingOn) return; selectWord(details.localPosition); + _requestFocus(); } void _textSecondaryTapUp(TapUpDetails details) { @@ -1618,6 +1636,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); _selB = null; _updateTextSelection(); + _requestFocus(); } void _onTextPanUpdate(DragUpdateDetails details) { @@ -1911,20 +1930,22 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } Widget? magnifier; - final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); - final magnifierEnabled = - (magnifierParams.enabled ?? PlatformBehaviorDefaults.shouldShowTextSelectionMagnifier) && - (magnifierParams.shouldBeShown?.call(_controller!, magnifierParams) ?? true); - if (magnifierEnabled && (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b)) { - final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA : _textSelB; - final magCenter = textAnchor?.anchorPoint; - final charSize = textAnchor!.rect.size * _currentZoom; - final h = textAnchor.direction == PdfTextDirection.vrtl ? charSize.width : charSize.height; - if (magCenter != null && h < magnifierParams.magnifierSizeThreshold) { + if (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA! : _textSelB!; + final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); + + final magnifierEnabled = + (magnifierParams.enabled ?? PlatformBehaviorDefaults.shouldShowTextSelectionMagnifier) && + (magnifierParams.shouldBeShownForAnchor ?? _shouldBeShownForAnchor)( + textAnchor, + _controller!, + magnifierParams, + ); + if (magnifierEnabled) { final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)(textAnchor, magnifierParams); final magnifierMain = _buildMagnifier(context, magRect, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = builder(context, magnifierParams, magnifierMain, magRect.size); + magnifier = builder(context, textAnchor, magnifierParams, magnifierMain, magRect.size); if (magnifier != null && !isPositionalWidget(magnifier)) { final offset = calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; @@ -1941,9 +1962,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ), ); } + } else { + _magnifierRect = null; } - } else { - _magnifierRect = null; } final showContextMenuAutomatically = @@ -2060,6 +2081,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ]; } + bool _shouldBeShownForAnchor( + PdfTextSelectionAnchor textAnchor, + PdfViewerController controller, + PdfViewerSelectionMagnifierParams params, + ) { + final h = textAnchor.direction == PdfTextDirection.vrtl ? textAnchor.rect.size.width : textAnchor.rect.size.height; + return h * _currentZoom < params.magnifierSizeThreshold; + } + Widget _buildHandle(BuildContext context, Path path, PdfViewerTextSelectionAnchorHandleState state) { final baseColor = Theme.of(context).textSelectionTheme.selectionColor ?? DefaultSelectionStyle.of(context).selectionColor!; @@ -2153,20 +2183,16 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Calculate the rectangle shown in the magnifier for the given text anchor. Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { final c = textAnchor.page.charRects[textAnchor.index]; - return switch (textAnchor.direction) { - PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => Rect.fromLTRB( - textAnchor.rect.left - c.height * 2, - textAnchor.rect.top - c.height * .2, - textAnchor.rect.right + c.height * 2, - textAnchor.rect.bottom + c.height * .2, - ), - PdfTextDirection.vrtl => Rect.fromLTRB( - textAnchor.rect.left - c.width * .2, - textAnchor.rect.top - c.width * 2, - textAnchor.rect.right + c.width * .2, - textAnchor.rect.bottom + c.width * 2, - ), + final baseUnit = switch (textAnchor.direction) { + PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, + PdfTextDirection.vrtl => c.width, }; + return Rect.fromLTRB( + textAnchor.rect.left - baseUnit * 2, + textAnchor.rect.top - baseUnit * .2, + textAnchor.rect.right + baseUnit * 2, + textAnchor.rect.bottom + baseUnit * .2, + ); } Widget _buildMagnifier(BuildContext context, Rect rectToDraw, PdfViewerSelectionMagnifierParams magnifierParams) { @@ -2202,6 +2228,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget _buildMagnifierDecoration( BuildContext context, + PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params, Widget child, Size childSize, @@ -2289,6 +2316,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return; } _updateTextSelection(); + _requestFocus(); } bool _updateSelectionHandlesPan(Offset? panTo) { @@ -3151,6 +3179,12 @@ class PdfViewerController extends ValueListenable { /// The text selection delegate. PdfTextSelectionDelegate get textSelectionDelegate => _state; + + /// [FocusNode] associated to the [PdfViewer] if available. + FocusNode? get focusNode => _state._getFocusNode(); + + /// Request focus to the [PdfViewer]. + void requestFocus() => _state._requestFocus(); } /// [PdfViewerController.calcFitZoomMatrices] returns the list of this class. @@ -3333,6 +3367,7 @@ class _CanvasLinkPainter { } bool _handleLinkTap(Offset tapPosition) { + _state._requestFocus(); _cursor = MouseCursor.defer; final link = _findLinkAtPosition(tapPosition); if (link != null) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 3edaeaae..1acf3056 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -877,7 +877,7 @@ class PdfViewerSelectionMagnifierParams { this.magnifierSizeThreshold = 72, this.getMagnifierRectForAnchor, this.builder, - this.shouldBeShown, + this.shouldBeShownForAnchor, this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, }); @@ -903,8 +903,7 @@ class PdfViewerSelectionMagnifierParams { /// Function to determine whether the magnifier should be shown based on conditions such as zoom level. /// /// If [enabled] is false, this function is not called. - /// By default, the magnifier is shown if the zoom level is smaller than [scale]. - final PdfViewerMagnifierShouldBeShownFunction? shouldBeShown; + final PdfViewerMagnifierShouldBeShownFunction? shouldBeShownForAnchor; /// The maximum number of image bytes to be cached on memory. /// @@ -920,7 +919,7 @@ class PdfViewerSelectionMagnifierParams { other.magnifierSizeThreshold == magnifierSizeThreshold && other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && - other.shouldBeShown == shouldBeShown && + other.shouldBeShownForAnchor == shouldBeShownForAnchor && other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; } @@ -930,32 +929,28 @@ class PdfViewerSelectionMagnifierParams { magnifierSizeThreshold.hashCode ^ getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ - shouldBeShown.hashCode ^ + shouldBeShownForAnchor.hashCode ^ maxImageBytesCachedOnMemory.hashCode; } /// Function to get the magnifier rectangle for the anchor. /// -/// The following fragment illustrates how to get the magnifier rectangle for the anchor: +/// The following fragment illustrates one example of the code to calculate where on the document the magnifier should +/// be shown for: /// ///```dart /// getMagnifierRectForAnchor: (textAnchor, params) { -/// final c = textAnchor.page.charRects[textAnchor.index]; -/// return switch (textAnchor.direction) { -/// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => Rect.fromLTRB( -/// textAnchor.rect.left - c.height * 2, -/// textAnchor.rect.top - c.height * .2, -/// textAnchor.rect.right + c.height * 2, -/// textAnchor.rect.bottom + c.height * .2, -/// ), -/// PdfTextDirection.vrtl => Rect.fromLTRB( -/// textAnchor.rect.left - c.width * .2, -/// textAnchor.rect.top - c.width * 2, -/// textAnchor.rect.right + c.width * .2, -/// textAnchor.rect.bottom + c.width * 2, -/// ), -/// }; -/// } +/// final c = textAnchor.page.charRects[textAnchor.index]; +/// final baseUnit = switch (textAnchor.direction) { +/// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, +/// PdfTextDirection.vrtl => c.width, +/// }; +/// return Rect.fromLTRB( +/// textAnchor.rect.left - baseUnit * 2, +/// textAnchor.rect.top - baseUnit * .2, +/// textAnchor.rect.right + baseUnit * 2, +/// textAnchor.rect.bottom + baseUnit * .2, +/// ); ///``` typedef PdfViewerGetMagnifierRectForAnchor = Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params); @@ -970,10 +965,15 @@ typedef PdfViewerGetMagnifierRectForAnchor = /// If the function returns a widget of [Positioned] or [Align], the magnifier content is laid out as /// specified. Otherwise, the widget is laid out automatically. /// +/// [magnifierContent] is the widget that contains the magnified content. And you can embed it into your widget tree. +/// [magnifierContentSize] is the size of the magnified content in document coordinates; you can use the size to know +/// the aspect ratio of the magnified content. +/// /// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: /// /// ```dart -/// builder: (context, params, magnifierContent, magnifierContentSize) { +/// builder: (context, textAnchor, params, magnifierContent, magnifierContentSize) { +/// // calculate the scale to fit the magnifier content fit into 80x80 box /// final scale = 80 / min(magnifierContentSize.width, magnifierContentSize.height); /// return Container( /// decoration: BoxDecoration( @@ -993,14 +993,30 @@ typedef PdfViewerGetMagnifierRectForAnchor = typedef PdfViewerMagnifierBuilder = Widget? Function( BuildContext context, + PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params, Widget magnifierContent, Size magnifierContentSize, ); /// Function to determine whether the magnifier should be shown or not. +/// +/// Determine whether the magnifier should be shown for the text anchor, [textAnchor], +/// which points to a character in the text. +/// +/// The following fragment illustrates how to determine whether the magnifier should be shown based on the zoom level: +/// +/// ```dart +/// shouldBeShownForAnchor: (textAnchor, controller, params) { +/// final h = textAnchor.direction == PdfTextDirection.vrtl ? textAnchor.rect.size.width : textAnchor.rect.size.height; +/// return h * _currentZoom < params.magnifierSizeThreshold; +/// ``` typedef PdfViewerMagnifierShouldBeShownFunction = - bool Function(PdfViewerController controller, PdfViewerSelectionMagnifierParams params); + bool Function( + PdfTextSelectionAnchor textAnchor, + PdfViewerController controller, + PdfViewerSelectionMagnifierParams params, + ); /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 141829ac..e397e2e6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -110,6 +110,8 @@ class PdfDocumentViewBuilder extends StatefulWidget { } class _PdfDocumentViewBuilderState extends State { + StreamSubscription? _updateSubscription; + @override void initState() { super.initState(); @@ -119,24 +121,37 @@ class _PdfDocumentViewBuilderState extends State { ..load(); } + @override + void didUpdateWidget(covariant PdfDocumentViewBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget == oldWidget) { + return; + } + + oldWidget.documentRef.resolveListenable().removeListener(_onDocumentChanged); + widget.documentRef.resolveListenable() + ..addListener(_onDocumentChanged) + ..load(); + _onDocumentChanged(); + } + @override void dispose() { + _updateSubscription?.cancel(); widget.documentRef.resolveListenable().removeListener(_onDocumentChanged); super.dispose(); } void _onDocumentChanged() { if (mounted) { - widget.documentRef.resolveListenable().useDocument((document) { - document.loadPagesProgressively((_, _, _) { - if (mounted) { - setState(() {}); - return true; - } else { - return false; - } - }); + _updateSubscription?.cancel(); + final document = widget.documentRef.resolveListenable().document; + _updateSubscription = document?.events.listen((event) { + if (mounted && event.type == PdfDocumentEventType.pageStatusChanged) { + setState(() {}); + } }); + document?.loadPagesProgressively(); setState(() {}); } } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 5e5af234..ee82ee03 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -393,8 +393,8 @@ class _PdfDocumentPdfium extends PdfDocument { } @override - Future loadPagesProgressively( - PdfPageLoadingCallback? onPageLoadProgress, { + Future loadPagesProgressively({ + PdfPageLoadingCallback? onPageLoadProgress, T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 89174dcb..bdffaf65 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -325,8 +325,8 @@ abstract class PdfDocument { /// [data] is an optional data that can be used to pass additional information to the callback. /// /// It's always safe to call this function even if the pages are already loaded. - Future loadPagesProgressively( - PdfPageLoadingCallback? onPageLoadProgress, { + Future loadPagesProgressively({ + PdfPageLoadingCallback? onPageLoadProgress, T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }); @@ -346,7 +346,10 @@ abstract class PdfDocument { typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); /// PDF document event types. -enum PdfDocumentEventType { pageStatusChanged } +enum PdfDocumentEventType { + /// [PdfDocumentPageStatusChangedEvent]: Page status changed. + pageStatusChanged, +} /// Base class for PDF document events. abstract class PdfDocumentEvent { @@ -739,10 +742,16 @@ class PdfPageRawText { enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } /// Annotation rendering mode. -/// - [none]: Do not render annotations. -/// - [annotation]: Render annotations. -/// - [annotationAndForms]: Render annotations and forms. -enum PdfAnnotationRenderingMode { none, annotation, annotationAndForms } +enum PdfAnnotationRenderingMode { + /// Do not render annotations. + none, + + /// Render annotations. + annotation, + + /// Render annotations and forms. + annotationAndForms, +} /// Flags for [PdfPage.render]. /// @@ -965,12 +974,19 @@ class PdfPageText { } /// Text direction in PDF page. -/// -/// - [ltr]: left to right -/// - [rtl]: right to left -/// - [vrtl]: vertical (top to bottom), right to left. -/// - [unknown]: unknown direction, e.g., no text or no text direction can be determined. -enum PdfTextDirection { ltr, rtl, vrtl, unknown } +enum PdfTextDirection { + /// Left to Right + ltr, + + /// Right to Left + rtl, + + /// Vertical (top to bottom), Right to Left. + vrtl, + + /// Unknown direction, e.g., no text or no text direction can be determined. + unknown, +} /// Text fragment in PDF page. class PdfPageTextFragment { From af20f746de4a14e7ebf065a57d8caf6d84d19495 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 20 Jul 2025 04:30:20 +0900 Subject: [PATCH 217/663] Release pdfrx v2.0.0-beta6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index ff7fe6e4..515007b6 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.0.0-beta6 + +- BREAKING CHANGE: Changed `loadPagesProgressively` to use named parameter `onPageLoadProgress` for better API clarity +- IMPROVED: Enhanced focus management for better keyboard interaction support +- IMPROVED: Focus is now properly requested on text selection and interaction events +- IMPROVED: Added comprehensive documentation for enum values in API + # 2.0.0-beta5 - Minor fixes and improvements diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 126712b7..4a24e6ed 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.0-beta3 + pdfrx: ^2.0.0-beta6 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 931140dd..e3f77915 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta5 +version: 2.0.0-beta6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From cba670ac7074f0668bb9ed2c5cd20c5a63bce7c7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 20 Jul 2025 04:48:22 +0900 Subject: [PATCH 218/663] WIP --- packages/pdfrx/example/viewer/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index a9078c09..13c4cfa7 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta5" + version: "2.0.0-beta6" pdfrx_engine: dependency: "direct overridden" description: From 8748af02b0bd1becd8dafc7042c716e9f33d015f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 20 Jul 2025 04:59:33 +0900 Subject: [PATCH 219/663] WIP --- packages/pdfrx/lib/src/pdfrx_flutter.dart | 6 +----- packages/pdfrx/lib/src/utils/native/native.dart | 3 +++ packages/pdfrx/lib/src/utils/web/web.dart | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index e00c93e7..f466ca7c 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -3,7 +3,6 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:path_provider/path_provider.dart'; import '../pdfrx.dart'; import 'utils/platform.dart'; @@ -28,10 +27,7 @@ void pdfrxFlutterInitialize() { final asset = await rootBundle.load(name); return asset.buffer.asUint8List(); }; - Pdfrx.getCacheDirectory ??= () async { - final dir = await getTemporaryDirectory(); - return dir.path; - }; + Pdfrx.getCacheDirectory ??= getCacheDirectory; // Checking pdfium.wasm availability for Web and debug builds. if (kDebugMode) { diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 5c2dad20..884e338d 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; import '../../../pdfrx.dart'; @@ -19,6 +20,8 @@ void setClipboardData(String text) { Clipboard.setData(ClipboardData(text: text)); } +Future getCacheDirectory() async => (await getTemporaryDirectory()).path; + /// Override for the [PdfDocumentFactory] for native platforms; it is null. PdfDocumentFactory? get pdfDocumentFactoryOverride => null; diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 8d6d445f..e51585d6 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -18,6 +18,8 @@ void setClipboardData(String text) { web.window.navigator.clipboard.writeText(text); } +Future getCacheDirectory() async => throw UnimplementedError('No temporary directory available for web.'); + /// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; From ed091e635c23df77fa90ffe434615c0da3eb27cf Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 20 Jul 2025 05:08:19 +0900 Subject: [PATCH 220/663] Release pdfrx_engine v0.1.7 and pdfrx v2.0.0-beta7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pdfrx_engine v0.1.7: API improvements and documentation enhancements - pdfrx v2.0.0-beta7: Update to pdfrx_engine 0.1.7, platform-specific cache handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/example/viewer/pubspec.lock | 4 ++-- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 6 ++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 515007b6..b841dc1b 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.0-beta7 + +- Update to pdfrx_engine 0.1.7 +- IMPROVED: Platform-specific cache directory handling + # 2.0.0-beta6 - BREAKING CHANGE: Changed `loadPagesProgressively` to use named parameter `onPageLoadProgress` for better API clarity diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 13c4cfa7..29c766d3 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,14 +347,14 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta6" + version: "2.0.0-beta7" pdfrx_engine: dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.6" + version: "0.1.7" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index e3f77915..5cdaaf75 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta6 +version: 2.0.0-beta7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.6 + pdfrx_engine: ^0.1.7 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index b6fd4549..20ba87e7 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.7 + +- Improve `loadPagesProgressively` API by making `onPageLoadProgress` a named parameter +- Fix parentheses in premultiplied alpha flag check +- Improve documentation for enums + ## 0.1.6 - Add premultiplied alpha support with new flag `PdfPageRenderFlags.premultipliedAlpha` diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 1cc557c5..b7b9b35f 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.6 +version: 0.1.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From ad48aceee4c7d0aa548aaa10e962574db5cccc32 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 21 Jul 2025 02:21:17 +0900 Subject: [PATCH 221/663] Remove PdfDocumentRefPasswordMixin --- packages/pdfrx/lib/src/pdf_document_ref.dart | 32 +++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index cd179eaf..c6c5675c 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -57,6 +57,13 @@ abstract class PdfDocumentRef { /// [progressCallback] should be called when the document is loaded from remote source to notify the progress. Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback); + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + PdfPasswordProvider? get passwordProvider; + + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// or not. For more info, see [PdfPasswordProvider]. + bool get firstAttemptByEmptyPassword; + /// Classes that extends [PdfDocumentRef] should override this function to compare the equality by [sourceName] /// or such. @override @@ -67,17 +74,8 @@ abstract class PdfDocumentRef { int get hashCode => throw UnimplementedError(); } -mixin PdfDocumentRefPasswordMixin on PdfDocumentRef { - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - PdfPasswordProvider? get passwordProvider; - - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. - bool get firstAttemptByEmptyPassword; -} - /// A [PdfDocumentRef] that loads the document from asset. -class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixin { +class PdfDocumentRefAsset extends PdfDocumentRef { const PdfDocumentRefAsset( this.name, { this.passwordProvider, @@ -113,7 +111,7 @@ class PdfDocumentRefAsset extends PdfDocumentRef with PdfDocumentRefPasswordMixi } /// A [PdfDocumentRef] that loads the document from network. -class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin { +class PdfDocumentRefUri extends PdfDocumentRef { const PdfDocumentRefUri( this.uri, { this.passwordProvider, @@ -167,7 +165,7 @@ class PdfDocumentRefUri extends PdfDocumentRef with PdfDocumentRefPasswordMixin } /// A [PdfDocumentRef] that loads the document from file. -class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin { +class PdfDocumentRefFile extends PdfDocumentRef { const PdfDocumentRefFile( this.file, { this.passwordProvider, @@ -205,7 +203,7 @@ class PdfDocumentRefFile extends PdfDocumentRef with PdfDocumentRefPasswordMixin /// A [PdfDocumentRef] that loads the document from data. /// /// For [allowDataOwnershipTransfer], see [PdfDocument.openData]. -class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin { +class PdfDocumentRefData extends PdfDocumentRef { const PdfDocumentRefData( this.data, { required this.sourceName, @@ -250,7 +248,7 @@ class PdfDocumentRefData extends PdfDocumentRef with PdfDocumentRefPasswordMixin } /// A [PdfDocumentRef] that loads the document from custom source. -class PdfDocumentRefCustom extends PdfDocumentRef with PdfDocumentRefPasswordMixin { +class PdfDocumentRefCustom extends PdfDocumentRef { const PdfDocumentRefCustom({ required this.fileSize, required this.read, @@ -314,6 +312,12 @@ class PdfDocumentRefDirect extends PdfDocumentRef { @override int get hashCode => sourceName.hashCode; + + @override + bool get firstAttemptByEmptyPassword => throw UnimplementedError('Not applicable for PdfDocumentRefDirect'); + + @override + PdfPasswordProvider? get passwordProvider => throw UnimplementedError('Not applicable for PdfDocumentRefDirect'); } /// The class is used to load the referenced document and notify the listeners. From 97b21708611a4bfb0177e86dc537c764b6d72b0a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Jul 2025 02:42:33 +0900 Subject: [PATCH 222/663] More consistent behavior on disposed PdfDocument --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 6 +++++- packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 83c7b80d..68c6ad5b 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -339,11 +339,11 @@ class _PdfDocumentWasm extends PdfDocument { T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { - if (isDisposed) return; int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); if (firstPageIndex < 0) return; // All pages are already loaded for (; firstPageIndex < pages.length;) { + if (isDisposed) return; final result = await _sendCommand( 'loadPagesProgressively', parameters: { @@ -429,6 +429,7 @@ class _PdfPageWasm extends PdfPage { @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { + if (document.isDisposed) return []; final result = await _sendCommand( 'loadLinks', parameters: { @@ -460,6 +461,7 @@ class _PdfPageWasm extends PdfPage { @override Future loadText() async { + if (document.isDisposed) return ''; final result = await _sendCommand( 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, @@ -469,6 +471,7 @@ class _PdfPageWasm extends PdfPage { @override Future> loadTextCharRects() async { + if (document.isDisposed) return []; final result = await _sendCommand( 'loadTextCharRects', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, @@ -509,6 +512,7 @@ class _PdfPageWasm extends PdfPage { int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, }) async { + if (document.isDisposed) return null; fullWidth ??= this.width; fullHeight ??= this.height; width ??= fullWidth.toInt(); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index ee82ee03..28cd3fd7 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -741,9 +741,7 @@ class _PdfPagePdfium extends PdfPage { @override Future loadText() async { - if (document.isDisposed) { - throw StateError('Cannot load text from disposed document.'); - } + if (document.isDisposed) return ''; return await (await backgroundWorker).compute( (params) => using((arena) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); @@ -767,9 +765,7 @@ class _PdfPagePdfium extends PdfPage { @override Future> loadTextCharRects() async { - if (document.isDisposed) { - throw StateError('Cannot load text from disposed document.'); - } + if (document.isDisposed) return []; return await (await backgroundWorker).compute( (params) => using((arena) { final doubleSize = sizeOf(); From 53ebc9f5531d9c94416cda20e4484b2b43fe7156 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Jul 2025 17:16:13 +0900 Subject: [PATCH 223/663] pdfrx 2.0.0/pdfrx_engine 0.1.8 --- packages/pdfrx/CHANGELOG.md | 43 +++------------------- packages/pdfrx/README.md | 2 +- packages/pdfrx/example/viewer/pubspec.lock | 4 +- packages/pdfrx/pubspec.yaml | 4 +- packages/pdfrx_engine/CHANGELOG.md | 4 ++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 15 insertions(+), 44 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index b841dc1b..022cc598 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,47 +1,14 @@ -# 2.0.0-beta7 - -- Update to pdfrx_engine 0.1.7 -- IMPROVED: Platform-specific cache directory handling - -# 2.0.0-beta6 - -- BREAKING CHANGE: Changed `loadPagesProgressively` to use named parameter `onPageLoadProgress` for better API clarity -- IMPROVED: Enhanced focus management for better keyboard interaction support -- IMPROVED: Focus is now properly requested on text selection and interaction events -- IMPROVED: Added comprehensive documentation for enum values in API - -# 2.0.0-beta5 - -- Minor fixes and improvements - -# 2.0.0-beta4 - -- IMPROVED: Enhanced text selection behavior with better Select All functionality -- FIXED: Corrected operator precedence issue with premultiplied alpha flag check in WASM implementation -- IMPROVED: Context menu position handling when selecting all text -- Updated deprecated `withOpacity` to `withValue(alpha:)` for better forward compatibility - -# 2.0.0-beta3 - -- BREAKING CHANGE: Renamed `showContextMenuOnSelectionHandle` to `showContextMenuAutomatically` in `PdfTextSelectionParams` for better clarity - -# 2.0.0-beta2 - -- IMPROVED: Text selection API changed to async with `getSelectedTextRange()` method -- IMPROVED: Internal stability and performance improvements -- Updated to use pdfrx_engine 0.1.6 with premultiplied alpha support - -# 2.0.0-beta1 +# 2.0.0 This is a major release that introduces significant architectural changes and new features. - BREAKING CHANGE: Extracted PDF rendering engine into a separate `pdfrx_engine` package that is platform-agnostic -- NEW FEATURE: Text selection support with native platform UI including: +- NEW FEATURE/BREAKING CHANGE: Text selection support with native platform UI including: - Selection handles with drag support - - Magnifier/loupe for precise text selection + - Magnifier for precise text selection - Context menu with copy functionality -- IMPROVED: Better separation of concerns between rendering engine and Flutter UI layer -- IMPROVED: Platform-specific implementations are now more maintainable + - Brand-new text selection/text extraction API (not compatible with previous versions) +- Enhanced focus management for better keyboard interaction support # 1.3.4 diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 4a24e6ed..40838372 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.0-beta6 + pdfrx: ^2.0.0 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 29c766d3..d2a601b4 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,14 +347,14 @@ packages: path: "../.." relative: true source: path - version: "2.0.0-beta7" + version: "2.0.0" pdfrx_engine: dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.7" + version: "0.1.8" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 5cdaaf75..38037537 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0-beta7 +version: 2.0.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.7 + pdfrx_engine: ^0.1.8 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 20ba87e7..72ed0ecc 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.8 + +- More consistent behavior on disposed PdfDocument + ## 0.1.7 - Improve `loadPagesProgressively` API by making `onPageLoadProgress` a named parameter diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index b7b9b35f..13c5f27e 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.7 +version: 0.1.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 13c15390de6937fe28bc80c01585f68aa9560a12 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Jul 2025 18:06:51 +0900 Subject: [PATCH 224/663] doc updates --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f287116d..1f3b7d26 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.0.0-beta1 + pdfrx: ^2.0.0 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^1.0.0 + pdfrx_engine: ^0.1.8 ``` ## Development From 58ee46d0a1fd95cb31cebf4051af8ca03a686199 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Jul 2025 18:15:51 +0900 Subject: [PATCH 225/663] WIP --- packages/pdfrx_engine/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index c6fa1f34..6cdb62cd 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -1,7 +1,5 @@ # pdfrx_engine -**PRERELEASE NOTE**: This package is currently in pre-release. The APIs may change before the final release. - [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side processing. From 6a57e3a82d20d6814b8f5910d5faf91af6cd7df4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 22 Jul 2025 23:52:22 +0900 Subject: [PATCH 226/663] Wow, we misses a very basic setting: PdfTextSelectionParams.enabled --- packages/pdfrx/example/viewer/lib/main.dart | 1 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 60 +++++++++++-------- .../lib/src/widgets/pdf_viewer_params.dart | 4 ++ 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index ba58c252..d59fa5fe 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -318,6 +318,7 @@ class _MainPageState extends State with WidgetsBindingObserver { pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, textSelectionParams: PdfTextSelectionParams( + enabled: true, onTextSelectionChange: (textSelection) async { textSelections = await textSelection.getSelectedTextRange(); }, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 67bb6b74..0e2422c6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -424,12 +424,23 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _contextForFocusNode = context; return LayoutBuilder( builder: (context, constraints) { + final enableTextSelection = widget.params.textSelectionParams?.enabled ?? true; final isCopyTextEnabled = _document!.permissions?.allowsCopying != false; final enableSwipeToSelectText = widget.params.textSelectionParams?.textSelectionTriggeredBySwipe ?? PlatformBehaviorDefaults.shouldTextSelectionTriggeredBySwipe; final viewSize = Size(constraints.maxWidth, constraints.maxHeight); + _updateLayout(viewSize); + + final pages = CustomPaint( + foregroundPainter: _CustomPainter.fromFunctions( + _paintPages, + hitTestFunction: _hitTestForTextSelection, + ), + size: _layout!.documentSize, + ); + return Stack( children: [ iv.InteractiveViewer( @@ -447,31 +458,29 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, // PDF pages - child: MouseRegion( - cursor: SystemMouseCursors.move, - hitTestBehavior: HitTestBehavior.deferToChild, - child: MouseRegion( - cursor: SystemMouseCursors.text, - hitTestBehavior: HitTestBehavior.deferToChild, - child: GestureDetector( - onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, - onDoubleTapDown: widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, - onLongPressStart: widget.params.textSelectionParams?.textLongPress ?? _textLongPress, - onSecondaryTapUp: - widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, - onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, - onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, - onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, - child: CustomPaint( - foregroundPainter: _CustomPainter.fromFunctions( - _paintPages, - hitTestFunction: _hitTestForTextSelection, + child: + !enableTextSelection + ? pages + : MouseRegion( + cursor: SystemMouseCursors.move, + hitTestBehavior: HitTestBehavior.deferToChild, + child: MouseRegion( + cursor: SystemMouseCursors.text, + hitTestBehavior: HitTestBehavior.deferToChild, + child: GestureDetector( + onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, + onDoubleTapDown: widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, + onLongPressStart: + widget.params.textSelectionParams?.textLongPress ?? _textLongPress, + onSecondaryTapUp: + widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, + onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, + onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, + onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, + child: pages, + ), + ), ), - size: _layout!.documentSize, - ), - ), - ), - ), ), if (_initialized) ..._buildPageOverlayWidgets(context), if (_initialized && _canvasLinkPainter.isEnabled) _canvasLinkPainter.linkHandlingOverlay(viewSize), @@ -479,7 +488,8 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap).map( (e) => e, ), - if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), + if (_initialized && enableTextSelection) + ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), ], ); }, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 1acf3056..9b2b0a8f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -641,6 +641,7 @@ class PdfViewerParams { @immutable class PdfTextSelectionParams { const PdfTextSelectionParams({ + this.enabled = true, this.textSelectionTriggeredBySwipe, this.enableSelectionHandles, this.showContextMenuAutomatically, @@ -654,6 +655,9 @@ class PdfTextSelectionParams { this.magnifier, }); + /// Whether text selection is enabled. + final bool enabled; + /// Whether text selection is triggered by swipe. /// /// null to determine the behavior based on the platform; enabled on desktop, disabled on mobile/Web. From e7c470490f71b0055fe7162911519f84ae5f2383 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 00:00:10 +0900 Subject: [PATCH 227/663] Release pdfrx v2.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 022cc598..3dac528e 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.1 + +- FIXED: Added missing `PdfTextSelectionParams.enabled` property to control text selection functionality + # 2.0.0 This is a major release that introduces significant architectural changes and new features. diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 40838372..5292bb1b 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.0 + pdfrx: ^2.0.1 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 38037537..62e6705d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.0 +version: 2.0.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 99b8e984f0a797c21ee03f1c4a2b109fb171510f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 00:10:10 +0900 Subject: [PATCH 228/663] Shift-Space to go to previous page --- packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index d2a601b4..321b0b47 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.0" + version: "2.0.1" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 0e2422c6..e6460fc0 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -592,9 +592,12 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1, duration: duration); return true; case LogicalKeyboardKey.pageDown: - case LogicalKeyboardKey.space: _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1, duration: duration); return true; + case LogicalKeyboardKey.space: + final move = HardwareKeyboard.instance.isShiftPressed ? -1 : 1; + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + move, duration: duration); + return true; case LogicalKeyboardKey.home: _goToPage(pageNumber: 1, duration: duration); return true; From 3d651a4c3bf81392bbb74b343d8cea48a351cfbf Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 01:59:00 +0900 Subject: [PATCH 229/663] pdfrx_engine example update --- packages/pdfrx_engine/example/main.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx_engine/example/main.dart b/packages/pdfrx_engine/example/main.dart index 91ed9b5d..598fe8d6 100644 --- a/packages/pdfrx_engine/example/main.dart +++ b/packages/pdfrx_engine/example/main.dart @@ -51,9 +51,11 @@ Future main(List args) async { pageImage.dispose(); // Save as PNG - final outputFile = File('$outputDir/page_$pageNumber.png'); - await outputFile.writeAsBytes(img.encodePng(image)); - print('Saved: ${outputFile.path}'); + final outputImageFile = File('$outputDir/page_$pageNumber.png'); + await outputImageFile.writeAsBytes(img.encodePng(image)); + + final outputTextFile = File('$outputDir/page_$pageNumber.txt'); + await outputTextFile.writeAsString(await page.loadText()); } // Clean up From 15af7ee738925841e134b2d2fbaf6212ac0c22a1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 02:06:58 +0900 Subject: [PATCH 230/663] WIP --- packages/pdfrx/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 5292bb1b..bdff902b 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -108,7 +108,7 @@ You can customize the behaviors and the viewer look and feel by configuring [Pdf PdfViewer.asset( 'assets/test.pdf', // The easiest way to supply a password - passwordProvider: () => 'password', + passwordProvider: () => createSimplePasswordProvider('password'), ... ), @@ -118,14 +118,18 @@ See [Deal with Password Protected PDF Files using PasswordProvider](https://gith ### Text Selection -The following fragment enables text selection feature: +The text selection feature is enabled by default, allowing users to select text in the PDF viewer. You can customize the text selection behavior using [PdfTextSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams-class.html). + +The following example shows how to disable text selection in the PDF viewer: ```dart PdfViewer.asset( 'assets/test.pdf', params: PdfViewerParams( - enableTextSelection: true, - ... + textSelectionParams: PdfTextSelectionParams( + enabled: false, + ... + ), ), ... ), From 6d35227994ff5f006a1dcfb6d9de1779de008039 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 02:44:41 +0900 Subject: [PATCH 231/663] PdfTextSelection: getSelectedTextRange -> getSelectedTextRanges --- packages/pdfrx/example/viewer/lib/main.dart | 2 +- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 ++-- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index d59fa5fe..162b17e4 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -320,7 +320,7 @@ class _MainPageState extends State with WidgetsBindingObserver { textSelectionParams: PdfTextSelectionParams( enabled: true, onTextSelectionChange: (textSelection) async { - textSelections = await textSelection.getSelectedTextRange(); + textSelections = await textSelection.getSelectedTextRanges(); }, ), keyHandlerParams: PdfViewerKeyHandlerParams( diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e6460fc0..e34d64a5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2427,7 +2427,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } @override - Future> getSelectedTextRange() async { + Future> getSelectedTextRanges() async { final a = _selA; final b = _selB; if (a == null || b == null) { @@ -2452,7 +2452,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix @override Future getSelectedText() async { - final selections = await getSelectedTextRange(); + final selections = await getSelectedTextRanges(); if (selections.isEmpty) return ''; return selections.map((e) => e.text).join(); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 9b2b0a8f..7985d147 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -845,8 +845,8 @@ abstract class PdfTextSelection { /// but you have to ensure that your use of the text does not violate [isCopyAllowed] condition. Future getSelectedText(); - /// Get the selected text range. - Future> getSelectedTextRange(); + /// Get the selected text ranges. + Future> getSelectedTextRanges(); } /// Delegate for text selection actions. From d86b0b7d73b355699a4037fa2edf4201478765fe Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 23 Jul 2025 03:02:48 +0900 Subject: [PATCH 232/663] Release pdfrx v2.0.2 and pdfrx_engine v0.1.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 3dac528e..52e45735 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.2 + +- BREAKING CHANGE: Renamed `PdfTextSelection.getSelectedTextRange()` to `getSelectedTextRanges()` for consistency +- NEW FEATURE: Added Shift+Space keyboard shortcut to navigate to previous page + # 2.0.1 - FIXED: Added missing `PdfTextSelectionParams.enabled` property to control text selection functionality diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index bdff902b..71f9eec7 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.1 + pdfrx: ^2.0.2 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 62e6705d..3b6ae970 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.1 +version: 2.0.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.8 + pdfrx_engine: ^0.1.9 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 72ed0ecc..d44e723f 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.9 + +- Update example to include text extraction functionality + ## 0.1.8 - More consistent behavior on disposed PdfDocument diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 13c5f27e..b0c0bda5 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.8 +version: 0.1.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From f2e2fe44946ac910cd9b32d83eb8b2711896b575 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Jul 2025 03:59:36 +0900 Subject: [PATCH 233/663] Updates to improve text selection context menu --- packages/pdfrx/example/viewer/pubspec.lock | 4 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 336 +++++++++++------- .../lib/src/widgets/pdf_viewer_params.dart | 84 ++--- 3 files changed, 263 insertions(+), 161 deletions(-) diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 321b0b47..1ba41166 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,14 +347,14 @@ packages: path: "../.." relative: true source: path - version: "2.0.1" + version: "2.0.2" pdfrx_engine: dependency: "direct overridden" description: path: "../../../pdfrx_engine" relative: true source: path - version: "0.1.8" + version: "0.1.9" petitparser: dependency: transitive description: diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e34d64a5..785fde12 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -205,7 +205,9 @@ class PdfViewer extends StatefulWidget { State createState() => _PdfViewerState(); } -class _PdfViewerState extends State with SingleTickerProviderStateMixin implements PdfTextSelectionDelegate { +class _PdfViewerState extends State + with SingleTickerProviderStateMixin + implements PdfTextSelectionDelegate, DocumentCoordinateConverter { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; @@ -253,12 +255,13 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix bool _isSelectingAllText = false; - Offset? _textSelectionContextMenuGlobalPosition; + Offset? _textSelectionContextMenuDocumentPosition; Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; BuildContext? _contextForFocusNode; + Offset _mouseCursorOffset = Offset.zero; @override void initState() { @@ -441,56 +444,66 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix size: _layout!.documentSize, ); - return Stack( - children: [ - iv.InteractiveViewer( - transformationController: _txController, - constrained: false, - boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), - maxScale: widget.params.maxScale, - minScale: minScale, - panAxis: widget.params.panAxis, - panEnabled: widget.params.panEnabled, - scaleEnabled: widget.params.scaleEnabled, - onInteractionEnd: _onInteractionEnd, - onInteractionStart: _onInteractionStart, - onInteractionUpdate: widget.params.onInteractionUpdate, - interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, - onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, - // PDF pages - child: - !enableTextSelection - ? pages - : MouseRegion( - cursor: SystemMouseCursors.move, - hitTestBehavior: HitTestBehavior.deferToChild, - child: MouseRegion( - cursor: SystemMouseCursors.text, - hitTestBehavior: HitTestBehavior.deferToChild, - child: GestureDetector( - onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, - onDoubleTapDown: widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, - onLongPressStart: - widget.params.textSelectionParams?.textLongPress ?? _textLongPress, - onSecondaryTapUp: - widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, - onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, - onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, - onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, - child: pages, + return Listener( + onPointerDown: (event) => _mouseCursorOffset = event.localPosition, + onPointerMove: (event) => _mouseCursorOffset = event.localPosition, + onPointerUp: (event) => _mouseCursorOffset = event.localPosition, + child: Stack( + children: [ + GestureDetector( + onSecondaryTapUp: widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, + child: iv.InteractiveViewer( + transformationController: _txController, + constrained: false, + boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), + maxScale: widget.params.maxScale, + minScale: minScale, + panAxis: widget.params.panAxis, + panEnabled: widget.params.panEnabled, + scaleEnabled: widget.params.scaleEnabled, + onInteractionEnd: _onInteractionEnd, + onInteractionStart: _onInteractionStart, + onInteractionUpdate: widget.params.onInteractionUpdate, + interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, + onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + // PDF pages + child: + !enableTextSelection + ? pages + : MouseRegion( + cursor: SystemMouseCursors.move, + hitTestBehavior: HitTestBehavior.deferToChild, + child: MouseRegion( + cursor: SystemMouseCursors.text, + hitTestBehavior: HitTestBehavior.deferToChild, + child: GestureDetector( + onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, + onDoubleTapDown: + widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, + onLongPressStart: + widget.params.textSelectionParams?.textLongPress ?? _textLongPress, + onSecondaryTapUp: + widget.params.textSelectionParams?.textSecondaryTapUp ?? + _textSecondaryTapUp, + onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, + onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, + onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, + child: pages, + ), + ), ), - ), - ), - ), - if (_initialized) ..._buildPageOverlayWidgets(context), - if (_initialized && _canvasLinkPainter.isEnabled) _canvasLinkPainter.linkHandlingOverlay(viewSize), - if (_initialized && widget.params.viewerOverlayBuilder != null) - ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap).map( - (e) => e, + ), ), - if (_initialized && enableTextSelection) - ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), - ], + if (_initialized) ..._buildPageOverlayWidgets(context), + if (_initialized && _canvasLinkPainter.isEnabled) + _canvasLinkPainter.linkHandlingOverlay(viewSize), + if (_initialized && widget.params.viewerOverlayBuilder != null) + ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap) + .map((e) => e), + if (_initialized && enableTextSelection) + ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), + ], + ), ); }, ); @@ -1636,7 +1649,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix } void _textSecondaryTapUp(TapUpDetails details) { - _textSelectionContextMenuGlobalPosition = details.globalPosition; + _textSelectionContextMenuDocumentPosition = _globalToDocument(details.globalPosition); _invalidate(); } @@ -1644,7 +1657,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix if (_isInteractionGoingOn) return; _selPartMoving = _TextSelectionPart.free; _isSelectingAllText = false; - _textSelectionContextMenuGlobalPosition = null; + _textSelectionContextMenuDocumentPosition = null; _selA = _findTextAndIndexForPoint(details.localPosition); _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); _selB = null; @@ -1773,15 +1786,29 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _TextSelectionPart _hoverOn = _TextSelectionPart.none; List _placeTextSelectionWidgets(BuildContext context, Size viewSize, bool isCopyTextEnabled) { + Widget? createContextMenu(Offset? a, Offset? b) { + if (a == null) return null; + final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; + return ctxMenuBuilder(context, a, b, _textSelA, _textSelB, this, () { + _textSelectionContextMenuDocumentPosition = null; + _invalidate(); + }); + } + + List contextMenuIfNeeded() { + final contextMenu = createContextMenu(offsetToLocal(context, _textSelectionContextMenuDocumentPosition), null); + return [if (contextMenu != null) contextMenu]; + } + final renderBox = _renderBox; if (renderBox == null || _textSelA == null || _textSelB == null) { - return []; + return contextMenuIfNeeded(); } final rectA = _documentToRenderBox(_textSelA!.rect, renderBox); final rectB = _documentToRenderBox(_textSelB!.rect, renderBox); if (rectA == null || rectB == null) { - return []; + return contextMenuIfNeeded(); } final enableSelectionHandles = @@ -1984,7 +2011,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix widget.params.textSelectionParams?.showContextMenuAutomatically ?? PlatformBehaviorDefaults.showContextMenuAutomatically; bool showContextMenu = false; - if (_textSelectionContextMenuGlobalPosition != null) { + if (_textSelectionContextMenuDocumentPosition != null) { showContextMenu = true; } else if (showContextMenuAutomatically && _textSelA != null && @@ -1997,24 +2024,68 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget? contextMenu; if (showContextMenu && _selPartMoving == _TextSelectionPart.none && - (_textSelectionContextMenuGlobalPosition != null || + (_textSelectionContextMenuDocumentPosition != null || _selPartLastMoved == _TextSelectionPart.a || - _selPartLastMoved == _TextSelectionPart.b) && + _selPartLastMoved == _TextSelectionPart.b || + _isSelectingAllText) && isCopyTextEnabled) { - final offset = - _textSelectionContextMenuGlobalPosition != null - ? normalizeWidgetPosition( - renderBox.globalToLocal(_textSelectionContextMenuGlobalPosition!), - _contextMenuRect?.size, - ) - : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); - Offset.zero; - final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; - contextMenu = ctxMenuBuilder(context, _textSelA!, _textSelB!, this, () { - _textSelectionContextMenuGlobalPosition = null; - _invalidate(); - }); + final localOffset = + _textSelectionContextMenuDocumentPosition != null + ? offsetToLocal(context, _textSelectionContextMenuDocumentPosition!) + : null; + + Offset? a, b; + switch (Theme.of(context).platform) { + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.macOS: + a = _mouseCursorOffset; + if (_anchorARect != null && _anchorBRect != null) { + switch (_textSelA?.direction) { + case PdfTextDirection.ltr: + if (_anchorARect!.inflate(16).contains(a)) { + a = rectA.bottomLeft.translate(0, 8); + } + final selRect = rectA.expandToInclude(rectB); + if (selRect.height < 60 && selRect.width < 250) { + a = _anchorBRect!.bottomRight; + } + if (_anchorBRect!.inflate(16).contains(a)) { + a = _anchorBRect!.bottomRight; + } + case PdfTextDirection.rtl: + case PdfTextDirection.vrtl: + final distA = (_mouseCursorOffset - _anchorARect!.center).distanceSquared; + final distB = (_mouseCursorOffset - _anchorBRect!.center).distanceSquared; + if (distA < distB) { + a = _anchorARect!.bottomLeft.translate(8, 8); + } else { + a = _anchorBRect!.topRight.translate(8, 8); + } + default: + } + } + default: + a = localOffset; + switch (_textSelA?.direction) { + case PdfTextDirection.ltr: + a ??= _anchorARect?.topLeft; + b = localOffset == null ? _anchorBRect?.bottomLeft : null; + case PdfTextDirection.rtl: + case PdfTextDirection.vrtl: + a ??= _anchorARect?.topRight; + b = localOffset == null ? _anchorBRect?.bottomRight : null; + default: + } + } + + contextMenu = createContextMenu(a, b); if (contextMenu != null && !isPositionalWidget(contextMenu)) { + final offset = + localOffset != null + ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) + : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); contextMenu = Positioned( left: offset.dx, top: offset.dy, @@ -2196,15 +2267,15 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix /// Calculate the rectangle shown in the magnifier for the given text anchor. Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { final c = textAnchor.page.charRects[textAnchor.index]; - final baseUnit = switch (textAnchor.direction) { - PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, - PdfTextDirection.vrtl => c.width, + final (baseUnit, v, h) = switch (textAnchor.direction) { + PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => (c.height, 2.0, 0.2), + PdfTextDirection.vrtl => (c.width, 0.2, 2.0), }; return Rect.fromLTRB( - textAnchor.rect.left - baseUnit * 2, - textAnchor.rect.top - baseUnit * .2, - textAnchor.rect.right + baseUnit * 2, - textAnchor.rect.bottom + baseUnit * .2, + textAnchor.rect.left - baseUnit * v, + textAnchor.rect.top - baseUnit * h, + textAnchor.rect.right + baseUnit * v, + textAnchor.rect.bottom + baseUnit * h, ); } @@ -2261,49 +2332,22 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix Widget? _buildTextSelectionContextMenu( BuildContext context, - PdfTextSelectionAnchor a, - PdfTextSelectionAnchor b, + Offset anchorA, + Offset? anchorB, + PdfTextSelectionAnchor? a, + PdfTextSelectionAnchor? b, PdfTextSelectionDelegate textSelectionDelegate, void Function() dismissContextMenu, ) { - return Container( - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], - ), - padding: const EdgeInsets.all(2), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - RawMaterialButton( - visualDensity: VisualDensity.compact, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: - textSelectionDelegate.isCopyAllowed - ? () { - if (textSelectionDelegate.isCopyAllowed) { - textSelectionDelegate.copyTextSelection(); - } - } - : null, - child: Text( - 'Copy', - style: TextStyle(color: textSelectionDelegate.isCopyAllowed ? Colors.black : Colors.grey), - ), - ), - + return Align( + alignment: Alignment.topLeft, + child: AdaptiveTextSelectionToolbar.buttonItems( + anchors: TextSelectionToolbarAnchors(primaryAnchor: anchorA, secondaryAnchor: anchorB), + buttonItems: [ + if (textSelectionDelegate.isCopyAllowed && textSelectionDelegate.hasSelectedText) + ContextMenuButtonItem(onPressed: () => textSelectionDelegate.copyTextSelection(), label: 'Copy'), if (!textSelectionDelegate.isSelectingAllText) - RawMaterialButton( - visualDensity: VisualDensity.compact, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: - textSelectionDelegate.isSelectingAllText - ? null - : () { - textSelectionDelegate.selectAllText(); - }, - child: const Text('Select All'), - ), + ContextMenuButtonItem(onPressed: () => textSelectionDelegate.selectAllText(), label: 'Select All'), ], ), ); @@ -2361,7 +2405,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _onSelectionHandlePanUpdate(_TextSelectionPart handle, DragUpdateDetails details) { if (_isInteractionGoingOn) return; - _textSelectionContextMenuGlobalPosition = null; + _textSelectionContextMenuDocumentPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); } @@ -2393,7 +2437,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix void _clearTextSelections({bool invalidate = true}) { _selA = _selB = null; _textSelA = _textSelB = null; - _textSelectionContextMenuGlobalPosition = null; + _textSelectionContextMenuDocumentPosition = null; _isSelectingAllText = false; _updateTextSelection(invalidate: invalidate); } @@ -2426,6 +2470,9 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix return null; } + @override + bool get hasSelectedText => _selA != null && _selB != null; + @override Future> getSelectedTextRanges() async { final a = _selA; @@ -2498,7 +2545,7 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _selA = _selB = null; } _textSelA = _textSelB = null; - _textSelectionContextMenuGlobalPosition = _documentToGlobal(_centerPosition); + _textSelectionContextMenuDocumentPosition = null; _selPartLastMoved = _TextSelectionPart.none; _isSelectingAllText = true; _updateTextSelection(); @@ -2562,6 +2609,52 @@ class _PdfViewerState extends State with SingleTickerProviderStateMix _clearTextSelections(); return result; } + + @override + Offset? offsetToLocal(BuildContext context, Offset? position) { + if (position == null) return null; + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) return null; + final global = _documentToGlobal(position); + if (global == null) return null; + return renderBox.globalToLocal(global); + } + + @override + Rect? rectToLocal(BuildContext context, Rect? rect) { + if (rect == null) return null; + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) return null; + final globalTopLeft = _documentToGlobal(rect.topLeft); + final globalBottomRight = _documentToGlobal(rect.bottomRight); + if (globalTopLeft == null || globalBottomRight == null) return null; + return Rect.fromPoints(renderBox.globalToLocal(globalTopLeft), renderBox.globalToLocal(globalBottomRight)); + } + + @override + Offset? offsetToDocument(BuildContext context, Offset? position) { + if (position == null) return null; + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) return null; + final global = renderBox.localToGlobal(position); + return _globalToDocument(global); + } + + @override + Rect? rectToDocument(BuildContext context, Rect? rect) { + if (rect == null) return null; + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) return null; + final globalTopLeft = renderBox.localToGlobal(rect.topLeft); + final globalBottomRight = renderBox.localToGlobal(rect.bottomRight); + final docTopLeft = _globalToDocument(globalTopLeft); + final docBottomRight = _globalToDocument(globalBottomRight); + if (docTopLeft == null || docBottomRight == null) return null; + return Rect.fromPoints(docTopLeft, docBottomRight); + } + + @override + DocumentCoordinateConverter get doc2local => this; } class _PdfPageImageCache { @@ -2733,6 +2826,8 @@ class PdfTextSelectionAnchor { const PdfTextSelectionAnchor(this.rect, this.direction, this.type, this.page, this.index); /// The rectangle of the character, on which the anchor is associated to. + /// + /// This rectangle is in the document coordinates. final Rect rect; /// The text direction of the anchored character. @@ -3178,6 +3273,9 @@ class PdfViewerController extends ValueListenable { /// Converts the local position in the PDF document structure to the global position. Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); + /// Converts document coordinates to local coordinates. + DocumentCoordinateConverter get doc2local => _state; + /// Provided to workaround certain widgets eating wheel events. Use with [Listener.onPointerSignal]. void handlePointerSignalEvent(PointerSignalEvent event) { if (event is PointerScrollEvent) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 7985d147..f077c3d3 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -749,58 +749,39 @@ class PdfTextSelectionParams { /// Function to build the text selection context menu. /// +/// [anchorA], [anchorB] are the offsets of the text selection anchors in the local coordinates, which are normally +/// directly corresponding to the `primaryAnchor` and `secondaryAnchor` of [TextSelectionToolbarAnchors] if you use +/// [AdaptiveTextSelectionToolbar.buttonItems]. +/// /// [a], [b] are the text selection anchors that represent the selected text range. /// /// [textSelectionDelegate] provides access to the text selection actions such as copy and clear selection. /// Please note that the function does not copy the text if [PdfTextSelectionDelegate.isCopyAllowed] is false and -/// use of [PdfTextSelectionDelegate.selectedText] is also restricted by the same condition. +/// use of [PdfTextSelectionDelegate.getSelectedText]/[PdfTextSelectionDelegate.getSelectedTextRanges] is also restricted by the same condition. /// /// [dismissContextMenu] is the function to dismiss the context menu. /// -/// The following fragment is a simple example to build a context menu with copy and select all actions: +/// The following fragment is a simple example to build a context menu with "Copy" and "Select All" actions: /// /// ```dart /// Widget? _buildTextSelectionContextMenu( /// BuildContext context, -/// PdfTextSelectionAnchor a, -/// PdfTextSelectionAnchor b, +/// Offset anchorA, +/// Offset? anchorB, +/// PdfTextSelectionAnchor? a, +/// PdfTextSelectionAnchor? b, /// PdfTextSelectionDelegate textSelectionDelegate, /// void Function() dismissContextMenu, /// ) { -/// return Container( -/// decoration: BoxDecoration( -/// color: Colors.white, -/// boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], -/// ), -/// padding: const EdgeInsets.all(2), -/// child: Column( -/// mainAxisSize: MainAxisSize.min, -/// children: [ -/// RawMaterialButton( -/// visualDensity: VisualDensity.compact, -/// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, -/// onPressed: -/// textSelectionDelegate.isCopyAllowed -/// ? () { -/// if (textSelectionDelegate.isCopyAllowed) { -/// textSelectionDelegate.copyTextSelection(); -/// } -/// } -/// : null, -/// child: Text( -/// 'Copy', -/// style: TextStyle(color: textSelectionDelegate.isCopyAllowed ? Colors.black : Colors.grey), -/// ), -/// ), -/// if (textSelectionDelegate.isCopyAllowed) -/// RawMaterialButton( -/// visualDensity: VisualDensity.compact, -/// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, -/// onPressed: () { -/// textSelectionDelegate.selectAllText(); -/// }, -/// child: const Text('Select All'), -/// ), +/// return Align( +/// alignment: Alignment.topLeft, +/// child: AdaptiveTextSelectionToolbar.buttonItems( +/// anchors: TextSelectionToolbarAnchors(primaryAnchor: anchorA, secondaryAnchor: anchorB), +/// buttonItems: [ +/// if (textSelectionDelegate.isCopyAllowed && textSelectionDelegate.hasSelectedText) +/// ContextMenuButtonItem(onPressed: () => textSelectionDelegate.copyTextSelection(), label: 'Copy'), +/// if (!textSelectionDelegate.isSelectingAllText) +/// ContextMenuButtonItem(onPressed: () => textSelectionDelegate.selectAllText(), label: 'Select All'), /// ], /// ), /// ); @@ -809,8 +790,10 @@ class PdfTextSelectionParams { typedef PdfViewerTextSelectionContextMenuBuilder = Widget? Function( BuildContext context, - PdfTextSelectionAnchor a, - PdfTextSelectionAnchor b, + Offset anchorA, + Offset? anchorB, + PdfTextSelectionAnchor? a, + PdfTextSelectionAnchor? b, PdfTextSelectionDelegate textSelectionDelegate, void Function() dismissContextMenu, ); @@ -836,6 +819,9 @@ abstract class PdfTextSelection { /// Whether the copy action is allowed. bool get isCopyAllowed; + /// Whether the viewer has selected text. + bool get hasSelectedText; + /// Whether the viewer is currently selecting all text. bool get isSelectingAllText; @@ -871,6 +857,24 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { /// /// Please note that [position] is in document coordinates. Future selectWord(Offset position); + + /// Convert document coordinates to local coordinates and vice versa. + DocumentCoordinateConverter get doc2local; +} + +/// Utility class to convert document coordinates to local coordinates and vice versa. +abstract class DocumentCoordinateConverter { + /// Convert a document position to a local position in the specified [context]. + Offset? offsetToLocal(BuildContext context, Offset? position); + + /// Convert a document rectangle to a local rectangle in the specified [context]. + Rect? rectToLocal(BuildContext context, Rect? rect); + + /// Convert a local position in the specified [context] to a document position. + Offset? offsetToDocument(BuildContext context, Offset? position); + + /// Convert a local rectangle in the specified [context] to a document rectangle. + Rect? rectToDocument(BuildContext context, Rect? rect); } /// Parameters for the text selection magnifier. From 15406169d4b3d1a9668697a96c622aebd1fb3375 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Jul 2025 04:06:11 +0900 Subject: [PATCH 234/663] Release pdfrx v2.0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IMPROVED: Enhanced text selection context menu API with better anchor positioning and adaptive toolbar support 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 52e45735..6bc364d6 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.3 + +- IMPROVED: Enhanced text selection context menu API with better anchor positioning and adaptive toolbar support + # 2.0.2 - BREAKING CHANGE: Renamed `PdfTextSelection.getSelectedTextRange()` to `getSelectedTextRanges()` for consistency diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 71f9eec7..dd42e458 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.2 + pdfrx: ^2.0.3 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 3b6ae970..977b4a2c 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.2 +version: 2.0.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 700524063951bbd09e315165a569a48622e2052b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Jul 2025 22:20:24 +0900 Subject: [PATCH 235/663] FIXED: #426 GestureDetector for text selection should ignore events from touchpad to not interrupt touch-to-scroll --- .../pdfrx/example/viewer/macos/Podfile.lock | 26 +++++++++++++++++++ .../macos/Runner.xcodeproj/project.pbxproj | 18 +++++++++++++ packages/pdfrx/example/viewer/pubspec.lock | 2 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 6 +++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 44d40972..e79442df 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_macos (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfrx (0.0.6): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + pdfrx: + :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index 9263006c..dbb99d4a 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -242,6 +242,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 6A47EF1A6FB31E6B5F759E4B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -367,6 +368,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 6A47EF1A6FB31E6B5F759E4B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9C1586A419FF5DDDD0DFC35A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 1ba41166..60676410 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.2" + version: "2.0.3" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 785fde12..0bb3bd41 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -488,6 +488,12 @@ class _PdfViewerState extends State onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, + supportedDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.touch, + PointerDeviceKind.invertedStylus, + }, child: pages, ), ), From 5d97e5da554f458dc788703492442f1f482f79ec Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 24 Jul 2025 23:43:36 +0900 Subject: [PATCH 236/663] Release pdfrx v2.0.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 6bc364d6..e7bf5bfd 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.4 + +- FIXED: GestureDetector for text selection now ignores touchpad events to prevent interference with touch-to-scroll ([#426](https://github.com/espresso3389/pdfrx/issues/426)) + # 2.0.3 - IMPROVED: Enhanced text selection context menu API with better anchor positioning and adaptive toolbar support diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index dd42e458..a05ce7de 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.3 + pdfrx: ^2.0.4 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 977b4a2c..4b375c74 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.3 +version: 2.0.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 240a0ba3c31eb5263d348f98f8a400b0cd34a115 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 25 Jul 2025 01:22:54 +0900 Subject: [PATCH 237/663] WIP --- CLAUDE.md | 22 ++++++++++++---------- README.md | 4 ++-- packages/pdfrx/example/viewer/pubspec.lock | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c873451a..fa7bc8ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,7 +91,8 @@ Both packages may need to be released when changes are made: 2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes - Don't mention CI/CD changes and `CLAUDE.md` related changes (unless they are significant) 3. Update `packages/pdfrx_engine/README.md` if needed -4. Run `dart pub publish` in `packages/pdfrx_engine/` +4. Update `README.md` on the repo root if needed +5. Run `dart pub publish` in `packages/pdfrx_engine/` ### For pdfrx package updates @@ -102,22 +103,23 @@ Both packages may need to be released when changes are made: - Changes version in example fragments - Consider to add notes for new features or breaking changes - Notify the owner if you find any issues with the example app or documentation -4. Run `melos bootstrap` to update all dependencies -5. Run tests to ensure everything works +4. Update `README.md` on the repo root if needed +5. Run `melos bootstrap` to update all dependencies +6. Run tests to ensure everything works - Run `dart test` in `packages/pdfrx_engine/` - Run `flutter test` in `packages/pdfrx/` -6. Ensure the example app builds correctly +7. Ensure the example app builds correctly - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app -7. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" -8. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` -9. Push changes and tags to remote -10. Run `flutter pub publish` in `packages/pdfrx/` -11. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release +8. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" +9. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` +10. Push changes and tags to remote +11. Run `flutter pub publish` in `packages/pdfrx/` +12. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - If the PR references issues, please also comment on the issues - Follow the template below for comments (but modify it as needed): - ``` + ```md The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). ...Fix/update summary... diff --git a/README.md b/README.md index 1f3b7d26..c214f8b7 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.0.0 + pdfrx: ^2.0.4 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.8 + pdfrx_engine: ^0.1.9 ``` ## Development diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 60676410..59b24a61 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.3" + version: "2.0.4" pdfrx_engine: dependency: "direct overridden" description: From e90dbfce85b9dc2ed7a1ec04a9badb05fe017858 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 03:15:39 +0900 Subject: [PATCH 238/663] Now selection handle is shown according to pointing device --- .../pdfrx/lib/src/utils/native/native.dart | 14 - packages/pdfrx/lib/src/utils/web/web.dart | 14 - .../pdfrx/lib/src/widgets/pdf_viewer.dart | 462 +++++++++++------- .../lib/src/widgets/pdf_viewer_params.dart | 295 +++++++---- 4 files changed, 483 insertions(+), 302 deletions(-) diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 884e338d..6fc03997 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -24,17 +24,3 @@ Future getCacheDirectory() async => (await getTemporaryDirectory()).path /// Override for the [PdfDocumentFactory] for native platforms; it is null. PdfDocumentFactory? get pdfDocumentFactoryOverride => null; - -abstract class PlatformBehaviorDefaults { - /// Whether text selection should be triggered by swipe gestures or not. - static bool get shouldTextSelectionTriggeredBySwipe => !isMobile; - - /// Whether to show text selection handles. - static bool get shouldShowTextSelectionHandles => isMobile; - - /// Whether to automatically show context menu on text selection. - static bool get showContextMenuAutomatically => isMobile; - - /// Whether to show text selection magnifier. - static bool get shouldShowTextSelectionMagnifier => isMobile; -} diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index e51585d6..d8f39bf7 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -24,17 +24,3 @@ Future getCacheDirectory() async => throw UnimplementedError('No tempora PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; final _factoryWasm = PdfDocumentFactoryWasmImpl(); - -abstract class PlatformBehaviorDefaults { - /// Whether text selection should be triggered by swipe gestures or not. - static bool get shouldTextSelectionTriggeredBySwipe => false; - - /// Whether to show text selection handles. - static bool get shouldShowTextSelectionHandles => true; - - /// Whether to automatically show context menu on text selection. - static bool get showContextMenuAutomatically => true; - - /// Whether to show text selection magnifier. - static bool get shouldShowTextSelectionMagnifier => true; -} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 0bb3bd41..12990440 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -254,14 +254,17 @@ class _PdfViewerState extends State _TextSelectionPart _selPartLastMoved = _TextSelectionPart.none; bool _isSelectingAllText = false; + PointerDeviceKind? _selectionPointerDeviceKind; - Offset? _textSelectionContextMenuDocumentPosition; + Offset? _contextMenuDocumentPosition; + PdfViewerPart _contextMenuFor = PdfViewerPart.background; Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; BuildContext? _contextForFocusNode; - Offset _mouseCursorOffset = Offset.zero; + Offset _pointerOffset = Offset.zero; + PointerDeviceKind? _pointerDeviceKind; @override void initState() { @@ -427,74 +430,70 @@ class _PdfViewerState extends State _contextForFocusNode = context; return LayoutBuilder( builder: (context, constraints) { - final enableTextSelection = widget.params.textSelectionParams?.enabled ?? true; final isCopyTextEnabled = _document!.permissions?.allowsCopying != false; - final enableSwipeToSelectText = - widget.params.textSelectionParams?.textSelectionTriggeredBySwipe ?? - PlatformBehaviorDefaults.shouldTextSelectionTriggeredBySwipe; final viewSize = Size(constraints.maxWidth, constraints.maxHeight); _updateLayout(viewSize); - final pages = CustomPaint( - foregroundPainter: _CustomPainter.fromFunctions( - _paintPages, - hitTestFunction: _hitTestForTextSelection, - ), - size: _layout!.documentSize, - ); - return Listener( - onPointerDown: (event) => _mouseCursorOffset = event.localPosition, - onPointerMove: (event) => _mouseCursorOffset = event.localPosition, - onPointerUp: (event) => _mouseCursorOffset = event.localPosition, + onPointerDown: (details) => _handlePointerEvent(details, details.localPosition, details.kind), + onPointerMove: (details) => _handlePointerEvent(details, details.localPosition, details.kind), + onPointerUp: (details) => _handlePointerEvent(details, details.localPosition, details.kind), + onPointerHover: (event) => _handlePointerEvent(event, event.localPosition, event.kind), child: Stack( children: [ - GestureDetector( - onSecondaryTapUp: widget.params.textSelectionParams?.textSecondaryTapUp ?? _textSecondaryTapUp, - child: iv.InteractiveViewer( - transformationController: _txController, - constrained: false, - boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), - maxScale: widget.params.maxScale, - minScale: minScale, - panAxis: widget.params.panAxis, - panEnabled: widget.params.panEnabled, - scaleEnabled: widget.params.scaleEnabled, - onInteractionEnd: _onInteractionEnd, - onInteractionStart: _onInteractionStart, - onInteractionUpdate: widget.params.onInteractionUpdate, - interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, - onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, - // PDF pages + iv.InteractiveViewer( + transformationController: _txController, + constrained: false, + boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), + maxScale: widget.params.maxScale, + minScale: minScale, + panAxis: widget.params.panAxis, + panEnabled: widget.params.panEnabled, + scaleEnabled: widget.params.scaleEnabled, + onInteractionEnd: _onInteractionEnd, + onInteractionStart: _onInteractionStart, + onInteractionUpdate: widget.params.onInteractionUpdate, + interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, + onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + // PDF pages + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapUp: (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.tap), + onDoubleTapDown: + (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.doubleTap), + onLongPressStart: + (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.longPress), + onSecondaryTapUp: + (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.secondaryTap), child: - !enableTextSelection - ? pages + !isTextSelectionEnabled + // show PDF pages without text selection + ? CustomPaint( + foregroundPainter: _CustomPainter.fromFunctions(_paintPages), + size: _layout!.documentSize, + ) + // show PDF pages with text selection : MouseRegion( - cursor: SystemMouseCursors.move, + cursor: SystemMouseCursors.text, hitTestBehavior: HitTestBehavior.deferToChild, - child: MouseRegion( - cursor: SystemMouseCursors.text, - hitTestBehavior: HitTestBehavior.deferToChild, - child: GestureDetector( - onTapDown: widget.params.textSelectionParams?.textTap ?? _textTap, - onDoubleTapDown: - widget.params.textSelectionParams?.textDoubleTap ?? _textDoubleTap, - onLongPressStart: - widget.params.textSelectionParams?.textLongPress ?? _textLongPress, - onSecondaryTapUp: - widget.params.textSelectionParams?.textSecondaryTapUp ?? - _textSecondaryTapUp, - onPanStart: enableSwipeToSelectText ? _onTextPanStart : null, - onPanUpdate: enableSwipeToSelectText ? _onTextPanUpdate : null, - onPanEnd: enableSwipeToSelectText ? _onTextPanEnd : null, - supportedDevices: { - PointerDeviceKind.mouse, - PointerDeviceKind.stylus, - PointerDeviceKind.touch, - PointerDeviceKind.invertedStylus, - }, - child: pages, + child: GestureDetector( + onPanStart: enableSelectionHandles ? null : _onTextPanStart, + onPanUpdate: enableSelectionHandles ? null : _onTextPanUpdate, + onPanEnd: enableSelectionHandles ? null : _onTextPanEnd, + supportedDevices: { + // PointerDeviceKind.trackpad is intentionally not included here + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.touch, + PointerDeviceKind.invertedStylus, + }, + child: CustomPaint( + painter: _CustomPainter.fromFunctions( + _paintPages, + hitTestFunction: _hitTestForTextSelection, + ), + size: _layout!.documentSize, ), ), ), @@ -506,8 +505,7 @@ class _PdfViewerState extends State if (_initialized && widget.params.viewerOverlayBuilder != null) ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap) .map((e) => e), - if (_initialized && enableTextSelection) - ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), + if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), ], ), ); @@ -1087,6 +1085,8 @@ class _PdfViewerState extends State } bool _hitTestForTextSelection(ui.Offset position) { + if (_selPartMoving != _TextSelectionPart.free && enableSelectionHandles) return false; + for (int i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(position)) continue; @@ -1638,24 +1638,63 @@ class _PdfViewerState extends State _getFocusNode()?.requestFocus(); } - void _textTap(TapDownDetails details) { - if (_isInteractionGoingOn) return; - _clearTextSelections(); - _requestFocus(); + void _handlePointerEvent(PointerEvent event, Offset localPosition, PointerDeviceKind? deviceKind) { + _pointerOffset = localPosition; + if (_pointerDeviceKind != deviceKind) { + _pointerDeviceKind = deviceKind; + _invalidate(); + } } - void _textDoubleTap(TapDownDetails details) { - _requestFocus(); + PdfViewerPart _onWhat(Offset localPosition) { + final p = _findTextAndIndexForPoint(localPosition); + if (p != null) { + if (_selA != null && _selB != null && _selA! <= p && p <= _selB!) { + return PdfViewerPart.selectedText; + } + return PdfViewerPart.nonSelectedText; + } + return PdfViewerPart.background; } - void _textLongPress(LongPressStartDetails details) { - if (_isInteractionGoingOn) return; - selectWord(details.localPosition); + void _handleGeneralTap(Offset globalPosition, PdfViewerGeneralTapType type) { + final docPosition = _globalToDocument(globalPosition); + final what = _onWhat(docPosition!); _requestFocus(); + + if (widget.params.onGeneralTap != null) { + final localPosition = doc2local.offsetToLocal(context, docPosition)!; + if (widget.params.onGeneralTap!( + _contextForFocusNode!, + _controller!, + PdfViewerGeneralTapHandlerDetails( + type: type, + localPosition: localPosition, + documentPosition: docPosition, + tapOn: what, + ), + )) { + return; // the tap was handled + } + } + switch (type) { + case PdfViewerGeneralTapType.tap: + _clearTextSelections(); + case PdfViewerGeneralTapType.longPress: + if (what == PdfViewerPart.nonSelectedText && isTextSelectionEnabled) { + selectWord(docPosition, deviceKind: _pointerDeviceKind); + } else { + showContextMenu(docPosition, forPart: what); + } + case PdfViewerGeneralTapType.secondaryTap: + showContextMenu(docPosition, forPart: what); + default: + } } - void _textSecondaryTapUp(TapUpDetails details) { - _textSelectionContextMenuDocumentPosition = _globalToDocument(details.globalPosition); + void showContextMenu(Offset docPosition, {PdfViewerPart? forPart}) { + _contextMenuDocumentPosition = docPosition; + _contextMenuFor = forPart ?? _onWhat(docPosition); _invalidate(); } @@ -1663,7 +1702,7 @@ class _PdfViewerState extends State if (_isInteractionGoingOn) return; _selPartMoving = _TextSelectionPart.free; _isSelectingAllText = false; - _textSelectionContextMenuDocumentPosition = null; + _contextMenuDocumentPosition = null; _selA = _findTextAndIndexForPoint(details.localPosition); _textSelectAnchor = Offset(_txController.value.x, _txController.value.y); _selB = null; @@ -1673,6 +1712,7 @@ class _PdfViewerState extends State void _onTextPanUpdate(DragUpdateDetails details) { _updateTextSelectRectTo(details.localPosition); + _selectionPointerDeviceKind = _pointerDeviceKind; } void _onTextPanEnd(DragEndDetails details) { @@ -1694,53 +1734,60 @@ class _PdfViewerState extends State } void _updateTextSelection({bool invalidate = true}) { - final a = _selA; - final b = _selB; - if (a == null || b == null) { - _textSelA = _textSelB = null; - } else if (a.text.pageNumber == b.text.pageNumber) { - final page = _document!.pages[a.text.pageNumber - 1]; - final pageRect = _layout!.pageLayouts[a.text.pageNumber - 1]; - final range = a.text.getRangeFromAB(a.index, b.index); - _textSelA = PdfTextSelectionAnchor( - a.text.charRects[range.start].toRectInDocument(page: page, pageRect: pageRect), - range.firstFragment?.direction ?? PdfTextDirection.ltr, - PdfTextSelectionAnchorType.a, - a.text, - a.index, - ); - _textSelB = PdfTextSelectionAnchor( - a.text.charRects[range.end - 1].toRectInDocument(page: page, pageRect: pageRect), - range.lastFragment?.direction ?? PdfTextDirection.ltr, - PdfTextSelectionAnchorType.b, - a.text, - b.index, - ); + if (isTextSelectionEnabled) { + final a = _selA; + final b = _selB; + if (a == null || b == null) { + _textSelA = _textSelB = null; + } else if (a.text.pageNumber == b.text.pageNumber) { + final page = _document!.pages[a.text.pageNumber - 1]; + final pageRect = _layout!.pageLayouts[a.text.pageNumber - 1]; + final range = a.text.getRangeFromAB(a.index, b.index); + _textSelA = PdfTextSelectionAnchor( + a.text.charRects[range.start].toRectInDocument(page: page, pageRect: pageRect), + range.firstFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.a, + a.text, + a.index, + ); + _textSelB = PdfTextSelectionAnchor( + a.text.charRects[range.end - 1].toRectInDocument(page: page, pageRect: pageRect), + range.lastFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.b, + a.text, + b.index, + ); + } else { + final first = a.text.pageNumber < b.text.pageNumber ? a : b; + final second = a.text.pageNumber < b.text.pageNumber ? b : a; + final rangeA = PdfPageTextRange(pageText: first.text, start: first.index, end: first.text.charRects.length); + _textSelA = PdfTextSelectionAnchor( + first.text.charRects[first.index].toRectInDocument( + page: _document!.pages[first.text.pageNumber - 1], + pageRect: _layout!.pageLayouts[first.text.pageNumber - 1], + ), + rangeA.firstFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.a, + first.text, + first.index, + ); + final rangeB = PdfPageTextRange(pageText: second.text, start: 0, end: second.index + 1); + _textSelB = PdfTextSelectionAnchor( + second.text.charRects[second.index].toRectInDocument( + page: _document!.pages[second.text.pageNumber - 1], + pageRect: _layout!.pageLayouts[second.text.pageNumber - 1], + ), + rangeB.lastFragment?.direction ?? PdfTextDirection.ltr, + PdfTextSelectionAnchorType.b, + second.text, + second.index, + ); + } } else { - final first = a.text.pageNumber < b.text.pageNumber ? a : b; - final second = a.text.pageNumber < b.text.pageNumber ? b : a; - final rangeA = PdfPageTextRange(pageText: first.text, start: first.index, end: first.text.charRects.length); - _textSelA = PdfTextSelectionAnchor( - first.text.charRects[first.index].toRectInDocument( - page: _document!.pages[first.text.pageNumber - 1], - pageRect: _layout!.pageLayouts[first.text.pageNumber - 1], - ), - rangeA.firstFragment?.direction ?? PdfTextDirection.ltr, - PdfTextSelectionAnchorType.a, - first.text, - first.index, - ); - final rangeB = PdfPageTextRange(pageText: second.text, start: 0, end: second.index + 1); - _textSelB = PdfTextSelectionAnchor( - second.text.charRects[second.index].toRectInDocument( - page: _document!.pages[second.text.pageNumber - 1], - pageRect: _layout!.pageLayouts[second.text.pageNumber - 1], - ), - rangeB.lastFragment?.direction ?? PdfTextDirection.ltr, - PdfTextSelectionAnchorType.b, - second.text, - second.index, - ); + _selA = _selB = null; + _textSelA = _textSelB = null; + _contextMenuDocumentPosition = null; + _isSelectingAllText = false; } if (invalidate) { @@ -1791,23 +1838,42 @@ class _PdfViewerState extends State Rect? _contextMenuRect; _TextSelectionPart _hoverOn = _TextSelectionPart.none; + bool get enableSelectionHandles => + widget.params.textSelectionParams?.enableSelectionHandles ?? _pointerDeviceKind == PointerDeviceKind.touch; + List _placeTextSelectionWidgets(BuildContext context, Size viewSize, bool isCopyTextEnabled) { - Widget? createContextMenu(Offset? a, Offset? b) { + Widget? createContextMenu(Offset? a, Offset? b, PdfViewerPart contextMenuFor) { if (a == null) return null; - final ctxMenuBuilder = widget.params.textSelectionParams?.buildContextMenu ?? _buildTextSelectionContextMenu; - return ctxMenuBuilder(context, a, b, _textSelA, _textSelB, this, () { - _textSelectionContextMenuDocumentPosition = null; - _invalidate(); - }); + final ctxMenuBuilder = widget.params.buildContextMenu ?? _buildContextMenu; + return ctxMenuBuilder( + context, + PdfViewerContextMenuBuilderParams( + isTextSelectionEnabled: isTextSelectionEnabled, + anchorA: a, + anchorB: b, + a: _textSelA, + b: _textSelB, + contextMenuFor: contextMenuFor, + textSelectionDelegate: this, + dismissContextMenu: () { + _contextMenuDocumentPosition = null; + _invalidate(); + }, + ), + ); } List contextMenuIfNeeded() { - final contextMenu = createContextMenu(offsetToLocal(context, _textSelectionContextMenuDocumentPosition), null); + final contextMenu = createContextMenu( + offsetToLocal(context, _contextMenuDocumentPosition), + null, + _contextMenuFor, + ); return [if (contextMenu != null) contextMenu]; } final renderBox = _renderBox; - if (renderBox == null || _textSelA == null || _textSelB == null) { + if (!isTextSelectionEnabled || renderBox == null || _textSelA == null || _textSelB == null) { return contextMenuIfNeeded(); } @@ -1817,15 +1883,12 @@ class _PdfViewerState extends State return contextMenuIfNeeded(); } - final enableSelectionHandles = - widget.params.textSelectionParams?.enableSelectionHandles ?? - PlatformBehaviorDefaults.shouldShowTextSelectionHandles; - double? aLeft, aTop, aRight, aBottom; double? bLeft, bTop, bRight; Widget? anchorA, anchorB; - if (enableSelectionHandles && _selPartMoving != _TextSelectionPart.free) { + if ((enableSelectionHandles || _selectionPointerDeviceKind == PointerDeviceKind.touch) && + _selPartMoving != _TextSelectionPart.free) { final builder = widget.params.textSelectionParams?.buildSelectionHandle ?? _buildDefaultSelectionHandle; if (_textSelA != null) { @@ -1878,15 +1941,9 @@ class _PdfViewerState extends State _anchorARect = _anchorBRect = null; } - bool abOrder() { - final v = _selA!.text.pageNumber - _selB!.text.pageNumber; - if (v != 0) return v < 0; - return _selA!.index < _selB!.index; - } - final textAnchorMoving = switch (_selPartMoving) { - _TextSelectionPart.a => abOrder() ? _TextSelectionPart.a : _TextSelectionPart.b, - _TextSelectionPart.b => abOrder() ? _TextSelectionPart.b : _TextSelectionPart.a, + _TextSelectionPart.a => _selA! < _selB! ? _TextSelectionPart.a : _TextSelectionPart.b, + _TextSelectionPart.b => _selA! < _selB! ? _TextSelectionPart.b : _TextSelectionPart.a, _ => _selPartMoving, }; @@ -1981,7 +2038,7 @@ class _PdfViewerState extends State final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = - (magnifierParams.enabled ?? PlatformBehaviorDefaults.shouldShowTextSelectionMagnifier) && + (magnifierParams.enabled ?? _selectionPointerDeviceKind == PointerDeviceKind.touch) && (magnifierParams.shouldBeShownForAnchor ?? _shouldBeShownForAnchor)( textAnchor, _controller!, @@ -2015,9 +2072,9 @@ class _PdfViewerState extends State final showContextMenuAutomatically = widget.params.textSelectionParams?.showContextMenuAutomatically ?? - PlatformBehaviorDefaults.showContextMenuAutomatically; + _selectionPointerDeviceKind == PointerDeviceKind.touch; bool showContextMenu = false; - if (_textSelectionContextMenuDocumentPosition != null) { + if (_contextMenuDocumentPosition != null) { showContextMenu = true; } else if (showContextMenuAutomatically && _textSelA != null && @@ -2025,20 +2082,19 @@ class _PdfViewerState extends State _selPartMoving == _TextSelectionPart.none) { // Show context menu on mobile when selection is not moving. showContextMenu = true; + _contextMenuFor = PdfViewerPart.selectedText; } Widget? contextMenu; if (showContextMenu && _selPartMoving == _TextSelectionPart.none && - (_textSelectionContextMenuDocumentPosition != null || + (_contextMenuDocumentPosition != null || _selPartLastMoved == _TextSelectionPart.a || _selPartLastMoved == _TextSelectionPart.b || _isSelectingAllText) && isCopyTextEnabled) { final localOffset = - _textSelectionContextMenuDocumentPosition != null - ? offsetToLocal(context, _textSelectionContextMenuDocumentPosition!) - : null; + _contextMenuDocumentPosition != null ? offsetToLocal(context, _contextMenuDocumentPosition!) : null; Offset? a, b; switch (Theme.of(context).platform) { @@ -2046,7 +2102,7 @@ class _PdfViewerState extends State case TargetPlatform.linux: case TargetPlatform.windows: case TargetPlatform.macOS: - a = _mouseCursorOffset; + a = _pointerOffset; if (_anchorARect != null && _anchorBRect != null) { switch (_textSelA?.direction) { case PdfTextDirection.ltr: @@ -2062,8 +2118,8 @@ class _PdfViewerState extends State } case PdfTextDirection.rtl: case PdfTextDirection.vrtl: - final distA = (_mouseCursorOffset - _anchorARect!.center).distanceSquared; - final distB = (_mouseCursorOffset - _anchorBRect!.center).distanceSquared; + final distA = (_pointerOffset - _anchorARect!.center).distanceSquared; + final distB = (_pointerOffset - _anchorBRect!.center).distanceSquared; if (distA < distB) { a = _anchorARect!.bottomLeft.translate(8, 8); } else { @@ -2086,7 +2142,7 @@ class _PdfViewerState extends State } } - contextMenu = createContextMenu(a, b); + contextMenu = createContextMenu(a, b, _contextMenuFor); if (contextMenu != null && !isPositionalWidget(contextMenu)) { final offset = localOffset != null @@ -2336,25 +2392,35 @@ class _PdfViewerState extends State ); } - Widget? _buildTextSelectionContextMenu( - BuildContext context, - Offset anchorA, - Offset? anchorB, - PdfTextSelectionAnchor? a, - PdfTextSelectionAnchor? b, - PdfTextSelectionDelegate textSelectionDelegate, - void Function() dismissContextMenu, - ) { + Widget? _buildContextMenu(BuildContext context, PdfViewerContextMenuBuilderParams params) { + final items = [ + if (params.isTextSelectionEnabled && + params.textSelectionDelegate.isCopyAllowed && + params.textSelectionDelegate.hasSelectedText) + ContextMenuButtonItem( + onPressed: () => params.textSelectionDelegate.copyTextSelection(), + label: 'Copy', + type: ContextMenuButtonType.copy, + ), + if (params.isTextSelectionEnabled && !params.textSelectionDelegate.isSelectingAllText) + ContextMenuButtonItem( + onPressed: () => params.textSelectionDelegate.selectAllText(), + label: 'Select All', + type: ContextMenuButtonType.selectAll, + ), + ]; + + widget.params.customizeContextMenuItems?.call(params, items); + + if (items.isEmpty) { + return null; + } + return Align( alignment: Alignment.topLeft, child: AdaptiveTextSelectionToolbar.buttonItems( - anchors: TextSelectionToolbarAnchors(primaryAnchor: anchorA, secondaryAnchor: anchorB), - buttonItems: [ - if (textSelectionDelegate.isCopyAllowed && textSelectionDelegate.hasSelectedText) - ContextMenuButtonItem(onPressed: () => textSelectionDelegate.copyTextSelection(), label: 'Copy'), - if (!textSelectionDelegate.isSelectingAllText) - ContextMenuButtonItem(onPressed: () => textSelectionDelegate.selectAllText(), label: 'Select All'), - ], + anchors: TextSelectionToolbarAnchors(primaryAnchor: params.anchorA, secondaryAnchor: params.anchorB), + buttonItems: items, ), ); } @@ -2411,7 +2477,7 @@ class _PdfViewerState extends State void _onSelectionHandlePanUpdate(_TextSelectionPart handle, DragUpdateDetails details) { if (_isInteractionGoingOn) return; - _textSelectionContextMenuDocumentPosition = null; + _contextMenuDocumentPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); } @@ -2443,7 +2509,7 @@ class _PdfViewerState extends State void _clearTextSelections({bool invalidate = true}) { _selA = _selB = null; _textSelA = _textSelB = null; - _textSelectionContextMenuDocumentPosition = null; + _contextMenuDocumentPosition = null; _isSelectingAllText = false; _updateTextSelection(invalidate: invalidate); } @@ -2510,6 +2576,9 @@ class _PdfViewerState extends State return selections.map((e) => e.text).join(); } + @override + bool get isTextSelectionEnabled => widget.params.textSelectionParams?.enabled ?? true; + @override bool get isCopyAllowed => _document!.permissions?.allowsCopying != false; @@ -2551,14 +2620,14 @@ class _PdfViewerState extends State _selA = _selB = null; } _textSelA = _textSelB = null; - _textSelectionContextMenuDocumentPosition = null; + _contextMenuDocumentPosition = null; _selPartLastMoved = _TextSelectionPart.none; _isSelectingAllText = true; _updateTextSelection(); } @override - Future selectWord(Offset offset) async { + Future selectWord(Offset offset, {PointerDeviceKind? deviceKind}) async { for (int i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(offset)) { @@ -2598,8 +2667,10 @@ class _PdfViewerState extends State break; } + _selPartMoving = _TextSelectionPart.none; _selPartLastMoved = _TextSelectionPart.a; _isSelectingAllText = false; + _selectionPointerDeviceKind = deviceKind; _notifyTextSelectionChange(); } @@ -2820,6 +2891,34 @@ class _TextSelectionPoint { final int index; final Offset point; + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! _TextSelectionPoint) return false; + return text == other.text && index == other.index && point == other.point; + } + + bool operator <(_TextSelectionPoint other) { + if (text.pageNumber != other.text.pageNumber) { + return text.pageNumber < other.text.pageNumber; + } + return index < other.index; + } + + bool operator >(_TextSelectionPoint other) => !(this <= other); + + bool operator <=(_TextSelectionPoint other) { + if (text.pageNumber != other.text.pageNumber) { + return text.pageNumber < other.text.pageNumber; + } + return index <= other.index; + } + + bool operator >=(_TextSelectionPoint other) => !(this < other); + + @override + int get hashCode => text.hashCode ^ index.hashCode ^ point.hashCode; + @override String toString() => '$_TextSelectionPoint(text: $text, index: $index, point: $point)'; } @@ -3242,7 +3341,7 @@ class PdfViewerController extends ValueListenable { /// Returns the hit-test result if the specified offset is inside a page; otherwise null. /// /// [useDocumentLayoutCoordinates] specifies whether the offset is in the document layout coordinates; - /// if true, the offset is in the document layout coordinates; otherwise, the offset is in the widget coordinates. + /// if true, the offset is in the document layout coordinates; otherwise, the offset is in the widget's local coordinates. PdfPageHitTestResult? getPdfPageHitTestResult(Offset offset, {required bool useDocumentLayoutCoordinates}) => _state._getPdfPageHitTestResult(offset, useDocumentLayoutCoordinates: useDocumentLayoutCoordinates); @@ -3498,15 +3597,6 @@ class _CanvasLinkPainter { return false; } - void _handleLinkMouseCursor(Offset position, void Function(void Function()) setState) { - final link = _findLinkAtPosition(position); - final newCursor = link == null ? MouseCursor.defer : SystemMouseCursors.click; - if (newCursor != _cursor) { - _cursor = newCursor; - setState(() {}); - } - } - /// Creates a [GestureDetector] for handling link taps and mouse cursor. Widget linkHandlingOverlay(Size size) { return GestureDetector( @@ -3517,13 +3607,19 @@ class _CanvasLinkPainter { builder: (context, setState) { return MouseRegion( hitTestBehavior: HitTestBehavior.translucent, - onHover: (event) => _handleLinkMouseCursor(event.localPosition, setState), + onHover: (event) { + final link = _findLinkAtPosition(event.localPosition); + final newCursor = link == null ? MouseCursor.defer : SystemMouseCursors.click; + if (newCursor != _cursor) { + _cursor = newCursor; + setState(() {}); + } + }, onExit: (event) { _cursor = MouseCursor.defer; setState(() {}); }, cursor: _cursor, - child: IgnorePointer(child: SizedBox(width: size.width, height: size.height)), ); }, ), diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index f077c3d3..e33987c1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -38,6 +38,8 @@ class PdfViewerParams { this.onInteractionStart, this.onInteractionUpdate, this.interactionEndFrictionCoefficient = _kDrag, + this.onSecondaryTapUp, + this.onLongPressStart, this.onDocumentChanged, this.calculateInitialPageNumber, this.calculateInitialZoom, @@ -61,6 +63,9 @@ class PdfViewerParams { this.linkWidgetBuilder, this.pagePaintCallbacks, this.pageBackgroundPaintCallbacks, + this.onGeneralTap, + this.buildContextMenu, + this.customizeContextMenuItems, this.onKey, this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.behaviorControlParams = const PdfViewerBehaviorControlParams(), @@ -234,6 +239,16 @@ class PdfViewerParams { /// See [InteractiveViewer.interactionEndFrictionCoefficient] for details. final double interactionEndFrictionCoefficient; + /// Function to call when the text is secondary tapped (right-click). + /// + /// By default, secondary tap on non-text area to open text context menu. + final void Function(TapUpDetails details)? onSecondaryTapUp; + + /// Function to call when the text is long pressed. + /// + /// By default, long press on non-text area to open text context menu. + final void Function(LongPressStartDetails details)? onLongPressStart; + // Used as the coefficient of friction in the inertial translation animation. // This value was eyeballed to give a feel similar to Google Photos. static const double _kDrag = 0.0000135; @@ -475,6 +490,31 @@ class PdfViewerParams { /// For the detail usage, see [PdfViewerPagePaintCallback]. final List? pageBackgroundPaintCallbacks; + /// Function to handle general tap events. + /// + /// This function is called when the user taps on the viewer. + /// It can be used to handle general tap events such as single tap, double tap, long press, etc. + /// The function returns true if it processes the tap; otherwise, returns false. + /// + /// When the function returns true, the tap is considered handled and the viewer does not process it further. + final PdfViewerGeneralTapHandler? onGeneralTap; + + /// Function to build context menu. + /// + /// - If the function returns null, no context menu is shown. + /// - If the function is null, the default context menu will be used. + /// + /// When you implement the function, you should consider whether to call [customizeContextMenuItems] internally + /// or not according to your use case. + final PdfViewerContextMenuBuilder? buildContextMenu; + + /// Function to customize the context menu items. + /// + /// This function is called when the context menu is built and can be used to customize the context menu items. + /// This function may not be called if the context menu is build using [buildContextMenu]. [buildContextMenu] is + /// responsible for building the context menu items (i.e. it should decide whether to call this function internally or not) + final PdfViewerContextMenuUpdateMenuItemsFunction? customizeContextMenuItems; + /// Function to handle key events. /// /// See [PdfViewerOnKeyCallback] for the details. @@ -555,6 +595,8 @@ class PdfViewerParams { other.onInteractionStart == onInteractionStart && other.onInteractionUpdate == onInteractionUpdate && other.interactionEndFrictionCoefficient == interactionEndFrictionCoefficient && + other.onSecondaryTapUp == onSecondaryTapUp && + other.onLongPressStart == onLongPressStart && other.onDocumentChanged == onDocumentChanged && other.calculateInitialPageNumber == calculateInitialPageNumber && other.calculateInitialZoom == calculateInitialZoom && @@ -577,6 +619,9 @@ class PdfViewerParams { other.linkWidgetBuilder == linkWidgetBuilder && other.pagePaintCallbacks == pagePaintCallbacks && other.pageBackgroundPaintCallbacks == pageBackgroundPaintCallbacks && + other.onGeneralTap == onGeneralTap && + other.buildContextMenu == buildContextMenu && + other.customizeContextMenuItems == customizeContextMenuItems && other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && @@ -608,6 +653,8 @@ class PdfViewerParams { onInteractionStart.hashCode ^ onInteractionUpdate.hashCode ^ interactionEndFrictionCoefficient.hashCode ^ + onSecondaryTapUp.hashCode ^ + onLongPressStart.hashCode ^ onDocumentChanged.hashCode ^ calculateInitialPageNumber.hashCode ^ calculateInitialZoom.hashCode ^ @@ -630,6 +677,9 @@ class PdfViewerParams { linkWidgetBuilder.hashCode ^ pagePaintCallbacks.hashCode ^ pageBackgroundPaintCallbacks.hashCode ^ + onGeneralTap.hashCode ^ + buildContextMenu.hashCode ^ + customizeContextMenuItems.hashCode ^ onKey.hashCode ^ keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ @@ -642,45 +692,29 @@ class PdfViewerParams { class PdfTextSelectionParams { const PdfTextSelectionParams({ this.enabled = true, - this.textSelectionTriggeredBySwipe, this.enableSelectionHandles, this.showContextMenuAutomatically, - this.buildContextMenu, this.buildSelectionHandle, this.onTextSelectionChange, - this.textTap, - this.textDoubleTap, - this.textLongPress, - this.textSecondaryTapUp, this.magnifier, }); /// Whether text selection is enabled. final bool enabled; - /// Whether text selection is triggered by swipe. - /// - /// null to determine the behavior based on the platform; enabled on desktop, disabled on mobile/Web. - final bool? textSelectionTriggeredBySwipe; - - /// Whether to show selection handles. + /// Whether to use selection handles or not. /// - /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop. + /// If true, drag-to-select is disabled and only the selection handles are used to select text. + /// null to determine the behavior based on pointing device. final bool? enableSelectionHandles; /// Whether to automatically show context menu on text selection. /// /// Normally, on desktop, the context menu is shown on right-click. /// If this is true, the context menu is shown on selection handle. - /// null to determine the behavior based on the platform; enabled on mobile/Web, disabled on desktop. + /// null to determine the behavior based on pointing device. final bool? showContextMenuAutomatically; - /// Function to build context menu for text selection. - /// - /// - If the function returns null, no context menu is shown. - /// - If the function is null, the default context menu will be used. - final PdfViewerTextSelectionContextMenuBuilder? buildContextMenu; - /// Function to build anchor handle for text selection. /// /// - If the function returns null, no anchor handle is shown. @@ -690,28 +724,6 @@ class PdfTextSelectionParams { /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; - /// Function to call when the text is tapped. - /// - /// By default, tapping on the text resets the text selection. - /// You can set empty function to disable the default behavior. - final void Function(TapDownDetails details)? textTap; - - /// Function to call when the text is double tapped. - /// - /// By default, double tapping on the text do nothing. - final void Function(TapDownDetails details)? textDoubleTap; - - /// Function to call when the text is long pressed. - /// - /// By default, long pressing on the text selects the word under the pointer. - /// You can set empty function to disable the default behavior. - final void Function(LongPressStartDetails details)? textLongPress; - - /// Function to call when the text is secondary tapped (right-click). - /// - /// By default, secondary tap on the text open the context menu. - final void Function(TapUpDetails details)? textSecondaryTapUp; - /// Parameters for the magnifier. final PdfViewerSelectionMagnifierParams? magnifier; @@ -719,14 +731,8 @@ class PdfTextSelectionParams { bool operator ==(Object other) { if (identical(this, other)) return true; return other is PdfTextSelectionParams && - other.textSelectionTriggeredBySwipe == textSelectionTriggeredBySwipe && - other.buildContextMenu == buildContextMenu && other.buildSelectionHandle == buildSelectionHandle && other.onTextSelectionChange == onTextSelectionChange && - other.textTap == textTap && - other.textDoubleTap == textDoubleTap && - other.textLongPress == textLongPress && - other.textSecondaryTapUp == textSecondaryTapUp && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && other.magnifier == magnifier; @@ -734,14 +740,8 @@ class PdfTextSelectionParams { @override int get hashCode => - textSelectionTriggeredBySwipe.hashCode ^ - buildContextMenu.hashCode ^ buildSelectionHandle.hashCode ^ onTextSelectionChange.hashCode ^ - textTap.hashCode ^ - textDoubleTap.hashCode ^ - textLongPress.hashCode ^ - textSecondaryTapUp.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ magnifier.hashCode; @@ -749,54 +749,112 @@ class PdfTextSelectionParams { /// Function to build the text selection context menu. /// -/// [anchorA], [anchorB] are the offsets of the text selection anchors in the local coordinates, which are normally -/// directly corresponding to the `primaryAnchor` and `secondaryAnchor` of [TextSelectionToolbarAnchors] if you use -/// [AdaptiveTextSelectionToolbar.buttonItems]. -/// -/// [a], [b] are the text selection anchors that represent the selected text range. -/// -/// [textSelectionDelegate] provides access to the text selection actions such as copy and clear selection. -/// Please note that the function does not copy the text if [PdfTextSelectionDelegate.isCopyAllowed] is false and -/// use of [PdfTextSelectionDelegate.getSelectedText]/[PdfTextSelectionDelegate.getSelectedTextRanges] is also restricted by the same condition. -/// -/// [dismissContextMenu] is the function to dismiss the context menu. -/// /// The following fragment is a simple example to build a context menu with "Copy" and "Select All" actions: /// /// ```dart /// Widget? _buildTextSelectionContextMenu( /// BuildContext context, -/// Offset anchorA, -/// Offset? anchorB, -/// PdfTextSelectionAnchor? a, -/// PdfTextSelectionAnchor? b, -/// PdfTextSelectionDelegate textSelectionDelegate, -/// void Function() dismissContextMenu, +/// PdfViewerTextSelectionContextMenuBuilderParams params, /// ) { +/// +/// final items = [ +/// if (params.isTextSelectionEnabled && +/// params.textSelectionDelegate.isCopyAllowed && +/// params.textSelectionDelegate.hasSelectedText) +/// ContextMenuButtonItem( +/// onPressed: () => params.textSelectionDelegate.copyTextSelection(), +/// label: 'Copy', +/// type: ContextMenuButtonType.copy, +/// ), +/// if (params.isTextSelectionEnabled && !params.textSelectionDelegate.isSelectingAllText) +/// ContextMenuButtonItem( +/// onPressed: () => params.textSelectionDelegate.selectAllText(), +/// label: 'Select All', +/// type: ContextMenuButtonType.selectAll, +/// ), +/// ]; +/// +/// widget.params.customizeContextMenuItems?.call(params, items); +/// +/// if (items.isEmpty) { +/// return null; +/// } +/// /// return Align( /// alignment: Alignment.topLeft, /// child: AdaptiveTextSelectionToolbar.buttonItems( -/// anchors: TextSelectionToolbarAnchors(primaryAnchor: anchorA, secondaryAnchor: anchorB), -/// buttonItems: [ -/// if (textSelectionDelegate.isCopyAllowed && textSelectionDelegate.hasSelectedText) -/// ContextMenuButtonItem(onPressed: () => textSelectionDelegate.copyTextSelection(), label: 'Copy'), -/// if (!textSelectionDelegate.isSelectingAllText) -/// ContextMenuButtonItem(onPressed: () => textSelectionDelegate.selectAllText(), label: 'Select All'), -/// ], +/// anchors: TextSelectionToolbarAnchors(primaryAnchor: params.anchorA, secondaryAnchor: params.anchorB), +/// buttonItems: items, /// ), /// ); /// } -/// ``` -typedef PdfViewerTextSelectionContextMenuBuilder = - Widget? Function( - BuildContext context, - Offset anchorA, - Offset? anchorB, - PdfTextSelectionAnchor? a, - PdfTextSelectionAnchor? b, - PdfTextSelectionDelegate textSelectionDelegate, - void Function() dismissContextMenu, - ); +/// +/// See [PdfViewerParams.customizeContextMenuItems] for more. +typedef PdfViewerContextMenuBuilder = Widget? Function(BuildContext context, PdfViewerContextMenuBuilderParams params); + +typedef PdfViewerContextMenuUpdateMenuItemsFunction = + void Function(PdfViewerContextMenuBuilderParams params, List items); + +/// Parameters for the text selection context menu builder. +/// +/// [anchorA], [anchorB] are the offsets of the text selection anchors in the local coordinates, which are normally +/// directly corresponding to the `primaryAnchor` and `secondaryAnchor` of [TextSelectionToolbarAnchors] if you use +/// [AdaptiveTextSelectionToolbar.buttonItems]. +/// +/// [a], [b] are the text selection anchors that represent the selected text range. +/// +/// [textSelectionDelegate] provides access to the text selection actions such as copy and clear selection. +/// Please note that the function does not copy the text if [PdfTextSelectionDelegate.isCopyAllowed] is false and +/// use of [PdfTextSelectionDelegate.getSelectedText]/[PdfTextSelectionDelegate.getSelectedTextRanges] is also restricted by the same condition. +/// +/// [dismissContextMenu] is the function to dismiss the context menu. +class PdfViewerContextMenuBuilderParams { + const PdfViewerContextMenuBuilderParams({ + required this.isTextSelectionEnabled, + required this.anchorA, + required this.textSelectionDelegate, + required this.dismissContextMenu, + required this.contextMenuFor, + this.anchorB, + this.a, + this.b, + }); + + final bool isTextSelectionEnabled; + + /// The primary anchor offset in the local coordinates. + final Offset anchorA; + + /// The secondary anchor offset in the local coordinates. + final Offset? anchorB; + + /// The primary text selection anchor. + final PdfTextSelectionAnchor? a; + + /// The secondary text selection anchor. + final PdfTextSelectionAnchor? b; + + /// The text selection delegate to access text selection actions. + final PdfTextSelectionDelegate textSelectionDelegate; + + /// For what target part the context menu will be built. + final PdfViewerPart contextMenuFor; + + /// Function to dismiss the context menu. + final void Function() dismissContextMenu; +} + +/// Where the user taps on. +enum PdfViewerPart { + /// Selected text. + selectedText, + + /// Non-selected text. + nonSelectedText, + + /// Background (it means either page area or outside of page area). + background, +} /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } @@ -816,6 +874,11 @@ typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection te /// Text selection abstract class PdfTextSelection { + /// Whether the text selection is enabled by the configuration. + /// + /// See [PdfTextSelectionParams.enabled]. + bool get isTextSelectionEnabled; + /// Whether the copy action is allowed. bool get isCopyAllowed; @@ -894,7 +957,7 @@ class PdfViewerSelectionMagnifierParams { /// Whether the magnifier is enabled. /// - /// If null, the magnifier is enabled by default on mobile/web and disabled on desktop. + /// null to determine the behavior based on pointing device. final bool? enabled; /// If the character size (in pt.) is smaller than this value, the magnifier will be shown. @@ -1101,6 +1164,42 @@ typedef PdfViewerOverlaysBuilder = /// typically it is [GestureDetector.onTapUp]'s [TapUpDetails.localPosition]. typedef PdfViewerHandleLinkTap = bool Function(Offset position); +/// Function to handle tap events. +/// +/// This function is called when the user taps on the viewer. +/// It can be used to handle general tap events such as single tap, double tap, long press, etc. +/// The function returns true if it processes the tap; otherwise, returns false. +/// +/// When the function returns true, the tap is considered handled and the viewer does not process it further. +typedef PdfViewerGeneralTapHandler = + bool Function(BuildContext context, PdfViewerController controller, PdfViewerGeneralTapHandlerDetails details); + +/// Describes the type of the tap. +class PdfViewerGeneralTapHandlerDetails { + const PdfViewerGeneralTapHandlerDetails({ + required this.localPosition, + required this.documentPosition, + required this.type, + required this.tapOn, + }); + + /// The global position of the tap. + final Offset localPosition; + + /// The document position of the tap. + final Offset documentPosition; + + /// The type of the tap. + /// + /// This is used to determine the type of the tap, such as single tap, double tap, long press, etc. + final PdfViewerGeneralTapType type; + + /// Where the tap is occurred on. + /// + /// This is used to determine where the tap is occurred, such as on selected text, non-selected text, or background. + final PdfViewerPart tapOn; +} + /// Function to build page overlays. /// /// [pageRect] is the rectangle of the page in the viewer. @@ -1296,6 +1395,20 @@ class PdfViewerKeyHandlerParams { parentNode.hashCode; } +enum PdfViewerGeneralTapType { + /// Tap gesture. + tap, + + /// Double tap gesture. + doubleTap, + + /// Long press gesture. + longPress, + + /// Secondary tap gesture. + secondaryTap, +} + /// Parameters to customize the behavior of the PDF viewer. /// /// These parameters are to tune the behavior/performance of the PDF viewer. From cf3bcbc2ac3981ce88a212117373c42679474d4f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 04:13:09 +0900 Subject: [PATCH 239/663] Release pdfrx v2.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 11 +++++++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/example/viewer/pubspec.lock | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c214f8b7..e82c8e29 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.0.4 + pdfrx: ^2.1.0 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e7bf5bfd..3bb25712 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,14 @@ +# 2.1.0 + +- BREAKING CHANGE: Text selection handles are now automatically shown/hidden based on the pointing device type used + - Touch input shows selection handles for precise control + - Non-touch input hides handles for cleaner interaction + - Removing some of text selection related parameters to simplify the API +- BREAKING CHANGE: Now context menu is not only for text selection but also for general tap events + - `PdfViewerParams.buildContextMenu` and `PdfViewerParams.customizeContextMenuItems` to customize context menu + - Introduces `PdfViewerContextMenuBuilderParams` (many context menu related parameters are moved to this class) +- BREAKING CHANGE: Tap handler functions are integrated into `PdfViewerParams.onGeneralTap` for better consistency + # 2.0.4 - FIXED: GestureDetector for text selection now ignores touchpad events to prevent interference with touch-to-scroll ([#426](https://github.com/espresso3389/pdfrx/issues/426)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index a05ce7de..ba80ac7e 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.0.4 + pdfrx: ^2.1.0 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock index 59b24a61..b1820305 100644 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ b/packages/pdfrx/example/viewer/pubspec.lock @@ -347,7 +347,7 @@ packages: path: "../.." relative: true source: path - version: "2.0.4" + version: "2.1.0" pdfrx_engine: dependency: "direct overridden" description: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4b375c74..8a64def0 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.0.4 +version: 2.1.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 833e41ba5564e3c1d641d40269deb5e538d3c6a3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 04:23:01 +0900 Subject: [PATCH 240/663] :( --- packages/pdfrx/example/viewer/pubspec.lock | 581 --------------------- packages/pdfrx/pubspec_overrides.yaml | 4 - 2 files changed, 585 deletions(-) delete mode 100644 packages/pdfrx/example/viewer/pubspec.lock delete mode 100644 packages/pdfrx/pubspec_overrides.yaml diff --git a/packages/pdfrx/example/viewer/pubspec.lock b/packages/pdfrx/example/viewer/pubspec.lock deleted file mode 100644 index b1820305..00000000 --- a/packages/pdfrx/example/viewer/pubspec.lock +++ /dev/null @@ -1,581 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dart_pubspec_licenses: - dependency: transitive - description: - name: dart_pubspec_licenses - sha256: "23ddb78ff9204d08e3109ced67cd3c6c6a066f581b0edf5ee092fc3e1127f4ea" - url: "https://pub.dev" - source: hosted - version: "3.0.4" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file_selector: - dependency: "direct main" - description: - name: file_selector - sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - file_selector_android: - dependency: transitive - description: - name: file_selector_android - sha256: "6bba3d590ee9462758879741abc132a19133600dd31832f55627442f1ebd7b54" - url: "https://pub.dev" - source: hosted - version: "0.5.1+14" - file_selector_ios: - dependency: transitive - description: - name: file_selector_ios - sha256: "94b98ad950b8d40d96fee8fa88640c2e4bd8afcdd4817993bd04e20310f45420" - url: "https://pub.dev" - source: hosted - version: "0.5.3+1" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" - url: "https://pub.dev" - source: hosted - version: "0.9.3+2" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" - url: "https://pub.dev" - source: hosted - version: "0.9.4+3" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b - url: "https://pub.dev" - source: hosted - version: "2.6.2" - file_selector_web: - dependency: transitive - description: - name: file_selector_web - sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 - url: "https://pub.dev" - source: hosted - version: "0.9.4+2" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" - url: "https://pub.dev" - source: hosted - version: "0.9.3+4" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - http: - dependency: transitive - description: - name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" - url: "https://pub.dev" - source: hosted - version: "4.5.4" - intl: - dependency: transitive - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" - url: "https://pub.dev" - source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.dev" - source: hosted - version: "2.2.17" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - pdfrx: - dependency: "direct main" - description: - path: "../.." - relative: true - source: path - version: "2.1.0" - pdfrx_engine: - dependency: "direct overridden" - description: - path: "../../../pdfrx_engine" - relative: true - source: path - version: "0.1.9" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - rxdart: - dependency: "direct main" - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - synchronized: - dependency: "direct main" - description: - name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" - url: "https://pub.dev" - source: hosted - version: "6.3.1" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" - url: "https://pub.dev" - source: hosted - version: "6.3.16" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" - url: "https://pub.dev" - source: hosted - version: "6.3.3" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" - url: "https://pub.dev" - source: hosted - version: "3.2.2" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.27.0" diff --git a/packages/pdfrx/pubspec_overrides.yaml b/packages/pdfrx/pubspec_overrides.yaml deleted file mode 100644 index 1a6ef295..00000000 --- a/packages/pdfrx/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# melos_managed_dependency_overrides: pdfrx_engine -dependency_overrides: - pdfrx_engine: - path: ../pdfrx_engine From fd5cf1ea667da3e79fd74ac29b5cf08860dcd09a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 12:52:50 +0900 Subject: [PATCH 241/663] Add mock pdfrxInitialize impl. to deal with pub.dev analyzer's complaint --- packages/pdfrx_engine/.gitignore | 3 +++ packages/pdfrx_engine/lib/pdfrx_engine.dart | 2 +- .../pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart diff --git a/packages/pdfrx_engine/.gitignore b/packages/pdfrx_engine/.gitignore index f76622cf..b5050d45 100644 --- a/packages/pdfrx_engine/.gitignore +++ b/packages/pdfrx_engine/.gitignore @@ -9,3 +9,6 @@ pubspec_overrides.yaml /build/ /test/.tmp/ + +/tmp/ +**/*.exe diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 82abe669..1aebdc96 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -2,4 +2,4 @@ library; export 'src/pdfrx_api.dart'; export 'src/pdfrx_dart.dart'; -export 'src/pdfrx_initialize_dart.dart'; +export 'src/pdfrx_initialize_dart.dart' if (dart.library.js) 'src/mock/pdfrx_initialize_mock.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart new file mode 100644 index 00000000..fd3a49dc --- /dev/null +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart @@ -0,0 +1,9 @@ +/// Mock implementation of pdfrxInitialize "that will never be supposed to be called." +/// +/// Without this level of abstraction, pub.dev's analyzer will complain about the WASM incompatibility... +Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease}) async { + throw UnimplementedError( + 'Wow, this is not supposed to be called.\n' + 'For WASM support, use Flutter and initialize with pdfrxFlutterInitialize function.', + ); +} From 650bbd8b4e942cbf9c603bc4dd95340be9011a75 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 12:56:15 +0900 Subject: [PATCH 242/663] Release pdfrx_engine v0.1.10 --- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index d44e723f..f1ab20d1 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.10 + +- Add mock pdfrxInitialize implementation for WASM compatibility to address pub.dev analyzer complaints + ## 0.1.9 - Update example to include text extraction functionality diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index b0c0bda5..cf9b6f76 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.9 +version: 0.1.10 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From fa89cce831d239e1cb98cf4af3e511e6723913c3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 26 Jul 2025 12:58:07 +0900 Subject: [PATCH 243/663] Release pdfrx v2.1.1 --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 3bb25712..bbd74d80 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.1 + +- Update pdfrx_engine dependency to 0.1.10 for WASM compatibility improvements + # 2.1.0 - BREAKING CHANGE: Text selection handles are now automatically shown/hidden based on the pointing device type used diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8a64def0..c787a7f3 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.0 +version: 2.1.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.9 + pdfrx_engine: ^0.1.10 collection: crypto: ^3.0.6 ffi: From 722411258bc98b382ecc4c630ea4820288c1239a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 27 Jul 2025 03:00:08 +0900 Subject: [PATCH 244/663] Add code to prevent right-click context menu shown on Flutter Web --- .vscode/settings.json | 1 + packages/pdfrx/lib/src/pdfrx_flutter.dart | 2 ++ .../pdfrx/lib/src/utils/native/native.dart | 10 ++++++ packages/pdfrx/lib/src/utils/web/web.dart | 35 +++++++++++++++++++ .../internals/pdf_viewer_key_handler.dart | 10 +++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 ++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d3bd2986..6c928931 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,7 @@ "COLORSPACE", "COLORTYPE", "COMBOBOX", + "contextmenu", "credentialless", "Cupertino", "d_reclen", diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index f466ca7c..43b8e85d 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -29,6 +29,8 @@ void pdfrxFlutterInitialize() { }; Pdfrx.getCacheDirectory ??= getCacheDirectory; + platformInitialize(); + // Checking pdfium.wasm availability for Web and debug builds. if (kDebugMode) { () async { diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 6fc03997..2a1bc45e 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -24,3 +24,13 @@ Future getCacheDirectory() async => (await getTemporaryDirectory()).path /// Override for the [PdfDocumentFactory] for native platforms; it is null. PdfDocumentFactory? get pdfDocumentFactoryOverride => null; + +/// Initializes the Pdfrx library for native platforms. +/// +/// This function is here to maintain a consistent API with web and other platforms. +void platformInitialize() {} + +/// Reports focus changes for the Web platform to handle right-click context menus. +/// +/// For native platforms, this function does nothing. +void focusReportForPreventingContextMenuWeb(Object viewer, bool hasFocus) {} diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index d8f39bf7..d1e04ffa 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,3 +1,6 @@ +import 'dart:js_interop'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:web/web.dart' as web; @@ -24,3 +27,35 @@ Future getCacheDirectory() async => throw UnimplementedError('No tempora PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; final _factoryWasm = PdfDocumentFactoryWasmImpl(); + +final _focusObject = {}; + +/// Initializes the Pdfrx library for Web. +/// +/// For Web, this function currently setup "contextmenu" event listener to prevent the default context menu from +/// appearing on right-click. +void platformInitialize() { + web.document.addEventListener( + 'contextmenu', + ((web.Event event) { + // Prevent the default context menu from appearing on right-click. + if (_focusObject.isNotEmpty) { + debugPrint('pdfrx: Context menu event prevented because PdfViewer has focus.'); + event.preventDefault(); + } else { + debugPrint('pdfrx: Context menu event allowed.'); + } + }).toJS, + ); +} + +/// Reports focus changes for the Web platform to handle right-click context menus. +/// +/// For native platforms, this function does nothing. +void focusReportForPreventingContextMenuWeb(Object viewer, bool hasFocus) { + if (hasFocus) { + _focusObject.add(viewer); + } else { + _focusObject.remove(viewer); + } +} diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index 8dadaaf1..416251b8 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -9,12 +9,19 @@ import '../../../pdfrx.dart'; /// A widget that handles key events for the PDF viewer. class PdfViewerKeyHandler extends StatefulWidget { - const PdfViewerKeyHandler({required this.child, required this.onKeyRepeat, required this.params, super.key}); + const PdfViewerKeyHandler({ + required this.child, + required this.onKeyRepeat, + required this.params, + this.onFocusChange, + super.key, + }); /// Called on every key repeat. /// /// See [PdfViewerOnKeyCallback] for the parameters. final bool Function(PdfViewerKeyHandlerParams, LogicalKeyboardKey, bool) onKeyRepeat; + final ValueChanged? onFocusChange; final PdfViewerKeyHandlerParams params; final Widget child; @@ -57,6 +64,7 @@ class _PdfViewerKeyHandlerState extends State { parentNode: widget.params.parentNode, autofocus: widget.params.autofocus, canRequestFocus: widget.params.canRequestFocus, + onFocusChange: widget.onFocusChange, onKeyEvent: (node, event) { if (event is KeyDownEvent) { // Key pressed down diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 12990440..f62bf920 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -370,6 +370,7 @@ class _PdfViewerState extends State @override void dispose() { + focusReportForPreventingContextMenuWeb(this, false); _documentSubscription?.cancel(); _textSelectionChangedDebounceTimer?.cancel(); _interactionEndedTimer?.cancel(); @@ -423,6 +424,7 @@ class _PdfViewerState extends State color: widget.params.backgroundColor, child: PdfViewerKeyHandler( onKeyRepeat: _onKey, + onFocusChange: (hasFocus) => focusReportForPreventingContextMenuWeb(this, hasFocus), params: widget.params.keyHandlerParams, child: StreamBuilder( stream: _updateStream, From 56e57eaca6d3ab178b7e71a3f5f9ab7b7874ce0e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 27 Jul 2025 03:09:20 +0900 Subject: [PATCH 245/663] Make downloader code private to native. --- packages/pdfrx_engine/lib/pdfrx_engine.dart | 2 +- .../lib/src/pdfium_downloader.dart | 66 ----------------- .../lib/src/pdfrx_initialize_dart.dart | 70 ++++++++++++++++++- 3 files changed, 68 insertions(+), 70 deletions(-) delete mode 100644 packages/pdfrx_engine/lib/src/pdfium_downloader.dart diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 1aebdc96..c3b0c6b1 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -1,5 +1,5 @@ library; +export 'src/mock/pdfrx_initialize_mock.dart' if (dart.library.io) 'src/pdfrx_initialize_dart.dart'; export 'src/pdfrx_api.dart'; export 'src/pdfrx_dart.dart'; -export 'src/pdfrx_initialize_dart.dart' if (dart.library.js) 'src/mock/pdfrx_initialize_mock.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfium_downloader.dart b/packages/pdfrx_engine/lib/src/pdfium_downloader.dart deleted file mode 100644 index 375fadae..00000000 --- a/packages/pdfrx_engine/lib/src/pdfium_downloader.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:io'; - -import 'package:archive/archive_io.dart'; -import 'package:http/http.dart' as http; - -/// PdfiumDownloader is a utility class to download the Pdfium module for various platforms. -class PdfiumDownloader { - PdfiumDownloader._(); - - /// The release of pdfium to download. - static const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; - - /// Downloads the pdfium module for the current platform and architecture. - /// - /// Currently, the following platforms are supported: - /// - Windows x64 - /// - Linux x64, arm64 - /// - macOS x64, arm64 - /// - /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. - static Future downloadAndGetPdfiumModulePath( - String tmpPath, { - String? pdfiumRelease = pdfrxCurrentPdfiumRelease, - }) async { - final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; - final platform = pa[1]!; - final arch = pa[2]!; - if (platform == 'windows' && arch == 'x64') { - return await _downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); - } - if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await _downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); - } - if (platform == 'macos') { - return await _downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); - } else { - throw Exception('Unsupported platform: $platform-$arch'); - } - } - - /// Downloads the pdfium module for the given platform and architecture. - static Future _downloadPdfium( - String tmpRoot, - String platform, - String arch, - String modulePath, - String? pdfiumRelease, - ) async { - final tmpDir = Directory('$tmpRoot/$platform-$arch'); - final targetPath = '${tmpDir.path}/$modulePath'; - if (await File(targetPath).exists()) return targetPath; - - final uri = - 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; - final tgz = await http.Client().get(Uri.parse(uri)); - if (tgz.statusCode != 200) { - throw Exception('Failed to download pdfium: $uri'); - } - final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); - try { - await tmpDir.delete(recursive: true); - } catch (_) {} - await extractArchiveToDisk(archive, tmpDir.path); - return targetPath; - } -} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 046fb396..9e733f76 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -1,7 +1,9 @@ import 'dart:io'; +import 'package:archive/archive_io.dart'; +import 'package:http/http.dart' as http; + import '../pdfrx_engine.dart'; -import 'pdfium_downloader.dart'; bool _isInitialized = false; @@ -18,7 +20,7 @@ bool _isInitialized = false; /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({ String? tmpPath, - String? pdfiumRelease = PdfiumDownloader.pdfrxCurrentPdfiumRelease, + String? pdfiumRelease = _PdfiumDownloader.pdfrxCurrentPdfiumRelease, }) async { if (_isInitialized) return; @@ -33,8 +35,70 @@ Future pdfrxInitialize({ if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); + Pdfrx.pdfiumModulePath = await _PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); } _isInitialized = true; } + +/// PdfiumDownloader is a utility class to download the Pdfium module for various platforms. +class _PdfiumDownloader { + _PdfiumDownloader._(); + + /// The release of pdfium to download. + static const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; + + /// Downloads the pdfium module for the current platform and architecture. + /// + /// Currently, the following platforms are supported: + /// - Windows x64 + /// - Linux x64, arm64 + /// - macOS x64, arm64 + /// + /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. + static Future downloadAndGetPdfiumModulePath( + String tmpPath, { + String? pdfiumRelease = pdfrxCurrentPdfiumRelease, + }) async { + final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; + final platform = pa[1]!; + final arch = pa[2]!; + if (platform == 'windows' && arch == 'x64') { + return await downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); + } + if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { + return await downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); + } + if (platform == 'macos') { + return await downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); + } else { + throw Exception('Unsupported platform: $platform-$arch'); + } + } + + /// Downloads the pdfium module for the given platform and architecture. + static Future downloadPdfium( + String tmpRoot, + String platform, + String arch, + String modulePath, + String? pdfiumRelease, + ) async { + final tmpDir = Directory('$tmpRoot/$platform-$arch'); + final targetPath = '${tmpDir.path}/$modulePath'; + if (await File(targetPath).exists()) return targetPath; + + final uri = + 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; + final tgz = await http.Client().get(Uri.parse(uri)); + if (tgz.statusCode != 200) { + throw Exception('Failed to download pdfium: $uri'); + } + final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); + try { + await tmpDir.delete(recursive: true); + } catch (_) {} + await extractArchiveToDisk(archive, tmpDir.path); + return targetPath; + } +} From bbc8d0f944da884bd7f66fbbed1675266a1e744a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 27 Jul 2025 03:14:34 +0900 Subject: [PATCH 246/663] Release pdfrx_engine v0.1.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index f1ab20d1..81732f72 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.11 + +- Make PdfiumDownloader class private to native implementation + ## 0.1.10 - Add mock pdfrxInitialize implementation for WASM compatibility to address pub.dev analyzer complaints diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index cf9b6f76..36788739 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.10 +version: 0.1.11 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From ac136f29684d8b2dba3c5bb54eb33be1299693cc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 27 Jul 2025 03:26:48 +0900 Subject: [PATCH 247/663] Release pdfrx v2.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e82c8e29..8051cd0a 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.0 + pdfrx: ^2.1.2 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.9 + pdfrx_engine: ^0.1.11 ``` ## Development diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index bbd74d80..4243d85e 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.2 + +- FIXED: Prevent right-click context menu from showing on Flutter Web +- Update pdfrx_engine dependency to 0.1.11 + # 2.1.1 - Update pdfrx_engine dependency to 0.1.10 for WASM compatibility improvements diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index ba80ac7e..b37df3b0 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.0 + pdfrx: ^2.1.2 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index c787a7f3..53c0127b 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.1 +version: 2.1.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.10 + pdfrx_engine: ^0.1.11 collection: crypto: ^3.0.6 ffi: From b3b335ef179684ba9e68ce0cfe41ed100d49d7fd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 28 Jul 2025 02:53:33 +0900 Subject: [PATCH 248/663] Enhance README and documentation for text selection customization features --- packages/pdfrx/README.md | 9 +++++++-- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b37df3b0..915fad34 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -135,13 +135,18 @@ PdfViewer.asset( ), ``` -For more text selection customization, see [Text Selection](https://github.com/espresso3389/pdfrx/wiki/Text-Selection). +The text selection feature supports various customizations, such as: + +- Context Menu Customization using [PdfViewerParams.buildContextMenu](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/buildContextMenu.html) +- Text Selection Magnifier Customization using [PdfTextSelectionParams.magnifier](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/magnifier.html) + +For more text selection customization, see [Text Selection](https://github.com/espresso3389/pdfrx/wiki/%5B2.1.X%5D-Text-Selection). ### PDF Feature Support - [PDF Link Handling](https://github.com/espresso3389/pdfrx/wiki/PDF-Link-Handling) - [Document Outline (a.k.a Bookmarks)](https://github.com/espresso3389/pdfrx/wiki/Document-Outline-(a.k.a-Bookmarks)) -- [Text Search](https://github.com/espresso3389/pdfrx/wiki/Text-Search) +- [Text Search](https://github.com/espresso3389/pdfrx/wiki/%5B2.1.X%5D-Text-Search) ### Viewer Customization diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index e33987c1..af9cb686 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -788,6 +788,7 @@ class PdfTextSelectionParams { /// ), /// ); /// } +/// ``` /// /// See [PdfViewerParams.customizeContextMenuItems] for more. typedef PdfViewerContextMenuBuilder = Widget? Function(BuildContext context, PdfViewerContextMenuBuilderParams params); From 5a0d4be169c32507ea1edcdfe35e3e4928fabea8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 29 Jul 2025 02:15:54 +0900 Subject: [PATCH 249/663] Introduces PdfDocumentMissingFontsEvent --- .vscode/settings.json | 6 + packages/pdfrx/assets/pdfium_worker.js | 341 +++++++++++++++++-- packages/pdfrx/example/viewer/lib/main.dart | 5 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 34 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 78 +++++ 5 files changed, 438 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6c928931..2d91cd1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,6 +24,7 @@ "CALGRAY", "calloc", "CALRGB", + "ccall", "Charcodes", "clippath", "cmap", @@ -34,6 +35,7 @@ "contextmenu", "credentialless", "Cupertino", + "cwrap", "d_reclen", "dartdoc", "dartify", @@ -53,6 +55,7 @@ "emscripten", "endtemplate", "errn", + "externref", "fcntl", "ffigen", "FILEACCESS", @@ -111,6 +114,7 @@ "LISTBOX", "listenables", "localtime", + "LOGFONT", "longjmp", "LTRB", "LTWH", @@ -180,6 +184,7 @@ "Subrect", "supercedes", "swiftpm", + "SYSFONTINFO", "SYSTEMTIME", "TEXTONLY", "TEXTPAGE", @@ -190,6 +195,7 @@ "TRAPNET", "TRUETYPE", "tzset", + "uleb", "unawaited", "uncollapsed", "unlinkat", diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index b858400c..86b474af 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -16,6 +16,7 @@ const Pdfium = { Pdfium.stackSave = Pdfium.wasmExports['emscripten_stack_get_current']; Pdfium.stackRestore = Pdfium.wasmExports['_emscripten_stack_restore']; Pdfium.setThrew = Pdfium.wasmExports['setThrew']; + Pdfium.__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; }, /** @@ -30,6 +31,18 @@ const Pdfium = { * @type {WebAssembly.Table} */ wasmTable: null, + /** + * @type {WebAssembly.Table} + */ + wasmTableMirror: [], + /** + * @type {WeakMap} + */ + functionsInTableMap: null, + /** + * @type {number[]} + */ + freeTableIndexes: [], /** * @type {function():number} */ @@ -42,6 +55,10 @@ const Pdfium = { * @type {function(number, number):void} */ setThrew: null, + /** + * @type {function(number):number} + */ + __emscripten_stack_alloc: null, /** * Invoke a function from the WASM table @@ -59,6 +76,178 @@ const Pdfium = { Pdfium.setThrew(1, 0); } }, + + getCFunc: (ident) => Pdfium.wasmExports['_' + ident], + writeArrayToMemory: (array, buffer) => HEAP8.set(array, buffer), + stackAlloc: (sz) => Pdfium.__emscripten_stack_alloc(sz), + stringToUTF8OnStack: (str) => { + const size = StringUtils.lengthBytesUTF8(str) + 1; + const ret = Pdfium.stackAlloc(size); + StringUtils.stringToUtf8Bytes(str, ret); + return ret; + }, + ccall: (ident, returnType, argTypes, args, opts) => { + const toC = { + string: (str) => { + let ret = 0; + if (str !== null && str !== undefined && str !== 0) { + ret = Pdfium.stringToUTF8OnStack(str); + } + return ret; + }, + array: (arr) => { + const ret = Pdfium.stackAlloc(arr.length); + Pdfium.writeArrayToMemory(arr, ret); + return ret; + }, + }; + function convertReturnValue(ret) { + if (returnType === 'string') return UTF8ToString(ret); + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + const func = Pdfium.getCFunc(ident); + const cArgs = []; + let stack = 0; + if (args) { + for (let i = 0; i < args.length; i++) { + const converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Pdfium.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + let ret = func(...cArgs); + function onDone(ret) { + if (stack !== 0) Pdfium.stackRestore(stack); + return convertReturnValue(ret); + } + ret = onDone(ret); + return ret; + }, + cwrap: (ident, returnType, argTypes, opts) => { + const numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean'); + const numericRet = returnType !== 'string'; + if (numericRet && numericArgs && !opts) { + return Pdfium.getCFunc(ident); + } + return (...args) => Pdfium.ccall(ident, returnType, argTypes, args, opts); + }, + uleb128Encode: (n, target) => { + if (n < 128) { + target.push(n); + } else { + target.push(n % 128 | 128, n >> 7); + } + }, + sigToWasmTypes: (sig) => { + const typeNames = { + i: 'i32', + j: 'i64', + f: 'f32', + d: 'f64', + e: 'externref', + p: 'i32', + }; + const type = { + parameters: [], + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]], + }; + for (let i = 1; i < sig.length; ++i) { + type.parameters.push(typeNames[sig[i]]); + } + return type; + }, + generateFuncType: (sig, target) => { + const sigRet = sig.slice(0, 1); + const sigParam = sig.slice(1); + const typeCodes = { i: 127, p: 127, j: 126, f: 125, d: 124, e: 111 }; + target.push(96); + Pdfium.uleb128Encode(sigParam.length, target); + for (let i = 0; i < sigParam.length; ++i) { + target.push(typeCodes[sigParam[i]]); + } + if (sigRet == 'v') { + target.push(0); + } else { + target.push(1, typeCodes[sigRet]); + } + }, + convertJsFunctionToWasm: (func, sig) => { + if (typeof WebAssembly.Function == 'function') { + return new WebAssembly.Function(Pdfium.sigToWasmTypes(sig), func); + } + const typeSectionBody = [1]; + Pdfium.generateFuncType(sig, typeSectionBody); + const bytes = [0, 97, 115, 109, 1, 0, 0, 0, 1]; + Pdfium.uleb128Encode(typeSectionBody.length, bytes); + bytes.push(...typeSectionBody); + bytes.push(2, 7, 1, 1, 101, 1, 102, 0, 0, 7, 5, 1, 1, 102, 0, 0); + const module = new WebAssembly.Module(new Uint8Array(bytes)); + const instance = new WebAssembly.Instance(module, { e: { f: func } }); + const wrappedFunc = instance.exports['f']; + return wrappedFunc; + }, + updateTableMap: (offset, count) => { + if (Pdfium.functionsInTableMap) { + for (let i = offset; i < offset + count; i++) { + const item = Pdfium.wasmTable.get(i); + if (item) { + Pdfium.functionsInTableMap.set(item, i); + } + } + } + }, + getFunctionAddress: (func) => { + if (!Pdfium.functionsInTableMap) { + Pdfium.functionsInTableMap = new WeakMap(); + Pdfium.updateTableMap(0, Pdfium.wasmTable.length); + } + return Pdfium.functionsInTableMap.get(func) || 0; + }, + getEmptyTableSlot: () => { + if (Pdfium.freeTableIndexes.length) return Pdfium.freeTableIndexes.pop(); + try { + Pdfium.wasmTable.grow(1); + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; + } + return Pdfium.wasmTable.length - 1; + }, + /** + * @param {function} func Function to add + * @param {string} sig Signature of the function + * @return {number} Function index in the table + */ + addFunction: (func, sig) => { + const rtn = Pdfium.getFunctionAddress(func); + if (rtn) { + return rtn; + } + const ret = Pdfium.getEmptyTableSlot(); + try { + Pdfium.wasmTable.set(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + const wrapped = Pdfium.convertJsFunctionToWasm(func, sig); + Pdfium.wasmTable.set(ret, wrapped); + } + Pdfium.functionsInTableMap.set(func, ret); + return ret; + }, + removeFunction: (index) => { + Pdfium.functionsInTableMap.delete(Pdfium.wasmTable.get(index)); + Pdfium.wasmTable.set(index, null); + Pdfium.freeTableIndexes.push(index); + }, }; /** @@ -609,8 +798,36 @@ function loadDocumentFromData(params) { /** @type {Object} */ const disposers = {}; +/** @typedef {{face: string, weight: number, italic: boolean, charset: number, pitch_family: number}} FontQuery + * @typedef {Object} FontQueries +*/ +/** @type {FontQueries} */ +let lastMissingFonts = {}; + +/** @type {Object} */ +let missingFonts = {}; + /** - * @typedef {{docHandle: number,permissions: number, securityHandlerRevision: number, pages: PdfPage[], formHandle: number, formInfo: number}} PdfDocument + * + * @param {number} docHandle + * @returns {FontQueries} Missing fonts new found. + */ +function _updateMissingFonts(docHandle) { + if (Object.keys(lastMissingFonts).length === 0) return; + + const existing = missingFonts[docHandle] ?? {}; + missingFonts[docHandle] = { ...existing, ...lastMissingFonts }; + const result = lastMissingFonts; + lastMissingFonts = {}; + return result; +} + +function _resetMissingFonts() { + missingFonts = {}; +} + +/** + * @typedef {{docHandle: number,permissions: number, securityHandlerRevision: number, pages: PdfPage[], formHandle: number, formInfo: number, missingFonts: FontQueries}} PdfDocument * @typedef {{pageIndex: number, width: number, height: number, rotation: number, isLoaded: boolean}} PdfPage * @typedef {{errorCode: number, errorCodeStr: string|undefined, message: string}} PdfError */ @@ -635,6 +852,9 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { }; } + missingFonts[docHandle] = {}; + lastMissingFonts = {}; + const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); const permissions = Pdfium.wasmExports.FPDF_GetDocPermissions(docHandle); const securityHandlerRevision = Pdfium.wasmExports.FPDF_GetSecurityHandlerRevision(docHandle); @@ -659,6 +879,8 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { } } disposers[docHandle] = onDispose; + _updateMissingFonts(docHandle); + return { docHandle: docHandle, permissions: permissions, @@ -666,6 +888,7 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { pages: pages, formHandle: formHandle, formInfo: formInfo, + missingFonts: missingFonts[docHandle], }; } catch (e) { try { @@ -694,6 +917,7 @@ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountT const t = timeoutMs != null ? Date.now() + timeoutMs : null; /** @type {PdfPage[]} */ const pages = []; + _resetMissingFonts(); for (let i = pagesLoadedCountSoFar; i < end; i++) { const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, i); if (!pageHandle) { @@ -713,17 +937,18 @@ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountT break; } } + _updateMissingFonts(docHandle); return pages; } /** * @param {{docHandle: number, loadUnitDuration: number}} params - * @returns {{pages: PdfPage[]}} + * @returns {{pages: PdfPage[], missingFonts: FontQueries}} */ function loadPagesProgressively(params) { const { docHandle, firstPageIndex, loadUnitDuration } = params; const pages = _loadPagesInLimitedTime(docHandle, firstPageIndex, null, loadUnitDuration); - return { pages }; + return { pages, missingFonts: missingFonts[docHandle] }; } /** @@ -739,6 +964,7 @@ function closeDocument(params) { Pdfium.wasmExports.FPDF_CloseDocument(params.docHandle); disposers[params.docHandle](); delete disposers[params.docHandle]; + delete missingFonts[params.docHandle]; return { message: 'Document closed' }; } @@ -820,7 +1046,12 @@ function closePage(params) { * flags: number, * formHandle: number * }} params - * @returns + * @returns {{ + * imageData: ArrayBuffer, + * width: number, + * height: number, + * missingFonts: FontQueries + * }} */ function renderPage(params) { const { @@ -843,6 +1074,7 @@ function renderPage(params) { let bitmap = 0; try { + _resetMissingFonts(); pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); if (!pageHandle) { throw new Error(`Failed to load page ${pageIndex} from document ${docHandle}`); @@ -866,17 +1098,9 @@ function renderPage(params) { const PdfAnnotationRenderingMode_annotationAndForms = 2; const premultipliedAlpha = 0x80000000; - const pdfiumFlags = (flags & 0xffff) | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0); - Pdfium.wasmExports.FPDF_RenderPageBitmap( - bitmap, - pageHandle, - -x, - -y, - fullWidth, - fullHeight, - 0, - pdfiumFlags - ); + const pdfiumFlags = + (flags & 0xffff) | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0); + Pdfium.wasmExports.FPDF_RenderPageBitmap(bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, pdfiumFlags); if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, flags); @@ -896,11 +1120,14 @@ function renderPage(params) { dest.set(src); } + _updateMissingFonts(docHandle); + return { result: { imageData: copiedBuffer, width: width, height: height, + missingFonts: missingFonts[docHandle], }, transfer: [copiedBuffer], }; @@ -921,9 +1148,10 @@ function _memset(ptr, value, num) { /** * * @param {{pageIndex: number, docHandle: number}} params - * @returns {{fullText: string}} + * @returns {{fullText: string, missingFonts: FontQueries}} */ function loadText(params) { + _resetMissingFonts(); const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); @@ -938,7 +1166,9 @@ function loadText(params) { Pdfium.wasmExports.FPDFText_ClosePage(textPage); Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - return { fullText }; + + _updateMissingFonts(docHandle); + return { fullText, missingFonts: missingFonts[docHandle] }; } /** @@ -984,10 +1214,7 @@ function loadTextCharRects(params) { * @returns {{links: Array}} */ function loadLinks(params) { - const links = [ - ..._loadAnnotLinks(params), - ...(params.enableAutoLinkDetection ? _loadWebLinks(params) : []), - ]; + const links = [..._loadAnnotLinks(params), ...(params.enableAutoLinkDetection ? _loadWebLinks(params) : [])]; return { links: links, }; @@ -1231,7 +1458,7 @@ async function initializePdfium(params = {}) { // Hot-restart or such may call this multiple times, so we can skip re-initialization return; } - + console.log(`Loading PDFium WASM module from ${pdfiumWasmUrl}`); const fetchOptions = { @@ -1249,6 +1476,8 @@ async function initializePdfium(params = {}) { Pdfium.initWith(result.instance.exports); Pdfium.wasmExports.FPDF_InitLibrary(); + initializeMissingFontReporter(); + pdfiumInitialized = true; postMessage({ type: 'ready' }); @@ -1263,6 +1492,74 @@ async function initializePdfium(params = {}) { } } +function initializeMissingFontReporter() { + // kBase14FontNames + const fontNamesToIgnore = { + "Courier" : true, + "Courier-Bold" : true, + "Courier-BoldOblique" : true, + "Courier-Oblique" : true, + "Helvetica" : true, + "Helvetica-Bold" : true, + "Helvetica-BoldOblique" : true, + "Helvetica-Oblique" : true, + "Times-Roman" : true, + "Times-Bold" : true, + "Times-BoldItalic" : true, + "Times-Italic" : true, + "Symbol" : true, + "ZapfDingbats" : true, + }; + + const defaultSysFontInfo = Pdfium.wasmExports.FPDF_GetDefaultSystemFontInfo(); + const defSysFontInfo = new Int32Array(Pdfium.memory.buffer, defaultSysFontInfo, 9); + function invokeDefault(index, func) { + const funcIndex = defSysFontInfo[index]; + if (!funcIndex) return undefined; + return Pdfium.invokeFunc(funcIndex, func); + } + + // NOTE: The buffer passed to FPDF_SetSystemFontInfo must be kept alive because it is used by the PDFium library + const buf = Pdfium.wasmExports.malloc(4 * 9); + const sysFontInfo = new Int32Array(Pdfium.memory.buffer, buf, 9); + sysFontInfo[0] = 1; // version + + // void Release(PDF_SYSFONTINFO* pThis) + sysFontInfo[1] = Pdfium.addFunction((pThis) => invokeDefault(1, function (func) { return func(defaultSysFontInfo); }), 'vi'); + // void EnumFonts(FPDF_SYSFONTINFO* pThis, void* pMapper) + sysFontInfo[2] = Pdfium.addFunction((pThis, pMapper) => invokeDefault(2, function (func) { return func(defaultSysFontInfo, pMapper); }), 'vii'); + // void* MapFont(FPDF_SYSFONTINFO* pThis, int weight, FPDF_BOOL bItalic, int charset, int pitch_family, const char* face, FPDF_BOOL* bExact) + sysFontInfo[3] = Pdfium.addFunction((pThis, weight, bItalic, charset, pitchFamily, face, bExact) => { + const result = invokeDefault(3, function (func) { return func(defaultSysFontInfo, weight, bItalic, charset, pitchFamily, face, bExact); }); + if (!result) { + const faceName = StringUtils.utf8BytesToString(new Uint8Array(Pdfium.memory.buffer, face)); + if (fontNamesToIgnore[faceName]) return 0; + if (lastMissingFonts[faceName]) return 0; + lastMissingFonts[faceName] = { + face: faceName, + weight: weight, + italic: !!bItalic, + charset: charset, + pitchFamily: pitchFamily, + }; + } + return result; + }, 'iiiiiiii'); + + // void* GetFont(FPDF_SYSFONTINFO* pThis, const char* face) + sysFontInfo[4] = Pdfium.addFunction((pThis, face) => invokeDefault(4, function (func) { return func(defaultSysFontInfo, face); }), 'iii'); + // unsigned long GetFontData(FPDF_SYSFONTINFO* pThis, void* hFont, unsigned int table, unsigned char* buffer, unsigned long buf_size); + sysFontInfo[5] = Pdfium.addFunction((pThis, hFont, table, buffer, buf_size) => invokeDefault(5, function (func) { return func(defaultSysFontInfo, hFont, table, buffer, buf_size); }), 'iiiiii'); + // unsigned long GetFaceName(FPDF_SYSFONTINFO* pThis, void* hFont, char* buffer, unsigned long buf_size) + sysFontInfo[6] = Pdfium.addFunction((pThis, hFont, buffer, buf_size) => invokeDefault(6, function (func) { return func(defaultSysFontInfo, hFont, buffer, buf_size); }), 'iiiii'); + // int GetFontCharset(FPDF_SYSFONTINFO* pThis, void* hFont) + sysFontInfo[7] = Pdfium.addFunction((pThis, hFont) => invokeDefault(7, function (func) { return func(defaultSysFontInfo, hFont); }), 'iii'); + // void DeleteFont(FPDF_SYSFONTINFO* pThis, void* hFont); + sysFontInfo[8] = Pdfium.addFunction((pThis, hFont) => invokeDefault(8, function (func) { return func(defaultSysFontInfo, hFont); }), 'vii'); + + Pdfium.wasmExports.FPDF_SetSystemFontInfo(buf); +} + onmessage = function (e) { const data = e.data; diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 162b17e4..19ad4d68 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -443,6 +443,11 @@ class _MainPageState extends State with WidgetsBindingObserver { outline.value = await document.loadOutline(); textSearcher.value = PdfTextSearcher(controller)..addListener(_update); controller.requestFocus(); + controller.document.events.listen((event) { + if (event is PdfDocumentMissingFontsEvent) { + debugPrint('Missing fonts: ${event.missingFonts.map((f) => f.face).join(', ')}'); + } + }); }, ), ); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 68c6ad5b..43c31f58 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -18,11 +18,11 @@ extension type _PdfiumWasmCommunicator(JSObject _) implements JSObject { /// Registers a callback function and returns its ID @JS('registerCallback') - external int _registerCallback(JSFunction callback); + external int registerCallback(JSFunction callback); /// Unregisters a callback by its ID @JS('unregisterCallback') - external void _unregisterCallback(int callbackId); + external void unregisterCallback(int callbackId); } /// Get the global PdfiumWasmCommunicator instance @@ -32,14 +32,14 @@ external _PdfiumWasmCommunicator get _pdfiumWasmCommunicator; /// A handle to a registered callback that can be unregistered class _PdfiumWasmCallback { _PdfiumWasmCallback.register(JSFunction callback) - : id = _pdfiumWasmCommunicator._registerCallback(callback), + : id = _pdfiumWasmCommunicator.registerCallback(callback), _communicator = _pdfiumWasmCommunicator; final int id; final _PdfiumWasmCommunicator _communicator; void unregister() { - _communicator._unregisterCallback(id); + _communicator.unregisterCallback(id); } } @@ -287,6 +287,7 @@ class _PdfDocumentWasm extends PdfDocument { _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) : permissions = parsePermissions(document) { pages = parsePages(this, document['pages'] as List); + updateMissingFonts(document['missingFonts']); } final Map document; @@ -362,6 +363,8 @@ class _PdfDocumentWasm extends PdfDocument { subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); } + updateMissingFonts(result['missingFonts']); + if (onPageLoadProgress != null) { if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { // If the callback returns false, stop loading more pages @@ -374,6 +377,26 @@ class _PdfDocumentWasm extends PdfDocument { @override late final List pages; + void updateMissingFonts(Map? missingFonts) { + if (missingFonts == null || missingFonts.isEmpty) { + return; + } + final fontQueries = []; + for (final entry in missingFonts.entries) { + final font = entry.value as Map; + fontQueries.add( + PdfFontQuery( + face: font['face'] as String, + weight: (font['weight'] as num).toInt(), + italic: (font['italic'] as bool), + charset: PdfFontCharset.fromPdfiumCharsetId((font['charset'] as num).toInt()), + pitchFamily: (font['pitchFamily'] as num).toInt(), + ), + ); + } + subject.add(PdfDocumentMissingFontsEvent(this, fontQueries)); + } + static PdfPermissions? parsePermissions(Map document) { final perms = (document['permissions'] as num).toInt(); final securityHandlerRevision = (document['securityHandlerRevision'] as num).toInt(); @@ -466,6 +489,7 @@ class _PdfPageWasm extends PdfPage { 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); + document.updateMissingFonts(result['missingFonts']); return result['fullText'] as String; } @@ -552,6 +576,8 @@ class _PdfPageWasm extends PdfPage { } } + document.updateMissingFonts(result['missingFonts']); + return PdfImageWeb(width: width, height: height, pixels: pixels); } } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index bdffaf65..c650bf97 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -349,6 +349,7 @@ typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumbe enum PdfDocumentEventType { /// [PdfDocumentPageStatusChangedEvent]: Page status changed. pageStatusChanged, + missingFonts, // [PdfDocumentMissingFontsEvent]: Missing fonts changed. } /// Base class for PDF document events. @@ -374,6 +375,20 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { final List pages; } +/// Event that is triggered when the list of missing fonts in the PDF document has changed. +class PdfDocumentMissingFontsEvent implements PdfDocumentEvent { + PdfDocumentMissingFontsEvent(this.document, this.missingFonts); + + @override + PdfDocumentEventType get type => PdfDocumentEventType.missingFonts; + + @override + final PdfDocument document; + + /// The list of missing fonts. + final List missingFonts; +} + /// Handles a PDF page in [PdfDocument]. /// /// See [PdfDocument.pages]. @@ -1570,3 +1585,66 @@ bool _listEquals(List? a, List? b) { } return true; } + +class PdfFontQuery { + const PdfFontQuery({ + required this.face, + required this.weight, + required this.italic, + required this.charset, + required this.pitchFamily, + }); + + /// Font face name. + final String face; + + /// Font weight. + final int weight; + + /// Whether the font is italic. + final bool italic; + + /// PDFium's charset ID. + final PdfFontCharset charset; + + /// Pitch family flags. + /// + /// It can be any combination of the following values: + /// - `fixed` = 1 + /// - `roman` = 16 + /// - `script` = 64 + final int pitchFamily; + + bool get isFixed => (pitchFamily & 1) != 0; + bool get isRoman => (pitchFamily & 16) != 0; + bool get isScript => (pitchFamily & 64) != 0; +} + +/// PDFium font charset ID. +/// +enum PdfFontCharset { + ansi(0), + default_(1), + symbol(2), + shiftJis(128), + hangul(129), + gb2312(134), + chineseBig5(136), + greek(161), + vietnamese(163), + hebrew(177), + arabic(178), + cyrillic(204), + thai(222), + easternEuropean(238); + + const PdfFontCharset(this.pdfiumCharsetId); + + /// PDFium's charset ID. + final int pdfiumCharsetId; + + static final _value2Enum = {for (final e in PdfFontCharset.values) e.pdfiumCharsetId: e}; + + /// Convert PDFium's charset ID to [PdfFontCharset]. + static PdfFontCharset fromPdfiumCharsetId(int id) => _value2Enum[id]!; +} From 348df146cb622fa5176b868ab0aa467f5203fe17 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 30 Jul 2025 04:25:58 +0900 Subject: [PATCH 250/663] WIP: reloadFonts/addFontData for PDFium WASM --- packages/pdfrx/assets/pdfium_worker.js | 223 ++++++++++-------- packages/pdfrx/lib/src/pdfrx_flutter.dart | 4 +- .../pdfrx/lib/src/utils/native/native.dart | 4 +- packages/pdfrx/lib/src/utils/web/web.dart | 6 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 27 ++- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 16 +- .../lib/src/native/pdfrx_pdfium.dart | 11 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 26 +- 8 files changed, 193 insertions(+), 124 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 86b474af..a03f7122 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -313,7 +313,7 @@ class FileSystemEmulator { /** * Register file * @param {string} fn Filename - * @param {FileContext} context I/O functions/data + * @param {FileContext|DirectoryContext} context I/O functions/data */ registerFile(fn, context) { this.fn2context[fn] = context; @@ -343,15 +343,6 @@ class FileSystemEmulator { }); } - /** - * Register directory - * @param {string} fn Filename - * @param {*} entries Directory entries (For directories, the name should be terminated with /) - */ - registerDirectoryWithEntries(fn, entries) { - this.registerFile(fn, { entries }); - } - /** * Unregister file/directory context * @param {string} fn Filename @@ -393,26 +384,34 @@ class FileSystemEmulator { /** * Seek to a position in a file * @param {number} fd File descriptor - * @param {number} offset_low Offset low - * @param {number} offset_high Offset high - * @param {number} whence Whence - * @param {number} newOffset New offset * @returns {number} New offset */ - seek(fd, offset_low, offset_high, whence, newOffset) { - const context = this.fd2context[fd]; - if (offset_high !== 0) { - throw new Error('seek: offset_high is not supported'); + seek(fd) { + let offset, whence, newOffset; + if (arguments.length == 4) { + // (fd: number, offset: BigInt, whence: number, newOffset: number) + offset = Number(arguments[1]); // BigInt to Number + whence = arguments[2]; + newOffset = arguments[3]; + } else if (arguments.length == 5) { + // (fd: number, offset_low: number, offset_high: number, whence: number, newOffset: number) + offset = arguments[1]; // offset_low; offset_high is ignored + whence = arguments[3]; + newOffset = arguments[4]; + } else { + throw new Error(`seek: invalid arguments count: ${arguments.length}`); } + + const context = this.fd2context[fd]; switch (whence) { case 0: // SEEK_SET - context.position = offset_low; + context.position = offset; break; case 1: // SEEK_CUR - context.position += offset_low; + context.position += offset; break; case 2: // SEEK_END - context.position = context.size + offset_low; + context.position = context.size + offset; break; } const offsetLowHigh = new Uint32Array(Pdfium.memory.buffer, newOffset, 2); @@ -505,7 +504,7 @@ class FileSystemEmulator { /** * __syscall_getdents64 * @param {num} fd - * @param {num} dirp + * @param {num} dirp struct linux_dirent64 * @param {num} count * @returns {num} */ @@ -513,12 +512,14 @@ class FileSystemEmulator { /** @type {DirectoryFileDescriptorContext} */ const context = this.fd2context[fd]; const entries = context.entries; - if (entries == null) return 0; + context.getdents_position = context.getdents_position || 0; + if (entries == null) return -1; let written = 0; const DT_REG = 8, DT_DIR = 4; _memset(dirp, 0, count); - for (let i = context.position; i < entries.length; i++) { + for (; context.position < entries.length; context.position++) { + const i = context.position; let d_type, d_name; if (entries[i].endsWith('/')) { d_type = DT_DIR; @@ -529,13 +530,15 @@ class FileSystemEmulator { } const d_nameLength = StringUtils.lengthBytesUTF8(d_name) + 1; const size = 8 + 8 + 2 + 1 + d_nameLength; - if (written + size > count) break; + const buffer = new Uint8Array(Pdfium.memory.buffer, dirp + written, size); // d_off const d_off = written + size; buffer[8] = d_off & 255; buffer[9] = (d_off >> 8) & 255; + buffer[10] = (d_off >> 16) & 255; + buffer[11] = (d_off >> 24) & 255; // d_reclen buffer[16] = size & 255; buffer[17] = (size >> 8) & 255; @@ -598,6 +601,7 @@ const emEnv = { throw Infinity; }, _gmtime_js: function (time, tmPtr) { + time = Number(time); const date = new Date(time * 1000); const tm = new Int32Array(Pdfium.memory.buffer, tmPtr, 9); tm[0] = date.getUTCSeconds(); @@ -800,7 +804,7 @@ const disposers = {}; /** @typedef {{face: string, weight: number, italic: boolean, charset: number, pitch_family: number}} FontQuery * @typedef {Object} FontQueries -*/ + */ /** @type {FontQueries} */ let lastMissingFonts = {}; @@ -808,13 +812,13 @@ let lastMissingFonts = {}; let missingFonts = {}; /** - * - * @param {number} docHandle + * + * @param {number} docHandle * @returns {FontQueries} Missing fonts new found. */ function _updateMissingFonts(docHandle) { if (Object.keys(lastMissingFonts).length === 0) return; - + const existing = missingFonts[docHandle] ?? {}; missingFonts[docHandle] = { ...existing, ...lastMissingFonts }; const result = lastMissingFonts; @@ -1372,6 +1376,95 @@ function _pdfDestFromDest(dest, docHandle) { return null; } +/** + * Setup the system font info in PDFium. + */ +function _initializeFontEnvironment() { + // kBase14FontNames + const fontNamesToIgnore = { + Courier: true, + 'Courier-Bold': true, + 'Courier-BoldOblique': true, + 'Courier-Oblique': true, + Helvetica: true, + 'Helvetica-Bold': true, + 'Helvetica-BoldOblique': true, + 'Helvetica-Oblique': true, + 'Times-Roman': true, + 'Times-Bold': true, + 'Times-BoldItalic': true, + 'Times-Italic': true, + Symbol: true, + ZapfDingbats: true, + }; + + // load the default system font info and modify only MapFont (index=3) entry with our one, which + // wraps the original function and adds our custom logic + const sysFontInfoBuffer = Pdfium.wasmExports.FPDF_GetDefaultSystemFontInfo(); + const sysFontInfo = new Int32Array(Pdfium.memory.buffer, sysFontInfoBuffer, 9); // struct _FPDF_SYSFONTINFO + + // void* MapFont( + // struct _FPDF_SYSFONTINFO* pThis, + // int weight, + // FPDF_BOOL bItalic, + // int charset, + // int pitch_family, + // const char* face, + // FPDF_BOOL* bExact); + const mapFont = sysFontInfo[3]; + sysFontInfo[3] = Pdfium.addFunction((pThis, weight, bItalic, charset, pitchFamily, face, bExact) => { + const result = Pdfium.invokeFunc(mapFont, (func) => + func(sysFontInfoBuffer, weight, bItalic, charset, pitchFamily, face, bExact) + ); + if (!result) { + // the font face is missing + const faceName = StringUtils.utf8BytesToString(new Uint8Array(Pdfium.memory.buffer, face)); + if (fontNamesToIgnore[faceName] || lastMissingFonts[faceName]) return 0; + lastMissingFonts[faceName] = { + face: faceName, + weight: weight, + italic: !!bItalic, + charset: charset, + pitchFamily: pitchFamily, + }; + } + return result; + }, 'iiiiiiii'); + + // when registering a new SetSystemFontInfo, the previous one is automatically released + // and the only last one remains on memory + Pdfium.wasmExports.FPDF_SetSystemFontInfo(sysFontInfoBuffer); +} + +/** + * Reload fonts in PDFium. + * + * The function is based on the fact that PDFium reloads all the fonts when FPDF_SetSystemFontInfo is called. + */ +function reloadFonts() { + console.log('Reloading system fonts in PDFium...'); + _initializeFontEnvironment(); + return { message: 'Fonts reloaded' }; +} +/** + * @type {{[face: string]: string}} + */ +const fontFileNames = {}; +let fontFilesId = 0; + +/** + * Add font data to the file system. + * @param {{face: string, data: ArrayBuffer}} params + */ +function addFontData(params) { + console.log(`Adding font data for face: ${params.face}`); + const { face, data } = params; + fontFileNames[face] ??= `font_${++fontFilesId}.ttf`; + fileSystem.registerFileWithData(`/usr/share/fonts/${fontFileNames[face]}`, data); + fileSystem.registerFile('/usr/share/fonts', { entries: Object.values(fontFileNames) }); + return { message: `Font ${face} added`, face: face, fileName: fontFileNames[face] }; +} + /** * Functions that can be called from the main thread */ @@ -1387,6 +1480,8 @@ const functions = { loadText, loadTextCharRects, loadLinks, + reloadFonts, + addFontData, }; /** @@ -1476,7 +1571,7 @@ async function initializePdfium(params = {}) { Pdfium.initWith(result.instance.exports); Pdfium.wasmExports.FPDF_InitLibrary(); - initializeMissingFontReporter(); + _initializeFontEnvironment(); pdfiumInitialized = true; @@ -1492,74 +1587,6 @@ async function initializePdfium(params = {}) { } } -function initializeMissingFontReporter() { - // kBase14FontNames - const fontNamesToIgnore = { - "Courier" : true, - "Courier-Bold" : true, - "Courier-BoldOblique" : true, - "Courier-Oblique" : true, - "Helvetica" : true, - "Helvetica-Bold" : true, - "Helvetica-BoldOblique" : true, - "Helvetica-Oblique" : true, - "Times-Roman" : true, - "Times-Bold" : true, - "Times-BoldItalic" : true, - "Times-Italic" : true, - "Symbol" : true, - "ZapfDingbats" : true, - }; - - const defaultSysFontInfo = Pdfium.wasmExports.FPDF_GetDefaultSystemFontInfo(); - const defSysFontInfo = new Int32Array(Pdfium.memory.buffer, defaultSysFontInfo, 9); - function invokeDefault(index, func) { - const funcIndex = defSysFontInfo[index]; - if (!funcIndex) return undefined; - return Pdfium.invokeFunc(funcIndex, func); - } - - // NOTE: The buffer passed to FPDF_SetSystemFontInfo must be kept alive because it is used by the PDFium library - const buf = Pdfium.wasmExports.malloc(4 * 9); - const sysFontInfo = new Int32Array(Pdfium.memory.buffer, buf, 9); - sysFontInfo[0] = 1; // version - - // void Release(PDF_SYSFONTINFO* pThis) - sysFontInfo[1] = Pdfium.addFunction((pThis) => invokeDefault(1, function (func) { return func(defaultSysFontInfo); }), 'vi'); - // void EnumFonts(FPDF_SYSFONTINFO* pThis, void* pMapper) - sysFontInfo[2] = Pdfium.addFunction((pThis, pMapper) => invokeDefault(2, function (func) { return func(defaultSysFontInfo, pMapper); }), 'vii'); - // void* MapFont(FPDF_SYSFONTINFO* pThis, int weight, FPDF_BOOL bItalic, int charset, int pitch_family, const char* face, FPDF_BOOL* bExact) - sysFontInfo[3] = Pdfium.addFunction((pThis, weight, bItalic, charset, pitchFamily, face, bExact) => { - const result = invokeDefault(3, function (func) { return func(defaultSysFontInfo, weight, bItalic, charset, pitchFamily, face, bExact); }); - if (!result) { - const faceName = StringUtils.utf8BytesToString(new Uint8Array(Pdfium.memory.buffer, face)); - if (fontNamesToIgnore[faceName]) return 0; - if (lastMissingFonts[faceName]) return 0; - lastMissingFonts[faceName] = { - face: faceName, - weight: weight, - italic: !!bItalic, - charset: charset, - pitchFamily: pitchFamily, - }; - } - return result; - }, 'iiiiiiii'); - - // void* GetFont(FPDF_SYSFONTINFO* pThis, const char* face) - sysFontInfo[4] = Pdfium.addFunction((pThis, face) => invokeDefault(4, function (func) { return func(defaultSysFontInfo, face); }), 'iii'); - // unsigned long GetFontData(FPDF_SYSFONTINFO* pThis, void* hFont, unsigned int table, unsigned char* buffer, unsigned long buf_size); - sysFontInfo[5] = Pdfium.addFunction((pThis, hFont, table, buffer, buf_size) => invokeDefault(5, function (func) { return func(defaultSysFontInfo, hFont, table, buffer, buf_size); }), 'iiiiii'); - // unsigned long GetFaceName(FPDF_SYSFONTINFO* pThis, void* hFont, char* buffer, unsigned long buf_size) - sysFontInfo[6] = Pdfium.addFunction((pThis, hFont, buffer, buf_size) => invokeDefault(6, function (func) { return func(defaultSysFontInfo, hFont, buffer, buf_size); }), 'iiiii'); - // int GetFontCharset(FPDF_SYSFONTINFO* pThis, void* hFont) - sysFontInfo[7] = Pdfium.addFunction((pThis, hFont) => invokeDefault(7, function (func) { return func(defaultSysFontInfo, hFont); }), 'iii'); - // void DeleteFont(FPDF_SYSFONTINFO* pThis, void* hFont); - sysFontInfo[8] = Pdfium.addFunction((pThis, hFont) => invokeDefault(8, function (func) { return func(defaultSysFontInfo, hFont); }), 'vii'); - - Pdfium.wasmExports.FPDF_SetSystemFontInfo(buf); -} - onmessage = function (e) { const data = e.data; diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 43b8e85d..651ea5e8 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -19,8 +19,8 @@ bool _isInitialized = false; void pdfrxFlutterInitialize() { if (_isInitialized) return; - if (pdfDocumentFactoryOverride != null) { - PdfDocumentFactory.instance = pdfDocumentFactoryOverride!; + if (pdfrxEntryFunctionsOverride != null) { + PdfrxEntryFunctions.instance = pdfrxEntryFunctionsOverride!; } Pdfrx.loadAsset ??= (name) async { diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 2a1bc45e..e89d66fe 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -22,8 +22,8 @@ void setClipboardData(String text) { Future getCacheDirectory() async => (await getTemporaryDirectory()).path; -/// Override for the [PdfDocumentFactory] for native platforms; it is null. -PdfDocumentFactory? get pdfDocumentFactoryOverride => null; +/// Override for the [PdfrxEntryFunctions] for native platforms; it is null. +PdfrxEntryFunctions? get pdfrxEntryFunctionsOverride => null; /// Initializes the Pdfrx library for native platforms. /// diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index d1e04ffa..c64bd46f 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -23,10 +23,10 @@ void setClipboardData(String text) { Future getCacheDirectory() async => throw UnimplementedError('No temporary directory available for web.'); -/// Override for the [PdfDocumentFactory] for web platforms to use WASM implementation. -PdfDocumentFactory? get pdfDocumentFactoryOverride => _factoryWasm; +/// Override for the [PdfrxEntryFunctions] for web platforms to use WASM implementation. +PdfrxEntryFunctions? get pdfrxEntryFunctionsOverride => _factoryWasm; -final _factoryWasm = PdfDocumentFactoryWasmImpl(); +final _factoryWasm = PdfrxEntryFunctionsWasmImpl(); final _focusObject = {}; diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 43c31f58..3d1f7672 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -43,8 +43,12 @@ class _PdfiumWasmCallback { } } -Future> _sendCommand(String command, {Map? parameters}) async { - final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify()).toDart; +Future> _sendCommand( + String command, { + Map? parameters, + JSArray? transfer, +}) async { + final result = await _pdfiumWasmCommunicator.sendCommand(command, parameters?.jsify(), transfer).toDart; return (result.dartify()) as Map; } @@ -52,8 +56,8 @@ Future> _sendCommand(String command, {Map reloadFonts() async { + await _init(); + await _sendCommand('reloadFonts', parameters: {'dummy': true}); + } + + @override + Future addFontData({required String face, required Uint8List data}) async { + await _init(); + final jsData = data.buffer.toJS; + await _sendCommand('addFontData', parameters: {'face': face, 'data': jsData}, transfer: [jsData].toJS); + } } class _PdfDocumentWasm extends PdfDocument { diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index f3b27918..ba8a2f0f 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -3,16 +3,16 @@ import 'dart:typed_data'; import '../../pdfrx_engine.dart'; -/// This is an empty implementation of [PdfDocumentFactory] that just throws [UnimplementedError]. +/// This is an empty implementation of [PdfrxEntryFunctions] that just throws [UnimplementedError]. /// /// This is used to indicate that the factory is not initialized. -class PdfDocumentFactoryImpl implements PdfDocumentFactory { - PdfDocumentFactoryImpl(); +class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { + PdfrxEntryFunctionsImpl(); Future unimplemented() { throw UnimplementedError( - 'PdfDocumentFactory.instance is not initialized. ' - 'Please call pdfrxInitialize()/pdfrxFlutterInitialize() or explicitly set PdfDocumentFactory.instance.', + 'PdfrxEntryFunctions.instance is not initialized. ' + 'Please call pdfrxInitialize()/pdfrxFlutterInitialize() or explicitly set PdfrxEntryFunctions.instance.', ); } @@ -66,4 +66,10 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { Map? headers, bool withCredentials = false, }) => unimplemented(); + + @override + Future reloadFonts() => unimplemented(); + + @override + Future addFontData({required String face, required Uint8List data}) => unimplemented(); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 28cd3fd7..d650f70e 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -67,8 +67,8 @@ void _init() { final backgroundWorker = BackgroundWorker.create(); -class PdfDocumentFactoryImpl implements PdfDocumentFactory { - PdfDocumentFactoryImpl(); +class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { + PdfrxEntryFunctionsImpl(); @override Future openAsset( @@ -300,6 +300,13 @@ class PdfDocumentFactoryImpl implements PdfDocumentFactory { throw PdfException('Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).'); } } + + @override + Future reloadFonts() => throw UnimplementedError('FIXME: PdfrxEntryFunctions.reloadFonts is not implemented.'); + + @override + Future addFontData({required String face, required Uint8List data}) => + throw UnimplementedError('FIXME: PdfrxEntryFunctions.addFontData is not implemented.'); } extension _FpdfUtf8StringExt on String { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index c650bf97..f56b7123 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -23,6 +23,7 @@ class Pdfrx { /// Font paths scanned by pdfium if supported. /// + /// It should be set before calling any Pdfrx's functions. /// It is not supported on Flutter Web. static final fontPaths = []; @@ -62,8 +63,8 @@ class Pdfrx { static FutureOr Function()? getCacheDirectory; } -abstract class PdfDocumentFactory { - static PdfDocumentFactory instance = PdfDocumentFactoryImpl(); +abstract class PdfrxEntryFunctions { + static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); Future openAsset( String name, { @@ -110,6 +111,10 @@ abstract class PdfDocumentFactory { Map? headers, bool withCredentials = false, }); + + Future reloadFonts(); + + Future addFontData({required String face, required Uint8List data}); } /// Callback function to notify download progress. @@ -174,7 +179,7 @@ abstract class PdfDocument { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, - }) => PdfDocumentFactory.instance.openFile( + }) => PdfrxEntryFunctions.instance.openFile( filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -195,7 +200,7 @@ abstract class PdfDocument { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, - }) => PdfDocumentFactory.instance.openAsset( + }) => PdfrxEntryFunctions.instance.openAsset( name, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -225,7 +230,7 @@ abstract class PdfDocument { String? sourceName, bool allowDataOwnershipTransfer = false, void Function()? onDispose, - }) => PdfDocumentFactory.instance.openData( + }) => PdfrxEntryFunctions.instance.openData( data, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -263,7 +268,7 @@ abstract class PdfDocument { bool useProgressiveLoading = false, int? maxSizeToCacheOnMemory, void Function()? onDispose, - }) => PdfDocumentFactory.instance.openCustom( + }) => PdfrxEntryFunctions.instance.openCustom( read: read, fileSize: fileSize, sourceName: sourceName, @@ -305,7 +310,7 @@ abstract class PdfDocument { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, - }) => PdfDocumentFactory.instance.openUri( + }) => PdfrxEntryFunctions.instance.openUri( uri, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -1618,6 +1623,10 @@ class PdfFontQuery { bool get isFixed => (pitchFamily & 1) != 0; bool get isRoman => (pitchFamily & 16) != 0; bool get isScript => (pitchFamily & 64) != 0; + + @override + String toString() => + 'PdfFontQuery(face: "$face", weight: $weight, italic: $italic, charset: $charset, pitchFamily: $pitchFamily)'; } /// PDFium font charset ID. @@ -1647,4 +1656,7 @@ enum PdfFontCharset { /// Convert PDFium's charset ID to [PdfFontCharset]. static PdfFontCharset fromPdfiumCharsetId(int id) => _value2Enum[id]!; + + @override + String toString() => '$name($pdfiumCharsetId)'; } From a9b3ed474965eabc17b892006a1bc4b7a8c902ac Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 30 Jul 2025 04:54:53 +0900 Subject: [PATCH 251/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f62bf920..75dfba42 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2734,6 +2734,14 @@ class _PdfViewerState extends State @override DocumentCoordinateConverter get doc2local => this; + + void forceRepaintAllPageImages() { + _imageCache.cancelAllPendingRenderings(); + _magnifierImageCache.cancelAllPendingRenderings(); + _imageCache.releaseAllImages(); + _magnifierImageCache.releaseAllImages(); + _invalidate(); + } } class _PdfPageImageCache { @@ -3403,6 +3411,9 @@ class PdfViewerController extends ValueListenable { /// Request focus to the [PdfViewer]. void requestFocus() => _state._requestFocus(); + + /// Force redraw all the page images. + void forceRepaintAllPageImages() => _state.forceRepaintAllPageImages(); } /// [PdfViewerController.calcFitZoomMatrices] returns the list of this class. From ca6e1e1fd0def5d1e22eed27cdaa4d9ca8050d11 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 31 Jul 2025 03:39:07 +0900 Subject: [PATCH 252/663] WIP --- packages/pdfrx/assets/pdfium_worker.js | 12 +++++++++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 8 +++++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 4 ++++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 20 ++++++++++++++++--- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index a03f7122..c791df9b 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1465,6 +1465,17 @@ function addFontData(params) { return { message: `Font ${face} added`, face: face, fileName: fontFileNames[face] }; } +function clearAllFontData() { + console.log(`Clearing all font data`); + for (const face in fontFileNames) { + const fileName = fontFileNames[face]; + fileSystem.unregisterFile(`/usr/share/fonts/${fileName}`); + } + fileSystem.registerFile('/usr/share/fonts', { entries: [] }); + fontFileNames = {}; + return { message: 'All font data cleared' }; +} + /** * Functions that can be called from the main thread */ @@ -1482,6 +1493,7 @@ const functions = { loadLinks, reloadFonts, addFontData, + clearAllFontData, }; /** diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 3d1f7672..7da2fd5e 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -298,6 +298,12 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { final jsData = data.buffer.toJS; await _sendCommand('addFontData', parameters: {'face': face, 'data': jsData}, transfer: [jsData].toJS); } + + @override + Future clearAllFontData() async { + await _init(); + await _sendCommand('clearAllFontData', parameters: {'dummy': true}); + } } class _PdfDocumentWasm extends PdfDocument { @@ -405,7 +411,7 @@ class _PdfDocumentWasm extends PdfDocument { PdfFontQuery( face: font['face'] as String, weight: (font['weight'] as num).toInt(), - italic: (font['italic'] as bool), + isItalic: (font['italic'] as bool), charset: PdfFontCharset.fromPdfiumCharsetId((font['charset'] as num).toInt()), pitchFamily: (font['pitchFamily'] as num).toInt(), ), diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 75dfba42..6e50cb6e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1088,7 +1088,7 @@ class _PdfViewerState extends State bool _hitTestForTextSelection(ui.Offset position) { if (_selPartMoving != _TextSelectionPart.free && enableSelectionHandles) return false; - + if (_document == null || _layout == null) return false; for (int i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(position)) continue; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index d650f70e..22a10e99 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -307,6 +307,10 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future addFontData({required String face, required Uint8List data}) => throw UnimplementedError('FIXME: PdfrxEntryFunctions.addFontData is not implemented.'); + + @override + Future clearAllFontData() => + throw UnimplementedError('FIXME: PdfrxEntryFunctions.clearAllFontData is not implemented.'); } extension _FpdfUtf8StringExt on String { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index f56b7123..e515806d 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -115,6 +115,8 @@ abstract class PdfrxEntryFunctions { Future reloadFonts(); Future addFontData({required String face, required Uint8List data}); + + Future clearAllFontData(); } /// Callback function to notify download progress. @@ -1595,7 +1597,7 @@ class PdfFontQuery { const PdfFontQuery({ required this.face, required this.weight, - required this.italic, + required this.isItalic, required this.charset, required this.pitchFamily, }); @@ -1607,7 +1609,7 @@ class PdfFontQuery { final int weight; /// Whether the font is italic. - final bool italic; + final bool isItalic; /// PDFium's charset ID. final PdfFontCharset charset; @@ -1624,9 +1626,13 @@ class PdfFontQuery { bool get isRoman => (pitchFamily & 16) != 0; bool get isScript => (pitchFamily & 64) != 0; + String _getPitchFamily() { + return [if (isFixed) 'fixed', if (isRoman) 'roman', if (isScript) 'script'].join(','); + } + @override String toString() => - 'PdfFontQuery(face: "$face", weight: $weight, italic: $italic, charset: $charset, pitchFamily: $pitchFamily)'; + 'PdfFontQuery(face: "$face", weight: $weight, italic: $isItalic, charset: $charset, pitchFamily: $pitchFamily=[${_getPitchFamily()}])'; } /// PDFium font charset ID. @@ -1635,9 +1641,17 @@ enum PdfFontCharset { ansi(0), default_(1), symbol(2), + + /// Japanese shiftJis(128), + + /// Korean hangul(129), + + /// Chinese Simplified gb2312(134), + + /// Chinese Traditional chineseBig5(136), greek(161), vietnamese(163), From 44cfea881fc5a0e3973aa489ce5419effdefbff4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 1 Aug 2025 01:10:19 +0900 Subject: [PATCH 253/663] WIP: #432; magnifier target area is still strange if we select character like "l" and "i" --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index bdffaf65..e2769abe 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -620,6 +620,12 @@ abstract class PdfPage { handleLine(lineStart, inputFullText.length); } + if (rotation.index != 0) { + for (int i = 0; i < outputCharRects.length; i++) { + outputCharRects[i] = outputCharRects[i].rotateReverse(rotation.index, this); + } + } + final fragments = []; final text = PdfPageText( pageNumber: pageNumber, @@ -654,6 +660,12 @@ abstract class PdfPage { final inputFullText = await loadText(); final inputCharRects = await loadTextCharRects(); + if (rotation.index != 0) { + for (int i = 0; i < inputCharRects.length; i++) { + inputCharRects[i] = inputCharRects[i].rotate(rotation.index, this); + } + } + final fullText = StringBuffer(); final charRects = []; @@ -691,6 +703,7 @@ abstract class PdfPage { fullText.write(inputFullText.substring(prevEnd)); charRects.addAll(inputCharRects.sublist(prevEnd)); } + return PdfPageRawText(fullText.toString(), charRects); } From 8f55fffc73ebab110f643d81b91d2977968c9fdc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 1 Aug 2025 02:13:41 +0900 Subject: [PATCH 254/663] FIXED: The UI is distorted when selecting text Fixes #432 --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f62bf920..4a21a201 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1837,6 +1837,7 @@ class _PdfViewerState extends State Rect? _anchorARect; Rect? _anchorBRect; Rect? _magnifierRect; + Rect? _previousMagnifierRect; Rect? _contextMenuRect; _TextSelectionPart _hoverOn = _TextSelectionPart.none; @@ -2054,7 +2055,8 @@ class _PdfViewerState extends State if (magnifier != null && !isPositionalWidget(magnifier)) { final offset = calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; - magnifier = Positioned( + magnifier = AnimatedPositioned( + duration: _previousMagnifierRect != null ? const Duration(milliseconds: 100) : Duration.zero, left: offset.dx, top: offset.dy, child: WidgetSizeSniffer( @@ -2066,9 +2068,10 @@ class _PdfViewerState extends State }, ), ); + _previousMagnifierRect = _magnifierRect; } } else { - _magnifierRect = null; + _magnifierRect = _previousMagnifierRect = null; } } @@ -2331,9 +2334,15 @@ class _PdfViewerState extends State /// Calculate the rectangle shown in the magnifier for the given text anchor. Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { final c = textAnchor.page.charRects[textAnchor.index]; + + final (width, height) = switch (_document!.pages[textAnchor.page.pageNumber - 1].rotation.index & 1) { + 0 => (c.width, c.height), + _ => (c.height, c.width), + }; + final (baseUnit, v, h) = switch (textAnchor.direction) { - PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => (c.height, 2.0, 0.2), - PdfTextDirection.vrtl => (c.width, 0.2, 2.0), + PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => (height, 2.0, 0.2), + PdfTextDirection.vrtl => (width, 0.2, 2.0), }; return Rect.fromLTRB( textAnchor.rect.left - baseUnit * v, From e659affa13b238e272a2470710a3370c3ada2a2a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 1 Aug 2025 02:20:17 +0900 Subject: [PATCH 255/663] The list returned by PdfPage.loadTextCharRects don't have to be read-only. No consistency issues if user modify it. --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 68c6ad5b..4b382e52 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:collection'; import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui_web' as ui_web; @@ -481,7 +480,7 @@ class _PdfPageWasm extends PdfPage { final r = rect as List; return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); }).toList(); - return UnmodifiableListView(charRectsAll); + return charRectsAll; } @override From 048f8d74d29afb2a6ba15553edfb7baf3d054e12 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 1 Aug 2025 02:27:55 +0900 Subject: [PATCH 256/663] Release pdfrx_engine v0.1.12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 81732f72..115ef767 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.12 + +- Fix text character rectangle rotation handling in `loadTextCharRects()` and related methods + ## 0.1.11 - Make PdfiumDownloader class private to native implementation diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 36788739..41e6a15c 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.11 +version: 0.1.12 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 0cec9ba8e0661a63f85a8fb122471b72e63751bb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 1 Aug 2025 02:28:10 +0900 Subject: [PATCH 257/663] Release pdfrx v2.1.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8051cd0a..1b959006 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.2 + pdfrx: ^2.1.3 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.11 + pdfrx_engine: ^0.1.12 ``` ## Development diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 4243d85e..926b4514 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.1.3 + +- FIXED: UI distortion when selecting text ([#432](https://github.com/espresso3389/pdfrx/issues/432)) +- The list returned by `PdfPage.loadTextCharRects()` is now mutable for better flexibility +- Enhanced README documentation for text selection customization features +- Update pdfrx_engine dependency to 0.1.12 + # 2.1.2 - FIXED: Prevent right-click context menu from showing on Flutter Web diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 915fad34..b05eead4 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.2 + pdfrx: ^2.1.3 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 53c0127b..4ae29063 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.2 +version: 2.1.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.11 + pdfrx_engine: ^0.1.12 collection: crypto: ^3.0.6 ffi: From 382f31b3821e8f6e30bd1d9e3994eb1320ce911c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 5 Aug 2025 21:41:01 +0900 Subject: [PATCH 258/663] Update comments --- .../lib/src/mock/pdfrx_initialize_mock.dart | 15 +++++++++++++-- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart index fd3a49dc..f8172a76 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart @@ -1,6 +1,17 @@ -/// Mock implementation of pdfrxInitialize "that will never be supposed to be called." +/// @docImport '../pdfrx_api.dart'; +library; + +/// Initializes the Pdfrx library for Dart. /// -/// Without this level of abstraction, pub.dev's analyzer will complain about the WASM incompatibility... +/// This function sets up the following: +/// +/// - [Pdfrx.getCacheDirectory] is set to return the system temporary directory. +/// - [Pdfrx.pdfiumModulePath] is configured to point to the pdfium module. +/// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. +/// - If Pdfium module is not found, it will be downloaded from the internet. +/// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). +/// +/// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease}) async { throw UnimplementedError( 'Wow, this is not supposed to be called.\n' diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index e2769abe..8c1cd117 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1,4 +1,8 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first + +/// Pdfrx API +library; + import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; From f79e5e5b36028234b03618d7f3c22343b6edca7d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 6 Aug 2025 16:06:19 +0900 Subject: [PATCH 259/663] WIP --- packages/pdfrx/assets/pdfium_worker.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index c791df9b..6b097acc 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -379,6 +379,7 @@ class FileSystemEmulator { const context = this.fd2context[fd]; context.close?.call(context); delete this.fd2context[fd]; + return 0; } /** @@ -417,6 +418,7 @@ class FileSystemEmulator { const offsetLowHigh = new Uint32Array(Pdfium.memory.buffer, newOffset, 2); offsetLowHigh[0] = context.position; offsetLowHigh[1] = 0; + return 0; } /** @@ -439,6 +441,7 @@ class FileSystemEmulator { } const bytes_written = new Uint32Array(Pdfium.memory.buffer, ret_ptr, 1); bytes_written[0] = written; + return 0; } /** @@ -462,6 +465,7 @@ class FileSystemEmulator { } const bytes_read = new Uint32Array(Pdfium.memory.buffer, ret_ptr, 1); bytes_read[0] = total; + return 0; } sync(fd) { From 8796eba7e5119302485db7f9eda350999ed13b96 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 7 Aug 2025 02:22:49 +0900 Subject: [PATCH 260/663] WIP --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 4a21a201..e373da54 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1088,7 +1088,7 @@ class _PdfViewerState extends State bool _hitTestForTextSelection(ui.Offset position) { if (_selPartMoving != _TextSelectionPart.free && enableSelectionHandles) return false; - + if (_document == null || _layout == null) return false; for (int i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(position)) continue; @@ -2743,6 +2743,14 @@ class _PdfViewerState extends State @override DocumentCoordinateConverter get doc2local => this; + + void forceRepaintAllPageImages() { + _imageCache.cancelAllPendingRenderings(); + _magnifierImageCache.cancelAllPendingRenderings(); + _imageCache.releaseAllImages(); + _magnifierImageCache.releaseAllImages(); + _invalidate(); + } } class _PdfPageImageCache { @@ -3109,16 +3117,16 @@ class PdfViewerController extends ValueListenable { /// Get the associated document. /// /// Please note that the field does not ensure that the [PdfDocument] is alive during long asynchronous operations. - /// If you want to do some time consuming asynchronous operation, use [useDocument] instead. - @Deprecated('Use useDocument instead') + /// + /// **If you want to do some time consuming asynchronous operation, consider to use [useDocument] instead.** PdfDocument get document => _state._document!; /// Get the associated pages. /// /// Please note that the field does not ensure that the associated [PdfDocument] is alive during long asynchronous - /// operations. If you want to do some time consuming asynchronous operation, use [useDocument] instead. - /// For page count, use [pageCount] instead. - @Deprecated('Use useDocument instead') + /// operations. For page count, use [pageCount] instead. + /// + /// **If you want to do some time consuming asynchronous operation, consider to use [useDocument] instead.** List get pages => _state._document!.pages; /// Get the page count of the document. @@ -3412,6 +3420,9 @@ class PdfViewerController extends ValueListenable { /// Request focus to the [PdfViewer]. void requestFocus() => _state._requestFocus(); + + /// Force redraw all the page images. + void forceRepaintAllPageImages() => _state.forceRepaintAllPageImages(); } /// [PdfViewerController.calcFitZoomMatrices] returns the list of this class. From bfe9e08f0f8bc33141f021fe663d55026be71327 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 7 Aug 2025 03:10:24 +0900 Subject: [PATCH 261/663] WASM related fixes and updates --- packages/pdfrx/assets/pdfium_worker.js | 247 ++++++++++++++++++++++--- 1 file changed, 222 insertions(+), 25 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index b858400c..619cd1eb 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -16,6 +16,7 @@ const Pdfium = { Pdfium.stackSave = Pdfium.wasmExports['emscripten_stack_get_current']; Pdfium.stackRestore = Pdfium.wasmExports['_emscripten_stack_restore']; Pdfium.setThrew = Pdfium.wasmExports['setThrew']; + Pdfium.__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; }, /** @@ -30,6 +31,18 @@ const Pdfium = { * @type {WebAssembly.Table} */ wasmTable: null, + /** + * @type {WebAssembly.Table} + */ + wasmTableMirror: [], + /** + * @type {WeakMap} + */ + functionsInTableMap: null, + /** + * @type {number[]} + */ + freeTableIndexes: [], /** * @type {function():number} */ @@ -42,6 +55,10 @@ const Pdfium = { * @type {function(number, number):void} */ setThrew: null, + /** + * @type {function(number):number} + */ + __emscripten_stack_alloc: null, /** * Invoke a function from the WASM table @@ -59,6 +76,178 @@ const Pdfium = { Pdfium.setThrew(1, 0); } }, + + getCFunc: (ident) => Pdfium.wasmExports['_' + ident], + writeArrayToMemory: (array, buffer) => HEAP8.set(array, buffer), + stackAlloc: (sz) => Pdfium.__emscripten_stack_alloc(sz), + stringToUTF8OnStack: (str) => { + const size = StringUtils.lengthBytesUTF8(str) + 1; + const ret = Pdfium.stackAlloc(size); + StringUtils.stringToUtf8Bytes(str, ret); + return ret; + }, + ccall: (ident, returnType, argTypes, args, opts) => { + const toC = { + string: (str) => { + let ret = 0; + if (str !== null && str !== undefined && str !== 0) { + ret = Pdfium.stringToUTF8OnStack(str); + } + return ret; + }, + array: (arr) => { + const ret = Pdfium.stackAlloc(arr.length); + Pdfium.writeArrayToMemory(arr, ret); + return ret; + }, + }; + function convertReturnValue(ret) { + if (returnType === 'string') return UTF8ToString(ret); + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + const func = Pdfium.getCFunc(ident); + const cArgs = []; + let stack = 0; + if (args) { + for (let i = 0; i < args.length; i++) { + const converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Pdfium.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + let ret = func(...cArgs); + function onDone(ret) { + if (stack !== 0) Pdfium.stackRestore(stack); + return convertReturnValue(ret); + } + ret = onDone(ret); + return ret; + }, + cwrap: (ident, returnType, argTypes, opts) => { + const numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean'); + const numericRet = returnType !== 'string'; + if (numericRet && numericArgs && !opts) { + return Pdfium.getCFunc(ident); + } + return (...args) => Pdfium.ccall(ident, returnType, argTypes, args, opts); + }, + uleb128Encode: (n, target) => { + if (n < 128) { + target.push(n); + } else { + target.push(n % 128 | 128, n >> 7); + } + }, + sigToWasmTypes: (sig) => { + const typeNames = { + i: 'i32', + j: 'i64', + f: 'f32', + d: 'f64', + e: 'externref', + p: 'i32', + }; + const type = { + parameters: [], + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]], + }; + for (let i = 1; i < sig.length; ++i) { + type.parameters.push(typeNames[sig[i]]); + } + return type; + }, + generateFuncType: (sig, target) => { + const sigRet = sig.slice(0, 1); + const sigParam = sig.slice(1); + const typeCodes = { i: 127, p: 127, j: 126, f: 125, d: 124, e: 111 }; + target.push(96); + Pdfium.uleb128Encode(sigParam.length, target); + for (let i = 0; i < sigParam.length; ++i) { + target.push(typeCodes[sigParam[i]]); + } + if (sigRet == 'v') { + target.push(0); + } else { + target.push(1, typeCodes[sigRet]); + } + }, + convertJsFunctionToWasm: (func, sig) => { + if (typeof WebAssembly.Function == 'function') { + return new WebAssembly.Function(Pdfium.sigToWasmTypes(sig), func); + } + const typeSectionBody = [1]; + Pdfium.generateFuncType(sig, typeSectionBody); + const bytes = [0, 97, 115, 109, 1, 0, 0, 0, 1]; + Pdfium.uleb128Encode(typeSectionBody.length, bytes); + bytes.push(...typeSectionBody); + bytes.push(2, 7, 1, 1, 101, 1, 102, 0, 0, 7, 5, 1, 1, 102, 0, 0); + const module = new WebAssembly.Module(new Uint8Array(bytes)); + const instance = new WebAssembly.Instance(module, { e: { f: func } }); + const wrappedFunc = instance.exports['f']; + return wrappedFunc; + }, + updateTableMap: (offset, count) => { + if (Pdfium.functionsInTableMap) { + for (let i = offset; i < offset + count; i++) { + const item = Pdfium.wasmTable.get(i); + if (item) { + Pdfium.functionsInTableMap.set(item, i); + } + } + } + }, + getFunctionAddress: (func) => { + if (!Pdfium.functionsInTableMap) { + Pdfium.functionsInTableMap = new WeakMap(); + Pdfium.updateTableMap(0, Pdfium.wasmTable.length); + } + return Pdfium.functionsInTableMap.get(func) || 0; + }, + getEmptyTableSlot: () => { + if (Pdfium.freeTableIndexes.length) return Pdfium.freeTableIndexes.pop(); + try { + Pdfium.wasmTable.grow(1); + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; + } + return Pdfium.wasmTable.length - 1; + }, + /** + * @param {function} func Function to add + * @param {string} sig Signature of the function + * @return {number} Function index in the table + */ + addFunction: (func, sig) => { + const rtn = Pdfium.getFunctionAddress(func); + if (rtn) { + return rtn; + } + const ret = Pdfium.getEmptyTableSlot(); + try { + Pdfium.wasmTable.set(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + const wrapped = Pdfium.convertJsFunctionToWasm(func, sig); + Pdfium.wasmTable.set(ret, wrapped); + } + Pdfium.functionsInTableMap.set(func, ret); + return ret; + }, + removeFunction: (index) => { + Pdfium.functionsInTableMap.delete(Pdfium.wasmTable.get(index)); + Pdfium.wasmTable.set(index, null); + Pdfium.freeTableIndexes.push(index); + }, }; /** @@ -124,7 +313,7 @@ class FileSystemEmulator { /** * Register file * @param {string} fn Filename - * @param {FileContext} context I/O functions/data + * @param {FileContext|DirectoryContext} context I/O functions/data */ registerFile(fn, context) { this.fn2context[fn] = context; @@ -154,15 +343,6 @@ class FileSystemEmulator { }); } - /** - * Register directory - * @param {string} fn Filename - * @param {*} entries Directory entries (For directories, the name should be terminated with /) - */ - registerDirectoryWithEntries(fn, entries) { - this.registerFile(fn, { entries }); - } - /** * Unregister file/directory context * @param {string} fn Filename @@ -199,36 +379,46 @@ class FileSystemEmulator { const context = this.fd2context[fd]; context.close?.call(context); delete this.fd2context[fd]; + return 0; } /** * Seek to a position in a file * @param {number} fd File descriptor - * @param {number} offset_low Offset low - * @param {number} offset_high Offset high - * @param {number} whence Whence - * @param {number} newOffset New offset * @returns {number} New offset */ - seek(fd, offset_low, offset_high, whence, newOffset) { - const context = this.fd2context[fd]; - if (offset_high !== 0) { - throw new Error('seek: offset_high is not supported'); + seek(fd) { + let offset, whence, newOffset; + if (arguments.length == 4) { + // (fd: number, offset: BigInt, whence: number, newOffset: number) + offset = Number(arguments[1]); // BigInt to Number + whence = arguments[2]; + newOffset = arguments[3]; + } else if (arguments.length == 5) { + // (fd: number, offset_low: number, offset_high: number, whence: number, newOffset: number) + offset = arguments[1]; // offset_low; offset_high is ignored + whence = arguments[3]; + newOffset = arguments[4]; + } else { + throw new Error(`seek: invalid arguments count: ${arguments.length}`); } + + const context = this.fd2context[fd]; switch (whence) { case 0: // SEEK_SET - context.position = offset_low; + context.position = offset; break; case 1: // SEEK_CUR - context.position += offset_low; + context.position += offset; break; case 2: // SEEK_END - context.position = context.size + offset_low; + context.position = context.size + offset; break; } const offsetLowHigh = new Uint32Array(Pdfium.memory.buffer, newOffset, 2); offsetLowHigh[0] = context.position; offsetLowHigh[1] = 0; + return 0; } /** @@ -251,6 +441,7 @@ class FileSystemEmulator { } const bytes_written = new Uint32Array(Pdfium.memory.buffer, ret_ptr, 1); bytes_written[0] = written; + return 0; } /** @@ -274,6 +465,7 @@ class FileSystemEmulator { } const bytes_read = new Uint32Array(Pdfium.memory.buffer, ret_ptr, 1); bytes_read[0] = total; + return 0; } sync(fd) { @@ -316,7 +508,7 @@ class FileSystemEmulator { /** * __syscall_getdents64 * @param {num} fd - * @param {num} dirp + * @param {num} dirp struct linux_dirent64 * @param {num} count * @returns {num} */ @@ -324,12 +516,14 @@ class FileSystemEmulator { /** @type {DirectoryFileDescriptorContext} */ const context = this.fd2context[fd]; const entries = context.entries; - if (entries == null) return 0; + context.getdents_position = context.getdents_position || 0; + if (entries == null) return -1; let written = 0; const DT_REG = 8, DT_DIR = 4; _memset(dirp, 0, count); - for (let i = context.position; i < entries.length; i++) { + for (; context.position < entries.length; context.position++) { + const i = context.position; let d_type, d_name; if (entries[i].endsWith('/')) { d_type = DT_DIR; @@ -340,13 +534,15 @@ class FileSystemEmulator { } const d_nameLength = StringUtils.lengthBytesUTF8(d_name) + 1; const size = 8 + 8 + 2 + 1 + d_nameLength; - if (written + size > count) break; + const buffer = new Uint8Array(Pdfium.memory.buffer, dirp + written, size); // d_off const d_off = written + size; buffer[8] = d_off & 255; buffer[9] = (d_off >> 8) & 255; + buffer[10] = (d_off >> 16) & 255; + buffer[11] = (d_off >> 24) & 255; // d_reclen buffer[16] = size & 255; buffer[17] = (size >> 8) & 255; @@ -409,6 +605,7 @@ const emEnv = { throw Infinity; }, _gmtime_js: function (time, tmPtr) { + time = Number(time); const date = new Date(time * 1000); const tm = new Int32Array(Pdfium.memory.buffer, tmPtr, 9); tm[0] = date.getUTCSeconds(); From 1174be6ce28c4cf34a9e9a17562b52a41e6e9580 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 7 Aug 2025 14:47:23 +0900 Subject: [PATCH 262/663] WIP on wasm_missing_fonts_t1 --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 10 +++++----- packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 +++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6e50cb6e..6bb8f41e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -3108,16 +3108,16 @@ class PdfViewerController extends ValueListenable { /// Get the associated document. /// /// Please note that the field does not ensure that the [PdfDocument] is alive during long asynchronous operations. - /// If you want to do some time consuming asynchronous operation, use [useDocument] instead. - @Deprecated('Use useDocument instead') + /// + /// **If you want to do some time consuming asynchronous operation, consider to use [useDocument] instead.** PdfDocument get document => _state._document!; /// Get the associated pages. /// /// Please note that the field does not ensure that the associated [PdfDocument] is alive during long asynchronous - /// operations. If you want to do some time consuming asynchronous operation, use [useDocument] instead. - /// For page count, use [pageCount] instead. - @Deprecated('Use useDocument instead') + /// operations. For page count, use [pageCount] instead. + /// + /// **If you want to do some time consuming asynchronous operation, consider to use [useDocument] instead.** List get pages => _state._document!.pages; /// Get the page count of the document. diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index ba8a2f0f..96f6e9c3 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -72,4 +72,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future addFontData({required String face, required Uint8List data}) => unimplemented(); + + @override + Future clearAllFontData() => unimplemented(); } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index e515806d..1ad916d1 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1574,7 +1574,7 @@ class PdfPoint { /// Compares two lists for element-by-element equality. /// -/// **NOTE: This function is copiedd from flutter's `foundation` library to remove dependency to Flutter** +/// **NOTE: This function is copied from flutter's `foundation` library to remove dependency to Flutter** bool _listEquals(List? a, List? b) { if (a == null) { return b == null; From cdd6c40023a327b8f5d49fe886a6b9c4a503f7c3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 7 Aug 2025 14:49:57 +0900 Subject: [PATCH 263/663] WIP --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 8c1cd117..bbcb14d1 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1569,7 +1569,7 @@ class PdfPoint { /// Compares two lists for element-by-element equality. /// -/// **NOTE: This function is copiedd from flutter's `foundation` library to remove dependency to Flutter** +/// **NOTE: This function is copied from flutter's `foundation` library to remove dependency to Flutter** bool _listEquals(List? a, List? b) { if (a == null) { return b == null; From 174604561ab87a71ee1c4d7e51d61b98e54629d6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 10 Aug 2025 02:19:48 +0900 Subject: [PATCH 264/663] WIP: stability fixes --- .gitignore | 1 + packages/pdfrx/assets/pdfium_worker.js | 2 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 53 +++++++++++--------- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 6 +++ 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 2be28b81..d99440f9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ migrate_working_dir/ .claude/ pubspec_overrides.yaml +.mcp.json diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 619cd1eb..48923ac1 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -516,8 +516,8 @@ class FileSystemEmulator { /** @type {DirectoryFileDescriptorContext} */ const context = this.fd2context[fd]; const entries = context.entries; + if (entries == null) return -1; // not a directory context.getdents_position = context.getdents_position || 0; - if (entries == null) return -1; let written = 0; const DT_REG = 8, DT_DIR = 4; diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 4b382e52..e13d5462 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -338,36 +338,39 @@ class _PdfDocumentWasm extends PdfDocument { T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { - int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); - if (firstPageIndex < 0) return; // All pages are already loaded + if (isDisposed) return; + await synchronized(() async { + int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); + if (firstPageIndex < 0) return; // All pages are already loaded - for (; firstPageIndex < pages.length;) { - if (isDisposed) return; - final result = await _sendCommand( - 'loadPagesProgressively', - parameters: { - 'docHandle': document['docHandle'], - 'firstPageIndex': firstPageIndex, - 'loadUnitDuration': loadUnitDuration.inMilliseconds, - }, - ); - final pagesLoaded = parsePages(this, result['pages'] as List); - firstPageIndex += pagesLoaded.length; - for (final page in pagesLoaded) { - pages[page.pageNumber - 1] = page; // Update the existing page - } + for (; firstPageIndex < pages.length;) { + if (isDisposed) return; + final result = await _sendCommand( + 'loadPagesProgressively', + parameters: { + 'docHandle': document['docHandle'], + 'firstPageIndex': firstPageIndex, + 'loadUnitDuration': loadUnitDuration.inMilliseconds, + }, + ); + final pagesLoaded = parsePages(this, result['pages'] as List); + firstPageIndex += pagesLoaded.length; + for (final page in pagesLoaded) { + pages[page.pageNumber - 1] = page; // Update the existing page + } - if (!subject.isClosed) { - subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); - } + if (!subject.isClosed) { + subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); + } - if (onPageLoadProgress != null) { - if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { - // If the callback returns false, stop loading more pages - break; + if (onPageLoadProgress != null) { + if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { + // If the callback returns false, stop loading more pages + break; + } } } - } + }); } @override diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index bbcb14d1..93f7cc81 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -664,6 +664,12 @@ abstract class PdfPage { final inputFullText = await loadText(); final inputCharRects = await loadTextCharRects(); + if (inputFullText.length != inputCharRects.length) { + throw Exception( + 'Page $pageNumber: Internal Error: text and character rects length mismatch (${inputFullText.length} <=> ${inputCharRects.length})', + ); + } + if (rotation.index != 0) { for (int i = 0; i < inputCharRects.length; i++) { inputCharRects[i] = inputCharRects[i].rotate(rotation.index, this); From 31920a883b3442f8eba0b9143b0d47b83cc43ac2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 16 Aug 2025 01:26:46 +0900 Subject: [PATCH 265/663] Fix for the previous merge --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 11 ++-- .../lib/src/native/native_utils.dart | 50 +++++++++++++++++ .../lib/src/native/pdf_file_cache.dart | 5 +- .../lib/src/native/pdfrx_pdfium.dart | 53 +++++++++++-------- 4 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/native/native_utils.dart diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 0b4e53b9..3cd9334c 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -387,12 +387,13 @@ class _PdfDocumentWasm extends PdfDocument { subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); } - updateMissingFonts(result['missingFonts']); + updateMissingFonts(result['missingFonts']); - if (onPageLoadProgress != null) { - if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { - // If the callback returns false, stop loading more pages - break; + if (onPageLoadProgress != null) { + if (!await onPageLoadProgress(firstPageIndex, pages.length, data)) { + // If the callback returns false, stop loading more pages + break; + } } } }); diff --git a/packages/pdfrx_engine/lib/src/native/native_utils.dart b/packages/pdfrx_engine/lib/src/native/native_utils.dart new file mode 100644 index 00000000..3a4e1077 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/native_utils.dart @@ -0,0 +1,50 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../../pdfrx_engine.dart'; + +/// Helper function to get the cache directory for a specific purpose and name. +Future getCacheDirectory( + String part1, [ + String? part2, + String? part3, + String? part4, + String? part5, + String? part6, + String? part7, + String? part8, + String? part9, + String? part10, + String? part11, + String? part12, + String? part13, + String? part14, + String? part15, +]) async { + if (Pdfrx.getCacheDirectory == null) { + throw StateError('Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.'); + } + final dir = Directory( + path.join( + await Pdfrx.getCacheDirectory!(), + part1, + part2, + part3, + part4, + part5, + part6, + part7, + part8, + part9, + part10, + part11, + part12, + part13, + part14, + part15, + ), + ); + await dir.create(recursive: true); + return dir; +} diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 7e1633da..d20004dd 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -12,6 +12,7 @@ import 'package:synchronized/extension.dart'; import '../pdfrx_api.dart'; import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; +import 'native_utils.dart'; final _rafFinalizer = Finalizer((raf) { // Attempt to close the file if it hasn't been closed explicitly. @@ -243,7 +244,6 @@ class PdfFileCache { if (Pdfrx.getCacheDirectory == null) { throw StateError('Pdfrx.getCacheDirectory is not set. Please set it to get cache directory.'); } - final cacheDir = await Pdfrx.getCacheDirectory!(); final fnHash = sha1 .convert(utf8.encode(uri.toString())) .bytes @@ -252,8 +252,7 @@ class PdfFileCache { final dir1 = fnHash.substring(0, 2); final dir2 = fnHash.substring(2, 4); final body = fnHash.substring(4); - final dir = Directory(path.join(cacheDir, 'pdfrx.cache', dir1, dir2)); - await dir.create(recursive: true); + final dir = await getCacheDirectory('pdfrx.cache', dir1, dir2); return File(path.join(dir.path, '$body.pdf')); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 22a10e99..ffce5fd3 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -7,8 +7,10 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:synchronized/extension.dart'; import '../pdfrx_api.dart'; +import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; @@ -38,31 +40,40 @@ DynamicLibrary _getModule() { /// Loaded PDFium module. final pdfium = pdfium_bindings.pdfium(_getModule()); +Directory? _appLocalFontPath; + bool _initialized = false; /// Initializes PDFium library. -void _init() { +Future _init() async { if (_initialized) return; - using((arena) { - final config = arena.allocate(sizeOf()); - config.ref.version = 2; - - if (Pdfrx.fontPaths.isNotEmpty) { - final fontPathArray = arena.allocate>(sizeOf>() * (Pdfrx.fontPaths.length + 1)); - for (int i = 0; i < Pdfrx.fontPaths.length; i++) { - fontPathArray[i] = Pdfrx.fontPaths[i].toUtf8(arena); + await pdfium.synchronized(() async { + if (_initialized) return; + + _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); + + return using((arena) { + final config = arena.allocate(sizeOf()); + config.ref.version = 2; + + final fontPaths = [_appLocalFontPath!.path, ...Pdfrx.fontPaths]; + if (fontPaths.isNotEmpty) { + final fontPathArray = arena.allocate>(sizeOf>() * (fontPaths.length + 1)); + for (int i = 0; i < fontPaths.length; i++) { + fontPathArray[i] = fontPaths[i].toUtf8(arena); + } + fontPathArray[fontPaths.length] = nullptr; + config.ref.m_pUserFontPaths = fontPathArray; + } else { + config.ref.m_pUserFontPaths = nullptr; } - fontPathArray[Pdfrx.fontPaths.length] = nullptr; - config.ref.m_pUserFontPaths = fontPathArray; - } else { - config.ref.m_pUserFontPaths = nullptr; - } - config.ref.m_pIsolate = nullptr; - config.ref.m_v8EmbedderSlot = 0; - pdfium.FPDF_InitLibraryWithConfig(config); + config.ref.m_pIsolate = nullptr; + config.ref.m_v8EmbedderSlot = 0; + pdfium.FPDF_InitLibraryWithConfig(config); + _initialized = true; + }); }); - _initialized = true; } final backgroundWorker = BackgroundWorker.create(); @@ -117,8 +128,8 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, - }) { - _init(); + }) async { + await _init(); return _openByFunc( (password) async => (await backgroundWorker).computeWithArena((arena, params) { final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); @@ -172,7 +183,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { - _init(); + await _init(); maxSizeToCacheOnMemory ??= 1024 * 1024; // the default is 1MB From 01ca21af7396af37467ff32592bd3fba87ed1c49 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 22 Aug 2025 22:03:36 +0900 Subject: [PATCH 266/663] FIXED: #441 Text coords was not correctly calculated if CropBox/MediaBox has non-0 origin --- .gitignore | 2 ++ packages/pdfrx/assets/pdfium_worker.js | 17 +++++++-- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 32 ++++++++++++++--- .../lib/src/native/pdfrx_pdfium.dart | 36 +++++++++++++++---- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index d99440f9..4f807b50 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ migrate_working_dir/ pubspec_overrides.yaml .mcp.json + +.serena/ diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 9c83d116..133a9320 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -836,7 +836,7 @@ function _resetMissingFonts() { /** * @typedef {{docHandle: number,permissions: number, securityHandlerRevision: number, pages: PdfPage[], formHandle: number, formInfo: number, missingFonts: FontQueries}} PdfDocument - * @typedef {{pageIndex: number, width: number, height: number, rotation: number, isLoaded: boolean}} PdfPage + * @typedef {{pageIndex: number, width: number, height: number, rotation: number, isLoaded: boolean, bbLeft: number, bbBottom: number}} PdfPage * @typedef {{errorCode: number, errorCodeStr: string|undefined, message: string}} PdfError */ @@ -883,6 +883,8 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { height: firstPage.height, rotation: firstPage.rotation, isLoaded: false, + bbLeft: 0, + bbBottom: 0, }); } } @@ -933,12 +935,21 @@ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountT throw new Error(`FPDF_LoadPage failed (${_getErrorMessage(error)})`); } + const rectBuffer = Pdfium.wasmExports.malloc(4 * 4); // FS_RECTF: float[4] + Pdfium.wasmExports.FPDF_GetPageBoundingBox(pageHandle, rectBuffer); + const rect = new Float32Array(Pdfium.memory.buffer, rectBuffer, 4); + const bbLeft = rect[0]; + const bbBottom = rect[3]; + Pdfium.wasmExports.free(rectBuffer); + pages.push({ pageIndex: i, - width: Pdfium.wasmExports.FPDF_GetPageWidth(pageHandle), - height: Pdfium.wasmExports.FPDF_GetPageHeight(pageHandle), + width: Pdfium.wasmExports.FPDF_GetPageWidthF(pageHandle), + height: Pdfium.wasmExports.FPDF_GetPageHeightF(pageHandle), rotation: Pdfium.wasmExports.FPDFPage_GetRotation(pageHandle), isLoaded: true, + bbLeft: bbLeft, + bbBottom: bbBottom, }); Pdfium.wasmExports.FPDF_ClosePage(pageHandle); if (t != null && Date.now() > t) { diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 3cd9334c..95480d0f 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -442,6 +442,8 @@ class _PdfDocumentWasm extends PdfDocument { page['height'], (page['rotation'] as num).toInt(), (page['isLoaded'] as bool?) ?? false, + (page['bbLeft'] as num).toDouble(), + (page['bbBottom'] as num).toDouble(), ), ) .toList(); @@ -463,8 +465,16 @@ class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken } class _PdfPageWasm extends PdfPage { - _PdfPageWasm(this.document, int pageIndex, this.width, this.height, int rotation, this.isLoaded) - : pageNumber = pageIndex + 1, + _PdfPageWasm( + this.document, + int pageIndex, + this.width, + this.height, + int rotation, + this.isLoaded, + this.bbLeft, + this.bbBottom, + ) : pageNumber = pageIndex + 1, rotation = PdfPageRotation.values[rotation]; @override @@ -493,7 +503,12 @@ class _PdfPageWasm extends PdfPage { final rects = (link['rects'] as List).map((r) { final rect = r as List; - return PdfRect(rect[0] as double, rect[1] as double, rect[2] as double, rect[3] as double); + return PdfRect( + (rect[0] as double) - bbLeft, + (rect[1] as double) - bbBottom, + (rect[2] as double) - bbLeft, + (rect[3] as double) - bbBottom, + ); }).toList(); final url = link['url']; if (url is String) { @@ -528,7 +543,12 @@ class _PdfPageWasm extends PdfPage { final charRectsAll = (result['charRects'] as List).map((rect) { final r = rect as List; - return PdfRect(r[0] as double, r[1] as double, r[2] as double, r[3] as double); + return PdfRect( + (r[0] as double) - bbLeft, + (r[1] as double) - bbBottom, + (r[2] as double) - bbLeft, + (r[3] as double) - bbBottom, + ); }).toList(); return charRectsAll; } @@ -548,6 +568,10 @@ class _PdfPageWasm extends PdfPage { @override final bool isLoaded; + final double bbLeft; + + final double bbBottom; + @override Future render({ int x = 0, diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index ffce5fd3..c6830e00 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -467,14 +467,18 @@ class _PdfDocumentPdfium extends PdfDocument { ? pageCount : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); final t = params.timeoutUs != null ? (Stopwatch()..start()) : null; - final pages = <({double width, double height, int rotation})>[]; + final pages = <({double width, double height, int rotation, double bbLeft, double bbBottom})>[]; for (int i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); try { + final rect = arena.allocate(sizeOf()); + pdfium.FPDF_GetPageBoundingBox(page, rect); pages.add(( width: pdfium.FPDF_GetPageWidthF(page), height: pdfium.FPDF_GetPageHeightF(page), rotation: pdfium.FPDFPage_GetRotation(page), + bbLeft: rect.ref.left.toDouble(), + bbBottom: rect.ref.bottom.toDouble(), )); } finally { pdfium.FPDF_ClosePage(page); @@ -504,6 +508,8 @@ class _PdfDocumentPdfium extends PdfDocument { width: pageData.width, height: pageData.height, rotation: PdfPageRotation.values[pageData.rotation], + bbLeft: pageData.bbLeft, + bbBottom: pageData.bbBottom, isLoaded: true, ), ); @@ -519,6 +525,8 @@ class _PdfDocumentPdfium extends PdfDocument { width: last.width, height: last.height, rotation: last.rotation, + bbLeft: last.bbLeft, + bbBottom: last.bbBottom, isLoaded: false, ), ); @@ -602,6 +610,12 @@ class _PdfPagePdfium extends PdfPage { @override final double height; + /// Bounding box left + final double bbLeft; + + /// Bounding box bottom + final double bbBottom; + @override final PdfPageRotation rotation; @@ -614,6 +628,8 @@ class _PdfPagePdfium extends PdfPage { required this.width, required this.height, required this.rotation, + required this.bbLeft, + required this.bbBottom, required this.isLoaded, }); @@ -808,7 +824,7 @@ class _PdfPagePdfium extends PdfPage { rectBuffer.offset(doubleSize * 3), // B rectBuffer.offset(doubleSize), // T ); - charRects.add(_rectFromLTRBBuffer(rectBuffer)); + charRects.add(_rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom)); } return charRects; } finally { @@ -816,7 +832,7 @@ class _PdfPagePdfium extends PdfPage { pdfium.FPDF_ClosePage(page); } }), - (docHandle: document.document.address, pageNumber: pageNumber), + (docHandle: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom), ); } @@ -862,7 +878,7 @@ class _PdfPagePdfium extends PdfPage { rectBuffer.offset(doubleSize * 2), rectBuffer.offset(doubleSize * 3), ); - return _rectFromLTRBBuffer(rectBuffer); + return _rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom); }); return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); }); @@ -872,7 +888,7 @@ class _PdfPagePdfium extends PdfPage { pdfium.FPDFText_ClosePage(textPage); pdfium.FPDF_ClosePage(page); } - }, (document: document.document.address, pageNumber: pageNumber)); + }, (document: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom)); static String _getLinkUrl(pdfium_bindings.FPDF_PAGELINK linkPage, int linkIndex, Arena arena) { final urlLength = pdfium.FPDFLink_GetURL(linkPage, linkIndex, nullptr, 0); @@ -964,6 +980,14 @@ class _PdfPagePdfium extends PdfPage { return null; } } + + static PdfRect _rectFromLTRBBuffer(Pointer buffer, double bbLeft, double bbBottom) { + final left = buffer[0] - bbLeft; + final top = buffer[1] - bbBottom; + final right = buffer[2] - bbLeft; + final bottom = buffer[3] - bbBottom; + return PdfRect(left, top, right, bottom); + } } class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { @@ -1011,8 +1035,6 @@ class _PdfImagePdfium extends PdfImage { } } -PdfRect _rectFromLTRBBuffer(Pointer buffer) => PdfRect(buffer[0], buffer[1], buffer[2], buffer[3]); - extension _PointerExt on Pointer { Pointer offset(int offsetInBytes) => Pointer.fromAddress(address + offsetInBytes); } From a937a98988ed88b76672dd1929f2499b615b4113 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 24 Aug 2025 02:06:28 +0900 Subject: [PATCH 267/663] Release pdfrx v2.1.4 and pdfrx_engine v0.1.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pdfrx_engine v0.1.13: - Add font loading APIs for WASM - Fix text coordinate calculation with non-zero origin CropBox/MediaBox (#441) - Improve WASM stability - pdfrx v2.1.4: - Include all fixes from pdfrx_engine v0.1.13 - Add support for dynamic font loading with PdfDocumentMissingFontsEvent 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 7 +++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 926b4514..f8ed586a 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.1.4 + +- FIXED: Text coordinate calculation when CropBox/MediaBox has non-zero origin ([#441](https://github.com/espresso3389/pdfrx/issues/441)) +- Add support for dynamic font loading in WASM with `PdfDocumentMissingFontsEvent` +- Improve WASM stability and font handling mechanisms +- Update pdfrx_engine dependency to 0.1.13 + # 2.1.3 - FIXED: UI distortion when selecting text ([#432](https://github.com/espresso3389/pdfrx/issues/432)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b05eead4..1f690edf 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.3 + pdfrx: ^2.1.4 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4ae29063..8e7b39cc 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.3 +version: 2.1.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ environment: flutter: ">=3.29.0" dependencies: - pdfrx_engine: ^0.1.12 + pdfrx_engine: ^0.1.13 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 115ef767..6709c0e9 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.13 + +- Add font loading APIs for WASM: `reloadFonts()` and `addFontData()` methods +- Add `PdfDocumentMissingFontsEvent` to notify about missing fonts in PDF documents +- FIXED: Text coordinate calculation when CropBox/MediaBox has non-zero origin ([#441](https://github.com/espresso3389/pdfrx/issues/441)) +- Improve WASM stability and font handling + ## 0.1.12 - Fix text character rectangle rotation handling in `loadTextCharRects()` and related methods diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 41e6a15c..4d845cf1 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.12 +version: 0.1.13 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 4507a77d18cd7eae13ec4e9127c8cbd87fa03fdb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 24 Aug 2025 03:06:14 +0900 Subject: [PATCH 268/663] FIXED: #439 PdfDocumentViewBuilder reload document on every widget change --- packages/pdfrx/lib/src/widgets/pdf_widgets.dart | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index e397e2e6..4f97545e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -124,15 +124,13 @@ class _PdfDocumentViewBuilderState extends State { @override void didUpdateWidget(covariant PdfDocumentViewBuilder oldWidget) { super.didUpdateWidget(oldWidget); - if (widget == oldWidget) { - return; + if (widget.documentRef != oldWidget.documentRef) { + oldWidget.documentRef.resolveListenable().removeListener(_onDocumentChanged); + widget.documentRef.resolveListenable() + ..addListener(_onDocumentChanged) + ..load(); + _onDocumentChanged(); } - - oldWidget.documentRef.resolveListenable().removeListener(_onDocumentChanged); - widget.documentRef.resolveListenable() - ..addListener(_onDocumentChanged) - ..load(); - _onDocumentChanged(); } @override From 5f00a2eae7bc416f446529336e811d0d6137d387 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 24 Aug 2025 03:10:16 +0900 Subject: [PATCH 269/663] Release pdfrx v2.1.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FIXED: PdfDocumentViewBuilder was incorrectly reloading document on every widget change (#439) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index f8ed586a..399176af 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.5 + +- FIXED: PdfDocumentViewBuilder was incorrectly reloading document on every widget change ([#439](https://github.com/espresso3389/pdfrx/issues/439)) + # 2.1.4 - FIXED: Text coordinate calculation when CropBox/MediaBox has non-zero origin ([#441](https://github.com/espresso3389/pdfrx/issues/441)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 1f690edf..896d1e1c 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.4 + pdfrx: ^2.1.5 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8e7b39cc..72eb217a 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.4 +version: 2.1.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 6b5c2dbbdc1f0e6406bf314c26839946da80e4f0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 24 Aug 2025 11:20:50 +0900 Subject: [PATCH 270/663] WIP: migrate to melos 7.x.x --- melos.yaml | 8 -------- packages/pdfrx/example/viewer/pubspec.yaml | 3 ++- packages/pdfrx/pubspec.yaml | 3 ++- packages/pdfrx_engine/pubspec.yaml | 3 ++- pubspec.yaml | 13 ++++++++----- 5 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 melos.yaml diff --git a/melos.yaml b/melos.yaml deleted file mode 100644 index 271e401e..00000000 --- a/melos.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: pdfrx - -packages: - - packages/** - -scripts: - analyze: - exec: dart analyze . diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index fff04372..93f717cb 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -3,8 +3,9 @@ description: "Demonstrates how to use the pdfrx plugin." publish_to: 'none' environment: - sdk: '>=3.3.0 <4.0.0' + sdk: '>=3.9.0 <4.0.0' flutter: '>=3.19.0' +resolution: workspace dependencies: flutter: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 72eb217a..8e688863 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -9,8 +9,9 @@ screenshots: path: screenshot.jpg environment: - sdk: '>=3.7.0 <4.0.0' + sdk: '>=3.9.0 <4.0.0' flutter: ">=3.29.0" +resolution: workspace dependencies: pdfrx_engine: ^0.1.13 diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 4d845cf1..7709bd3d 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -6,7 +6,8 @@ repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_eng issue_tracker: https://github.com/espresso3389/pdfrx/issues environment: - sdk: ^3.8.1 + sdk: ^3.9.0 +resolution: workspace # Add regular dependencies here. dependencies: diff --git a/pubspec.yaml b/pubspec.yaml index c399324d..9f4d3288 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,10 @@ -name: pdfrx_monorepo +name: pdfrx_workspace environment: - sdk: '>=3.0.0 <4.0.0' - + sdk: ">=3.9.0 <4.0.0" dev_dependencies: - melos: ^6.3.3 - + melos: ^7.1.0 +workspace: + - packages/pdfrx + - packages/pdfrx_engine + - packages/pdfrx/example/viewer +melos: From a2bce7eb6700a352920e2e322691acdd0d891a09 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 24 Aug 2025 11:21:11 +0900 Subject: [PATCH 271/663] WIP: melos format --- packages/pdfrx/bin/remove_wasm_modules.dart | 7 +- packages/pdfrx/example/viewer/lib/main.dart | 399 ++++++++---------- .../example/viewer/lib/markers_view.dart | 12 +- .../example/viewer/lib/outline_view.dart | 17 +- .../example/viewer/lib/password_dialog.dart | 10 +- .../pdfrx/example/viewer/lib/search_view.dart | 62 +-- .../example/viewer/lib/thumbnails_view.dart | 15 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 51 ++- .../lib/src/widgets/interactive_viewer.dart | 13 +- .../widgets/internals/pdf_error_widget.dart | 5 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 159 +++---- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 10 +- packages/pdfrx/test/pdf_viewer_test.dart | 7 +- 13 files changed, 326 insertions(+), 441 deletions(-) diff --git a/packages/pdfrx/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart index 15c8c1ea..e7382010 100644 --- a/packages/pdfrx/bin/remove_wasm_modules.dart +++ b/packages/pdfrx/bin/remove_wasm_modules.dart @@ -42,10 +42,9 @@ Future main(List args) async { // Read the pubspec.yaml content var pubspecYaml = pubspecFile.readAsStringSync(); - final modifiedYaml = - revert - ? pubspecYaml.replaceAll('# - assets/', '- assets/') - : pubspecYaml.replaceAll('- assets/', '# - assets/'); + final modifiedYaml = revert + ? pubspecYaml.replaceAll('# - assets/', '- assets/') + : pubspecYaml.replaceAll('- assets/', '# - assets/'); if (modifiedYaml == pubspecYaml) { print('No changes needed.'); } else { diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 19ad4d68..505bf4aa 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -101,9 +101,7 @@ class _MainPageState extends State with WidgetsBindingObserver { return Row( children: [ if (!isMobileDevice) ...[ - Expanded( - child: Text(_fileName(documentRef?.sourceName) ?? 'No document loaded'), - ), + Expanded(child: Text(_fileName(documentRef?.sourceName) ?? 'No document loaded')), SizedBox(width: 10), FilledButton(onPressed: () => openFile(), child: Text('Open File')), SizedBox(width: 20), @@ -111,31 +109,23 @@ class _MainPageState extends State with WidgetsBindingObserver { Spacer(), ], IconButton( - visualDensity: visualDensity, - onPressed: documentRef == null ? null : () => _changeLayoutType(), - icon: Icon(Icons.pages)), + visualDensity: visualDensity, + onPressed: documentRef == null ? null : () => _changeLayoutType(), + icon: Icon(Icons.pages), + ), IconButton( visualDensity: visualDensity, - icon: const Icon( - Icons.circle, - color: Colors.red, - ), + icon: const Icon(Icons.circle, color: Colors.red), onPressed: documentRef == null ? null : () => _addCurrentSelectionToMarkers(Colors.red), ), IconButton( visualDensity: visualDensity, - icon: const Icon( - Icons.circle, - color: Colors.green, - ), + icon: const Icon(Icons.circle, color: Colors.green), onPressed: documentRef == null ? null : () => _addCurrentSelectionToMarkers(Colors.green), ), IconButton( visualDensity: visualDensity, - icon: const Icon( - Icons.circle, - color: Colors.orangeAccent, - ), + icon: const Icon(Icons.circle, color: Colors.orangeAccent), onPressed: documentRef == null ? null : () => _addCurrentSelectionToMarkers(Colors.orangeAccent), ), IconButton( @@ -230,12 +220,14 @@ class _MainPageState extends State with WidgetsBindingObserver { ), ClipRect( // NOTE: without ClipRect, TabBar shown even if the width is 0 - child: const TabBar(tabs: [ - Tab(icon: Icon(Icons.search), text: 'Search'), - Tab(icon: Icon(Icons.menu_book), text: 'TOC'), - Tab(icon: Icon(Icons.image), text: 'Pages'), - Tab(icon: Icon(Icons.bookmark), text: 'Markers'), - ]), + child: const TabBar( + tabs: [ + Tab(icon: Icon(Icons.search), text: 'Search'), + Tab(icon: Icon(Icons.menu_book), text: 'TOC'), + Tab(icon: Icon(Icons.image), text: 'Pages'), + Tab(icon: Icon(Icons.bookmark), text: 'Markers'), + ], + ), ), Expanded( child: TabBarView( @@ -249,17 +241,13 @@ class _MainPageState extends State with WidgetsBindingObserver { ), ValueListenableBuilder( valueListenable: outline, - builder: (context, outline, child) => OutlineView( - outline: outline, - controller: controller, - ), + builder: (context, outline, child) => + OutlineView(outline: outline, controller: controller), ), ValueListenableBuilder( valueListenable: documentRef, - builder: (context, documentRef, child) => ThumbnailsView( - documentRef: documentRef, - controller: controller, - ), + builder: (context, documentRef, child) => + ThumbnailsView(documentRef: documentRef, controller: controller), ), MarkersView( markers: _markers.values.expand((e) => e).toList(), @@ -290,168 +278,153 @@ class _MainPageState extends State with WidgetsBindingObserver { child: Stack( children: [ ValueListenableBuilder( - valueListenable: documentRef, - builder: (context, docRef, child) { - if (docRef == null) { - return const Center( - child: Text( - 'No document loaded', - style: TextStyle(fontSize: 20), - ), - ); - } - return PdfViewer( - docRef, - // PdfViewer.asset( - // 'assets/hello.pdf', - // PdfViewer.file( - // r"D:\pdfrx\example\assets\hello.pdf", - // PdfViewer.uri( - // Uri.parse( - // 'https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf'), - // Set password provider to show password dialog - //passwordProvider: () => passwordDialog(context), - controller: controller, - params: PdfViewerParams( - layoutPages: _layoutPages[_layoutTypeIndex], - scrollHorizontallyByMouseWheel: isHorizontalLayout, - pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, - pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, - textSelectionParams: PdfTextSelectionParams( - enabled: true, - onTextSelectionChange: (textSelection) async { - textSelections = await textSelection.getSelectedTextRanges(); - }, - ), - keyHandlerParams: PdfViewerKeyHandlerParams( - autofocus: true, - ), - useAlternativeFitScaleAsMinScale: false, - maxScale: 8, - onViewSizeChanged: (viewSize, oldViewSize, controller) { - if (oldViewSize != null) { - // - // Calculate the matrix to keep the center position during device - // screen rotation - // - // The most important thing here is that the transformation matrix - // is not changed on the view change. - final centerPosition = controller.value.calcPosition(oldViewSize); - final newMatrix = controller.calcMatrixFor(centerPosition); - // Don't change the matrix in sync; the callback might be called - // during widget-tree's build process. - Future.delayed( - const Duration(milliseconds: 200), - () => controller.goTo(newMatrix), - ); - } + valueListenable: documentRef, + builder: (context, docRef, child) { + if (docRef == null) { + return const Center(child: Text('No document loaded', style: TextStyle(fontSize: 20))); + } + return PdfViewer( + docRef, + // PdfViewer.asset( + // 'assets/hello.pdf', + // PdfViewer.file( + // r"D:\pdfrx\example\assets\hello.pdf", + // PdfViewer.uri( + // Uri.parse( + // 'https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf'), + // Set password provider to show password dialog + //passwordProvider: () => passwordDialog(context), + controller: controller, + params: PdfViewerParams( + layoutPages: _layoutPages[_layoutTypeIndex], + scrollHorizontallyByMouseWheel: isHorizontalLayout, + pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, + pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, + textSelectionParams: PdfTextSelectionParams( + enabled: true, + onTextSelectionChange: (textSelection) async { + textSelections = await textSelection.getSelectedTextRanges(); }, - viewerOverlayBuilder: (context, size, handleLinkTap) => [ - // - // Example use of GestureDetector to handle custom gestures - // - // GestureDetector( - // behavior: HitTestBehavior.translucent, - // // If you use GestureDetector on viewerOverlayBuilder, it breaks link-tap handling - // // and you should manually handle it using onTapUp callback - // onTapUp: (details) { - // handleLinkTap(details.localPosition); - // }, - // onDoubleTap: () { - // controller.zoomUp(loop: true); - // }, - // // Make the GestureDetector covers all the viewer widget's area - // // but also make the event go through to the viewer. - // child: IgnorePointer( - // child: - // SizedBox(width: size.width, height: size.height), - // ), - // ), + ), + keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), + useAlternativeFitScaleAsMinScale: false, + maxScale: 8, + onViewSizeChanged: (viewSize, oldViewSize, controller) { + if (oldViewSize != null) { // - // Scroll-thumbs example + // Calculate the matrix to keep the center position during device + // screen rotation // - // Show vertical scroll thumb on the right; it has page number on it - PdfViewerScrollThumb( - controller: controller, - orientation: ScrollbarOrientation.right, - thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( - color: Colors.black, - child: isHorizontalLayout - ? null - : Center( - child: Text( - pageNumber.toString(), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ), - // Just a simple horizontal scroll thumb on the bottom - PdfViewerScrollThumb( - controller: controller, - orientation: ScrollbarOrientation.bottom, - thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( - color: Colors.black, - child: !isHorizontalLayout - ? null - : Center( - child: Text( - pageNumber.toString(), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ), - ], + // The most important thing here is that the transformation matrix + // is not changed on the view change. + final centerPosition = controller.value.calcPosition(oldViewSize); + final newMatrix = controller.calcMatrixFor(centerPosition); + // Don't change the matrix in sync; the callback might be called + // during widget-tree's build process. + Future.delayed(const Duration(milliseconds: 200), () => controller.goTo(newMatrix)); + } + }, + viewerOverlayBuilder: (context, size, handleLinkTap) => [ // - // Loading progress indicator example + // Example use of GestureDetector to handle custom gestures // - loadingBannerBuilder: (context, bytesDownloaded, totalBytes) => Center( - child: CircularProgressIndicator( - value: totalBytes != null ? bytesDownloaded / totalBytes : null, - backgroundColor: Colors.grey, - ), - ), + // GestureDetector( + // behavior: HitTestBehavior.translucent, + // // If you use GestureDetector on viewerOverlayBuilder, it breaks link-tap handling + // // and you should manually handle it using onTapUp callback + // onTapUp: (details) { + // handleLinkTap(details.localPosition); + // }, + // onDoubleTap: () { + // controller.zoomUp(loop: true); + // }, + // // Make the GestureDetector covers all the viewer widget's area + // // but also make the event go through to the viewer. + // child: IgnorePointer( + // child: + // SizedBox(width: size.width, height: size.height), + // ), + // ), // - // Link handling example + // Scroll-thumbs example // - linkHandlerParams: PdfLinkHandlerParams( - onLinkTap: (link) { - if (link.url != null) { - navigateToUrl(link.url!); - } else if (link.dest != null) { - controller.goToDest(link.dest); - } - }, + // Show vertical scroll thumb on the right; it has page number on it + PdfViewerScrollThumb( + controller: controller, + orientation: ScrollbarOrientation.right, + thumbSize: const Size(40, 25), + thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + color: Colors.black, + child: isHorizontalLayout + ? null + : Center( + child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), + ), + ), ), - pagePaintCallbacks: [ - if (textSearcher.value != null) textSearcher.value!.pageTextMatchPaintCallback, - _paintMarkers, - ], - onDocumentChanged: (document) async { - if (document == null) { - textSearcher.value?.dispose(); - textSearcher.value = null; - outline.value = null; - textSelections = null; - _markers.clear(); + // Just a simple horizontal scroll thumb on the bottom + PdfViewerScrollThumb( + controller: controller, + orientation: ScrollbarOrientation.bottom, + thumbSize: const Size(40, 25), + thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + color: Colors.black, + child: !isHorizontalLayout + ? null + : Center( + child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), + ), + ), + ), + ], + // + // Loading progress indicator example + // + loadingBannerBuilder: (context, bytesDownloaded, totalBytes) => Center( + child: CircularProgressIndicator( + value: totalBytes != null ? bytesDownloaded / totalBytes : null, + backgroundColor: Colors.grey, + ), + ), + // + // Link handling example + // + linkHandlerParams: PdfLinkHandlerParams( + onLinkTap: (link) { + if (link.url != null) { + navigateToUrl(link.url!); + } else if (link.dest != null) { + controller.goToDest(link.dest); } }, - onViewerReady: (document, controller) async { - outline.value = await document.loadOutline(); - textSearcher.value = PdfTextSearcher(controller)..addListener(_update); - controller.requestFocus(); - controller.document.events.listen((event) { - if (event is PdfDocumentMissingFontsEvent) { - debugPrint('Missing fonts: ${event.missingFonts.map((f) => f.face).join(', ')}'); - } - }); - }, ), - ); - }), + pagePaintCallbacks: [ + if (textSearcher.value != null) textSearcher.value!.pageTextMatchPaintCallback, + _paintMarkers, + ], + onDocumentChanged: (document) async { + if (document == null) { + textSearcher.value?.dispose(); + textSearcher.value = null; + outline.value = null; + textSelections = null; + _markers.clear(); + } + }, + onViewerReady: (document, controller) async { + outline.value = await document.loadOutline(); + textSearcher.value = PdfTextSearcher(controller)..addListener(_update); + controller.requestFocus(); + controller.document.events.listen((event) { + if (event is PdfDocumentMissingFontsEvent) { + debugPrint('Missing fonts: ${event.missingFonts.map((f) => f.face).join(', ')}'); + } + }); + }, + ), + ); + }, + ), ], ), ), @@ -470,10 +443,7 @@ class _MainPageState extends State with WidgetsBindingObserver { ..color = marker.color.withAlpha(100) ..style = PaintingStyle.fill; - canvas.drawRect( - marker.range.bounds.toRectInDocument(page: page, pageRect: pageRect), - paint, - ); + canvas.drawRect(marker.range.bounds.toRectInDocument(page: page, pageRect: pageRect), paint); } } @@ -513,10 +483,7 @@ class _MainPageState extends State with WidgetsBindingObserver { ); x += page.width + params.margin; } - return PdfPageLayout( - pageLayouts: pageLayouts, - documentSize: Size(x, height), - ); + return PdfPageLayout(pageLayouts: pageLayouts, documentSize: Size(x, height)); }, // Facing pages layout (pages, params) { @@ -547,10 +514,7 @@ class _MainPageState extends State with WidgetsBindingObserver { } return PdfPageLayout( pageLayouts: pageLayouts, - documentSize: Size( - (params.margin + width) * 2 + params.margin, - y, - ), + documentSize: Size((params.margin + width) * 2 + params.margin, y), ); }, ]; @@ -591,14 +555,8 @@ class _MainPageState extends State with WidgetsBindingObserver { ), ), actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: const Text('Go'), - ), + TextButton(onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(context).pop(true), child: const Text('Go')), ], ); }, @@ -617,8 +575,11 @@ class _MainPageState extends State with WidgetsBindingObserver { ); return; } else { - documentRef.value = PdfDocumentRefFile(fileOrUri, - passwordProvider: () => passwordDialog(context), useProgressiveLoading: useProgressiveLoading); + documentRef.value = PdfDocumentRefFile( + fileOrUri, + passwordProvider: () => passwordDialog(context), + useProgressiveLoading: useProgressiveLoading, + ); return; } } @@ -626,13 +587,11 @@ class _MainPageState extends State with WidgetsBindingObserver { } Future openFile({bool useProgressiveLoading = true}) async { - final file = await fs.openFile(acceptedTypeGroups: [ - fs.XTypeGroup( - label: 'PDF files', - extensions: ['pdf'], - uniformTypeIdentifiers: ['com.adobe.pdf'], - ) - ]); + final file = await fs.openFile( + acceptedTypeGroups: [ + fs.XTypeGroup(label: 'PDF files', extensions: ['pdf'], uniformTypeIdentifiers: ['com.adobe.pdf']), + ], + ); if (file == null) return; if (kIsWeb) { final bytes = await file.readAsBytes(); @@ -663,11 +622,7 @@ class _MainPageState extends State with WidgetsBindingObserver { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (kIsWeb) - const Text( - 'Note: The URL must be CORS-enabled.', - style: TextStyle(color: Colors.red), - ), + if (kIsWeb) const Text('Note: The URL must be CORS-enabled.', style: TextStyle(color: Colors.red)), TextField( controller: controller, decoration: const InputDecoration(hintText: 'URL'), @@ -675,14 +630,8 @@ class _MainPageState extends State with WidgetsBindingObserver { ], ), actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(controller.text), - child: const Text('Open'), - ), + TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(context).pop(controller.text), child: const Text('Open')), ], ); }, diff --git a/packages/pdfrx/example/viewer/lib/markers_view.dart b/packages/pdfrx/example/viewer/lib/markers_view.dart index 7f227735..0405f823 100644 --- a/packages/pdfrx/example/viewer/lib/markers_view.dart +++ b/packages/pdfrx/example/viewer/lib/markers_view.dart @@ -8,12 +8,7 @@ class Marker { } class MarkersView extends StatefulWidget { - const MarkersView({ - required this.markers, - super.key, - this.onTap, - this.onDeleteTap, - }); + const MarkersView({required this.markers, super.key, this.onTap, this.onDeleteTap}); final List markers; final void Function(Marker marker)? onTap; @@ -46,10 +41,7 @@ class _MarkersViewState extends State { ), Align( alignment: Alignment.centerRight, - child: IconButton( - icon: const Icon(Icons.delete), - onPressed: () => widget.onDeleteTap?.call(marker), - ), + child: IconButton(icon: const Icon(Icons.delete), onPressed: () => widget.onDeleteTap?.call(marker)), ), ], ), diff --git a/packages/pdfrx/example/viewer/lib/outline_view.dart b/packages/pdfrx/example/viewer/lib/outline_view.dart index f7dbee1a..39e4589b 100644 --- a/packages/pdfrx/example/viewer/lib/outline_view.dart +++ b/packages/pdfrx/example/viewer/lib/outline_view.dart @@ -5,11 +5,7 @@ import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; class OutlineView extends StatelessWidget { - const OutlineView({ - required this.outline, - required this.controller, - super.key, - }); + const OutlineView({required this.outline, required this.controller, super.key}); final List? outline; final PdfViewerController controller; @@ -26,15 +22,8 @@ class OutlineView extends StatelessWidget { return InkWell( onTap: () => controller.goToDest(item.node.dest), child: Container( - margin: EdgeInsets.only( - left: item.level * 16.0 + 8, - top: 8, - bottom: 8, - ), - child: Text( - item.node.title, - softWrap: false, - ), + margin: EdgeInsets.only(left: item.level * 16.0 + 8, top: 8, bottom: 8), + child: Text(item.node.title, softWrap: false), ), ); }, diff --git a/packages/pdfrx/example/viewer/lib/password_dialog.dart b/packages/pdfrx/example/viewer/lib/password_dialog.dart index 611a79ef..23fdbfee 100644 --- a/packages/pdfrx/example/viewer/lib/password_dialog.dart +++ b/packages/pdfrx/example/viewer/lib/password_dialog.dart @@ -19,14 +19,8 @@ Future passwordDialog(BuildContext context) async { onSubmitted: (value) => Navigator.of(context).pop(value), ), actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(null), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(textController.text), - child: const Text('OK'), - ), + TextButton(onPressed: () => Navigator.of(context).pop(null), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(context).pop(textController.text), child: const Text('OK')), ], ); }, diff --git a/packages/pdfrx/example/viewer/lib/search_view.dart b/packages/pdfrx/example/viewer/lib/search_view.dart index 94af9c7c..14363e8b 100644 --- a/packages/pdfrx/example/viewer/lib/search_view.dart +++ b/packages/pdfrx/example/viewer/lib/search_view.dart @@ -7,10 +7,7 @@ import 'package:synchronized/extension.dart'; // Simple Text Search View // class TextSearchView extends StatefulWidget { - const TextSearchView({ - required this.textSearcher, - super.key, - }); + const TextSearchView({required this.textSearcher, super.key}); final PdfTextSearcher textSearcher; @@ -57,8 +54,9 @@ class _TextSearchViewState extends State { } for (int i = _matchIndexToListIndex.length; i < widget.textSearcher.matches.length; i++) { if (i == 0 || widget.textSearcher.matches[i - 1].pageNumber != widget.textSearcher.matches[i].pageNumber) { - _listIndexToMatchIndex - .add(-widget.textSearcher.matches[i].pageNumber); // negative index to indicate page header + _listIndexToMatchIndex.add( + -widget.textSearcher.matches[i].pageNumber, + ); // negative index to indicate page header } _matchIndexToListIndex.add(_listIndexToMatchIndex.length); _listIndexToMatchIndex.add(i); @@ -74,10 +72,7 @@ class _TextSearchViewState extends State { return Column( children: [ widget.textSearcher.isSearching - ? LinearProgressIndicator( - value: widget.textSearcher.searchProgress, - minHeight: 4, - ) + ? LinearProgressIndicator(value: widget.textSearcher.searchProgress, minHeight: 4) : const SizedBox(height: 4), Row( children: [ @@ -90,9 +85,7 @@ class _TextSearchViewState extends State { autofocus: true, focusNode: focusNode, controller: searchTextController, - decoration: const InputDecoration( - contentPadding: EdgeInsets.only(right: 50), - ), + decoration: const InputDecoration(contentPadding: EdgeInsets.only(right: 50)), textInputAction: TextInputAction.search, // onSubmitted: (value) { // // just focus back to the text field @@ -104,10 +97,7 @@ class _TextSearchViewState extends State { alignment: Alignment.centerRight, child: Text( '${widget.textSearcher.currentIndex! + 1} / ${widget.textSearcher.matches.length}', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), + style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), ], @@ -173,13 +163,7 @@ class _TextSearchViewState extends State { height: itemHeight, alignment: Alignment.bottomLeft, padding: const EdgeInsets.only(bottom: 10), - child: Text( - 'Page ${-matchIndex}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), + child: Text('Page ${-matchIndex}', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ); } }, @@ -199,11 +183,7 @@ class _TextSearchViewState extends State { curve: Curves.decelerate, ); } else if (newPos < pos.pixels) { - scrollController.animateTo( - newPos, - duration: const Duration(milliseconds: 300), - curve: Curves.decelerate, - ); + scrollController.animateTo(newPos, duration: const Duration(milliseconds: 300), curve: Curves.decelerate); } if (mounted) setState(() {}); @@ -271,12 +251,7 @@ class _SearchResultTileState extends State { onTap: () => widget.onTap(), child: Container( decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.black12, - width: 0.5, - ), - ), + border: Border(bottom: BorderSide(color: Colors.black12, width: 0.5)), ), padding: const EdgeInsets.all(3), child: text, @@ -287,14 +262,9 @@ class _SearchResultTileState extends State { } TextSpan createTextSpanForMatch(PdfPageText? pageText, PdfPageTextRange match, {TextStyle? style}) { - style ??= const TextStyle( - fontSize: 14, - ); + style ??= const TextStyle(fontSize: 14); if (pageText == null) { - return TextSpan( - text: match.text, - style: style, - ); + return TextSpan(text: match.text, style: style); } final fullText = pageText.fullText; int first = 0; @@ -322,9 +292,7 @@ class _SearchResultTileState extends State { TextSpan(text: header), TextSpan( text: body, - style: const TextStyle( - backgroundColor: Colors.yellow, - ), + style: const TextStyle(backgroundColor: Colors.yellow), ), TextSpan(text: footer), ], @@ -336,9 +304,7 @@ class _SearchResultTileState extends State { /// A helper class to cache loaded page texts. class PdfPageTextCache { final PdfTextSearcher textSearcher; - PdfPageTextCache({ - required this.textSearcher, - }); + PdfPageTextCache({required this.textSearcher}); final _pageTextRefs = {}; diff --git a/packages/pdfrx/example/viewer/lib/thumbnails_view.dart b/packages/pdfrx/example/viewer/lib/thumbnails_view.dart index c6360c58..17480565 100644 --- a/packages/pdfrx/example/viewer/lib/thumbnails_view.dart +++ b/packages/pdfrx/example/viewer/lib/thumbnails_view.dart @@ -29,20 +29,11 @@ class ThumbnailsView extends StatelessWidget { SizedBox( height: 220, child: InkWell( - onTap: () => controller!.goToPage( - pageNumber: index + 1, - anchor: PdfPageAnchor.top, - ), - child: PdfPageView( - document: document, - pageNumber: index + 1, - alignment: Alignment.center, - ), + onTap: () => controller!.goToPage(pageNumber: index + 1, anchor: PdfPageAnchor.top), + child: PdfPageView(document: document, pageNumber: index + 1, alignment: Alignment.center), ), ), - Text( - '${index + 1}', - ), + Text('${index + 1}'), ], ), ); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 95480d0f..d5a35b95 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -71,13 +71,12 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { Pdfrx.pdfiumWasmModulesUrl ??= _pdfiumWasmModulesUrlFromMetaTag(); pdfiumWasmWorkerUrl = _getWorkerUrl(); final moduleUrl = _resolveUrl(Pdfrx.pdfiumWasmModulesUrl ?? defaultWasmModulePath); - final script = - web.document.createElement('script') as web.HTMLScriptElement - ..type = 'text/javascript' - ..charset = 'utf-8' - ..async = true - ..type = 'module' - ..src = _resolveUrl('pdfium_client.js', baseUrl: moduleUrl); + final script = web.document.createElement('script') as web.HTMLScriptElement + ..type = 'text/javascript' + ..charset = 'utf-8' + ..async = true + ..type = 'module' + ..src = _resolveUrl('pdfium_client.js', baseUrl: moduleUrl); web.document.querySelector('head')!.appendChild(script); final completer = Completer(); final sub1 = script.onLoad.listen((_) => completer.complete()); @@ -500,16 +499,15 @@ class _PdfPageWasm extends PdfPage { if (link is! Map) { throw FormatException('Unexpected link structure: $link'); } - final rects = - (link['rects'] as List).map((r) { - final rect = r as List; - return PdfRect( - (rect[0] as double) - bbLeft, - (rect[1] as double) - bbBottom, - (rect[2] as double) - bbLeft, - (rect[3] as double) - bbBottom, - ); - }).toList(); + final rects = (link['rects'] as List).map((r) { + final rect = r as List; + return PdfRect( + (rect[0] as double) - bbLeft, + (rect[1] as double) - bbBottom, + (rect[2] as double) - bbLeft, + (rect[3] as double) - bbBottom, + ); + }).toList(); final url = link['url']; if (url is String) { return PdfLink(rects, url: Uri.tryParse(url)); @@ -540,16 +538,15 @@ class _PdfPageWasm extends PdfPage { 'loadTextCharRects', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); - final charRectsAll = - (result['charRects'] as List).map((rect) { - final r = rect as List; - return PdfRect( - (r[0] as double) - bbLeft, - (r[1] as double) - bbBottom, - (r[2] as double) - bbLeft, - (r[3] as double) - bbBottom, - ); - }).toList(); + final charRectsAll = (result['charRects'] as List).map((rect) { + final r = rect as List; + return PdfRect( + (r[0] as double) - bbLeft, + (r[1] as double) - bbBottom, + (r[2] as double) - bbLeft, + (r[3] as double) - bbBottom, + ); + }).toList(); return charRectsAll; } diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index cc834073..58765d71 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -579,8 +579,8 @@ class _InteractiveViewerState extends State with TickerProvid // calculating the translation to put the viewport inside that Quad is more // complicated than this when rotated. // https://github.com/flutter/flutter/issues/57698 - final Matrix4 correctedMatrix = - matrix.clone()..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); + final Matrix4 correctedMatrix = matrix.clone() + ..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); // Double check that the corrected translation fits. final Quad correctedViewport = _transformViewport(correctedMatrix, _viewport); @@ -1134,11 +1134,10 @@ Quad _transformViewport(Matrix4 matrix, Rect viewport) { // Find the axis aligned bounding box for the rect rotated about its center by // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { - final Matrix4 rotationMatrix = - Matrix4.identity() - ..translate(rect.size.width / 2, rect.size.height / 2) - ..rotateZ(rotation) - ..translate(-rect.size.width / 2, -rect.size.height / 2); + final Matrix4 rotationMatrix = Matrix4.identity() + ..translate(rect.size.width / 2, rect.size.height / 2) + ..rotateZ(rotation) + ..translate(-rect.size.width / 2, -rect.size.height / 2); final Quad boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)), diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart index 1be2d128..ae8b09f2 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart @@ -23,7 +23,10 @@ Widget pdfErrorWidget(BuildContext context, Object error, {StackTrace? stackTrac child: Icon(Icons.error, size: 50, color: Colors.yellow), alignment: PlaceholderAlignment.middle, ), - TextSpan(text: ' $error\n\n', style: const TextStyle(color: Colors.white)), + TextSpan( + text: ' $error\n\n', + style: const TextStyle(color: Colors.white), + ), if (stackTrace != null) TextSpan(text: stackTrace.toString(), style: const TextStyle(fontSize: 14)), if (error is PdfPasswordException && isWindows) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e373da54..9683b6a1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -462,43 +462,42 @@ class _PdfViewerState extends State child: GestureDetector( behavior: HitTestBehavior.opaque, onTapUp: (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.tap), - onDoubleTapDown: - (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.doubleTap), - onLongPressStart: - (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.longPress), - onSecondaryTapUp: - (d) => _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.secondaryTap), - child: - !isTextSelectionEnabled - // show PDF pages without text selection - ? CustomPaint( - foregroundPainter: _CustomPainter.fromFunctions(_paintPages), - size: _layout!.documentSize, - ) - // show PDF pages with text selection - : MouseRegion( - cursor: SystemMouseCursors.text, - hitTestBehavior: HitTestBehavior.deferToChild, - child: GestureDetector( - onPanStart: enableSelectionHandles ? null : _onTextPanStart, - onPanUpdate: enableSelectionHandles ? null : _onTextPanUpdate, - onPanEnd: enableSelectionHandles ? null : _onTextPanEnd, - supportedDevices: { - // PointerDeviceKind.trackpad is intentionally not included here - PointerDeviceKind.mouse, - PointerDeviceKind.stylus, - PointerDeviceKind.touch, - PointerDeviceKind.invertedStylus, - }, - child: CustomPaint( - painter: _CustomPainter.fromFunctions( - _paintPages, - hitTestFunction: _hitTestForTextSelection, - ), - size: _layout!.documentSize, + onDoubleTapDown: (d) => + _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.doubleTap), + onLongPressStart: (d) => + _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.longPress), + onSecondaryTapUp: (d) => + _handleGeneralTap(d.globalPosition, PdfViewerGeneralTapType.secondaryTap), + child: !isTextSelectionEnabled + // show PDF pages without text selection + ? CustomPaint( + foregroundPainter: _CustomPainter.fromFunctions(_paintPages), + size: _layout!.documentSize, + ) + // show PDF pages with text selection + : MouseRegion( + cursor: SystemMouseCursors.text, + hitTestBehavior: HitTestBehavior.deferToChild, + child: GestureDetector( + onPanStart: enableSelectionHandles ? null : _onTextPanStart, + onPanUpdate: enableSelectionHandles ? null : _onTextPanUpdate, + onPanEnd: enableSelectionHandles ? null : _onTextPanEnd, + supportedDevices: { + // PointerDeviceKind.trackpad is intentionally not included here + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.touch, + PointerDeviceKind.invertedStylus, + }, + child: CustomPaint( + painter: _CustomPainter.fromFunctions( + _paintPages, + hitTestFunction: _hitTestForTextSelection, ), + size: _layout!.documentSize, ), ), + ), ), ), if (_initialized) ..._buildPageOverlayWidgets(context), @@ -756,12 +755,11 @@ class _PdfViewerState extends State _minScale = _defaultMinScale; return; } - _minScale = - !widget.params.useAlternativeFitScaleAsMinScale - ? widget.params.minScale - : _alternativeFitScale == null - ? _coverScale! - : min(_coverScale!, _alternativeFitScale!); + _minScale = !widget.params.useAlternativeFitScaleAsMinScale + ? widget.params.minScale + : _alternativeFitScale == null + ? _coverScale! + : min(_coverScale!, _alternativeFitScale!); } void _calcZoomStopTable() { @@ -852,15 +850,14 @@ class _PdfViewerState extends State pageRect: rectExternal, params: widget.params, // FIXME: workaround for link widget eats wheel events. - wrapperBuilder: - (child) => Listener( - child: child, - onPointerSignal: (event) { - if (event is PointerScrollEvent) { - _onWheelDelta(event); - } - }, - ), + wrapperBuilder: (child) => Listener( + child: child, + onPointerSignal: (event) { + if (event is PointerScrollEvent) { + _onWheelDelta(event); + } + }, + ), ), ); } @@ -1047,11 +1044,9 @@ class _PdfViewerState extends State unusedPageList, maxImageCacheBytes, currentPage, - dist: - (pageNumber) => - (_layout!.pageLayouts[pageNumber - 1].center - - _layout!.pageLayouts[currentPage.pageNumber - 1].center) - .distanceSquared, + dist: (pageNumber) => + (_layout!.pageLayouts[pageNumber - 1].center - _layout!.pageLayouts[currentPage.pageNumber - 1].center) + .distanceSquared, ); } } @@ -1368,7 +1363,10 @@ class _PdfViewerState extends State } Matrix4 _calcMatrixForRectInsidePage({required int pageNumber, required PdfRect rect, PdfPageAnchor? anchor}) { - return _calcMatrixForArea(rect: _calcRectForRectInsidePage(pageNumber: pageNumber, rect: rect), anchor: anchor); + return _calcMatrixForArea( + rect: _calcRectForRectInsidePage(pageNumber: pageNumber, rect: rect), + anchor: anchor, + ); } Matrix4? _calcMatrixForDest(PdfDest? dest) { @@ -1381,12 +1379,11 @@ class _PdfViewerState extends State switch (dest.command) { case PdfDestCommand.xyz: if (params != null && params.length >= 2) { - final zoom = - params.length >= 3 - ? params[2] != null && params[2] != 0.0 - ? params[2]! - : _currentZoom - : 1.0; + final zoom = params.length >= 3 + ? params[2] != null && params[2] != 0.0 + ? params[2]! + : _currentZoom + : 1.0; final hw = _viewSize!.width / 2 / zoom; final hh = _viewSize!.height / 2 / zoom; return _calcMatrixFor( @@ -1499,7 +1496,10 @@ class _PdfViewerState extends State required Rect rect, PdfPageAnchor? anchor, Duration duration = const Duration(milliseconds: 200), - }) => _goTo(_calcMatrixForArea(rect: rect, anchor: anchor), duration: duration); + }) => _goTo( + _calcMatrixForArea(rect: rect, anchor: anchor), + duration: duration, + ); Future _goToPage({ required int pageNumber, @@ -1518,7 +1518,10 @@ class _PdfViewerState extends State } _gotoTargetPageNumber = pageNumber; - await _goTo(_calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), duration: duration); + await _goTo( + _calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), + duration: duration, + ); _setCurrentPageNumber(targetPageNumber); } @@ -1529,7 +1532,10 @@ class _PdfViewerState extends State Duration duration = const Duration(milliseconds: 200), }) async { _gotoTargetPageNumber = pageNumber; - await _goTo(_calcMatrixForRectInsidePage(pageNumber: pageNumber, rect: rect, anchor: anchor), duration: duration); + await _goTo( + _calcMatrixForRectInsidePage(pageNumber: pageNumber, rect: rect, anchor: anchor), + duration: duration, + ); _setCurrentPageNumber(pageNumber); } @@ -1572,8 +1578,10 @@ class _PdfViewerState extends State double _getNextZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: true, loop: loop); double _getPreviousZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: false, loop: loop); - Future _setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => - _goTo(_calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!), duration: duration); + Future _setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => _goTo( + _calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!), + duration: duration, + ); Offset get _centerPosition => _txController.value.calcPosition(_viewSize!); @@ -2098,8 +2106,9 @@ class _PdfViewerState extends State _selPartLastMoved == _TextSelectionPart.b || _isSelectingAllText) && isCopyTextEnabled) { - final localOffset = - _contextMenuDocumentPosition != null ? offsetToLocal(context, _contextMenuDocumentPosition!) : null; + final localOffset = _contextMenuDocumentPosition != null + ? offsetToLocal(context, _contextMenuDocumentPosition!) + : null; Offset? a, b; switch (Theme.of(context).platform) { @@ -2149,10 +2158,9 @@ class _PdfViewerState extends State contextMenu = createContextMenu(a, b, _contextMenuFor); if (contextMenu != null && !isPositionalWidget(contextMenu)) { - final offset = - localOffset != null - ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) - : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); + final offset = localOffset != null + ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) + : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); contextMenu = Positioned( left: offset.dx, top: offset.dy, @@ -3660,10 +3668,9 @@ class _CanvasLinkPainter { return; } - final paint = - Paint() - ..color = _state.widget.params.linkHandlerParams?.linkColor ?? Colors.blue.withAlpha(50) - ..style = PaintingStyle.fill; + final paint = Paint() + ..color = _state.widget.params.linkHandlerParams?.linkColor ?? Colors.blue.withAlpha(50) + ..style = PaintingStyle.fill; for (final link in links) { for (final rect in link.rects) { final rectLink = rect.toRectInDocument(page: page, pageRect: pageRect); diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 4f97545e..1ffbd72b 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -290,11 +290,11 @@ class _PdfPageViewState extends State { widget.document!.pages[widget.pageNumber - 1], _image != null ? RawImage( - image: _image, - width: _pageSize!.width * scale, - height: _pageSize!.height * scale, - fit: BoxFit.fill, - ) + image: _image, + width: _pageSize!.width * scale, + height: _pageSize!.height * scale, + fit: BoxFit.fill, + ) : null, ); } diff --git a/packages/pdfrx/test/pdf_viewer_test.dart b/packages/pdfrx/test/pdf_viewer_test.dart index 8021db81..761ce138 100644 --- a/packages/pdfrx/test/pdf_viewer_test.dart +++ b/packages/pdfrx/test/pdf_viewer_test.dart @@ -13,10 +13,9 @@ void main() { // For testing purpose, we should run on the command line // and pdfrxInitialize is a better way to initialize the library. setUp(() => pdfrxInitialize()); - Pdfrx.createHttpClient = - () => MockClient((request) async { - return http.Response.bytes(await testPdfFile.readAsBytes(), 200); - }); + Pdfrx.createHttpClient = () => MockClient((request) async { + return http.Response.bytes(await testPdfFile.readAsBytes(), 200); + }); testWidgets('PdfViewer.uri', (tester) async { await binding.setSurfaceSize(Size(1080, 1920)); From 61930e9ef39d90c3710a999051e7364907663989 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 27 Aug 2025 02:04:50 +0900 Subject: [PATCH 272/663] docs(pdfrx): document initialization for Flutter (pdfrxFlutterInitialize) and link non-Flutter (pdfrx_engine/pdfrxInitialize). Closes #447 --- packages/pdfrx/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 896d1e1c..2d6905f9 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -65,6 +65,25 @@ dependencies: **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. +### Initialization + +If you access the document API directly (for example, opening a [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) before any pdfrx widget is built), call [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) once during app startup: + +```dart +import 'package:flutter/widgets.dart'; +import 'package:pdfrx/pdfrx.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await pdfrxFlutterInitialize(); // Required when using engine APIs before widgets + runApp(const MyApp()); +} +``` + +- Widget-first usage: no action needed; initialization happens automatically. +- Direct engine usage first: call [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) once before opening documents. +- For non-Flutter (pure Dart) use cases, call [pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html); refer to [the example](https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine#example-code). + ### Note for Windows **REQUIRED: You must enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode) to build pdfrx on Windows.** From 121435c672910bcc97512c601e5bdc69d623f8b4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 27 Aug 2025 02:19:13 +0900 Subject: [PATCH 273/663] chore: add symlink for CLAUDE.md in AGENTS.md --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 00000000..681311eb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file From fabe3dd6b4149b57b8d23a979db1cf2250123c44 Mon Sep 17 00:00:00 2001 From: pdfrx-bot Date: Wed, 27 Aug 2025 02:28:42 +0900 Subject: [PATCH 274/663] Release pdfrx v2.1.6 --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1b959006..07c28cc5 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.3 + pdfrx: ^2.1.6 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.12 + pdfrx_engine: ^0.1.13 ``` ## Development diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 399176af..d22a672f 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.6 + +- DOCS: Document initialization for Flutter (`pdfrxFlutterInitialize`) and link to non-Flutter initialization (`pdfrxInitialize`). Closes #447 +- No functional changes + # 2.1.5 - FIXED: PdfDocumentViewBuilder was incorrectly reloading document on every widget change ([#439](https://github.com/espresso3389/pdfrx/issues/439)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 2d6905f9..83182d0e 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.5 + pdfrx: ^2.1.6 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8e688863..b6948d8d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.5 +version: 2.1.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 6c663c693881f9ce0b3db8ff70cd2a1b9e88b5ca Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 27 Aug 2025 02:47:02 +0900 Subject: [PATCH 275/663] refactor: replace translation and scaling methods with double variants in InteractiveViewer --- .../pdfrx/lib/src/widgets/interactive_viewer.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 58765d71..e7ffa6ec 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -542,7 +542,7 @@ class _InteractiveViewerState extends State with TickerProvid alignedTranslation = translation; } - final Matrix4 nextMatrix = matrix.clone()..translate(alignedTranslation.dx, alignedTranslation.dy); + final Matrix4 nextMatrix = matrix.clone()..translateByDouble(alignedTranslation.dx, alignedTranslation.dy, 0, 1); // Transform the viewport to determine where its four corners will be after // the child has been transformed. @@ -626,7 +626,7 @@ class _InteractiveViewerState extends State with TickerProvid ); final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); final double clampedScale = clampedTotalScale / currentScale; - return matrix.clone()..scale(clampedScale); + return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); } // Return a new matrix representing the given matrix after applying the given @@ -637,9 +637,9 @@ class _InteractiveViewerState extends State with TickerProvid } final Offset focalPointScene = _transformer.toScene(focalPoint); return matrix.clone() - ..translate(focalPointScene.dx, focalPointScene.dy) + ..translateByDouble(focalPointScene.dx, focalPointScene.dy, 0, 1) ..rotateZ(-rotation) - ..translate(-focalPointScene.dx, -focalPointScene.dy); + ..translateByDouble(-focalPointScene.dx, -focalPointScene.dy, 0, 1); } // Returns true iff the given _GestureType is enabled. @@ -1135,9 +1135,9 @@ Quad _transformViewport(Matrix4 matrix, Rect viewport) { // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { final Matrix4 rotationMatrix = Matrix4.identity() - ..translate(rect.size.width / 2, rect.size.height / 2) + ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) ..rotateZ(rotation) - ..translate(-rect.size.width / 2, -rect.size.height / 2); + ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); final Quad boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)), From 98374f090ca90af287c43c3015f280a21c2163d5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 27 Aug 2025 02:53:11 +0900 Subject: [PATCH 276/663] Release pdfrx v2.1.7 --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index d22a672f..67fc0920 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.7 + +- Refactor InteractiveViewer to use Matrix4 double variants for better numerical precision + # 2.1.6 - DOCS: Document initialization for Flutter (`pdfrxFlutterInitialize`) and link to non-Flutter initialization (`pdfrxInitialize`). Closes #447 diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b6948d8d..a16c6ca9 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.6 +version: 2.1.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From e52d48e32398a2dd068f928edd9b379d1d51ed0a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 01:51:41 +0900 Subject: [PATCH 277/663] translate -> translateByDouble --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 9683b6a1..543eb177 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -633,16 +633,16 @@ class _PdfViewerState extends State return true; } case LogicalKeyboardKey.arrowDown: - _goToManipulated((m) => m.translate(0.0, -widget.params.scrollByArrowKey)); + _goToManipulated((m) => m.translateByDouble(0.0, -widget.params.scrollByArrowKey, 0, 1)); return true; case LogicalKeyboardKey.arrowUp: - _goToManipulated((m) => m.translate(0.0, widget.params.scrollByArrowKey)); + _goToManipulated((m) => m.translateByDouble(0.0, widget.params.scrollByArrowKey, 0, 1)); return true; case LogicalKeyboardKey.arrowLeft: - _goToManipulated((m) => m.translate(widget.params.scrollByArrowKey, 0.0)); + _goToManipulated((m) => m.translateByDouble(widget.params.scrollByArrowKey, 0.0, 0, 1)); return true; case LogicalKeyboardKey.arrowRight: - _goToManipulated((m) => m.translate(-widget.params.scrollByArrowKey, 0.0)); + _goToManipulated((m) => m.translateByDouble(-widget.params.scrollByArrowKey, 0.0, 0, 1)); return true; case LogicalKeyboardKey.keyA: if (isCommandKeyPressed) { From 205f980c1329af497df0403b11b560fde5506bc2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 02:26:43 +0900 Subject: [PATCH 278/663] FIXED: unstoppable key-repeat on certain keys --- .../internals/pdf_viewer_key_handler.dart | 72 +++---------------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 18 ++--- .../lib/src/widgets/pdf_viewer_params.dart | 19 +---- 3 files changed, 23 insertions(+), 86 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index 416251b8..0a096e7b 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -1,14 +1,12 @@ // ignore_for_file: public_member_api_docs -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../pdfrx.dart'; /// A widget that handles key events for the PDF viewer. -class PdfViewerKeyHandler extends StatefulWidget { +class PdfViewerKeyHandler extends StatelessWidget { const PdfViewerKeyHandler({ required this.child, required this.onKeyRepeat, @@ -25,74 +23,26 @@ class PdfViewerKeyHandler extends StatefulWidget { final PdfViewerKeyHandlerParams params; final Widget child; - @override - State createState() => _PdfViewerKeyHandlerState(); -} - -class _PdfViewerKeyHandlerState extends State { - Timer? _timer; - LogicalKeyboardKey? _currentKey; - - void _startRepeating(FocusNode node, LogicalKeyboardKey key) { - _currentKey = key; - - // Initial delay before starting to repeat - _timer = Timer(widget.params.initialDelay, () { - // Start repeating at the specified interval - _timer = Timer.periodic(widget.params.repeatInterval, (_) { - widget.onKeyRepeat(widget.params, _currentKey!, false); - }); - }); - } - - void _stopRepeating() { - _timer?.cancel(); - _timer = null; - _currentKey = null; - } - - @override - void dispose() { - _stopRepeating(); - super.dispose(); - } - @override Widget build(BuildContext context) { return Focus( - focusNode: widget.params.focusNode, - parentNode: widget.params.parentNode, - autofocus: widget.params.autofocus, - canRequestFocus: widget.params.canRequestFocus, - onFocusChange: widget.onFocusChange, + focusNode: params.focusNode, + parentNode: params.parentNode, + autofocus: params.autofocus, + canRequestFocus: params.canRequestFocus, + onFocusChange: onFocusChange, onKeyEvent: (node, event) { - if (event is KeyDownEvent) { - // Key pressed down - if (_currentKey == null) { - if (widget.onKeyRepeat(widget.params, event.logicalKey, true)) { - _startRepeating(node, event.logicalKey); - return KeyEventResult.handled; - } - } - } else if (event is KeyUpEvent) { - // Key released - if (_currentKey == event.logicalKey) { - _stopRepeating(); + if (event is KeyDownEvent || event is KeyRepeatEvent) { + if (onKeyRepeat(params, event.logicalKey, event is KeyDownEvent)) { return KeyEventResult.handled; } + } else if (event is KeyUpEvent) { + return KeyEventResult.handled; } return KeyEventResult.ignored; }, child: Builder( - builder: (context) { - final focusNode = Focus.of(context); - return ListenableBuilder( - listenable: focusNode, - builder: (context, _) { - return widget.child; - }, - ); - }, + builder: (context) => ListenableBuilder(listenable: Focus.of(context), builder: (context, _) => child), ), ); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 543eb177..e2e16089 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -604,32 +604,34 @@ class _PdfViewerState extends State if (result != null) { return result; } - final duration = widget.params.keyHandlerParams.repeatInterval; + // NOTE: repeatInterval should be shorter than the actual key repeat interval of the platform + // because the animation should finish before the next key event. + const repeatInterval = Duration(milliseconds: 100); switch (key) { case LogicalKeyboardKey.pageUp: - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1, duration: duration); + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) - 1, duration: repeatInterval); return true; case LogicalKeyboardKey.pageDown: - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1, duration: duration); + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + 1, duration: repeatInterval); return true; case LogicalKeyboardKey.space: final move = HardwareKeyboard.instance.isShiftPressed ? -1 : 1; - _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + move, duration: duration); + _goToPage(pageNumber: (_gotoTargetPageNumber ?? _pageNumber!) + move, duration: repeatInterval); return true; case LogicalKeyboardKey.home: - _goToPage(pageNumber: 1, duration: duration); + _goToPage(pageNumber: 1, duration: repeatInterval); return true; case LogicalKeyboardKey.end: - _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd, duration: duration); + _goToPage(pageNumber: _document!.pages.length, anchor: widget.params.pageAnchorEnd, duration: repeatInterval); return true; case LogicalKeyboardKey.equal: if (isCommandKeyPressed) { - _zoomUp(duration: duration); + _zoomUp(duration: repeatInterval); return true; } case LogicalKeyboardKey.minus: if (isCommandKeyPressed) { - _zoomDown(duration: duration); + _zoomDown(duration: repeatInterval); return true; } case LogicalKeyboardKey.arrowDown: diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index af9cb686..61f90e26 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1352,23 +1352,16 @@ typedef PdfViewerOnKeyCallback = /// Parameters for the built-in key handler. /// -/// [initialDelay] is the initial delay before the key repeat starts. -/// [repeatInterval] is the interval between key repeats. -/// /// For [autofocus], [canRequestFocus], [focusNode], and [parentNode], /// please refer to the documentation of [Focus] widget. class PdfViewerKeyHandlerParams { const PdfViewerKeyHandlerParams({ - this.initialDelay = const Duration(milliseconds: 500), - this.repeatInterval = const Duration(milliseconds: 100), this.autofocus = false, this.canRequestFocus = true, this.focusNode, this.parentNode, }); - final Duration initialDelay; - final Duration repeatInterval; final bool autofocus; final bool canRequestFocus; final FocusNode? focusNode; @@ -1378,22 +1371,14 @@ class PdfViewerKeyHandlerParams { operator ==(covariant PdfViewerKeyHandlerParams other) { if (identical(this, other)) return true; - return other.initialDelay == initialDelay && - other.repeatInterval == repeatInterval && - other.autofocus == autofocus && + return other.autofocus == autofocus && other.canRequestFocus == canRequestFocus && other.focusNode == focusNode && other.parentNode == parentNode; } @override - int get hashCode => - initialDelay.hashCode ^ - repeatInterval.hashCode ^ - autofocus.hashCode ^ - canRequestFocus.hashCode ^ - focusNode.hashCode ^ - parentNode.hashCode; + int get hashCode => autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; } enum PdfViewerGeneralTapType { From be29db16af56ab83c1dac0280547f2bc6e2a5f37 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 02:38:00 +0900 Subject: [PATCH 279/663] WIP --- CLAUDE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fa7bc8ed..bb3da19c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ -# CLAUDE.md +# AGENTS.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to AI agents and developers when working with code in this repository. ## Project Overview @@ -89,7 +89,7 @@ Both packages may need to be released when changes are made: - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) 2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes - - Don't mention CI/CD changes and `CLAUDE.md` related changes (unless they are significant) + - Don't mention CI/CD changes and `CLAUDE.md`/`AGENTS.md` related changes (unless they are significant) 3. Update `packages/pdfrx_engine/README.md` if needed 4. Update `README.md` on the repo root if needed 5. Run `dart pub publish` in `packages/pdfrx_engine/` @@ -124,7 +124,7 @@ Both packages may need to be released when changes are made: ...Fix/update summary... - Written by 🤖[Claude Code](https://claude.ai/code) + Written by [AGENT SIGNATURE] ``` - Focus on the release notes and what was fixed/changed rather than upgrade instructions From 89dc036c0ec7fd379abf6f1f71fc24ca29ffe6f5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 03:23:20 +0900 Subject: [PATCH 280/663] Release pdfrx v2.1.8 --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 67fc0920..b9aec718 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.8 + +- FIXED: Unstoppable key-repeat on certain keys + # 2.1.7 - Refactor InteractiveViewer to use Matrix4 double variants for better numerical precision diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index a16c6ca9..0078125a 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.7 +version: 2.1.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 096e75c5c761fe363cdb95ec814dd3a091148a18 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 03:37:00 +0900 Subject: [PATCH 281/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e2e16089..65add741 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1257,9 +1257,9 @@ class _PdfViewerState extends State final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; if (widget.params.scrollHorizontallyByMouseWheel) { - m.translate(dy, dx); + m.translateByDouble(dy, dx, 0, 1); } else { - m.translate(dx, dy); + m.translateByDouble(dx, dy, 0, 1); } _txController.value = m; _stopInteraction(); @@ -1284,7 +1284,7 @@ class _PdfViewerState extends State final x = position.dx.range(hw, layout.documentSize.width - hw); final y = position.dy.range(hh, layout.documentSize.height - hh); - return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize).scaled(1.0, 1.0, newZoom); + return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize).scaledByDouble(1.0, 1.0, newZoom, 1.0); } /// Calculate matrix to center the specified position. From 57242816da6a084f51c57bed67969caaed4a5c0c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 03:53:22 +0900 Subject: [PATCH 282/663] Release pdfrx v2.1.9 --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index b9aec718..f2746083 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.9 + +- Replace deprecated Matrix4 methods (`translate` -> `translateByDouble`, `scaled` -> `scaledByDouble`) for improved numerical precision + # 2.1.8 - FIXED: Unstoppable key-repeat on certain keys diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 0078125a..fd2573b4 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.8 +version: 2.1.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 4d68439a504ce9d663dcc31e198f79f8d0d6deae Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 03:55:47 +0900 Subject: [PATCH 283/663] Update README.md files for v2.1.9 release --- README.md | 2 +- packages/pdfrx/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07c28cc5..0089b3aa 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.6 + pdfrx: ^2.1.9 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 83182d0e..15cdc622 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.6 + pdfrx: ^2.1.9 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. From 3a3d7702b62aac2828da9d519b2075150a6d7234 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 28 Aug 2025 15:48:30 +0900 Subject: [PATCH 284/663] WIP: document update --- packages/pdfrx/README.md | 17 ++++++++++++----- packages/pdfrx_engine/README.md | 11 ++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 15cdc622..27a623fd 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -73,16 +73,14 @@ If you access the document API directly (for example, opening a [PdfDocument](ht import 'package:flutter/widgets.dart'; import 'package:pdfrx/pdfrx.dart'; -Future main() async { +Future main() { WidgetsFlutterBinding.ensureInitialized(); - await pdfrxFlutterInitialize(); // Required when using engine APIs before widgets + pdfrxFlutterInitialize(); // Required when using engine APIs before widgets runApp(const MyApp()); } ``` -- Widget-first usage: no action needed; initialization happens automatically. -- Direct engine usage first: call [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) once before opening documents. -- For non-Flutter (pure Dart) use cases, call [pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html); refer to [the example](https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine#example-code). +For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization) ### Note for Windows @@ -117,6 +115,15 @@ To restore the WASM binaries, run the following command: dart run pdfrx:remove_wasm_modules --revert ``` +## PdfViewer constructors + +For opening PDF files from various sources, there are several constructors available in [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html): + +- [PdfViewer.asset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html) - Load from Flutter assets +- [PdfViewer.file](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html) - Load from local file +- [PdfViewer.data](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.data.html) - Load from memory (Uint8List) +- [PdfViewer.network](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - Load from network URL + ## Customizations/Features You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 6cdb62cd..43b87648 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -40,16 +40,25 @@ void main() async { } ``` +You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure the native PDFium library is properly loaded. For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization) + ## PDF API - Easy to use PDF APIs - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document interface + - [PdfDocument.openFile](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) - Open PDF from file path + - [PdfDocument.openData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory (Uint8List) + - [PdfDocument.openUri](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openUri.html) - Open PDF from stream (advanced use case) + - [PdfDocument.openAsset](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openAsset.html) - Open PDF from Flutter asset - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation and rendering + - [PdfPage.render](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap + - [PdfPage.loadText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content from page + - [PdfPage.loadLinks](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html) - Extract links from page - PDFium bindings - For advanced use cases, you can access the raw PDFium bindings via `package:pdfrx_engine/src/native/pdfium_bindings.dart` - Note: Direct use of PDFium bindings is not recommended for most use cases -## When to Use pdfrx_engine vs pdfrx +## When to Use pdfrx_engine vs. pdfrx **Use pdfrx_engine when:** From 2cc32c1e2aa2a054602d20a5e7cf60bcc2d6a889 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 30 Aug 2025 13:21:37 +0900 Subject: [PATCH 285/663] #452: add dismissPdfiumWasmWarnings to dismiss pdfrxFlutterInitialize warnings --- packages/pdfrx/lib/src/pdfrx_flutter.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 651ea5e8..8fcc2018 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -16,7 +16,10 @@ bool _isInitialized = false; /// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. /// /// For Dart (non-Flutter) programs, you should call [pdfrxInitialize] instead. -void pdfrxFlutterInitialize() { +/// +/// The function shows PDFium WASM module warnings in debug mode by default. +/// You can disable these warnings by setting [dismissPdfiumWasmWarnings] to true. +void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { if (_isInitialized) return; if (pdfrxEntryFunctionsOverride != null) { @@ -32,7 +35,7 @@ void pdfrxFlutterInitialize() { platformInitialize(); // Checking pdfium.wasm availability for Web and debug builds. - if (kDebugMode) { + if (kDebugMode && !dismissPdfiumWasmWarnings) { () async { try { await Pdfrx.loadAsset!('packages/pdfrx/assets/pdfium.wasm'); From 64a4ff3eca036d0d8ccae605674e16a134fb89ad Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 3 Sep 2025 23:59:11 +0900 Subject: [PATCH 286/663] #454: Remove .orCancel on aminateTo --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 65add741..4e5133d2 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1457,9 +1457,7 @@ class _PdfViewerState extends State end: _makeMatrixInSafeRange(destination), ).animate(_animController); _animGoTo!.addListener(update); - await _animController.animateTo(1.0, duration: duration, curve: Curves.easeInOut).orCancel; - } on TickerCanceled { - // expected + await _animController.animateTo(1.0, duration: duration, curve: Curves.easeInOut); } finally { _animGoTo?.removeListener(update); } From 60b8b32f3934bdf860a3166cf0e93b761b6b1b06 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Thu, 4 Sep 2025 00:10:50 +0900 Subject: [PATCH 287/663] Release pdfrx v2.1.10 --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 4 +++- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0089b3aa..3620172d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.9 + pdfrx: ^2.1.10 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index f2746083..e22341a2 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.10 + +- NEW: Add `dismissPdfiumWasmWarnings` to `pdfrxFlutterInitialize` to optionally hide WASM warnings in debug (#452) +- FIXED: Remove use of `.orCancel` on `animateTo` to prevent animation cancellation errors (#454) + # 2.1.9 - Replace deprecated Matrix4 methods (`translate` -> `translateByDouble`, `scaled` -> `scaledByDouble`) for improved numerical precision diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 27a623fd..ce34969f 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.9 + pdfrx: ^2.1.10 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. @@ -82,6 +82,8 @@ Future main() { For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization) +Tip: To silence debug-time WASM warnings, call `pdfrxFlutterInitialize(dismissPdfiumWasmWarnings: true)` during startup. + ### Note for Windows **REQUIRED: You must enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode) to build pdfrx on Windows.** diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index fd2573b4..6585e3d4 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.9 +version: 2.1.10 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From b7dc256d28138c414ce81b4788f228cdb49c8fb2 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Thu, 4 Sep 2025 00:44:54 +0900 Subject: [PATCH 288/663] WIP --- CLAUDE.md | 2 +- packages/pdfrx/CHANGELOG.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bb3da19c..df0dd228 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -265,7 +265,7 @@ The following guidelines should be followed when writing documentation including ### Markdown Documentation Guidelines -- Include links to issues/PRs when relevant +- Include links to issues/PRs when relevant; `#NNN` -> `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` - Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible - `README.md` should provide an overview of the project, how to use it, and any important notes - `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e22341a2..ec4b79be 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,7 +1,7 @@ # 2.1.10 -- NEW: Add `dismissPdfiumWasmWarnings` to `pdfrxFlutterInitialize` to optionally hide WASM warnings in debug (#452) -- FIXED: Remove use of `.orCancel` on `animateTo` to prevent animation cancellation errors (#454) +- NEW: Add `dismissPdfiumWasmWarnings` to `pdfrxFlutterInitialize` to optionally hide WASM warnings in debug ([#452](https://github.com/espresso3389/pdfrx/issues/452)) +- FIXED: Remove use of `.orCancel` on `animateTo` to prevent animation cancellation errors ([#454](https://github.com/espresso3389/pdfrx/issues/454)) # 2.1.9 @@ -17,7 +17,7 @@ # 2.1.6 -- DOCS: Document initialization for Flutter (`pdfrxFlutterInitialize`) and link to non-Flutter initialization (`pdfrxInitialize`). Closes #447 +- DOCS: Document initialization for Flutter (`pdfrxFlutterInitialize`) and link to non-Flutter initialization (`pdfrxInitialize`). Closes [#447](https://github.com/espresso3389/pdfrx/issues/447) - No functional changes # 2.1.5 @@ -57,7 +57,7 @@ - `PdfViewerParams.buildContextMenu` and `PdfViewerParams.customizeContextMenuItems` to customize context menu - Introduces `PdfViewerContextMenuBuilderParams` (many context menu related parameters are moved to this class) - BREAKING CHANGE: Tap handler functions are integrated into `PdfViewerParams.onGeneralTap` for better consistency - + # 2.0.4 - FIXED: GestureDetector for text selection now ignores touchpad events to prevent interference with touch-to-scroll ([#426](https://github.com/espresso3389/pdfrx/issues/426)) From 24301c959c7c78902cc5938002159c95671e0e65 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 5 Sep 2025 00:33:05 +0900 Subject: [PATCH 289/663] WIP: Update PDFium binding file --- .../lib/src/native/pdfium_bindings.dart | 19524 +++++++--------- packages/pdfrx_engine/pubspec.yaml | 6 +- 2 files changed, 8972 insertions(+), 10558 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart index 661498e2..ae9b48f1 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart @@ -2,10124 +2,8355 @@ // dart format off // AUTO GENERATED FILE, DO NOT EDIT. -// +// // Generated by `package:ffigen`. // ignore_for_file: type=lint import 'dart:ffi' as ffi; +class pdfium{ +/// Holds the symbol lookup function. +final ffi.Pointer Function(String symbolName) _lookup; -class pdfium { - /// Holds the symbol lookup function. - final ffi.Pointer Function(String symbolName) - _lookup; +/// The symbols are looked up in [dynamicLibrary]. +pdfium(ffi.DynamicLibrary dynamicLibrary): _lookup = dynamicLibrary.lookup; - /// The symbols are looked up in [dynamicLibrary]. - pdfium(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; +/// The symbols are looked up with [lookup]. +pdfium.fromLookup(ffi.Pointer Function(String symbolName) lookup): _lookup = lookup; - /// The symbols are looked up with [lookup]. - pdfium.fromLookup( - ffi.Pointer Function(String symbolName) - lookup) - : _lookup = lookup; +/// Function: FPDF_InitLibraryWithConfig +/// Initialize the PDFium library and allocate global resources for it. +/// Parameters: +/// config - configuration information as above. +/// Return value: +/// None. +/// Comments: +/// You have to call this function before you can call any PDF +/// processing functions. +void FPDF_InitLibraryWithConfig(ffi.Pointer config, +) { + return _FPDF_InitLibraryWithConfig(config, +); +} + +late final _FPDF_InitLibraryWithConfigPtr = _lookup< + ffi.NativeFunction )>>('FPDF_InitLibraryWithConfig'); +late final _FPDF_InitLibraryWithConfig = _FPDF_InitLibraryWithConfigPtr.asFunction )>(); + +/// Function: FPDF_InitLibrary +/// Initialize the PDFium library (alternative form). +/// Parameters: +/// None +/// Return value: +/// None. +/// Comments: +/// Convenience function to call FPDF_InitLibraryWithConfig() with a +/// default configuration for backwards compatibility purposes. New +/// code should call FPDF_InitLibraryWithConfig() instead. This will +/// be deprecated in the future. +void FPDF_InitLibrary() { + return _FPDF_InitLibrary(); +} + +late final _FPDF_InitLibraryPtr = _lookup< + ffi.NativeFunction>('FPDF_InitLibrary'); +late final _FPDF_InitLibrary = _FPDF_InitLibraryPtr.asFunction(); + +/// Function: FPDF_DestroyLibrary +/// Release global resources allocated to the PDFium library by +/// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). +/// Parameters: +/// None. +/// Return value: +/// None. +/// Comments: +/// After this function is called, you must not call any PDF +/// processing functions. +/// +/// Calling this function does not automatically close other +/// objects. It is recommended to close other objects before +/// closing the library with this function. +void FPDF_DestroyLibrary() { + return _FPDF_DestroyLibrary(); +} + +late final _FPDF_DestroyLibraryPtr = _lookup< + ffi.NativeFunction>('FPDF_DestroyLibrary'); +late final _FPDF_DestroyLibrary = _FPDF_DestroyLibraryPtr.asFunction(); + +/// Function: FPDF_SetSandBoxPolicy +/// Set the policy for the sandbox environment. +/// Parameters: +/// policy - The specified policy for setting, for example: +/// FPDF_POLICY_MACHINETIME_ACCESS. +/// enable - True to enable, false to disable the policy. +/// Return value: +/// None. +void FPDF_SetSandBoxPolicy(int policy, +int enable, +) { + return _FPDF_SetSandBoxPolicy(policy, +enable, +); +} + +late final _FPDF_SetSandBoxPolicyPtr = _lookup< + ffi.NativeFunction>('FPDF_SetSandBoxPolicy'); +late final _FPDF_SetSandBoxPolicy = _FPDF_SetSandBoxPolicyPtr.asFunction(); + +/// Function: FPDF_LoadDocument +/// Open and load a PDF document. +/// Parameters: +/// file_path - Path to the PDF file (including extension). +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// See comments below regarding the encoding. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// Loaded document can be closed by FPDF_CloseDocument(). +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// The encoding for |file_path| is UTF-8. +/// +/// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, +/// depending on the security handler revision, will only accept one or +/// the other encoding. If |password|'s encoding and the PDF's expected +/// encoding do not match, FPDF_LoadDocument() will automatically +/// convert |password| to the other encoding. +FPDF_DOCUMENT FPDF_LoadDocument(FPDF_STRING file_path, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadDocument(file_path, +password, +); +} + +late final _FPDF_LoadDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_LoadDocument'); +late final _FPDF_LoadDocument = _FPDF_LoadDocumentPtr.asFunction(); + +/// Function: FPDF_LoadMemDocument +/// Open and load a PDF document from memory. +/// Parameters: +/// data_buf - Pointer to a buffer containing the PDF document. +/// size - Number of bytes in the PDF document. +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The memory buffer must remain valid when the document is open. +/// The loaded document can be closed by FPDF_CloseDocument. +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadMemDocument(ffi.Pointer data_buf, +int size, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadMemDocument(data_buf, +size, +password, +); +} + +late final _FPDF_LoadMemDocumentPtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument'); +late final _FPDF_LoadMemDocument = _FPDF_LoadMemDocumentPtr.asFunction , int , FPDF_BYTESTRING )>(); + +/// Experimental API. +/// Function: FPDF_LoadMemDocument64 +/// Open and load a PDF document from memory. +/// Parameters: +/// data_buf - Pointer to a buffer containing the PDF document. +/// size - Number of bytes in the PDF document. +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The memory buffer must remain valid when the document is open. +/// The loaded document can be closed by FPDF_CloseDocument. +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadMemDocument64(ffi.Pointer data_buf, +int size, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadMemDocument64(data_buf, +size, +password, +); +} + +late final _FPDF_LoadMemDocument64Ptr = _lookup< + ffi.NativeFunction , ffi.Size , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument64'); +late final _FPDF_LoadMemDocument64 = _FPDF_LoadMemDocument64Ptr.asFunction , int , FPDF_BYTESTRING )>(); + +/// Function: FPDF_LoadCustomDocument +/// Load PDF document from a custom access descriptor. +/// Parameters: +/// pFileAccess - A structure for accessing the file. +/// password - Optional password for decrypting the PDF file. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The application must keep the file resources |pFileAccess| points to +/// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| +/// itself does not need to outlive the FPDF_DOCUMENT. +/// +/// The loaded document can be closed with FPDF_CloseDocument(). +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadCustomDocument(ffi.Pointer pFileAccess, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadCustomDocument(pFileAccess, +password, +); +} + +late final _FPDF_LoadCustomDocumentPtr = _lookup< + ffi.NativeFunction , FPDF_BYTESTRING )>>('FPDF_LoadCustomDocument'); +late final _FPDF_LoadCustomDocument = _FPDF_LoadCustomDocumentPtr.asFunction , FPDF_BYTESTRING )>(); + +/// Function: FPDF_GetFileVersion +/// Get the file version of the given PDF document. +/// Parameters: +/// doc - Handle to a document. +/// fileVersion - The PDF file version. File version: 14 for 1.4, 15 +/// for 1.5, ... +/// Return value: +/// True if succeeds, false otherwise. +/// Comments: +/// If the document was created by FPDF_CreateNewDocument, +/// then this function will always fail. +int FPDF_GetFileVersion(FPDF_DOCUMENT doc, +ffi.Pointer fileVersion, +) { + return _FPDF_GetFileVersion(doc, +fileVersion, +); +} + +late final _FPDF_GetFileVersionPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetFileVersion'); +late final _FPDF_GetFileVersion = _FPDF_GetFileVersionPtr.asFunction )>(); + +/// Function: FPDF_GetLastError +/// Get last error code when a function fails. +/// Parameters: +/// None. +/// Return value: +/// A 32-bit integer indicating error code as defined above. +/// Comments: +/// If the previous SDK call succeeded, the return value of this +/// function is not defined. This function only works in conjunction +/// with APIs that mention FPDF_GetLastError() in their documentation. +int FPDF_GetLastError() { + return _FPDF_GetLastError(); +} + +late final _FPDF_GetLastErrorPtr = _lookup< + ffi.NativeFunction>('FPDF_GetLastError'); +late final _FPDF_GetLastError = _FPDF_GetLastErrorPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_DocumentHasValidCrossReferenceTable +/// Whether the document's cross reference table is valid or not. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// True if the PDF parser did not encounter problems parsing the cross +/// reference table. False if the parser could not parse the cross +/// reference table and the table had to be rebuild from other data +/// within the document. +/// Comments: +/// The return value can change over time as the PDF parser evolves. +int FPDF_DocumentHasValidCrossReferenceTable(FPDF_DOCUMENT document, +) { + return _FPDF_DocumentHasValidCrossReferenceTable(document, +); +} + +late final _FPDF_DocumentHasValidCrossReferenceTablePtr = _lookup< + ffi.NativeFunction>('FPDF_DocumentHasValidCrossReferenceTable'); +late final _FPDF_DocumentHasValidCrossReferenceTable = _FPDF_DocumentHasValidCrossReferenceTablePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetTrailerEnds +/// Get the byte offsets of trailer ends. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// buffer - The address of a buffer that receives the +/// byte offsets. +/// length - The size, in ints, of |buffer|. +/// Return value: +/// Returns the number of ints in the buffer on success, 0 on error. +/// +/// |buffer| is an array of integers that describes the exact byte offsets of the +/// trailer ends in the document. If |length| is less than the returned length, +/// or |document| or |buffer| is NULL, |buffer| will not be modified. +int FPDF_GetTrailerEnds(FPDF_DOCUMENT document, +ffi.Pointer buffer, +int length, +) { + return _FPDF_GetTrailerEnds(document, +buffer, +length, +); +} + +late final _FPDF_GetTrailerEndsPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetTrailerEnds'); +late final _FPDF_GetTrailerEnds = _FPDF_GetTrailerEndsPtr.asFunction , int )>(); + +/// Function: FPDF_GetDocPermissions +/// Get file permission flags of the document. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// A 32-bit integer indicating permission flags. Please refer to the +/// PDF Reference for detailed descriptions. If the document is not +/// protected or was unlocked by the owner, 0xffffffff will be returned. +int FPDF_GetDocPermissions(FPDF_DOCUMENT document, +) { + return _FPDF_GetDocPermissions(document, +); +} + +late final _FPDF_GetDocPermissionsPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDocPermissions'); +late final _FPDF_GetDocPermissions = _FPDF_GetDocPermissionsPtr.asFunction(); + +/// Function: FPDF_GetDocUserPermissions +/// Get user file permission flags of the document. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// A 32-bit integer indicating permission flags. Please refer to the +/// PDF Reference for detailed descriptions. If the document is not +/// protected, 0xffffffff will be returned. Always returns user +/// permissions, even if the document was unlocked by the owner. +int FPDF_GetDocUserPermissions(FPDF_DOCUMENT document, +) { + return _FPDF_GetDocUserPermissions(document, +); +} + +late final _FPDF_GetDocUserPermissionsPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDocUserPermissions'); +late final _FPDF_GetDocUserPermissions = _FPDF_GetDocUserPermissionsPtr.asFunction(); + +/// Function: FPDF_GetSecurityHandlerRevision +/// Get the revision for the security handler. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// The security handler revision number. Please refer to the PDF +/// Reference for a detailed description. If the document is not +/// protected, -1 will be returned. +int FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document, +) { + return _FPDF_GetSecurityHandlerRevision(document, +); +} + +late final _FPDF_GetSecurityHandlerRevisionPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSecurityHandlerRevision'); +late final _FPDF_GetSecurityHandlerRevision = _FPDF_GetSecurityHandlerRevisionPtr.asFunction(); + +/// Function: FPDF_GetPageCount +/// Get total number of pages in the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument. +/// Return value: +/// Total number of pages in the document. +int FPDF_GetPageCount(FPDF_DOCUMENT document, +) { + return _FPDF_GetPageCount(document, +); +} + +late final _FPDF_GetPageCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageCount'); +late final _FPDF_GetPageCount = _FPDF_GetPageCountPtr.asFunction(); + +/// Function: FPDF_LoadPage +/// Load a page inside the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument +/// page_index - Index number of the page. 0 for the first page. +/// Return value: +/// A handle to the loaded page, or NULL if page load fails. +/// Comments: +/// The loaded page can be rendered to devices using FPDF_RenderPage. +/// The loaded page can be closed using FPDF_ClosePage. +FPDF_PAGE FPDF_LoadPage(FPDF_DOCUMENT document, +int page_index, +) { + return _FPDF_LoadPage(document, +page_index, +); +} + +late final _FPDF_LoadPagePtr = _lookup< + ffi.NativeFunction>('FPDF_LoadPage'); +late final _FPDF_LoadPage = _FPDF_LoadPagePtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetPageWidthF +/// Get page width. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// Return value: +/// Page width (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm). +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageWidthF(FPDF_PAGE page, +) { + return _FPDF_GetPageWidthF(page, +); +} + +late final _FPDF_GetPageWidthFPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageWidthF'); +late final _FPDF_GetPageWidthF = _FPDF_GetPageWidthFPtr.asFunction(); + +/// Function: FPDF_GetPageWidth +/// Get page width. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// Return value: +/// Page width (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm). +/// Note: +/// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the +/// future. +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageWidth(FPDF_PAGE page, +) { + return _FPDF_GetPageWidth(page, +); +} + +late final _FPDF_GetPageWidthPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageWidth'); +late final _FPDF_GetPageWidth = _FPDF_GetPageWidthPtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetPageHeightF +/// Get page height. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// Return value: +/// Page height (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm) +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageHeightF(FPDF_PAGE page, +) { + return _FPDF_GetPageHeightF(page, +); +} + +late final _FPDF_GetPageHeightFPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageHeightF'); +late final _FPDF_GetPageHeightF = _FPDF_GetPageHeightFPtr.asFunction(); + +/// Function: FPDF_GetPageHeight +/// Get page height. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// Return value: +/// Page height (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm) +/// Note: +/// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the +/// future. +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageHeight(FPDF_PAGE page, +) { + return _FPDF_GetPageHeight(page, +); +} + +late final _FPDF_GetPageHeightPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageHeight'); +late final _FPDF_GetPageHeight = _FPDF_GetPageHeightPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetPageBoundingBox +/// Get the bounding box of the page. This is the intersection between +/// its media box and its crop box. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// rect - Pointer to a rect to receive the page bounding box. +/// On an error, |rect| won't be filled. +/// Return value: +/// True for success. +int FPDF_GetPageBoundingBox(FPDF_PAGE page, +ffi.Pointer rect, +) { + return _FPDF_GetPageBoundingBox(page, +rect, +); +} + +late final _FPDF_GetPageBoundingBoxPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetPageBoundingBox'); +late final _FPDF_GetPageBoundingBox = _FPDF_GetPageBoundingBoxPtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_GetPageSizeByIndexF +/// Get the size of the page at the given index. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// page_index - Page index, zero for the first page. +/// size - Pointer to a FS_SIZEF to receive the page size. +/// (in points). +/// Return value: +/// Non-zero for success. 0 for error (document or page not found). +int FPDF_GetPageSizeByIndexF(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer size, +) { + return _FPDF_GetPageSizeByIndexF(document, +page_index, +size, +); +} + +late final _FPDF_GetPageSizeByIndexFPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetPageSizeByIndexF'); +late final _FPDF_GetPageSizeByIndexF = _FPDF_GetPageSizeByIndexFPtr.asFunction )>(); + +/// Function: FPDF_GetPageSizeByIndex +/// Get the size of the page at the given index. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument. +/// page_index - Page index, zero for the first page. +/// width - Pointer to a double to receive the page width +/// (in points). +/// height - Pointer to a double to receive the page height +/// (in points). +/// Return value: +/// Non-zero for success. 0 for error (document or page not found). +/// Note: +/// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in +/// the future. +int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer width, +ffi.Pointer height, +) { + return _FPDF_GetPageSizeByIndex(document, +page_index, +width, +height, +); +} + +late final _FPDF_GetPageSizeByIndexPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetPageSizeByIndex'); +late final _FPDF_GetPageSizeByIndex = _FPDF_GetPageSizeByIndexPtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDF_RenderPageBitmap +/// Render contents of a page to a device independent bitmap. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). The bitmap handle can be created +/// by FPDFBitmap_Create or retrieved from an image +/// object by FPDFImageObj_GetBitmap. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// start_x - Left pixel position of the display area in +/// bitmap coordinates. +/// start_y - Top pixel position of the display area in bitmap +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// flags - 0 for normal display, or combination of the Page +/// Rendering flags defined above. With the FPDF_ANNOT +/// flag, it renders all annotations that do not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// Return value: +/// None. +void FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +) { + return _FPDF_RenderPageBitmap(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +); +} + +late final _FPDF_RenderPageBitmapPtr = _lookup< + ffi.NativeFunction>('FPDF_RenderPageBitmap'); +late final _FPDF_RenderPageBitmap = _FPDF_RenderPageBitmapPtr.asFunction(); + +/// Function: FPDF_RenderPageBitmapWithMatrix +/// Render contents of a page to a device independent bitmap. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). The bitmap handle can be created +/// by FPDFBitmap_Create or retrieved by +/// FPDFImageObj_GetBitmap. +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// matrix - The transform matrix, which must be invertible. +/// See PDF Reference 1.7, 4.2.2 Common Transformations. +/// clipping - The rect to clip to in device coords. +/// flags - 0 for normal display, or combination of the Page +/// Rendering flags defined above. With the FPDF_ANNOT +/// flag, it renders all annotations that do not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// Return value: +/// None. Note that behavior is undefined if det of |matrix| is 0. +void FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, +FPDF_PAGE page, +ffi.Pointer matrix, +ffi.Pointer clipping, +int flags, +) { + return _FPDF_RenderPageBitmapWithMatrix(bitmap, +page, +matrix, +clipping, +flags, +); +} + +late final _FPDF_RenderPageBitmapWithMatrixPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_RenderPageBitmapWithMatrix'); +late final _FPDF_RenderPageBitmapWithMatrix = _FPDF_RenderPageBitmapWithMatrixPtr.asFunction , ffi.Pointer , int )>(); + +/// Function: FPDF_ClosePage +/// Close a loaded PDF page. +/// Parameters: +/// page - Handle to the loaded page. +/// Return value: +/// None. +void FPDF_ClosePage(FPDF_PAGE page, +) { + return _FPDF_ClosePage(page, +); +} + +late final _FPDF_ClosePagePtr = _lookup< + ffi.NativeFunction>('FPDF_ClosePage'); +late final _FPDF_ClosePage = _FPDF_ClosePagePtr.asFunction(); + +/// Function: FPDF_CloseDocument +/// Close a loaded PDF document. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// None. +void FPDF_CloseDocument(FPDF_DOCUMENT document, +) { + return _FPDF_CloseDocument(document, +); +} + +late final _FPDF_CloseDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_CloseDocument'); +late final _FPDF_CloseDocument = _FPDF_CloseDocumentPtr.asFunction(); + +/// Function: FPDF_DeviceToPage +/// Convert the screen coordinates of a point to page coordinates. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// start_x - Left pixel position of the display area in +/// device coordinates. +/// start_y - Top pixel position of the display area in device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// device_x - X value in device coordinates to be converted. +/// device_y - Y value in device coordinates to be converted. +/// page_x - A pointer to a double receiving the converted X +/// value in page coordinates. +/// page_y - A pointer to a double receiving the converted Y +/// value in page coordinates. +/// Return value: +/// Returns true if the conversion succeeds, and |page_x| and |page_y| +/// successfully receives the converted coordinates. +/// Comments: +/// The page coordinate system has its origin at the left-bottom corner +/// of the page, with the X-axis on the bottom going to the right, and +/// the Y-axis on the left side going up. +/// +/// NOTE: this coordinate system can be altered when you zoom, scroll, +/// or rotate a page, however, a point on the page should always have +/// the same coordinate values in the page coordinate system. +/// +/// The device coordinate system is device dependent. For screen device, +/// its origin is at the left-top corner of the window. However this +/// origin can be altered by the Windows coordinate transformation +/// utilities. +/// +/// You must make sure the start_x, start_y, size_x, size_y +/// and rotate parameters have exactly same values as you used in +/// the FPDF_RenderPage() function call. +int FPDF_DeviceToPage(FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int device_x, +int device_y, +ffi.Pointer page_x, +ffi.Pointer page_y, +) { + return _FPDF_DeviceToPage(page, +start_x, +start_y, +size_x, +size_y, +rotate, +device_x, +device_y, +page_x, +page_y, +); +} + +late final _FPDF_DeviceToPagePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_DeviceToPage'); +late final _FPDF_DeviceToPage = _FPDF_DeviceToPagePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDF_PageToDevice +/// Convert the page coordinates of a point to screen coordinates. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// start_x - Left pixel position of the display area in +/// device coordinates. +/// start_y - Top pixel position of the display area in device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// page_x - X value in page coordinates. +/// page_y - Y value in page coordinate. +/// device_x - A pointer to an integer receiving the result X +/// value in device coordinates. +/// device_y - A pointer to an integer receiving the result Y +/// value in device coordinates. +/// Return value: +/// Returns true if the conversion succeeds, and |device_x| and +/// |device_y| successfully receives the converted coordinates. +/// Comments: +/// See comments for FPDF_DeviceToPage(). +int FPDF_PageToDevice(FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +double page_x, +double page_y, +ffi.Pointer device_x, +ffi.Pointer device_y, +) { + return _FPDF_PageToDevice(page, +start_x, +start_y, +size_x, +size_y, +rotate, +page_x, +page_y, +device_x, +device_y, +); +} + +late final _FPDF_PageToDevicePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_PageToDevice'); +late final _FPDF_PageToDevice = _FPDF_PageToDevicePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFBitmap_Create +/// Create a device independent bitmap (FXDIB). +/// Parameters: +/// width - The number of pixels in width for the bitmap. +/// Must be greater than 0. +/// height - The number of pixels in height for the bitmap. +/// Must be greater than 0. +/// alpha - A flag indicating whether the alpha channel is used. +/// Non-zero for using alpha, zero for not using. +/// Return value: +/// The created bitmap handle, or NULL if a parameter error or out of +/// memory. +/// Comments: +/// The bitmap always uses 4 bytes per pixel. The first byte is always +/// double word aligned. +/// +/// The byte order is BGRx (the last byte unused if no alpha channel) or +/// BGRA. +/// +/// The pixels in a horizontal line are stored side by side, with the +/// left most pixel stored first (with lower memory address). +/// Each line uses width * 4 bytes. +/// +/// Lines are stored one after another, with the top most line stored +/// first. There is no gap between adjacent lines. +/// +/// This function allocates enough memory for holding all pixels in the +/// bitmap, but it doesn't initialize the buffer. Applications can use +/// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS +/// allows it, this function can allocate up to 4 GB of memory. +FPDF_BITMAP FPDFBitmap_Create(int width, +int height, +int alpha, +) { + return _FPDFBitmap_Create(width, +height, +alpha, +); +} + +late final _FPDFBitmap_CreatePtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_Create'); +late final _FPDFBitmap_Create = _FPDFBitmap_CreatePtr.asFunction(); + +/// Function: FPDFBitmap_CreateEx +/// Create a device independent bitmap (FXDIB) +/// Parameters: +/// width - The number of pixels in width for the bitmap. +/// Must be greater than 0. +/// height - The number of pixels in height for the bitmap. +/// Must be greater than 0. +/// format - A number indicating for bitmap format, as defined +/// above. +/// first_scan - A pointer to the first byte of the first line if +/// using an external buffer. If this parameter is NULL, +/// then a new buffer will be created. +/// stride - Number of bytes for each scan line. The value must +/// be 0 or greater. When the value is 0, +/// FPDFBitmap_CreateEx() will automatically calculate +/// the appropriate value using |width| and |format|. +/// When using an external buffer, it is recommended for +/// the caller to pass in the value. +/// When not using an external buffer, it is recommended +/// for the caller to pass in 0. +/// Return value: +/// The bitmap handle, or NULL if parameter error or out of memory. +/// Comments: +/// Similar to FPDFBitmap_Create function, but allows for more formats +/// and an external buffer is supported. The bitmap created by this +/// function can be used in any place that a FPDF_BITMAP handle is +/// required. +/// +/// If an external buffer is used, then the caller should destroy the +/// buffer. FPDFBitmap_Destroy() will not destroy the buffer. +/// +/// It is recommended to use FPDFBitmap_GetStride() to get the stride +/// value. +FPDF_BITMAP FPDFBitmap_CreateEx(int width, +int height, +int format, +ffi.Pointer first_scan, +int stride, +) { + return _FPDFBitmap_CreateEx(width, +height, +format, +first_scan, +stride, +); +} + +late final _FPDFBitmap_CreateExPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFBitmap_CreateEx'); +late final _FPDFBitmap_CreateEx = _FPDFBitmap_CreateExPtr.asFunction , int )>(); + +/// Function: FPDFBitmap_GetFormat +/// Get the format of the bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The format of the bitmap. +/// Comments: +/// Only formats supported by FPDFBitmap_CreateEx are supported by this +/// function; see the list of such formats above. +int FPDFBitmap_GetFormat(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetFormat(bitmap, +); +} + +late final _FPDFBitmap_GetFormatPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetFormat'); +late final _FPDFBitmap_GetFormat = _FPDFBitmap_GetFormatPtr.asFunction(); + +/// Function: FPDFBitmap_FillRect +/// Fill a rectangle in a bitmap. +/// Parameters: +/// bitmap - The handle to the bitmap. Returned by +/// FPDFBitmap_Create. +/// left - The left position. Starting from 0 at the +/// left-most pixel. +/// top - The top position. Starting from 0 at the +/// top-most line. +/// width - Width in pixels to be filled. +/// height - Height in pixels to be filled. +/// color - A 32-bit value specifing the color, in 8888 ARGB +/// format. +/// Return value: +/// Returns whether the operation succeeded or not. +/// Comments: +/// This function sets the color and (optionally) alpha value in the +/// specified region of the bitmap. +/// +/// NOTE: If the alpha channel is used, this function does NOT +/// composite the background with the source color, instead the +/// background will be replaced by the source color and the alpha. +/// +/// If the alpha channel is not used, the alpha parameter is ignored. +int FPDFBitmap_FillRect(FPDF_BITMAP bitmap, +int left, +int top, +int width, +int height, +int color, +) { + return _FPDFBitmap_FillRect(bitmap, +left, +top, +width, +height, +color, +); +} + +late final _FPDFBitmap_FillRectPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_FillRect'); +late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction(); + +/// Function: FPDFBitmap_GetBuffer +/// Get data buffer of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The pointer to the first byte of the bitmap buffer. +/// Comments: +/// The stride may be more than width * number of bytes per pixel +/// +/// Applications can use this function to get the bitmap buffer pointer, +/// then manipulate any color and/or alpha values for any pixels in the +/// bitmap. +/// +/// Use FPDFBitmap_GetFormat() to find out the format of the data. +ffi.Pointer FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetBuffer(bitmap, +); +} + +late final _FPDFBitmap_GetBufferPtr = _lookup< + ffi.NativeFunction Function(FPDF_BITMAP )>>('FPDFBitmap_GetBuffer'); +late final _FPDFBitmap_GetBuffer = _FPDFBitmap_GetBufferPtr.asFunction Function(FPDF_BITMAP )>(); + +/// Function: FPDFBitmap_GetWidth +/// Get width of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The width of the bitmap in pixels. +int FPDFBitmap_GetWidth(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetWidth(bitmap, +); +} + +late final _FPDFBitmap_GetWidthPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetWidth'); +late final _FPDFBitmap_GetWidth = _FPDFBitmap_GetWidthPtr.asFunction(); + +/// Function: FPDFBitmap_GetHeight +/// Get height of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The height of the bitmap in pixels. +int FPDFBitmap_GetHeight(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetHeight(bitmap, +); +} + +late final _FPDFBitmap_GetHeightPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetHeight'); +late final _FPDFBitmap_GetHeight = _FPDFBitmap_GetHeightPtr.asFunction(); + +/// Function: FPDFBitmap_GetStride +/// Get number of bytes for each line in the bitmap buffer. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The number of bytes for each line in the bitmap buffer. +/// Comments: +/// The stride may be more than width * number of bytes per pixel. +int FPDFBitmap_GetStride(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetStride(bitmap, +); +} + +late final _FPDFBitmap_GetStridePtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetStride'); +late final _FPDFBitmap_GetStride = _FPDFBitmap_GetStridePtr.asFunction(); + +/// Function: FPDFBitmap_Destroy +/// Destroy a bitmap and release all related buffers. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// None. +/// Comments: +/// This function will not destroy any external buffers provided when +/// the bitmap was created. +void FPDFBitmap_Destroy(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_Destroy(bitmap, +); +} + +late final _FPDFBitmap_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_Destroy'); +late final _FPDFBitmap_Destroy = _FPDFBitmap_DestroyPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetPrintScaling +/// Whether the PDF document prefers to be scaled or not. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// None. +int FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetPrintScaling(document, +); +} + +late final _FPDF_VIEWERREF_GetPrintScalingPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintScaling'); +late final _FPDF_VIEWERREF_GetPrintScaling = _FPDF_VIEWERREF_GetPrintScalingPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetNumCopies +/// Returns the number of copies to be printed. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The number of copies to be printed. +int FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetNumCopies(document, +); +} + +late final _FPDF_VIEWERREF_GetNumCopiesPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetNumCopies'); +late final _FPDF_VIEWERREF_GetNumCopies = _FPDF_VIEWERREF_GetNumCopiesPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetPrintPageRange +/// Page numbers to initialize print dialog box when file is printed. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The print page range to be used for printing. +FPDF_PAGERANGE FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetPrintPageRange(document, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangePtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRange'); +late final _FPDF_VIEWERREF_GetPrintPageRange = _FPDF_VIEWERREF_GetPrintPageRangePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_VIEWERREF_GetPrintPageRangeCount +/// Returns the number of elements in a FPDF_PAGERANGE. +/// Parameters: +/// pagerange - Handle to the page range. +/// Return value: +/// The number of elements in the page range. Returns 0 on error. +int FPDF_VIEWERREF_GetPrintPageRangeCount(FPDF_PAGERANGE pagerange, +) { + return _FPDF_VIEWERREF_GetPrintPageRangeCount(pagerange, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangeCountPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeCount'); +late final _FPDF_VIEWERREF_GetPrintPageRangeCount = _FPDF_VIEWERREF_GetPrintPageRangeCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_VIEWERREF_GetPrintPageRangeElement +/// Returns an element from a FPDF_PAGERANGE. +/// Parameters: +/// pagerange - Handle to the page range. +/// index - Index of the element. +/// Return value: +/// The value of the element in the page range at a given index. +/// Returns -1 on error. +int FPDF_VIEWERREF_GetPrintPageRangeElement(FPDF_PAGERANGE pagerange, +int index, +) { + return _FPDF_VIEWERREF_GetPrintPageRangeElement(pagerange, +index, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangeElementPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeElement'); +late final _FPDF_VIEWERREF_GetPrintPageRangeElement = _FPDF_VIEWERREF_GetPrintPageRangeElementPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetDuplex +/// Returns the paper handling option to be used when printing from +/// the print dialog. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The paper handling option to be used when printing. +_FPDF_DUPLEXTYPE_ FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document, +) { + return _FPDF_DUPLEXTYPE_.fromValue(_FPDF_VIEWERREF_GetDuplex(document, +)); +} + +late final _FPDF_VIEWERREF_GetDuplexPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetDuplex'); +late final _FPDF_VIEWERREF_GetDuplex = _FPDF_VIEWERREF_GetDuplexPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetName +/// Gets the contents for a viewer ref, with a given key. The value must +/// be of type "name". +/// Parameters: +/// document - Handle to the loaded document. +/// key - Name of the key in the viewer pref dictionary, +/// encoded in UTF-8. +/// buffer - Caller-allocate buffer to receive the key, or NULL +/// - to query the required length. +/// length - Length of the buffer. +/// Return value: +/// The number of bytes in the contents, including the NULL terminator. +/// Thus if the return value is 0, then that indicates an error, such +/// as when |document| is invalid. If |length| is less than the required +/// length, or |buffer| is NULL, |buffer| will not be modified. +int FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int length, +) { + return _FPDF_VIEWERREF_GetName(document, +key, +buffer, +length, +); +} + +late final _FPDF_VIEWERREF_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_VIEWERREF_GetName'); +late final _FPDF_VIEWERREF_GetName = _FPDF_VIEWERREF_GetNamePtr.asFunction , int )>(); + +/// Function: FPDF_CountNamedDests +/// Get the count of named destinations in the PDF document. +/// Parameters: +/// document - Handle to a document +/// Return value: +/// The count of named destinations. +int FPDF_CountNamedDests(FPDF_DOCUMENT document, +) { + return _FPDF_CountNamedDests(document, +); +} + +late final _FPDF_CountNamedDestsPtr = _lookup< + ffi.NativeFunction>('FPDF_CountNamedDests'); +late final _FPDF_CountNamedDests = _FPDF_CountNamedDestsPtr.asFunction(); + +/// Function: FPDF_GetNamedDestByName +/// Get a the destination handle for the given name. +/// Parameters: +/// document - Handle to the loaded document. +/// name - The name of a destination. +/// Return value: +/// The handle to the destination. +FPDF_DEST FPDF_GetNamedDestByName(FPDF_DOCUMENT document, +FPDF_BYTESTRING name, +) { + return _FPDF_GetNamedDestByName(document, +name, +); +} + +late final _FPDF_GetNamedDestByNamePtr = _lookup< + ffi.NativeFunction>('FPDF_GetNamedDestByName'); +late final _FPDF_GetNamedDestByName = _FPDF_GetNamedDestByNamePtr.asFunction(); + +/// Function: FPDF_GetNamedDest +/// Get the named destination by index. +/// Parameters: +/// document - Handle to a document +/// index - The index of a named destination. +/// buffer - The buffer to store the destination name, +/// used as wchar_t*. +/// buflen [in/out] - Size of the buffer in bytes on input, +/// length of the result in bytes on output +/// or -1 if the buffer is too small. +/// Return value: +/// The destination handle for a given index, or NULL if there is no +/// named destination corresponding to |index|. +/// Comments: +/// Call this function twice to get the name of the named destination: +/// 1) First time pass in |buffer| as NULL and get buflen. +/// 2) Second time pass in allocated |buffer| and buflen to retrieve +/// |buffer|, which should be used as wchar_t*. +/// +/// If buflen is not sufficiently large, it will be set to -1 upon +/// return. +FPDF_DEST FPDF_GetNamedDest(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +ffi.Pointer buflen, +) { + return _FPDF_GetNamedDest(document, +index, +buffer, +buflen, +); +} + +late final _FPDF_GetNamedDestPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetNamedDest'); +late final _FPDF_GetNamedDest = _FPDF_GetNamedDestPtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketCount +/// Get the number of valid packets in the XFA entry. +/// Parameters: +/// document - Handle to the document. +/// Return value: +/// The number of valid packets, or -1 on error. +int FPDF_GetXFAPacketCount(FPDF_DOCUMENT document, +) { + return _FPDF_GetXFAPacketCount(document, +); +} + +late final _FPDF_GetXFAPacketCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetXFAPacketCount'); +late final _FPDF_GetXFAPacketCount = _FPDF_GetXFAPacketCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketName +/// Get the name of a packet in the XFA array. +/// Parameters: +/// document - Handle to the document. +/// index - Index number of the packet. 0 for the first packet. +/// buffer - Buffer for holding the name of the XFA packet. +/// buflen - Length of |buffer| in bytes. +/// Return value: +/// The length of the packet name in bytes, or 0 on error. +/// +/// |document| must be valid and |index| must be in the range [0, N), where N is +/// the value returned by FPDF_GetXFAPacketCount(). +/// |buffer| is only modified if it is non-NULL and |buflen| is greater than or +/// equal to the length of the packet name. The packet name includes a +/// terminating NUL character. |buffer| is unmodified on error. +int FPDF_GetXFAPacketName(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetXFAPacketName(document, +index, +buffer, +buflen, +); +} + +late final _FPDF_GetXFAPacketNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetXFAPacketName'); +late final _FPDF_GetXFAPacketName = _FPDF_GetXFAPacketNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketContent +/// Get the content of a packet in the XFA array. +/// Parameters: +/// document - Handle to the document. +/// index - Index number of the packet. 0 for the first packet. +/// buffer - Buffer for holding the content of the XFA packet. +/// buflen - Length of |buffer| in bytes. +/// out_buflen - Pointer to the variable that will receive the minimum +/// buffer size needed to contain the content of the XFA +/// packet. +/// Return value: +/// Whether the operation succeeded or not. +/// +/// |document| must be valid and |index| must be in the range [0, N), where N is +/// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be +/// NULL. When the aforementioned arguments are valid, the operation succeeds, +/// and |out_buflen| receives the content size. |buffer| is only modified if +/// |buffer| is non-null and long enough to contain the content. Callers must +/// check both the return value and the input |buflen| is no less than the +/// returned |out_buflen| before using the data in |buffer|. +int FPDF_GetXFAPacketContent(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_GetXFAPacketContent(document, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_GetXFAPacketContentPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_GetXFAPacketContent'); +late final _FPDF_GetXFAPacketContent = _FPDF_GetXFAPacketContentPtr.asFunction , int , ffi.Pointer )>(); + +/// Function: FPDFDOC_InitFormFillEnvironment +/// Initialize form fill environment. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// formInfo - Pointer to a FPDF_FORMFILLINFO structure. +/// Return Value: +/// Handle to the form fill module, or NULL on failure. +/// Comments: +/// This function should be called before any form fill operation. +/// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until +/// the returned FPDF_FORMHANDLE is closed. +FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, +ffi.Pointer formInfo, +) { + return _FPDFDOC_InitFormFillEnvironment(document, +formInfo, +); +} + +late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction )>>('FPDFDOC_InitFormFillEnvironment'); +late final _FPDFDOC_InitFormFillEnvironment = _FPDFDOC_InitFormFillEnvironmentPtr.asFunction )>(); + +/// Function: FPDFDOC_ExitFormFillEnvironment +/// Take ownership of |hHandle| and exit form fill environment. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This function is a no-op when |hHandle| is null. +void FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle, +) { + return _FPDFDOC_ExitFormFillEnvironment(hHandle, +); +} + +late final _FPDFDOC_ExitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction>('FPDFDOC_ExitFormFillEnvironment'); +late final _FPDFDOC_ExitFormFillEnvironment = _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction(); + +/// Function: FORM_OnAfterLoadPage +/// This method is required for implementing all the form related +/// functions. Should be invoked after user successfully loaded a +/// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnAfterLoadPage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +) { + return _FORM_OnAfterLoadPage(page, +hHandle, +); +} + +late final _FORM_OnAfterLoadPagePtr = _lookup< + ffi.NativeFunction>('FORM_OnAfterLoadPage'); +late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction(); + +/// Function: FORM_OnBeforeClosePage +/// This method is required for implementing all the form related +/// functions. Should be invoked before user closes the PDF page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnBeforeClosePage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +) { + return _FORM_OnBeforeClosePage(page, +hHandle, +); +} + +late final _FORM_OnBeforeClosePagePtr = _lookup< + ffi.NativeFunction>('FORM_OnBeforeClosePage'); +late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction(); + +/// Function: FORM_DoDocumentJSAction +/// This method is required for performing document-level JavaScript +/// actions. It should be invoked after the PDF document has been loaded. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// If there is document-level JavaScript action embedded in the +/// document, this method will execute the JavaScript action. Otherwise, +/// the method will do nothing. +void FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle, +) { + return _FORM_DoDocumentJSAction(hHandle, +); +} + +late final _FORM_DoDocumentJSActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentJSAction'); +late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); + +/// Function: FORM_DoDocumentOpenAction +/// This method is required for performing open-action when the document +/// is opened. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there are no open-actions embedded +/// in the document. +void FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle, +) { + return _FORM_DoDocumentOpenAction(hHandle, +); +} + +late final _FORM_DoDocumentOpenActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentOpenAction'); +late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr.asFunction(); + +/// Function: FORM_DoDocumentAAction +/// This method is required for performing the document's +/// additional-action. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// aaType - The type of the additional-actions which defined +/// above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there is no document +/// additional-action corresponding to the specified |aaType|. +void FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, +int aaType, +) { + return _FORM_DoDocumentAAction(hHandle, +aaType, +); +} + +late final _FORM_DoDocumentAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentAAction'); +late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction(); + +/// Function: FORM_DoPageAAction +/// This method is required for performing the page object's +/// additional-action when opened or closed. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// aaType - The type of the page object's additional-actions +/// which defined above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if no additional-action corresponding +/// to the specified |aaType| exists. +void FORM_DoPageAAction(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +int aaType, +) { + return _FORM_DoPageAAction(page, +hHandle, +aaType, +); +} + +late final _FORM_DoPageAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoPageAAction'); +late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction(); + +/// Function: FORM_OnMouseMove +/// Call this member function when the mouse cursor moves. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnMouseMove(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnMouseMovePtr = _lookup< + ffi.NativeFunction>('FORM_OnMouseMove'); +late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction(); + +/// Experimental API +/// Function: FORM_OnMouseWheel +/// Call this member function when the user scrolls the mouse wheel. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_coord - Specifies the coordinates of the cursor in PDF user +/// space. +/// delta_x - Specifies the amount of wheel movement on the x-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean left. +/// delta_y - Specifies the amount of wheel movement on the y-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean down. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// For |delta_x| and |delta_y|, the caller must normalize +/// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 +/// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines +/// WHEEL_DELTA as 120. +int FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +ffi.Pointer page_coord, +int delta_x, +int delta_y, +) { + return _FORM_OnMouseWheel(hHandle, +page, +modifier, +page_coord, +delta_x, +delta_y, +); +} + +late final _FORM_OnMouseWheelPtr = _lookup< + ffi.NativeFunction , ffi.Int , ffi.Int )>>('FORM_OnMouseWheel'); +late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction , int , int )>(); + +/// Function: FORM_OnFocus +/// This function focuses the form annotation at a given point. If the +/// annotation at the point already has focus, nothing happens. If there +/// is no annotation at the point, removes form focus. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True if there is an annotation at the given point and it has focus. +int FORM_OnFocus(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnFocus(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnFocusPtr = _lookup< + ffi.NativeFunction>('FORM_OnFocus'); +late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction(); + +/// Function: FORM_OnLButtonDown +/// Call this member function when the user presses the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonDown(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDown'); +late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction(); + +/// Function: FORM_OnRButtonDown +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnRButtonDown(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnRButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonDown'); +late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction(); + +/// Function: FORM_OnLButtonUp +/// Call this member function when the user releases the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in device. +/// page_y - Specifies the y-coordinate of the cursor in device. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonUp(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonUp'); +late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction(); + +/// Function: FORM_OnRButtonUp +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnRButtonUp(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnRButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonUp'); +late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction(); + +/// Function: FORM_OnLButtonDoubleClick +/// Call this member function when the user double clicks the +/// left mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDoubleClick(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonDoubleClick(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonDoubleClickPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDoubleClick'); +late final _FORM_OnLButtonDoubleClick = _FORM_OnLButtonDoubleClickPtr.asFunction(); + +/// Function: FORM_OnKeyDown +/// Call this member function when a nonsystem key is pressed. +/// Parameters: +/// hHandle - Handle to the form fill module, aseturned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, +) { + return _FORM_OnKeyDown(hHandle, +page, +nKeyCode, +modifier, +); +} + +late final _FORM_OnKeyDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyDown'); +late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction(); + +/// Function: FORM_OnKeyUp +/// Call this member function when a nonsystem key is released. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// Currently unimplemented and always returns false. PDFium reserves this +/// API and may implement it in the future on an as-needed basis. +int FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, +) { + return _FORM_OnKeyUp(hHandle, +page, +nKeyCode, +modifier, +); +} + +late final _FORM_OnKeyUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyUp'); +late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction(); + +/// Function: FORM_OnChar +/// Call this member function when a keystroke translates to a +/// nonsystem character. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nChar - The character code value itself. +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnChar(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nChar, +int modifier, +) { + return _FORM_OnChar(hHandle, +page, +nChar, +modifier, +); +} + +late final _FORM_OnCharPtr = _lookup< + ffi.NativeFunction>('FORM_OnChar'); +late final _FORM_OnChar = _FORM_OnCharPtr.asFunction(); + +/// Experimental API +/// Function: FORM_GetFocusedText +/// Call this function to obtain the text within the current focused +/// field, if any. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the form text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the form text string, |buffer| is +/// not modified. +/// Return Value: +/// Length in bytes for the text in the focused field. +int FORM_GetFocusedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FORM_GetFocusedText(hHandle, +page, +buffer, +buflen, +); +} + +late final _FORM_GetFocusedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetFocusedText'); +late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction , int )>(); + +/// Function: FORM_GetSelectedText +/// Call this function to obtain selected text within a form text +/// field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the selected text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the selected text string, +/// |buffer| is not modified. +/// Return Value: +/// Length in bytes of selected text in form text field or form combobox +/// text field. +int FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FORM_GetSelectedText(hHandle, +page, +buffer, +buflen, +); +} + +late final _FORM_GetSelectedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetSelectedText'); +late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction , int )>(); + +/// Experimental API +/// Function: FORM_ReplaceAndKeepSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the inserted text +/// will be selected. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceAndKeepSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, +) { + return _FORM_ReplaceAndKeepSelection(hHandle, +page, +wsText, +); +} + +late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceAndKeepSelection'); +late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr.asFunction(); + +/// Function: FORM_ReplaceSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the selection range +/// will be set to empty. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, +) { + return _FORM_ReplaceSelection(hHandle, +page, +wsText, +); +} + +late final _FORM_ReplaceSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceSelection'); +late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction(); + +/// Experimental API +/// Function: FORM_SelectAllText +/// Call this function to select all the text within the currently focused +/// form text field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// Whether the operation succeeded or not. +int FORM_SelectAllText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_SelectAllText(hHandle, +page, +); +} + +late final _FORM_SelectAllTextPtr = _lookup< + ffi.NativeFunction>('FORM_SelectAllText'); +late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction(); + +/// Function: FORM_CanUndo +/// Find out if it is possible for the current focused widget in a given +/// form to perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to undo. +int FORM_CanUndo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_CanUndo(hHandle, +page, +); +} + +late final _FORM_CanUndoPtr = _lookup< + ffi.NativeFunction>('FORM_CanUndo'); +late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); + +/// Function: FORM_CanRedo +/// Find out if it is possible for the current focused widget in a given +/// form to perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to redo. +int FORM_CanRedo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_CanRedo(hHandle, +page, +); +} + +late final _FORM_CanRedoPtr = _lookup< + ffi.NativeFunction>('FORM_CanRedo'); +late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); + +/// Function: FORM_Undo +/// Make the current focused widget perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the undo operation succeeded. +int FORM_Undo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_Undo(hHandle, +page, +); +} + +late final _FORM_UndoPtr = _lookup< + ffi.NativeFunction>('FORM_Undo'); +late final _FORM_Undo = _FORM_UndoPtr.asFunction(); + +/// Function: FORM_Redo +/// Make the current focused widget perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the redo operation succeeded. +int FORM_Redo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_Redo(hHandle, +page, +); +} + +late final _FORM_RedoPtr = _lookup< + ffi.NativeFunction>('FORM_Redo'); +late final _FORM_Redo = _FORM_RedoPtr.asFunction(); + +/// Function: FORM_ForceToKillFocus. +/// Call this member function to force to kill the focus of the form +/// field which has focus. If it would kill the focus of a form field, +/// save the value of form field if was changed by theuser. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle, +) { + return _FORM_ForceToKillFocus(hHandle, +); +} + +late final _FORM_ForceToKillFocusPtr = _lookup< + ffi.NativeFunction>('FORM_ForceToKillFocus'); +late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); + +/// Experimental API. +/// Function: FORM_GetFocusedAnnot. +/// Call this member function to get the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page_index - Buffer to hold the index number of the page which +/// contains the focused annotation. 0 for the first page. +/// Can't be NULL. +/// annot - Buffer to hold the focused annotation. Can't be NULL. +/// Return Value: +/// On success, return true and write to the out parameters. Otherwise +/// return false and leave the out parameters unmodified. +/// Comments: +/// Not currently supported for XFA forms - will report no focused +/// annotation. +/// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| +/// by this function is no longer needed. +/// This will return true and set |page_index| to -1 and |annot| to NULL, +/// if there is no focused annotation. +int FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle, +ffi.Pointer page_index, +ffi.Pointer annot, +) { + return _FORM_GetFocusedAnnot(handle, +page_index, +annot, +); +} + +late final _FORM_GetFocusedAnnotPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FORM_GetFocusedAnnot'); +late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FORM_SetFocusedAnnot. +/// Call this member function to set the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - Handle to an annotation. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() +/// instead. +int FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +) { + return _FORM_SetFocusedAnnot(handle, +annot, +); +} + +late final _FORM_SetFocusedAnnotPtr = _lookup< + ffi.NativeFunction>('FORM_SetFocusedAnnot'); +late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction(); + +/// Function: FPDFPage_HasFormFieldAtPoint +/// Get the form field type by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the type of the form field; -1 indicates no field. +/// See field types above. +int FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +double page_x, +double page_y, +) { + return _FPDFPage_HasFormFieldAtPoint(hHandle, +page, +page_x, +page_y, +); +} + +late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasFormFieldAtPoint'); +late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr.asFunction(); + +/// Function: FPDFPage_FormFieldZOrderAtPoint +/// Get the form field z-order by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the z-order of the form field; -1 indicates no field. +/// Higher numbers are closer to the front. +int FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +double page_x, +double page_y, +) { + return _FPDFPage_FormFieldZOrderAtPoint(hHandle, +page, +page_x, +page_y, +); +} + +late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_FormFieldZOrderAtPoint'); +late final _FPDFPage_FormFieldZOrderAtPoint = _FPDFPage_FormFieldZOrderAtPointPtr.asFunction(); + +/// Function: FPDF_SetFormFieldHighlightColor +/// Set the highlight color of the specified (or all) form fields +/// in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returned by +/// FPDF_LoadDocument(). +/// fieldType - A 32-bit integer indicating the type of a form +/// field (defined above). +/// color - The highlight color of the form field. Constructed by +/// 0xxxrrggbb. +/// Return Value: +/// None. +/// Comments: +/// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the +/// highlight color will be applied to all the form fields in the +/// document. +/// Please refresh the client window to show the highlight immediately +/// if necessary. +void FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, +int fieldType, +int color, +) { + return _FPDF_SetFormFieldHighlightColor(hHandle, +fieldType, +color, +); +} + +late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightColor'); +late final _FPDF_SetFormFieldHighlightColor = _FPDF_SetFormFieldHighlightColorPtr.asFunction(); + +/// Function: FPDF_SetFormFieldHighlightAlpha +/// Set the transparency of the form field highlight color in the +/// document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returaned by +/// FPDF_LoadDocument(). +/// alpha - The transparency of the form field highlight color, +/// between 0-255. +/// Return Value: +/// None. +void FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, +int alpha, +) { + return _FPDF_SetFormFieldHighlightAlpha(hHandle, +alpha, +); +} + +late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightAlpha'); +late final _FPDF_SetFormFieldHighlightAlpha = _FPDF_SetFormFieldHighlightAlphaPtr.asFunction(); + +/// Function: FPDF_RemoveFormFieldHighlight +/// Remove the form field highlight color in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// Please refresh the client window to remove the highlight immediately +/// if necessary. +void FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle, +) { + return _FPDF_RemoveFormFieldHighlight(hHandle, +); +} + +late final _FPDF_RemoveFormFieldHighlightPtr = _lookup< + ffi.NativeFunction>('FPDF_RemoveFormFieldHighlight'); +late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr.asFunction(); + +/// Function: FPDF_FFLDraw +/// Render FormFields and popup window on a page to a device independent +/// bitmap. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handles can be created by +/// FPDFBitmap_Create(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// start_x - Left pixel position of the display area in the +/// device coordinates. +/// start_y - Top pixel position of the display area in the device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees +/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 +/// degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined above. +/// Return Value: +/// None. +/// Comments: +/// This function is designed to render annotations that are +/// user-interactive, which are widget annotations (for FormFields) and +/// popup annotations. +/// With the FPDF_ANNOT flag, this function will render a popup annotation +/// when users mouse-hover on a non-widget annotation. Regardless of +/// FPDF_ANNOT flag, this function will always render widget annotations +/// for FormFields. +/// In order to implement the FormFill functions, implementation should +/// call this function after rendering functions, such as +/// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have +/// finished rendering the page contents. +void FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, +FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +) { + return _FPDF_FFLDraw(hHandle, +bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +); +} + +late final _FPDF_FFLDrawPtr = _lookup< + ffi.NativeFunction>('FPDF_FFLDraw'); +late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetFormType +/// Returns the type of form contained in the PDF document. +/// Parameters: +/// document - Handle to document. +/// Return Value: +/// Integer value representing one of the FORMTYPE_ values. +/// Comments: +/// If |document| is NULL, then the return value is FORMTYPE_NONE. +int FPDF_GetFormType(FPDF_DOCUMENT document, +) { + return _FPDF_GetFormType(document, +); +} + +late final _FPDF_GetFormTypePtr = _lookup< + ffi.NativeFunction>('FPDF_GetFormType'); +late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); + +/// Experimental API +/// Function: FORM_SetIndexSelected +/// Selects/deselects the value at the given |index| of the focused +/// annotation. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based index of value to be set as +/// selected/unselected +/// selected - true to select, false to deselect +/// Return Value: +/// TRUE if the operation succeeded. +/// FALSE if the operation failed or widget is not a supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Comboboxes +/// have at most a single value selected at a time which cannot be +/// deselected. Deselect on a combobox is a no-op that returns false. +/// Default implementation is a no-op that will return false for +/// other types. +/// Not currently supported for XFA forms - will return false. +int FORM_SetIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, +int selected, +) { + return _FORM_SetIndexSelected(hHandle, +page, +index, +selected, +); +} + +late final _FORM_SetIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_SetIndexSelected'); +late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction(); + +/// Experimental API +/// Function: FORM_IsIndexSelected +/// Returns whether or not the value at |index| of the focused +/// annotation is currently selected. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based Index of value to check +/// Return Value: +/// TRUE if value at |index| is currently selected. +/// FALSE if value at |index| is not selected or widget is not a +/// supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Default +/// implementation is a no-op that will return false for other types. +/// Not currently supported for XFA forms - will return false. +int FORM_IsIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, +) { + return _FORM_IsIndexSelected(hHandle, +page, +index, +); +} + +late final _FORM_IsIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_IsIndexSelected'); +late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction(); + +/// Function: FPDF_LoadXFA +/// If the document consists of XFA fields, call this method to +/// attempt to load XFA fields. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// Return Value: +/// TRUE upon success, otherwise FALSE. If XFA support is not built +/// into PDFium, performs no action and always returns FALSE. +int FPDF_LoadXFA(FPDF_DOCUMENT document, +) { + return _FPDF_LoadXFA(document, +); +} + +late final _FPDF_LoadXFAPtr = _lookup< + ffi.NativeFunction>('FPDF_LoadXFA'); +late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); + +/// Experimental API. +/// Check if an annotation subtype is currently supported for creation. +/// Currently supported subtypes: +/// - circle +/// - fileattachment +/// - freetext +/// - highlight +/// - ink +/// - link +/// - popup +/// - square, +/// - squiggly +/// - stamp +/// - strikeout +/// - text +/// - underline +/// +/// subtype - the subtype to be checked. +/// +/// Returns true if this subtype supported. +int FPDFAnnot_IsSupportedSubtype(int subtype, +) { + return _FPDFAnnot_IsSupportedSubtype(subtype, +); +} + +late final _FPDFAnnot_IsSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsSupportedSubtype'); +late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); + +/// Experimental API. +/// Create an annotation in |page| of the subtype |subtype|. If the specified +/// subtype is illegal or unsupported, then a new annotation will not be created. +/// Must call FPDFPage_CloseAnnot() when the annotation returned by this +/// function is no longer needed. +/// +/// page - handle to a page. +/// subtype - the subtype of the new annotation. +/// +/// Returns a handle to the new annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_CreateAnnot(FPDF_PAGE page, +int subtype, +) { + return _FPDFPage_CreateAnnot(page, +subtype, +); +} + +late final _FPDFPage_CreateAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CreateAnnot'); +late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the number of annotations in |page|. +/// +/// page - handle to a page. +/// +/// Returns the number of annotations in |page|. +int FPDFPage_GetAnnotCount(FPDF_PAGE page, +) { + return _FPDFPage_GetAnnotCount(page, +); +} + +late final _FPDFPage_GetAnnotCountPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotCount'); +late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); + +/// Experimental API. +/// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the +/// annotation returned by this function is no longer needed. +/// +/// page - handle to a page. +/// index - the index of the annotation. +/// +/// Returns a handle to the annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_GetAnnot(FPDF_PAGE page, +int index, +) { + return _FPDFPage_GetAnnot(page, +index, +); +} + +late final _FPDFPage_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnot'); +late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the index of |annot| in |page|. This is the opposite of +/// FPDFPage_GetAnnot(). +/// +/// page - handle to the page that the annotation is on. +/// annot - handle to an annotation. +/// +/// Returns the index of |annot|, or -1 on failure. +int FPDFPage_GetAnnotIndex(FPDF_PAGE page, +FPDF_ANNOTATION annot, +) { + return _FPDFPage_GetAnnotIndex(page, +annot, +); +} + +late final _FPDFPage_GetAnnotIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotIndex'); +late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction(); + +/// Experimental API. +/// Close an annotation. Must be called when the annotation returned by +/// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This +/// function does not remove the annotation from the document. +/// +/// annot - handle to an annotation. +void FPDFPage_CloseAnnot(FPDF_ANNOTATION annot, +) { + return _FPDFPage_CloseAnnot(annot, +); +} + +late final _FPDFPage_CloseAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CloseAnnot'); +late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); + +/// Experimental API. +/// Remove the annotation in |page| at |index|. +/// +/// page - handle to a page. +/// index - the index of the annotation. +/// +/// Returns true if successful. +int FPDFPage_RemoveAnnot(FPDF_PAGE page, +int index, +) { + return _FPDFPage_RemoveAnnot(page, +index, +); +} + +late final _FPDFPage_RemoveAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveAnnot'); +late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the subtype of an annotation. +/// +/// annot - handle to an annotation. +/// +/// Returns the annotation subtype. +int FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetSubtype(annot, +); +} + +late final _FPDFAnnot_GetSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetSubtype'); +late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); + +/// Experimental API. +/// Check if an annotation subtype is currently supported for object extraction, +/// update, and removal. +/// Currently supported subtypes: ink and stamp. +/// +/// subtype - the subtype to be checked. +/// +/// Returns true if this subtype supported. +int FPDFAnnot_IsObjectSupportedSubtype(int subtype, +) { + return _FPDFAnnot_IsObjectSupportedSubtype(subtype, +); +} + +late final _FPDFAnnot_IsObjectSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsObjectSupportedSubtype'); +late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); + +/// Experimental API. +/// Update |obj| in |annot|. |obj| must be in |annot| already and must have +/// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp +/// annotations are supported by this API. Also note that only path, image, and +/// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and +/// FPDFImageObj_*(). +/// +/// annot - handle to an annotation. +/// obj - handle to the object that |annot| needs to update. +/// +/// Return true if successful. +int FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, +) { + return _FPDFAnnot_UpdateObject(annot, +obj, +); +} + +late final _FPDFAnnot_UpdateObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_UpdateObject'); +late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction(); + +/// Experimental API. +/// Add a new InkStroke, represented by an array of points, to the InkList of +/// |annot|. The API creates an InkList if one doesn't already exist in |annot|. +/// This API works only for ink annotations. Please refer to ISO 32000-1:2008 +/// spec, section 12.5.6.13. +/// +/// annot - handle to an annotation. +/// points - pointer to a FS_POINTF array representing input points. +/// point_count - number of elements in |points| array. This should not exceed +/// the maximum value that can be represented by an int32_t). +/// +/// Returns the 0-based index at which the new InkStroke is added in the InkList +/// of the |annot|. Returns -1 on failure. +int FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot, +ffi.Pointer points, +int point_count, +) { + return _FPDFAnnot_AddInkStroke(annot, +points, +point_count, +); +} + +late final _FPDFAnnot_AddInkStrokePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_AddInkStroke'); +late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction , int )>(); + +/// Experimental API. +/// Removes an InkList in |annot|. +/// This API works only for ink annotations. +/// +/// annot - handle to an annotation. +/// +/// Return true on successful removal of /InkList entry from context of the +/// non-null ink |annot|. Returns false on failure. +int FPDFAnnot_RemoveInkList(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_RemoveInkList(annot, +); +} + +late final _FPDFAnnot_RemoveInkListPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveInkList'); +late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); + +/// Experimental API. +/// Add |obj| to |annot|. |obj| must have been created by +/// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and +/// will be owned by |annot|. Note that an |obj| cannot belong to more than one +/// |annot|. Currently, only ink and stamp annotations are supported by this API. +/// Also note that only path, image, and text objects have APIs for creation. +/// +/// annot - handle to an annotation. +/// obj - handle to the object that is to be added to |annot|. +/// +/// Return true if successful. +int FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, +) { + return _FPDFAnnot_AppendObject(annot, +obj, +); +} + +late final _FPDFAnnot_AppendObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AppendObject'); +late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction(); + +/// Experimental API. +/// Get the total number of objects in |annot|, including path objects, text +/// objects, external objects, image objects, and shading objects. +/// +/// annot - handle to an annotation. +/// +/// Returns the number of objects in |annot|. +int FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetObjectCount(annot, +); +} + +late final _FPDFAnnot_GetObjectCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObjectCount'); +late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); + +/// Experimental API. +/// Get the object in |annot| at |index|. +/// +/// annot - handle to an annotation. +/// index - the index of the object. +/// +/// Return a handle to the object, or NULL on failure. +FPDF_PAGEOBJECT FPDFAnnot_GetObject(FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_GetObject(annot, +index, +); +} + +late final _FPDFAnnot_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObject'); +late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction(); + +/// Experimental API. +/// Remove the object in |annot| at |index|. +/// +/// annot - handle to an annotation. +/// index - the index of the object to be removed. +/// +/// Return true if successful. +int FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_RemoveObject(annot, +index, +); +} + +late final _FPDFAnnot_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveObject'); +late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction(); + +/// Experimental API. +/// Set the color of an annotation. Fails when called on annotations with +/// appearance streams already defined; instead use +/// FPDFPageObj_Set{Stroke|Fill}Color(). +/// +/// annot - handle to an annotation. +/// type - type of the color to be set. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. +/// +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +int R, +int G, +int B, +int A, +) { + return _FPDFAnnot_SetColor(annot, +type.value, +R, +G, +B, +A, +); +} + +late final _FPDFAnnot_SetColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetColor'); +late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction(); + +/// Experimental API. +/// Get the color of an annotation. If no color is specified, default to yellow +/// for highlight annotation, black for all else. Fails when called on +/// annotations with appearance streams already defined; instead use +/// FPDFPageObj_Get{Stroke|Fill}Color(). +/// +/// annot - handle to an annotation. +/// type - type of the color requested. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. +/// +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFAnnot_GetColor(annot, +type.value, +R, +G, +B, +A, +); +} + +late final _FPDFAnnot_GetColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetColor'); +late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Check if the annotation is of a type that has attachment points +/// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that +/// encompasses the texts affected by the annotation. They provide the +/// coordinates in the page where the annotation is attached. Only text markup +/// annotations (i.e. highlight, strikeout, squiggly, and underline) and link +/// annotations have quadpoints. +/// +/// annot - handle to an annotation. +/// +/// Returns true if the annotation is of a type that has quadpoints, false +/// otherwise. +int FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_HasAttachmentPoints(annot, +); +} + +late final _FPDFAnnot_HasAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasAttachmentPoints'); +late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr.asFunction(); + +/// Experimental API. +/// Replace the attachment points (i.e. quadpoints) set of an annotation at +/// |quad_index|. This index needs to be within the result of +/// FPDFAnnot_CountAttachmentPoints(). +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. +/// +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - the quadpoints to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_SetAttachmentPoints(annot, +quad_index, +quad_points, +); +} + +late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetAttachmentPoints'); +late final _FPDFAnnot_SetAttachmentPoints = _FPDFAnnot_SetAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Append to the list of attachment points (i.e. quadpoints) of an annotation. +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. +/// +/// annot - handle to an annotation. +/// quad_points - the quadpoints to be set. +/// +/// Returns true if successful. +int FPDFAnnot_AppendAttachmentPoints(FPDF_ANNOTATION annot, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_AppendAttachmentPoints(annot, +quad_points, +); +} + +late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_AppendAttachmentPoints'); +late final _FPDFAnnot_AppendAttachmentPoints = _FPDFAnnot_AppendAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Get the number of sets of quadpoints of an annotation. +/// +/// annot - handle to an annotation. +/// +/// Returns the number of sets of quadpoints, or 0 on failure. +int FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_CountAttachmentPoints(annot, +); +} + +late final _FPDFAnnot_CountAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_CountAttachmentPoints'); +late final _FPDFAnnot_CountAttachmentPoints = _FPDFAnnot_CountAttachmentPointsPtr.asFunction(); + +/// Experimental API. +/// Get the attachment points (i.e. quadpoints) of an annotation. +/// +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - receives the quadpoints; must not be NULL. +/// +/// Returns true if successful. +int FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_GetAttachmentPoints(annot, +quad_index, +quad_points, +); +} + +late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetAttachmentPoints'); +late final _FPDFAnnot_GetAttachmentPoints = _FPDFAnnot_GetAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Set the annotation rectangle defining the location of the annotation. If the +/// annotation's appearance stream is defined and this annotation is of a type +/// without quadpoints, then update the bounding box too if the new rectangle +/// defines a bigger one. +/// +/// annot - handle to an annotation. +/// rect - the annotation rectangle to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, +) { + return _FPDFAnnot_SetRect(annot, +rect, +); +} + +late final _FPDFAnnot_SetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetRect'); +late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction )>(); + +/// Experimental API. +/// Get the annotation rectangle defining the location of the annotation. +/// +/// annot - handle to an annotation. +/// rect - receives the rectangle; must not be NULL. +/// +/// Returns true if successful. +int FPDFAnnot_GetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, +) { + return _FPDFAnnot_GetRect(annot, +rect, +); +} + +late final _FPDFAnnot_GetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetRect'); +late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction )>(); + +/// Experimental API. +/// Get the vertices of a polygon or polyline annotation. |buffer| is an array of +/// points of the annotation. If |length| is less than the returned length, or +/// |annot| or |buffer| is NULL, |buffer| will not be modified. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. +/// +/// Returns the number of points if the annotation is of type polygon or +/// polyline, 0 otherwise. +int FPDFAnnot_GetVertices(FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int length, +) { + return _FPDFAnnot_GetVertices(annot, +buffer, +length, +); +} + +late final _FPDFAnnot_GetVerticesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetVertices'); +late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of paths in the ink list of an ink annotation. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// +/// Returns the number of paths in the ink list if the annotation is of type ink, +/// 0 otherwise. +int FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetInkListCount(annot, +); +} + +late final _FPDFAnnot_GetInkListCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetInkListCount'); +late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); + +/// Experimental API. +/// Get a path in the ink list of an ink annotation. |buffer| is an array of +/// points of the path. If |length| is less than the returned length, or |annot| +/// or |buffer| is NULL, |buffer| will not be modified. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// path_index - index of the path +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. +/// +/// Returns the number of points of the path if the annotation is of type ink, 0 +/// otherwise. +int FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot, +int path_index, +ffi.Pointer buffer, +int length, +) { + return _FPDFAnnot_GetInkListPath(annot, +path_index, +buffer, +length, +); +} + +late final _FPDFAnnot_GetInkListPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetInkListPath'); +late final _FPDFAnnot_GetInkListPath = _FPDFAnnot_GetInkListPathPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the starting and ending coordinates of a line annotation. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// start - starting point +/// end - ending point +/// +/// Returns true if the annotation is of type line, |start| and |end| are not +/// NULL, false otherwise. +int FPDFAnnot_GetLine(FPDF_ANNOTATION annot, +ffi.Pointer start, +ffi.Pointer end, +) { + return _FPDFAnnot_GetLine(annot, +start, +end, +); +} + +late final _FPDFAnnot_GetLinePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFAnnot_GetLine'); +late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Set the characteristics of the annotation's border (rounded rectangle). +/// +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units +/// +/// Returns true if setting the border for |annot| succeeds, false otherwise. +/// +/// If |annot| contains an appearance stream that overrides the border values, +/// then the appearance stream will be removed on success. +int FPDFAnnot_SetBorder(FPDF_ANNOTATION annot, +double horizontal_radius, +double vertical_radius, +double border_width, +) { + return _FPDFAnnot_SetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, +); +} + +late final _FPDFAnnot_SetBorderPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetBorder'); +late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction(); + +/// Experimental API. +/// Get the characteristics of the annotation's border (rounded rectangle). +/// +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units +/// +/// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are +/// not NULL, false otherwise. +int FPDFAnnot_GetBorder(FPDF_ANNOTATION annot, +ffi.Pointer horizontal_radius, +ffi.Pointer vertical_radius, +ffi.Pointer border_width, +) { + return _FPDFAnnot_GetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, +); +} + +late final _FPDFAnnot_GetBorderPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetBorder'); +late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the JavaScript of an event of the annotation's additional actions. +/// |buffer| is only modified if |buflen| is large enough to hold the whole +/// JavaScript string. If |buflen| is smaller, the total size of the JavaScript +/// is still returned, but nothing is copied. If there is no JavaScript for +/// |event| in |annot|, an empty string is written to |buf| and 2 is returned, +/// denoting the size of the null terminator in the buffer. On other errors, +/// nothing is written to |buffer| and 0 is returned. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// event - event type, one of the FPDF_ANNOT_AACTION_* values. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes, including the 2-byte +/// null terminator. +int FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +int event, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormAdditionalActionJavaScript(hHandle, +annot, +event, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormAdditionalActionJavaScript'); +late final _FPDFAnnot_GetFormAdditionalActionJavaScript = _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction , int )>(); + +/// Experimental API. +/// Check if |annot|'s dictionary has |key| as a key. +/// +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns true if |key| exists. +int FPDFAnnot_HasKey(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_HasKey(annot, +key, +); +} + +late final _FPDFAnnot_HasKeyPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasKey'); +late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction(); + +/// Experimental API. +/// Get the type of the value corresponding to |key| in |annot|'s dictionary. +/// +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns the type of the dictionary value. +int FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_GetValueType(annot, +key, +); +} + +late final _FPDFAnnot_GetValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetValueType'); +late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction(); + +/// Experimental API. +/// Set the string value corresponding to |key| in |annot|'s dictionary, +/// overwriting the existing value if any. The value type would be +/// FPDF_OBJECT_STRING after this function call succeeds. +/// +/// annot - handle to an annotation. +/// key - the key to the dictionary entry to be set, encoded in UTF-8. +/// value - the string value to be set, encoded in UTF-16LE. +/// +/// Returns true if successful. +int FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +FPDF_WIDESTRING value, +) { + return _FPDFAnnot_SetStringValue(annot, +key, +value, +); +} + +late final _FPDFAnnot_SetStringValuePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetStringValue'); +late final _FPDFAnnot_SetStringValue = _FPDFAnnot_SetStringValuePtr.asFunction(); + +/// Experimental API. +/// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| +/// is only modified if |buflen| is longer than the length of contents. Note that +/// if |key| does not exist in the dictionary or if |key|'s corresponding value +/// in the dictionary is not a string (i.e. the value is not of type +/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied +/// to |buffer| and the return value would be 2. On other errors, nothing would +/// be added to |buffer| and the return value would be 0. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetStringValue(annot, +key, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetStringValue'); +late final _FPDFAnnot_GetStringValue = _FPDFAnnot_GetStringValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the float value corresponding to |key| in |annot|'s dictionary. Writes +/// value to |value| and returns True if |key| exists in the dictionary and +/// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False +/// otherwise. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// value - receives the value, must not be NULL. +/// +/// Returns True if value found, False otherwise. +int FPDFAnnot_GetNumberValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer value, +) { + return _FPDFAnnot_GetNumberValue(annot, +key, +value, +); +} + +late final _FPDFAnnot_GetNumberValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetNumberValue'); +late final _FPDFAnnot_GetNumberValue = _FPDFAnnot_GetNumberValuePtr.asFunction )>(); + +/// Experimental API. +/// Set the AP (appearance string) in |annot|'s dictionary for a given +/// |appearanceMode|. +/// +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// value - the string value to be set, encoded in UTF-16LE. If +/// nullptr is passed, the AP is cleared for that mode. If the +/// mode is Normal, APs for all modes are cleared. +/// +/// Returns true if successful. +int FPDFAnnot_SetAP(FPDF_ANNOTATION annot, +int appearanceMode, +FPDF_WIDESTRING value, +) { + return _FPDFAnnot_SetAP(annot, +appearanceMode, +value, +); +} + +late final _FPDFAnnot_SetAPPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetAP'); +late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction(); + +/// Experimental API. +/// Get the AP (appearance string) from |annot|'s dictionary for a given +/// |appearanceMode|. +/// |buffer| is only modified if |buflen| is large enough to hold the whole AP +/// string. If |buflen| is smaller, the total size of the AP is still returned, +/// but nothing is copied. +/// If there is no appearance stream for |annot| in |appearanceMode|, an empty +/// string is written to |buf| and 2 is returned. +/// On other errors, nothing is written to |buffer| and 0 is returned. +/// +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetAP(FPDF_ANNOTATION annot, +int appearanceMode, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetAP(annot, +appearanceMode, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetAPPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetAP'); +late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the annotation corresponding to |key| in |annot|'s dictionary. Common +/// keys for linking annotations include "IRT" and "Popup". Must call +/// FPDFPage_CloseAnnot() when the annotation returned by this function is no +/// longer needed. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// +/// Returns a handle to the linked annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_GetLinkedAnnot(annot, +key, +); +} + +late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLinkedAnnot'); +late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the annotation flags of |annot|. +/// +/// annot - handle to an annotation. +/// +/// Returns the annotation flags. +int FPDFAnnot_GetFlags(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFlags(annot, +); +} + +late final _FPDFAnnot_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFlags'); +late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); + +/// Experimental API. +/// Set the |annot|'s flags to be of the value |flags|. +/// +/// annot - handle to an annotation. +/// flags - the flag values to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, +int flags, +) { + return _FPDFAnnot_SetFlags(annot, +flags, +); +} + +late final _FPDFAnnot_SetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFlags'); +late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); + +/// Experimental API. +/// Get the annotation flags of |annot|. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// +/// Returns the annotation flags specific to interactive forms. +int FPDFAnnot_GetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormFieldFlags(handle, +annot, +); +} + +late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldFlags'); +late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr.asFunction(); + +/// Experimental API. +/// Sets the form field flags for an interactive form annotation. +/// +/// handle - the handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// flags - the form field flags to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int flags, +) { + return _FPDFAnnot_SetFormFieldFlags(handle, +annot, +flags, +); +} + +late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFormFieldFlags'); +late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr.asFunction(); + +/// Experimental API. +/// Retrieves an interactive form annotation whose rectangle contains a given +/// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned +/// is no longer needed. +/// +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - handle to the page, returned by FPDF_LoadPage function. +/// point - position in PDF "user space". +/// +/// Returns the interactive form annotation whose rectangle contains the given +/// coordinates on the page. If there is no such annotation, return NULL. +FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer point, +) { + return _FPDFAnnot_GetFormFieldAtPoint(hHandle, +page, +point, +); +} + +late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFormFieldAtPoint'); +late final _FPDFAnnot_GetFormFieldAtPoint = _FPDFAnnot_GetFormFieldAtPointPtr.asFunction )>(); + +/// Experimental API. +/// Gets the name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the name string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldName(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldName'); +late final _FPDFAnnot_GetFormFieldName = _FPDFAnnot_GetFormFieldNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the alternate name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the alternate name string, encoded in +/// UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldAlternateName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldAlternateName(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldAlternateName'); +late final _FPDFAnnot_GetFormFieldAlternateName = _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the form field type of |annot|, which is an interactive form annotation. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// +/// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on +/// success. Returns -1 on error. +/// See field types in fpdf_formfill.h. +int FPDFAnnot_GetFormFieldType(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormFieldType(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldType'); +late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr.asFunction(); + +/// Experimental API. +/// Gets the value of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldValue(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldValue'); +late final _FPDFAnnot_GetFormFieldValue = _FPDFAnnot_GetFormFieldValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of options in the |annot|'s "Opt" dictionary. Intended for +/// use with listbox and combobox widget annotations. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns the number of options in "Opt" dictionary on success. Return value +/// will be -1 if annotation does not have an "Opt" dictionary or other error. +int FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetOptionCount(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetOptionCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetOptionCount'); +late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr.asFunction(); + +/// Experimental API. +/// Get the string value for the label of the option at |index| in |annot|'s +/// "Opt" dictionary. Intended for use with listbox and combobox widget +/// annotations. |buffer| is only modified if |buflen| is longer than the length +/// of contents. If index is out of range or in case of other error, nothing +/// will be added to |buffer| and the return value will be 0. Note that +/// return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +/// If |annot| does not have an "Opt" array, |index| is out of range or if any +/// other error occurs, returns 0. +int FPDFAnnot_GetOptionLabel(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetOptionLabel(hHandle, +annot, +index, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetOptionLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetOptionLabel'); +late final _FPDFAnnot_GetOptionLabel = _FPDFAnnot_GetOptionLabelPtr.asFunction , int )>(); + +/// Experimental API. +/// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary +/// is selected. Intended for use with listbox and combobox widget annotations. +/// +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array. +/// +/// Returns true if the option at |index| in |annot|'s "Opt" dictionary is +/// selected, false otherwise. +int FPDFAnnot_IsOptionSelected(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_IsOptionSelected(handle, +annot, +index, +); +} + +late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsOptionSelected'); +late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr.asFunction(); + +/// Experimental API. +/// Get the float value of the font size for an |annot| with variable text. +/// If 0, the font is to be auto-sized: its size is computed as a function of +/// the height of the annotation rectangle. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// value - Required. Float which will be set to font size on success. +/// +/// Returns true if the font size was set in |value|, false on error or if +/// |value| not provided. +int FPDFAnnot_GetFontSize(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer value, +) { + return _FPDFAnnot_GetFontSize(hHandle, +annot, +value, +); +} + +late final _FPDFAnnot_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFontSize'); +late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction )>(); + +/// Experimental API. +/// Set the text color of an annotation. +/// +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R - the red component for the text color. +/// G - the green component for the text color. +/// B - the blue component for the text color. +/// +/// Returns true if successful. +/// +/// Currently supported subtypes: freetext. +/// The range for the color components is 0 to 255. +int FPDFAnnot_SetFontColor(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int R, +int G, +int B, +) { + return _FPDFAnnot_SetFontColor(handle, +annot, +R, +G, +B, +); +} + +late final _FPDFAnnot_SetFontColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFontColor'); +late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction(); + +/// Experimental API. +/// Get the RGB value of the font color for an |annot| with variable text. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// +/// Returns true if the font color was set, false on error or if the font +/// color was not provided. +int FPDFAnnot_GetFontColor(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +) { + return _FPDFAnnot_GetFontColor(hHandle, +annot, +R, +G, +B, +); +} + +late final _FPDFAnnot_GetFontColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetFontColor'); +late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Determine if |annot| is a form widget that is checked. Intended for use with +/// checkbox and radio button widgets. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns true if |annot| is a form widget and is checked, false otherwise. +int FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_IsChecked(hHandle, +annot, +); +} + +late final _FPDFAnnot_IsCheckedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsChecked'); +late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction(); + +/// Experimental API. +/// Set the list of focusable annotation subtypes. Annotations of subtype +/// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API +/// will override the existing subtypes. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - list of annotation subtype which can be tabbed over. +/// count - total number of annotation subtype in list. +/// Returns true if list of annotation subtype is set successfully, false +/// otherwise. +int FPDFAnnot_SetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, +) { + return _FPDFAnnot_SetFocusableSubtypes(hHandle, +subtypes, +count, +); +} + +late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_SetFocusableSubtypes'); +late final _FPDFAnnot_SetFocusableSubtypes = _FPDFAnnot_SetFocusableSubtypesPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the count of focusable annotation subtypes as set by host +/// for a |hHandle|. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// Returns the count of focusable annotation subtypes or -1 on error. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypesCount(FPDF_FORMHANDLE hHandle, +) { + return _FPDFAnnot_GetFocusableSubtypesCount(hHandle, +); +} + +late final _FPDFAnnot_GetFocusableSubtypesCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFocusableSubtypesCount'); +late final _FPDFAnnot_GetFocusableSubtypesCount = _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction(); + +/// Experimental API. +/// Get the list of focusable annotation subtype as set by host. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - receives the list of annotation subtype which can be tabbed +/// over. Caller must have allocated |subtypes| more than or +/// equal to the count obtained from +/// FPDFAnnot_GetFocusableSubtypesCount() API. +/// count - size of |subtypes|. +/// Returns true on success and set list of annotation subtype to |subtypes|, +/// false otherwise. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, +) { + return _FPDFAnnot_GetFocusableSubtypes(hHandle, +subtypes, +count, +); +} + +late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_GetFocusableSubtypes'); +late final _FPDFAnnot_GetFocusableSubtypes = _FPDFAnnot_GetFocusableSubtypesPtr.asFunction , int )>(); + +/// Experimental API. +/// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. +/// +/// annot - handle to an annotation. +/// +/// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, +/// if the input annot is NULL or input annot's subtype is not link. +FPDF_LINK FPDFAnnot_GetLink(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetLink(annot, +); +} + +late final _FPDFAnnot_GetLinkPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLink'); +late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); + +/// Experimental API. +/// Gets the count of annotations in the |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns number of controls in its control group or -1 on error. +int FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlCount(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormControlCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlCount'); +late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr.asFunction(); + +/// Experimental API. +/// Gets the index of |annot| in |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns index of a given |annot| in its control group or -1 on error. +int FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlIndex(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlIndex'); +late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr.asFunction(); + +/// Experimental API. +/// Gets the export value of |annot| which is an interactive form annotation. +/// Intended for use with radio button and checkbox widget annotations. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value +/// will be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldExportValue(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldExportValue'); +late final _FPDFAnnot_GetFormFieldExportValue = _FPDFAnnot_GetFormFieldExportValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Add a URI action to |annot|, overwriting the existing action, if any. +/// +/// annot - handle to a link annotation. +/// uri - the URI to be set, encoded in 7-bit ASCII. +/// +/// Returns true if successful. +int FPDFAnnot_SetURI(FPDF_ANNOTATION annot, +ffi.Pointer uri, +) { + return _FPDFAnnot_SetURI(annot, +uri, +); +} + +late final _FPDFAnnot_SetURIPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetURI'); +late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction )>(); + +/// Experimental API. +/// Get the attachment from |annot|. +/// +/// annot - handle to a file annotation. +/// +/// Returns the handle to the attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFileAttachment(annot, +); +} + +late final _FPDFAnnot_GetFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFileAttachment'); +late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr.asFunction(); + +/// Experimental API. +/// Add an embedded file with |name| to |annot|. +/// +/// annot - handle to a file annotation. +/// name - name of the new attachment. +/// +/// Returns a handle to the new attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, +FPDF_WIDESTRING name, +) { + return _FPDFAnnot_AddFileAttachment(annot, +name, +); +} + +late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AddFileAttachment'); +late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr.asFunction(); + +/// Function: FPDFText_LoadPage +/// Prepare information about all characters in a page. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage function +/// (in FPDFVIEW module). +/// Return value: +/// A handle to the text page information structure. +/// NULL if something goes wrong. +/// Comments: +/// Application must call FPDFText_ClosePage to release the text page +/// information. +FPDF_TEXTPAGE FPDFText_LoadPage(FPDF_PAGE page, +) { + return _FPDFText_LoadPage(page, +); +} + +late final _FPDFText_LoadPagePtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadPage'); +late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); + +/// Function: FPDFText_ClosePage +/// Release all resources allocated for a text page information +/// structure. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return Value: +/// None. +void FPDFText_ClosePage(FPDF_TEXTPAGE text_page, +) { + return _FPDFText_ClosePage(text_page, +); +} + +late final _FPDFText_ClosePagePtr = _lookup< + ffi.NativeFunction>('FPDFText_ClosePage'); +late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); + +/// Function: FPDFText_CountChars +/// Get number of characters in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return value: +/// Number of characters in the page. Return -1 for error. +/// Generated characters, like additional space characters, new line +/// characters, are also counted. +/// Comments: +/// Characters in a page form a "stream", inside the stream, each +/// character has an index. +/// We will use the index parameters in many of FPDFTEXT functions. The +/// first character in the page +/// has an index value of zero. +int FPDFText_CountChars(FPDF_TEXTPAGE text_page, +) { + return _FPDFText_CountChars(text_page, +); +} + +late final _FPDFText_CountCharsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountChars'); +late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); + +/// Function: FPDFText_GetUnicode +/// Get Unicode of a character in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The Unicode of the particular character. +/// If a character is not encoded in Unicode and Foxit engine can't +/// convert to Unicode, +/// the return value will be zero. +int FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetUnicode(text_page, +index, +); +} + +late final _FPDFText_GetUnicodePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetUnicode'); +late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetTextObject +/// Get the FPDF_PAGEOBJECT associated with a given character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The associated text object for the character at |index|, or NULL on +/// error. The returned text object, if non-null, is of type +/// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. +FPDF_PAGEOBJECT FPDFText_GetTextObject(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetTextObject(text_page, +index, +); +} + +late final _FPDFText_GetTextObjectPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetTextObject'); +late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_IsGenerated +/// Get if a character in a page is generated by PDFium. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is generated by PDFium. +/// 0 if the character is not generated by PDFium. +/// -1 if there was an error. +int FPDFText_IsGenerated(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_IsGenerated(text_page, +index, +); +} + +late final _FPDFText_IsGeneratedPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsGenerated'); +late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_IsHyphen +/// Get if a character in a page is a hyphen. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is a hyphen. +/// 0 if the character is not a hyphen. +/// -1 if there was an error. +int FPDFText_IsHyphen(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_IsHyphen(text_page, +index, +); +} + +late final _FPDFText_IsHyphenPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsHyphen'); +late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_HasUnicodeMapError +/// Get if a character in a page has an invalid unicode mapping. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character has an invalid unicode mapping. +/// 0 if the character has no known unicode mapping issues. +/// -1 if there was an error. +int FPDFText_HasUnicodeMapError(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_HasUnicodeMapError(text_page, +index, +); +} + +late final _FPDFText_HasUnicodeMapErrorPtr = _lookup< + ffi.NativeFunction>('FPDFText_HasUnicodeMapError'); +late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr.asFunction(); + +/// Function: FPDFText_GetFontSize +/// Get the font size of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The font size of the particular character, measured in points (about +/// 1/72 inch). This is the typographic size of the font (so called +/// "em size"). +double FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetFontSize(text_page, +index, +); +} + +late final _FPDFText_GetFontSizePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontSize'); +late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetFontInfo +/// Get the font name and flags of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// buffer - A buffer receiving the font name. +/// buflen - The length of |buffer| in bytes. +/// flags - Optional pointer to an int receiving the font flags. +/// These flags should be interpreted per PDF spec 1.7 +/// Section 5.7.1 Font Descriptor Flags. +/// Return value: +/// On success, return the length of the font name, including the +/// trailing NUL character, in bytes. If this length is less than or +/// equal to |length|, |buffer| is set to the font name, |flags| is +/// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on +/// failure. +int FPDFText_GetFontInfo(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer flags, +) { + return _FPDFText_GetFontInfo(text_page, +index, +buffer, +buflen, +flags, +); +} + +late final _FPDFText_GetFontInfoPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFText_GetFontInfo'); +late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetFontWeight +/// Get the font weight of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// On success, return the font weight of the particular character. If +/// |text_page| is invalid, if |index| is out of bounds, or if the +/// character's text object is undefined, return -1. +int FPDFText_GetFontWeight(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetFontWeight(text_page, +index, +); +} + +late final _FPDFText_GetFontWeightPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontWeight'); +late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetFillColor +/// Get the fill color of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the fill color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the fill color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the fill color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the fill color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetFillColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFText_GetFillColor(text_page, +index, +R, +G, +B, +A, +); +} + +late final _FPDFText_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetFillColor'); +late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetStrokeColor +/// Get the stroke color of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the stroke color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the stroke color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the stroke color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the stroke color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetStrokeColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFText_GetStrokeColor(text_page, +index, +R, +G, +B, +A, +); +} + +late final _FPDFText_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetStrokeColor'); +late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetCharAngle +/// Get character rotation angle. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return Value: +/// On success, return the angle value in radian. Value will always be +/// greater or equal to 0. If |text_page| is invalid, or if |index| is +/// out of bounds, then return -1. +double FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetCharAngle(text_page, +index, +); +} + +late final _FPDFText_GetCharAnglePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharAngle'); +late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction(); + +/// Function: FPDFText_GetCharBox +/// Get bounding box of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// left - Pointer to a double number receiving left position +/// of the character box. +/// right - Pointer to a double number receiving right position +/// of the character box. +/// bottom - Pointer to a double number receiving bottom position +/// of the character box. +/// top - Pointer to a double number receiving top position of +/// the character box. +/// Return Value: +/// On success, return TRUE and fill in |left|, |right|, |bottom|, and +/// |top|. If |text_page| is invalid, or if |index| is out of bounds, +/// then return FALSE, and the out parameters remain unmodified. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer left, +ffi.Pointer right, +ffi.Pointer bottom, +ffi.Pointer top, +) { + return _FPDFText_GetCharBox(text_page, +index, +left, +right, +bottom, +top, +); +} + +late final _FPDFText_GetCharBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetCharBox'); +late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetLooseCharBox +/// Get a "loose" bounding box of a particular character, i.e., covering +/// the entire glyph bounds, without taking the actual glyph shape into +/// account. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// rect - Pointer to a FS_RECTF receiving the character box. +/// Return Value: +/// On success, return TRUE and fill in |rect|. If |text_page| is +/// invalid, or if |index| is out of bounds, then return FALSE, and the +/// |rect| out parameter remains unmodified. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetLooseCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer rect, +) { + return _FPDFText_GetLooseCharBox(text_page, +index, +rect, +); +} + +late final _FPDFText_GetLooseCharBoxPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetLooseCharBox'); +late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDFText_GetMatrix +/// Get the effective transformation matrix for a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage(). +/// index - Zero-based index of the character. +/// matrix - Pointer to a FS_MATRIX receiving the transformation +/// matrix. +/// Return Value: +/// On success, return TRUE and fill in |matrix|. If |text_page| is +/// invalid, or if |index| is out of bounds, or if |matrix| is NULL, +/// then return FALSE, and |matrix| remains unmodified. +int FPDFText_GetMatrix(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer matrix, +) { + return _FPDFText_GetMatrix(text_page, +index, +matrix, +); +} + +late final _FPDFText_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetMatrix'); +late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction )>(); + +/// Function: FPDFText_GetCharOrigin +/// Get origin of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// x - Pointer to a double number receiving x coordinate of +/// the character origin. +/// y - Pointer to a double number receiving y coordinate of +/// the character origin. +/// Return Value: +/// Whether the call succeeded. If false, x and y are unchanged. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer x, +ffi.Pointer y, +) { + return _FPDFText_GetCharOrigin(text_page, +index, +x, +y, +); +} + +late final _FPDFText_GetCharOriginPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFText_GetCharOrigin'); +late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFText_GetCharIndexAtPos +/// Get the index of a character at or nearby a certain position on the +/// page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// x - X position in PDF "user space". +/// y - Y position in PDF "user space". +/// xTolerance - An x-axis tolerance value for character hit +/// detection, in point units. +/// yTolerance - A y-axis tolerance value for character hit +/// detection, in point units. +/// Return Value: +/// The zero-based index of the character at, or nearby the point (x,y). +/// If there is no character at or nearby the point, return value will +/// be -1. If an error occurs, -3 will be returned. +int FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, +double x, +double y, +double xTolerance, +double yTolerance, +) { + return _FPDFText_GetCharIndexAtPos(text_page, +x, +y, +xTolerance, +yTolerance, +); +} + +late final _FPDFText_GetCharIndexAtPosPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharIndexAtPos'); +late final _FPDFText_GetCharIndexAtPos = _FPDFText_GetCharIndexAtPosPtr.asFunction(); + +/// Function: FPDFText_GetText +/// Extract unicode text string from the page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start characters. +/// count - Number of UCS-2 values to be extracted. +/// result - A buffer (allocated by application) receiving the +/// extracted UCS-2 values. The buffer must be able to +/// hold `count` UCS-2 values plus a terminator. +/// Return Value: +/// Number of characters written into the result buffer, including the +/// trailing terminator. +/// Comments: +/// This function ignores characters without UCS-2 representations. +/// It considers all characters on the page, even those that are not +/// visible when the page has a cropbox. To filter out the characters +/// outside of the cropbox, use FPDF_GetPageBoundingBox() and +/// FPDFText_GetCharBox(). +int FPDFText_GetText(FPDF_TEXTPAGE text_page, +int start_index, +int count, +ffi.Pointer result, +) { + return _FPDFText_GetText(text_page, +start_index, +count, +result, +); +} + +late final _FPDFText_GetTextPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetText'); +late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction )>(); + +/// Function: FPDFText_CountRects +/// Counts number of rectangular areas occupied by a segment of text, +/// and caches the result for subsequent FPDFText_GetRect() calls. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start character. +/// count - Number of characters, or -1 for all remaining. +/// Return value: +/// Number of rectangles, 0 if text_page is null, or -1 on bad +/// start_index. +/// Comments: +/// This function, along with FPDFText_GetRect can be used by +/// applications to detect the position on the page for a text segment, +/// so proper areas can be highlighted. The FPDFText_* functions will +/// automatically merge small character boxes into bigger one if those +/// characters are on the same line and use same font settings. +int FPDFText_CountRects(FPDF_TEXTPAGE text_page, +int start_index, +int count, +) { + return _FPDFText_CountRects(text_page, +start_index, +count, +); +} + +late final _FPDFText_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountRects'); +late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction(); + +/// Function: FPDFText_GetRect +/// Get a rectangular area from the result generated by +/// FPDFText_CountRects. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// rect_index - Zero-based index for the rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |text_page| is invalid then return FALSE, and the out +/// parameters remain unmodified. If |text_page| is valid but +/// |rect_index| is out of bounds, then return FALSE and set the out +/// parameters to 0. +int FPDFText_GetRect(FPDF_TEXTPAGE text_page, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, +) { + return _FPDFText_GetRect(text_page, +rect_index, +left, +top, +right, +bottom, +); +} + +late final _FPDFText_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetRect'); +late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Function: FPDFText_GetBoundedText +/// Extract unicode text within a rectangular boundary on the page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// left - Left boundary. +/// top - Top boundary. +/// right - Right boundary. +/// bottom - Bottom boundary. +/// buffer - Caller-allocated buffer to receive UTF-16 values. +/// buflen - Number of UTF-16 values (not bytes) that `buffer` +/// is capable of holding. +/// Return Value: +/// If buffer is NULL or buflen is zero, return number of UTF-16 +/// values (not bytes) of text present within the rectangle, excluding +/// a terminating NUL. Generally you should pass a buffer at least one +/// larger than this if you want a terminating NUL, which will be +/// provided if space is available. Otherwise, return number of UTF-16 +/// values copied into the buffer, including the terminating NUL when +/// space for it is available. +/// Comment: +/// If the buffer is too small, as much text as will fit is copied into +/// it. May return a split surrogate in that case. +int FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, +double left, +double top, +double right, +double bottom, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFText_GetBoundedText(text_page, +left, +top, +right, +bottom, +buffer, +buflen, +); +} + +late final _FPDFText_GetBoundedTextPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFText_GetBoundedText'); +late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction , int )>(); + +/// Function: FPDFText_FindStart +/// Start a search. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// findwhat - A unicode match pattern. +/// flags - Option flags. +/// start_index - Start from this character. -1 for end of the page. +/// Return Value: +/// A handle for the search context. FPDFText_FindClose must be called +/// to release this handle. +FPDF_SCHHANDLE FPDFText_FindStart(FPDF_TEXTPAGE text_page, +FPDF_WIDESTRING findwhat, +int flags, +int start_index, +) { + return _FPDFText_FindStart(text_page, +findwhat, +flags, +start_index, +); +} + +late final _FPDFText_FindStartPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindStart'); +late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction(); + +/// Function: FPDFText_FindNext +/// Search in the direction from page start to end. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindNext(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindNext(handle, +); +} + +late final _FPDFText_FindNextPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindNext'); +late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); + +/// Function: FPDFText_FindPrev +/// Search in the direction from page end to start. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindPrev(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindPrev(handle, +); +} + +late final _FPDFText_FindPrevPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindPrev'); +late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); + +/// Function: FPDFText_GetSchResultIndex +/// Get the starting character index of the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Index for the starting character. +int FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle, +) { + return _FPDFText_GetSchResultIndex(handle, +); +} + +late final _FPDFText_GetSchResultIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchResultIndex'); +late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); + +/// Function: FPDFText_GetSchCount +/// Get the number of matched characters in the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Number of matched characters. +int FPDFText_GetSchCount(FPDF_SCHHANDLE handle, +) { + return _FPDFText_GetSchCount(handle, +); +} + +late final _FPDFText_GetSchCountPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchCount'); +late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); + +/// Function: FPDFText_FindClose +/// Release a search context. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// None. +void FPDFText_FindClose(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindClose(handle, +); +} + +late final _FPDFText_FindClosePtr = _lookup< + ffi.NativeFunction>('FPDFText_FindClose'); +late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); + +/// Function: FPDFLink_LoadWebLinks +/// Prepare information about weblinks in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return Value: +/// A handle to the page's links information structure, or +/// NULL if something goes wrong. +/// Comments: +/// Weblinks are those links implicitly embedded in PDF pages. PDF also +/// has a type of annotation called "link" (FPDFTEXT doesn't deal with +/// that kind of link). FPDFTEXT weblink feature is useful for +/// automatically detecting links in the page contents. For example, +/// things like "https://www.example.com" will be detected, so +/// applications can allow user to click on those characters to activate +/// the link, even the PDF doesn't come with link annotations. +/// +/// FPDFLink_CloseWebLinks must be called to release resources. +FPDF_PAGELINK FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page, +) { + return _FPDFLink_LoadWebLinks(text_page, +); +} + +late final _FPDFLink_LoadWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_LoadWebLinks'); +late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction(); + +/// Function: FPDFLink_CountWebLinks +/// Count number of detected web links. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// Number of detected web links. +int FPDFLink_CountWebLinks(FPDF_PAGELINK link_page, +) { + return _FPDFLink_CountWebLinks(link_page, +); +} + +late final _FPDFLink_CountWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountWebLinks'); +late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); + +/// Function: FPDFLink_GetURL +/// Fetch the URL information for a detected web link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// buffer - A unicode buffer for the result. +/// buflen - Number of 16-bit code units (not bytes) for the +/// buffer, including an additional terminator. +/// Return Value: +/// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit +/// code units (not bytes) needed to buffer the result (an additional +/// terminator is included in this count). +/// Otherwise, copy the result into |buffer|, truncating at |buflen| if +/// the result is too large to fit, and return the number of 16-bit code +/// units actually copied into the buffer (the additional terminator is +/// also included in this count). +/// If |link_index| does not correspond to a valid link, then the result +/// is an empty string. +int FPDFLink_GetURL(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFLink_GetURL(link_page, +link_index, +buffer, +buflen, +); +} + +late final _FPDFLink_GetURLPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFLink_GetURL'); +late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction , int )>(); + +/// Function: FPDFLink_CountRects +/// Count number of rectangular areas for the link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// Return Value: +/// Number of rectangular areas for the link. If |link_index| does +/// not correspond to a valid link, then 0 is returned. +int FPDFLink_CountRects(FPDF_PAGELINK link_page, +int link_index, +) { + return _FPDFLink_CountRects(link_page, +link_index, +); +} + +late final _FPDFLink_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountRects'); +late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); + +/// Function: FPDFLink_GetRect +/// Fetch the boundaries of a rectangle for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// rect_index - Zero-based index for a rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |link_page| is invalid or if |link_index| does not +/// correspond to a valid link, then return FALSE, and the out +/// parameters remain unmodified. +int FPDFLink_GetRect(FPDF_PAGELINK link_page, +int link_index, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, +) { + return _FPDFLink_GetRect(link_page, +link_index, +rect_index, +left, +top, +right, +bottom, +); +} + +late final _FPDFLink_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFLink_GetRect'); +late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFLink_GetTextRange +/// Fetch the start char index and char count for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// start_char_index - pointer to int receiving the start char index +/// char_count - pointer to int receiving the char count +/// Return Value: +/// On success, return TRUE and fill in |start_char_index| and +/// |char_count|. if |link_page| is invalid or if |link_index| does +/// not correspond to a valid link, then return FALSE and the out +/// parameters remain unmodified. +int FPDFLink_GetTextRange(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer start_char_index, +ffi.Pointer char_count, +) { + return _FPDFLink_GetTextRange(link_page, +link_index, +start_char_index, +char_count, +); +} + +late final _FPDFLink_GetTextRangePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_GetTextRange'); +late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFLink_CloseWebLinks +/// Release resources used by weblink feature. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// None. +void FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page, +) { + return _FPDFLink_CloseWebLinks(link_page, +); +} + +late final _FPDFLink_CloseWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CloseWebLinks'); +late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); + +/// Get the first child of |bookmark|, or the first top-level bookmark item. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. Pass NULL for the first top +/// level item. +/// +/// Returns a handle to the first child of |bookmark| or the first top-level +/// bookmark item. NULL if no child or top-level bookmark found. +/// Note that another name for the bookmarks is the document outline, as +/// described in ISO 32000-1:2008, section 12.3.3. +FPDF_BOOKMARK FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetFirstChild(document, +bookmark, +); +} + +late final _FPDFBookmark_GetFirstChildPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetFirstChild'); +late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr.asFunction(); + +/// Get the next sibling of |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. +/// +/// Returns a handle to the next sibling of |bookmark|, or NULL if this is the +/// last bookmark at this level. +/// +/// Note that the caller is responsible for handling circular bookmark +/// references, as may arise from malformed documents. +FPDF_BOOKMARK FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetNextSibling(document, +bookmark, +); +} + +late final _FPDFBookmark_GetNextSiblingPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetNextSibling'); +late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr.asFunction(); + +/// Get the title of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// buffer - buffer for the title. May be NULL. +/// buflen - the length of the buffer in bytes. May be 0. +/// +/// Returns the number of bytes in the title, including the terminating NUL +/// character. The number of bytes is returned regardless of the |buffer| and +/// |buflen| parameters. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The +/// string is terminated by a UTF16 NUL character. If |buflen| is less than the +/// required length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFBookmark_GetTitle(bookmark, +buffer, +buflen, +); +} + +late final _FPDFBookmark_GetTitlePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFBookmark_GetTitle'); +late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of chlidren of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns a signed integer that represents the number of sub-items the given +/// bookmark has. If the value is positive, child items shall be shown by default +/// (open state). If the value is negative, child items shall be hidden by +/// default (closed state). Please refer to PDF 32000-1:2008, Table 153. +/// Returns 0 if the bookmark has no children or is invalid. +int FPDFBookmark_GetCount(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetCount(bookmark, +); +} + +late final _FPDFBookmark_GetCountPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetCount'); +late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); + +/// Find the bookmark with |title| in |document|. +/// +/// document - handle to the document. +/// title - the UTF-16LE encoded Unicode title for which to search. +/// +/// Returns the handle to the bookmark, or NULL if |title| can't be found. +/// +/// FPDFBookmark_Find() will always return the first bookmark found even if +/// multiple bookmarks have the same |title|. +FPDF_BOOKMARK FPDFBookmark_Find(FPDF_DOCUMENT document, +FPDF_WIDESTRING title, +) { + return _FPDFBookmark_Find(document, +title, +); +} + +late final _FPDFBookmark_FindPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_Find'); +late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction(); + +/// Get the destination associated with |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the destination data, or NULL if no destination is +/// associated with |bookmark|. +FPDF_DEST FPDFBookmark_GetDest(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetDest(document, +bookmark, +); +} + +late final _FPDFBookmark_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetDest'); +late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction(); + +/// Get the action associated with |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the action data, or NULL if no action is associated +/// with |bookmark|. +/// If this function returns a valid handle, it is valid as long as |bookmark| is +/// valid. +/// If this function returns NULL, FPDFBookmark_GetDest() should be called to get +/// the |bookmark| destination data. +FPDF_ACTION FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetAction(bookmark, +); +} + +late final _FPDFBookmark_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetAction'); +late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction(); + +/// Get the type of |action|. +/// +/// action - handle to the action. +/// +/// Returns one of: +/// PDFACTION_UNSUPPORTED +/// PDFACTION_GOTO +/// PDFACTION_REMOTEGOTO +/// PDFACTION_URI +/// PDFACTION_LAUNCH +int FPDFAction_GetType(FPDF_ACTION action, +) { + return _FPDFAction_GetType(action, +); +} + +late final _FPDFAction_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetType'); +late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); + +/// Get the destination of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. |action| must be a |PDFACTION_GOTO| or +/// |PDFACTION_REMOTEGOTO|. +/// +/// Returns a handle to the destination data, or NULL on error, typically +/// because the arguments were bad or the action was of the wrong type. +/// +/// In the case of |PDFACTION_REMOTEGOTO|, you must first call +/// FPDFAction_GetFilePath(), then load the document at that path, then pass +/// the document handle from that document as |document| to FPDFAction_GetDest(). +FPDF_DEST FPDFAction_GetDest(FPDF_DOCUMENT document, +FPDF_ACTION action, +) { + return _FPDFAction_GetDest(document, +action, +); +} + +late final _FPDFAction_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetDest'); +late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction(); + +/// Get the file path of |action|. +/// +/// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or +/// |PDFACTION_REMOTEGOTO|. +/// buffer - a buffer for output the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFAction_GetFilePath(FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetFilePath(action, +buffer, +buflen, +); +} + +late final _FPDFAction_GetFilePathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetFilePath'); +late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction , int )>(); + +/// Get the URI path of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. Must be a |PDFACTION_URI|. +/// buffer - a buffer for the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the URI path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// The |buffer| may contain badly encoded data. The caller should validate the +/// output. e.g. Check to see if it is UTF-8. +/// +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +/// +/// Historically, the documentation for this API claimed |buffer| is always +/// encoded in 7-bit ASCII, but did not actually enforce it. +/// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 +/// added that enforcement, but that did not work well for real world PDFs that +/// used UTF-8. As of this writing, this API reverted back to its original +/// behavior prior to commit d609e84cee. +int FPDFAction_GetURIPath(FPDF_DOCUMENT document, +FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetURIPath(document, +action, +buffer, +buflen, +); +} + +late final _FPDFAction_GetURIPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetURIPath'); +late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction , int )>(); + +/// Get the page index of |dest|. +/// +/// document - handle to the document. +/// dest - handle to the destination. +/// +/// Returns the 0-based page index containing |dest|. Returns -1 on error. +int FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, +FPDF_DEST dest, +) { + return _FPDFDest_GetDestPageIndex(document, +dest, +); +} + +late final _FPDFDest_GetDestPageIndexPtr = _lookup< + ffi.NativeFunction>('FPDFDest_GetDestPageIndex'); +late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr.asFunction(); + +/// Experimental API. +/// Get the view (fit type) specified by |dest|. +/// +/// dest - handle to the destination. +/// pNumParams - receives the number of view parameters, which is at most 4. +/// pParams - buffer to write the view parameters. Must be at least 4 +/// FS_FLOATs long. +/// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if +/// |dest| does not specify a view. +int FPDFDest_GetView(FPDF_DEST dest, +ffi.Pointer pNumParams, +ffi.Pointer pParams, +) { + return _FPDFDest_GetView(dest, +pNumParams, +pParams, +); +} + +late final _FPDFDest_GetViewPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFDest_GetView'); +late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction , ffi.Pointer )>(); + +/// Get the (x, y, zoom) location of |dest| in the destination page, if the +/// destination is in [page /XYZ x y zoom] syntax. +/// +/// dest - handle to the destination. +/// hasXVal - out parameter; true if the x value is not null +/// hasYVal - out parameter; true if the y value is not null +/// hasZoomVal - out parameter; true if the zoom value is not null +/// x - out parameter; the x coordinate, in page coordinates. +/// y - out parameter; the y coordinate, in page coordinates. +/// zoom - out parameter; the zoom value. +/// Returns TRUE on successfully reading the /XYZ value. +/// +/// Note the [x, y, zoom] values are only set if the corresponding hasXVal, +/// hasYVal or hasZoomVal flags are true. +int FPDFDest_GetLocationInPage(FPDF_DEST dest, +ffi.Pointer hasXVal, +ffi.Pointer hasYVal, +ffi.Pointer hasZoomVal, +ffi.Pointer x, +ffi.Pointer y, +ffi.Pointer zoom, +) { + return _FPDFDest_GetLocationInPage(dest, +hasXVal, +hasYVal, +hasZoomVal, +x, +y, +zoom, +); +} + +late final _FPDFDest_GetLocationInPagePtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFDest_GetLocationInPage'); +late final _FPDFDest_GetLocationInPage = _FPDFDest_GetLocationInPagePtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Find a link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns a handle to the link, or NULL if no link found at the given point. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +FPDF_LINK FPDFLink_GetLinkAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkAtPoint(page, +x, +y, +); +} + +late final _FPDFLink_GetLinkAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkAtPoint'); +late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction(); + +/// Find the Z-order of link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns the Z-order of the link, or -1 if no link found at the given point. +/// Larger Z-order numbers are closer to the front. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +int FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkZOrderAtPoint(page, +x, +y, +); +} + +late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkZOrderAtPoint'); +late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr.asFunction(); + +/// Get destination info for |link|. +/// +/// document - handle to the document. +/// link - handle to the link. +/// +/// Returns a handle to the destination, or NULL if there is no destination +/// associated with the link. In this case, you should call FPDFLink_GetAction() +/// to retrieve the action associated with |link|. +FPDF_DEST FPDFLink_GetDest(FPDF_DOCUMENT document, +FPDF_LINK link, +) { + return _FPDFLink_GetDest(document, +link, +); +} + +late final _FPDFLink_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetDest'); +late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction(); + +/// Get action info for |link|. +/// +/// link - handle to the link. +/// +/// Returns a handle to the action associated to |link|, or NULL if no action. +/// If this function returns a valid handle, it is valid as long as |link| is +/// valid. +FPDF_ACTION FPDFLink_GetAction(FPDF_LINK link, +) { + return _FPDFLink_GetAction(link, +); +} + +late final _FPDFLink_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAction'); +late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); + +/// Enumerates all the link annotations in |page|. +/// +/// page - handle to the page. +/// start_pos - the start position, should initially be 0 and is updated with +/// the next start position on return. +/// link_annot - the link handle for |startPos|. +/// +/// Returns TRUE on success. +int FPDFLink_Enumerate(FPDF_PAGE page, +ffi.Pointer start_pos, +ffi.Pointer link_annot, +) { + return _FPDFLink_Enumerate(page, +start_pos, +link_annot, +); +} + +late final _FPDFLink_EnumeratePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_Enumerate'); +late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Gets FPDF_ANNOTATION object for |link_annot|. +/// +/// page - handle to the page in which FPDF_LINK object is present. +/// link_annot - handle to link annotation. +/// +/// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, +/// if the input link annot or page is NULL. +FPDF_ANNOTATION FPDFLink_GetAnnot(FPDF_PAGE page, +FPDF_LINK link_annot, +) { + return _FPDFLink_GetAnnot(page, +link_annot, +); +} + +late final _FPDFLink_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAnnot'); +late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction(); + +/// Get the rectangle for |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// rect - the annotation rectangle. +/// +/// Returns true on success. +int FPDFLink_GetAnnotRect(FPDF_LINK link_annot, +ffi.Pointer rect, +) { + return _FPDFLink_GetAnnotRect(link_annot, +rect, +); +} + +late final _FPDFLink_GetAnnotRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetAnnotRect'); +late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction )>(); + +/// Get the count of quadrilateral points to the |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// +/// Returns the count of quadrilateral points. +int FPDFLink_CountQuadPoints(FPDF_LINK link_annot, +) { + return _FPDFLink_CountQuadPoints(link_annot, +); +} + +late final _FPDFLink_CountQuadPointsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountQuadPoints'); +late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); + +/// Get the quadrilateral points for the specified |quad_index| in |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// quad_index - the specified quad point index. +/// quad_points - receives the quadrilateral points. +/// +/// Returns true on success. +int FPDFLink_GetQuadPoints(FPDF_LINK link_annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFLink_GetQuadPoints(link_annot, +quad_index, +quad_points, +); +} + +late final _FPDFLink_GetQuadPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetQuadPoints'); +late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction )>(); + +/// Experimental API +/// Gets an additional-action from |page|. +/// +/// page - handle to the page, as returned by FPDF_LoadPage(). +/// aa_type - the type of the page object's addtional-action, defined +/// in public/fpdf_formfill.h +/// +/// Returns the handle to the action data, or NULL if there is no +/// additional-action of type |aa_type|. +/// If this function returns a valid handle, it is valid as long as |page| is +/// valid. +FPDF_ACTION FPDF_GetPageAAction(FPDF_PAGE page, +int aa_type, +) { + return _FPDF_GetPageAAction(page, +aa_type, +); +} + +late final _FPDF_GetPageAActionPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageAAction'); +late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction(); + +/// Experimental API. +/// Get the file identifer defined in the trailer of |document|. +/// +/// document - handle to the document. +/// id_type - the file identifier type to retrieve. +/// buffer - a buffer for the file identifier. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file identifier, including the NUL +/// terminator. +/// +/// The |buffer| is always a byte string. The |buffer| is followed by a NUL +/// terminator. If |buflen| is less than the returned length, or |buffer| is +/// NULL, |buffer| will not be modified. +int FPDF_GetFileIdentifier(FPDF_DOCUMENT document, +FPDF_FILEIDTYPE id_type, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetFileIdentifier(document, +id_type.value, +buffer, +buflen, +); +} + +late final _FPDF_GetFileIdentifierPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetFileIdentifier'); +late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction , int )>(); + +/// Get meta-data |tag| content from |document|. +/// +/// document - handle to the document. +/// tag - the tag to retrieve. The tag can be one of: +/// Title, Author, Subject, Keywords, Creator, Producer, +/// CreationDate, or ModDate. +/// For detailed explanations of these tags and their respective +/// values, please refer to PDF Reference 1.6, section 10.2.1, +/// 'Document Information Dictionary'. +/// buffer - a buffer for the tag. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the tag, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +/// +/// For linearized files, FPDFAvail_IsFormAvail must be called before this, and +/// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there +/// is no guarantee the metadata has been loaded. +int FPDF_GetMetaText(FPDF_DOCUMENT document, +FPDF_BYTESTRING tag, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetMetaText(document, +tag, +buffer, +buflen, +); +} + +late final _FPDF_GetMetaTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetMetaText'); +late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction , int )>(); + +/// Get the page label for |page_index| from |document|. +/// +/// document - handle to the document. +/// page_index - the 0-based index of the page. +/// buffer - a buffer for the page label. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the page label, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDF_GetPageLabel(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetPageLabel(document, +page_index, +buffer, +buflen, +); +} + +late final _FPDF_GetPageLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetPageLabel'); +late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction , int )>(); + +/// Create a new PDF document. +/// +/// Returns a handle to a new document, or NULL on failure. +FPDF_DOCUMENT FPDF_CreateNewDocument() { + return _FPDF_CreateNewDocument(); +} + +late final _FPDF_CreateNewDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_CreateNewDocument'); +late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); + +/// Create a new PDF page. +/// +/// document - handle to document. +/// page_index - suggested 0-based index of the page to create. If it is larger +/// than document's current last index(L), the created page index +/// is the next available index -- L+1. +/// width - the page width in points. +/// height - the page height in points. +/// +/// Returns the handle to the new page or NULL on failure. +/// +/// The page should be closed with FPDF_ClosePage() when finished as +/// with any other page in the document. +FPDF_PAGE FPDFPage_New(FPDF_DOCUMENT document, +int page_index, +double width, +double height, +) { + return _FPDFPage_New(document, +page_index, +width, +height, +); +} + +late final _FPDFPage_NewPtr = _lookup< + ffi.NativeFunction>('FPDFPage_New'); +late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction(); + +/// Delete the page at |page_index|. +/// +/// document - handle to document. +/// page_index - the index of the page to delete. +void FPDFPage_Delete(FPDF_DOCUMENT document, +int page_index, +) { + return _FPDFPage_Delete(document, +page_index, +); +} + +late final _FPDFPage_DeletePtr = _lookup< + ffi.NativeFunction>('FPDFPage_Delete'); +late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); + +/// Experimental API. +/// Move the given pages to a new index position. +/// +/// page_indices - the ordered list of pages to move. No duplicates allowed. +/// page_indices_len - the number of elements in |page_indices| +/// dest_page_index - the new index position to which the pages in +/// |page_indices| are moved. +/// +/// Returns TRUE on success. If it returns FALSE, the document may be left in an +/// indeterminate state. +/// +/// Example: The PDF document starts out with pages [A, B, C, D], with indices +/// [0, 1, 2, 3]. +/// +/// > Move(doc, [3, 2], 2, 1); // returns true +/// > // The document has pages [A, D, C, B]. +/// > +/// > Move(doc, [0, 4, 3], 3, 1); // returns false +/// > // Returned false because index 4 is out of range. +/// > +/// > Move(doc, [0, 3, 1], 3, 2); // returns false +/// > // Returned false because index 2 is out of range for 3 page indices. +/// > +/// > Move(doc, [2, 2], 2, 0); // returns false +/// > // Returned false because [2, 2] contains duplicates. +int FPDF_MovePages(FPDF_DOCUMENT document, +ffi.Pointer page_indices, +int page_indices_len, +int dest_page_index, +) { + return _FPDF_MovePages(document, +page_indices, +page_indices_len, +dest_page_index, +); +} + +late final _FPDF_MovePagesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_MovePages'); +late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction , int , int )>(); + +/// Get the rotation of |page|. +/// +/// page - handle to a page +/// +/// Returns one of the following indicating the page rotation: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +int FPDFPage_GetRotation(FPDF_PAGE page, +) { + return _FPDFPage_GetRotation(page, +); +} + +late final _FPDFPage_GetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetRotation'); +late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); + +/// Set rotation for |page|. +/// +/// page - handle to a page. +/// rotate - the rotation value, one of: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +void FPDFPage_SetRotation(FPDF_PAGE page, +int rotate, +) { + return _FPDFPage_SetRotation(page, +rotate, +); +} + +late final _FPDFPage_SetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetRotation'); +late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); + +/// Insert |page_object| into |page|. +/// +/// page - handle to a page +/// page_object - handle to a page object. The |page_object| will be +/// automatically freed. +void FPDFPage_InsertObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFPage_InsertObject(page, +page_object, +); +} + +late final _FPDFPage_InsertObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObject'); +late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); + +/// Experimental API. +/// Remove |page_object| from |page|. +/// +/// page - handle to a page +/// page_object - handle to a page object to be removed. +/// +/// Returns TRUE on success. +/// +/// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free +/// it. +/// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all +/// FPDF_TEXTPAGE handles for |page| are no longer valid. +int FPDFPage_RemoveObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFPage_RemoveObject(page, +page_object, +); +} + +late final _FPDFPage_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveObject'); +late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction(); + +/// Get number of page objects inside |page|. +/// +/// page - handle to a page. +/// +/// Returns the number of objects in |page|. +int FPDFPage_CountObjects(FPDF_PAGE page, +) { + return _FPDFPage_CountObjects(page, +); +} + +late final _FPDFPage_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CountObjects'); +late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); + +/// Get object in |page| at |index|. +/// +/// page - handle to a page. +/// index - the index of a page object. +/// +/// Returns the handle to the page object, or NULL on failed. +FPDF_PAGEOBJECT FPDFPage_GetObject(FPDF_PAGE page, +int index, +) { + return _FPDFPage_GetObject(page, +index, +); +} + +late final _FPDFPage_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetObject'); +late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction(); + +/// Checks if |page| contains transparency. +/// +/// page - handle to a page. +/// +/// Returns TRUE if |page| contains transparency. +int FPDFPage_HasTransparency(FPDF_PAGE page, +) { + return _FPDFPage_HasTransparency(page, +); +} + +late final _FPDFPage_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasTransparency'); +late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); + +/// Generate the content of |page|. +/// +/// page - handle to a page. +/// +/// Returns TRUE on success. +/// +/// Before you save the page to a file, or reload the page, you must call +/// |FPDFPage_GenerateContent| or any changes to |page| will be lost. +int FPDFPage_GenerateContent(FPDF_PAGE page, +) { + return _FPDFPage_GenerateContent(page, +); +} + +late final _FPDFPage_GenerateContentPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GenerateContent'); +late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); + +/// Destroy |page_object| by releasing its resources. |page_object| must have +/// been created by FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). This function must be called on +/// newly-created objects if they are not added to a page through +/// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). +/// +/// page_object - handle to a page object. +void FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_Destroy(page_object, +); +} + +late final _FPDFPageObj_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Destroy'); +late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); + +/// Checks if |page_object| contains transparency. +/// +/// page_object - handle to a page object. +/// +/// Returns TRUE if |page_object| contains transparency. +int FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_HasTransparency(page_object, +); +} + +late final _FPDFPageObj_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_HasTransparency'); +late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr.asFunction(); + +/// Get type of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on +/// error. +int FPDFPageObj_GetType(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetType(page_object, +); +} + +late final _FPDFPageObj_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetType'); +late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); + +/// Experimental API. +/// Gets active state for |page_object| within page. +/// +/// page_object - handle to a page object. +/// active - pointer to variable that will receive if the page object is +/// active. This is a required parameter. Not filled if FALSE +/// is returned. +/// +/// For page objects where |active| is filled with FALSE, the |page_object| is +/// treated as if it wasn't in the document even though it is still held +/// internally. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_GetIsActive(FPDF_PAGEOBJECT page_object, +ffi.Pointer active, +) { + return _FPDFPageObj_GetIsActive(page_object, +active, +); +} + +late final _FPDFPageObj_GetIsActivePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetIsActive'); +late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction )>(); + +/// Experimental API. +/// Sets if |page_object| is active within page. +/// +/// page_object - handle to a page object. +/// active - a boolean specifying if the object is active. +/// +/// Returns TRUE on success. +/// +/// Page objects all start in the active state by default, and remain in that +/// state unless this function is called. +/// +/// When |active| is false, this makes the |page_object| be treated as if it +/// wasn't in the document even though it is still held internally. +int FPDFPageObj_SetIsActive(FPDF_PAGEOBJECT page_object, +int active, +) { + return _FPDFPageObj_SetIsActive(page_object, +active, +); +} + +late final _FPDFPageObj_SetIsActivePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetIsActive'); +late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction(); + +/// Transform |page_object| by the given matrix. +/// +/// page_object - handle to a page object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page_object|. +void FPDFPageObj_Transform(FPDF_PAGEOBJECT page_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPageObj_Transform(page_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPageObj_TransformPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Transform'); +late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction(); + +/// Experimental API. +/// Transform |page_object| by the given matrix. +/// +/// page_object - handle to a page object. +/// matrix - the transform matrix. +/// +/// Returns TRUE on success. +/// +/// This can be used to scale, rotate, shear and translate the |page_object|. +/// It is an improved version of FPDFPageObj_Transform() that does not do +/// unnecessary double to float conversions, and only uses 1 parameter for the +/// matrix. It also returns whether the operation succeeded or not. +int FPDFPageObj_TransformF(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_TransformF(page_object, +matrix, +); +} + +late final _FPDFPageObj_TransformFPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_TransformF'); +late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction )>(); + +/// Experimental API. +/// Get the transform matrix of a page object. +/// +/// page_object - handle to a page object. +/// matrix - pointer to struct to receive the matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and used to scale, rotate, shear and translate the page object. +/// +/// For page objects outside form objects, the matrix values are relative to the +/// page that contains it. +/// For page objects inside form objects, the matrix values are relative to the +/// form that contains it. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_GetMatrix(page_object, +matrix, +); +} + +late final _FPDFPageObj_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetMatrix'); +late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction )>(); + +/// Experimental API. +/// Set the transform matrix of a page object. +/// +/// page_object - handle to a page object. +/// matrix - pointer to struct with the matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the page object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_SetMatrix(page_object, +matrix, +); +} + +late final _FPDFPageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_SetMatrix'); +late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction )>(); + +/// Transform all annotations in |page|. +/// +/// page - handle to a page. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page| annotations. +void FPDFPage_TransformAnnots(FPDF_PAGE page, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPage_TransformAnnots(page, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPage_TransformAnnotsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_TransformAnnots'); +late final _FPDFPage_TransformAnnots = _FPDFPage_TransformAnnotsPtr.asFunction(); + +/// Create a new image object. +/// +/// document - handle to a document. +/// +/// Returns a handle to a new image object. +FPDF_PAGEOBJECT FPDFPageObj_NewImageObj(FPDF_DOCUMENT document, +) { + return _FPDFPageObj_NewImageObj(document, +); +} + +late final _FPDFPageObj_NewImageObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewImageObj'); +late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction(); + +/// Experimental API. +/// Get the marked content ID for the object. +/// +/// page_object - handle to a page object. +/// +/// Returns the page object's marked content ID, or -1 on error. +int FPDFPageObj_GetMarkedContentID(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetMarkedContentID(page_object, +); +} + +late final _FPDFPageObj_GetMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMarkedContentID'); +late final _FPDFPageObj_GetMarkedContentID = _FPDFPageObj_GetMarkedContentIDPtr.asFunction(); + +/// Experimental API. +/// Get number of content marks in |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the number of content marks in |page_object|, or -1 in case of +/// failure. +int FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_CountMarks(page_object, +); +} + +late final _FPDFPageObj_CountMarksPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CountMarks'); +late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); + +/// Experimental API. +/// Get content mark in |page_object| at |index|. +/// +/// page_object - handle to a page object. +/// index - the index of a page object. +/// +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, +int index, +) { + return _FPDFPageObj_GetMark(page_object, +index, +); +} + +late final _FPDFPageObj_GetMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMark'); +late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction(); + +/// Experimental API. +/// Add a new content mark to a |page_object|. +/// +/// page_object - handle to a page object. +/// name - the name (tag) of the mark. +/// +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING name, +) { + return _FPDFPageObj_AddMark(page_object, +name, +); +} + +late final _FPDFPageObj_AddMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_AddMark'); +late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction(); + +/// Experimental API. +/// Removes a content |mark| from a |page_object|. +/// The mark handle will be invalid after the removal. +/// +/// page_object - handle to a page object. +/// mark - handle to a content mark in that object to remove. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_RemoveMark(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +) { + return _FPDFPageObj_RemoveMark(page_object, +mark, +); +} + +late final _FPDFPageObj_RemoveMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_RemoveMark'); +late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction(); + +/// Experimental API. +/// Get the name of a content mark. +/// +/// mark - handle to a content mark. +/// buffer - buffer for holding the returned name in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the name. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetName(mark, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetName'); +late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the number of key/value pair parameters in |mark|. +/// +/// mark - handle to a content mark. +/// +/// Returns the number of key/value pair parameters |mark|, or -1 in case of +/// failure. +int FPDFPageObjMark_CountParams(FPDF_PAGEOBJECTMARK mark, +) { + return _FPDFPageObjMark_CountParams(mark, +); +} + +late final _FPDFPageObjMark_CountParamsPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_CountParams'); +late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr.asFunction(); + +/// Experimental API. +/// Get the key of a property in a content mark. +/// +/// mark - handle to a content mark. +/// index - index of the property. +/// buffer - buffer for holding the returned key in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the key. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the operation was successful, FALSE otherwise. +int FPDFPageObjMark_GetParamKey(FPDF_PAGEOBJECTMARK mark, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamKey(mark, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamKey'); +late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the type of the value of a property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// +/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. +int FPDFPageObjMark_GetParamValueType(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +) { + return _FPDFPageObjMark_GetParamValueType(mark, +key, +); +} + +late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_GetParamValueType'); +late final _FPDFPageObjMark_GetParamValueType = _FPDFPageObjMark_GetParamValueTypePtr.asFunction(); + +/// Experimental API. +/// Get the value of a number property in a content mark by key as int. +/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER +/// for this property. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// out_value - pointer to variable that will receive the value. Not filled if +/// false is returned. +/// +/// Returns TRUE if the key maps to a number value, FALSE otherwise. +int FPDFPageObjMark_GetParamIntValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer out_value, +) { + return _FPDFPageObjMark_GetParamIntValue(mark, +key, +out_value, +); +} + +late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObjMark_GetParamIntValue'); +late final _FPDFPageObjMark_GetParamIntValue = _FPDFPageObjMark_GetParamIntValuePtr.asFunction )>(); + +/// Experimental API. +/// Get the value of a string property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value in UTF-16LE. This is +/// only modified if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamStringValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamStringValue(mark, +key, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamStringValue'); +late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the value of a blob property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value. This is only modified +/// if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamBlobValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamBlobValue(mark, +key, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamBlobValue'); +late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Set the value of an int property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - int value to set. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetIntParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +int value, +) { + return _FPDFPageObjMark_SetIntParam(document, +page_object, +mark, +key, +value, +); +} + +late final _FPDFPageObjMark_SetIntParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetIntParam'); +late final _FPDFPageObjMark_SetIntParam = _FPDFPageObjMark_SetIntParamPtr.asFunction(); + +/// Experimental API. +/// Set the value of a string property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - string value to set. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetStringParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +FPDF_BYTESTRING value, +) { + return _FPDFPageObjMark_SetStringParam(document, +page_object, +mark, +key, +value, +); +} + +late final _FPDFPageObjMark_SetStringParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetStringParam'); +late final _FPDFPageObjMark_SetStringParam = _FPDFPageObjMark_SetStringParamPtr.asFunction(); + +/// Experimental API. +/// Set the value of a blob property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - pointer to blob value to set. +/// value_len - size in bytes of |value|. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetBlobParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer value, +int value_len, +) { + return _FPDFPageObjMark_SetBlobParam(document, +page_object, +mark, +key, +value, +value_len, +); +} + +late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPageObjMark_SetBlobParam'); +late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction , int )>(); + +/// Experimental API. +/// Removes a property from a content mark by key. +/// +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_RemoveParam(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +) { + return _FPDFPageObjMark_RemoveParam(page_object, +mark, +key, +); +} + +late final _FPDFPageObjMark_RemoveParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_RemoveParam'); +late final _FPDFPageObjMark_RemoveParam = _FPDFPageObjMark_RemoveParamPtr.asFunction(); + +/// Load an image from a JPEG image file and then set it into |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. +/// +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. +int FPDFImageObj_LoadJpegFile(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, +) { + return _FPDFImageObj_LoadJpegFile(pages, +count, +image_object, +file_access, +); +} + +late final _FPDFImageObj_LoadJpegFilePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFile'); +late final _FPDFImageObj_LoadJpegFile = _FPDFImageObj_LoadJpegFilePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); + +/// Load an image from a JPEG image file and then set it into |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. +/// +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. This function loads the JPEG image inline, so the image +/// content is copied to the file. This allows |file_access| and its associated +/// data to be deleted after this function returns. +int FPDFImageObj_LoadJpegFileInline(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, +) { + return _FPDFImageObj_LoadJpegFileInline(pages, +count, +image_object, +file_access, +); +} + +late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFileInline'); +late final _FPDFImageObj_LoadJpegFileInline = _FPDFImageObj_LoadJpegFileInlinePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); + +/// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. +/// +/// Set the transform matrix of |image_object|. +/// +/// image_object - handle to an image object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |image_object|. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFImageObj_SetMatrix(image_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFImageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_SetMatrix'); +late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction(); + +/// Set |bitmap| to |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// bitmap - handle of the bitmap. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetBitmap(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +FPDF_BITMAP bitmap, +) { + return _FPDFImageObj_SetBitmap(pages, +count, +image_object, +bitmap, +); +} + +late final _FPDFImageObj_SetBitmapPtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , FPDF_BITMAP )>>('FPDFImageObj_SetBitmap'); +late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction , int , FPDF_PAGEOBJECT , FPDF_BITMAP )>(); + +/// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only +/// operates on |image_object| and does not take the associated image mask into +/// account. It also ignores the matrix for |image_object|. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// image_object - handle to an image object. +/// +/// Returns the bitmap. +FPDF_BITMAP FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetBitmap(image_object, +); +} + +late final _FPDFImageObj_GetBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetBitmap'); +late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction(); + +/// Experimental API. +/// Get a bitmap rasterization of |image_object| that takes the image mask and +/// image matrix into account. To render correctly, the caller must provide the +/// |document| associated with |image_object|. If there is a |page| associated +/// with |image_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// document - handle to a document associated with |image_object|. +/// page - handle to an optional page associated with |image_object|. +/// image_object - handle to an image object. +/// +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetRenderedBitmap(document, +page, +image_object, +); +} + +late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetRenderedBitmap'); +late final _FPDFImageObj_GetRenderedBitmap = _FPDFImageObj_GetRenderedBitmapPtr.asFunction(); + +/// Get the decoded image data of |image_object|. The decoded data is the +/// uncompressed image data, i.e. the raw image data after having all filters +/// applied. |buffer| is only modified if |buflen| is longer than the length of +/// the decoded image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the decoded image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the decoded image data. +int FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageDataDecoded(image_object, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataDecoded'); +late final _FPDFImageObj_GetImageDataDecoded = _FPDFImageObj_GetImageDataDecodedPtr.asFunction , int )>(); + +/// Get the raw image data of |image_object|. The raw data is the image data as +/// stored in the PDF without applying any filters. |buffer| is only modified if +/// |buflen| is longer than the length of the raw image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the raw image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the raw image data. +int FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageDataRaw(image_object, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageDataRawPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataRaw'); +late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr.asFunction , int )>(); + +/// Get the number of filters (i.e. decoders) of the image in |image_object|. +/// +/// image_object - handle to an image object. +/// +/// Returns the number of |image_object|'s filters. +int FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetImageFilterCount(image_object, +); +} + +late final _FPDFImageObj_GetImageFilterCountPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetImageFilterCount'); +late final _FPDFImageObj_GetImageFilterCount = _FPDFImageObj_GetImageFilterCountPtr.asFunction(); + +/// Get the filter at |index| of |image_object|'s list of filters. Note that the +/// filters need to be applied in order, i.e. the first filter should be applied +/// first, then the second, etc. |buffer| is only modified if |buflen| is longer +/// than the length of the filter string. +/// +/// image_object - handle to an image object. +/// index - the index of the filter requested. +/// buffer - buffer for holding filter string, encoded in UTF-8. +/// buflen - length of the buffer. +/// +/// Returns the length of the filter string. +int FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageFilter(image_object, +index, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageFilterPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageFilter'); +late final _FPDFImageObj_GetImageFilter = _FPDFImageObj_GetImageFilterPtr.asFunction , int )>(); + +/// Get the image metadata of |image_object|, including dimension, DPI, bits per +/// pixel, and colorspace. If the |image_object| is not an image object or if it +/// does not have an image, then the return value will be false. Otherwise, +/// failure to retrieve any specific parameter would result in its value being 0. +/// +/// image_object - handle to an image object. +/// page - handle to the page that |image_object| is on. Required for +/// retrieving the image's bits per pixel and colorspace. +/// metadata - receives the image metadata; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer metadata, +) { + return _FPDFImageObj_GetImageMetadata(image_object, +page, +metadata, +); +} + +late final _FPDFImageObj_GetImageMetadataPtr = _lookup< + ffi.NativeFunction )>>('FPDFImageObj_GetImageMetadata'); +late final _FPDFImageObj_GetImageMetadata = _FPDFImageObj_GetImageMetadataPtr.asFunction )>(); + +/// Experimental API. +/// Get the image size in pixels. Faster method to get only image size. +/// +/// image_object - handle to an image object. +/// width - receives the image width in pixels; must not be NULL. +/// height - receives the image height in pixels; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, +ffi.Pointer width, +ffi.Pointer height, +) { + return _FPDFImageObj_GetImagePixelSize(image_object, +width, +height, +); +} + +late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFImageObj_GetImagePixelSize'); +late final _FPDFImageObj_GetImagePixelSize = _FPDFImageObj_GetImagePixelSizePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Get ICC profile decoded data of |image_object|. If the |image_object| is not +/// an image object or if it does not have an image, then the return value will +/// be false. It also returns false if the |image_object| has no ICC profile. +/// |buffer| is only modified if ICC profile exists and |buflen| is longer than +/// the length of the ICC profile decoded data. +/// +/// image_object - handle to an image object; must not be NULL. +/// page - handle to the page containing |image_object|; must not be +/// NULL. Required for retrieving the image's colorspace. +/// buffer - Buffer to receive ICC profile data; may be NULL if querying +/// required size via |out_buflen|. +/// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. +/// out_buflen - Pointer to receive the ICC profile data size in bytes; must +/// not be NULL. Will be set if this API returns true. +/// +/// Returns true if |out_buflen| is not null and an ICC profile exists for the +/// given |image_object|. +int FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFImageObj_GetIccProfileDataDecoded(image_object, +page, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFImageObj_GetIccProfileDataDecoded'); +late final _FPDFImageObj_GetIccProfileDataDecoded = _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction , int , ffi.Pointer )>(); + +/// Create a new path object at an initial position. +/// +/// x - initial horizontal position. +/// y - initial vertical position. +/// +/// Returns a handle to a new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath(double x, +double y, +) { + return _FPDFPageObj_CreateNewPath(x, +y, +); +} + +late final _FPDFPageObj_CreateNewPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewPath'); +late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr.asFunction(); + +/// Create a closed path consisting of a rectangle. +/// +/// x - horizontal position for the left boundary of the rectangle. +/// y - vertical position for the bottom boundary of the rectangle. +/// w - width of the rectangle. +/// h - height of the rectangle. +/// +/// Returns a handle to the new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect(double x, +double y, +double w, +double h, +) { + return _FPDFPageObj_CreateNewRect(x, +y, +w, +h, +); +} + +late final _FPDFPageObj_CreateNewRectPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewRect'); +late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr.asFunction(); + +/// Get the bounding box of |page_object|. +/// +/// page_object - handle to a page object. +/// left - pointer where the left coordinate will be stored +/// bottom - pointer where the bottom coordinate will be stored +/// right - pointer where the right coordinate will be stored +/// top - pointer where the top coordinate will be stored +/// +/// On success, returns TRUE and fills in the 4 coordinates. +int FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPageObj_GetBounds(page_object, +left, +bottom, +right, +top, +); +} + +late final _FPDFPageObj_GetBoundsPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetBounds'); +late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the quad points that bounds |page_object|. +/// +/// page_object - handle to a page object. +/// quad_points - pointer where the quadrilateral points will be stored. +/// +/// On success, returns TRUE and fills in |quad_points|. +/// +/// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page +/// object. When the object is rotated by a non-multiple of 90 degrees, this API +/// returns a tighter bound that cannot be represented with just the 4 sides of +/// a rectangle. +/// +/// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and +/// FPDF_PAGEOBJ_IMAGE. +int FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer quad_points, +) { + return _FPDFPageObj_GetRotatedBounds(page_object, +quad_points, +); +} + +late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetRotatedBounds'); +late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr.asFunction )>(); + +/// Set the blend mode of |page_object|. +/// +/// page_object - handle to a page object. +/// blend_mode - string containing the blend mode. +/// +/// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, +/// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, +/// Overlay, Saturation, Screen, SoftLight +void FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING blend_mode, +) { + return _FPDFPageObj_SetBlendMode(page_object, +blend_mode, +); +} + +late final _FPDFPageObj_SetBlendModePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetBlendMode'); +late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr.asFunction(); + +/// Set the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's stroke color. +/// G - the green component for the object's stroke color. +/// B - the blue component for the object's stroke color. +/// A - the stroke alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, +) { + return _FPDFPageObj_SetStrokeColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_SetStrokeColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeColor'); +late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr.asFunction(); + +/// Get the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the path stroke color. +/// G - the green component of the object's stroke color. +/// B - the blue component of the object's stroke color. +/// A - the stroke alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetStrokeColor(FPDF_PAGEOBJECT page_object, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFPageObj_GetStrokeColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetStrokeColor'); +late final _FPDFPageObj_GetStrokeColor = _FPDFPageObj_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Set the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_SetStrokeWidth(FPDF_PAGEOBJECT page_object, +double width, +) { + return _FPDFPageObj_SetStrokeWidth(page_object, +width, +); +} + +late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeWidth'); +late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr.asFunction(); + +/// Get the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_GetStrokeWidth(FPDF_PAGEOBJECT page_object, +ffi.Pointer width, +) { + return _FPDFPageObj_GetStrokeWidth(page_object, +width, +); +} + +late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetStrokeWidth'); +late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr.asFunction )>(); + +/// Get the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line join, or -1 on failure. +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_GetLineJoin(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetLineJoin(page_object, +); +} + +late final _FPDFPageObj_GetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineJoin'); +late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); + +/// Set the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// line_join - line join +/// +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_SetLineJoin(FPDF_PAGEOBJECT page_object, +int line_join, +) { + return _FPDFPageObj_SetLineJoin(page_object, +line_join, +); +} + +late final _FPDFPageObj_SetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineJoin'); +late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction(); + +/// Get the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line cap, or -1 on failure. +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_GetLineCap(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetLineCap(page_object, +); +} + +late final _FPDFPageObj_GetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineCap'); +late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); + +/// Set the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// line_cap - line cap +/// +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_SetLineCap(FPDF_PAGEOBJECT page_object, +int line_cap, +) { + return _FPDFPageObj_SetLineCap(page_object, +line_cap, +); +} + +late final _FPDFPageObj_SetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineCap'); +late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction(); + +/// Set the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's fill color. +/// G - the green component for the object's fill color. +/// B - the blue component for the object's fill color. +/// A - the fill alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, +) { + return _FPDFPageObj_SetFillColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_SetFillColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetFillColor'); +late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr.asFunction(); + +/// Get the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the object's fill color. +/// G - the green component of the object's fill color. +/// B - the blue component of the object's fill color. +/// A - the fill alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetFillColor(FPDF_PAGEOBJECT page_object, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFPageObj_GetFillColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetFillColor'); +late final _FPDFPageObj_GetFillColor = _FPDFPageObj_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the line dash |phase| of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - pointer where the dashing phase will be stored. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, +ffi.Pointer phase, +) { + return _FPDFPageObj_GetDashPhase(page_object, +phase, +); +} + +late final _FPDFPageObj_GetDashPhasePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetDashPhase'); +late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr.asFunction )>(); + +/// Experimental API. +/// Set the line dash phase of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, +double phase, +) { + return _FPDFPageObj_SetDashPhase(page_object, +phase, +); +} + +late final _FPDFPageObj_SetDashPhasePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetDashPhase'); +late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr.asFunction(); + +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line dash array size or -1 on failure. +int FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetDashCount(page_object, +); +} + +late final _FPDFPageObj_GetDashCountPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetDashCount'); +late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); + +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - pointer where the dashing array will be stored. +/// dash_count - number of elements in |dash_array|. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, +) { + return _FPDFPageObj_GetDashArray(page_object, +dash_array, +dash_count, +); +} + +late final _FPDFPageObj_GetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFPageObj_GetDashArray'); +late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr.asFunction , int )>(); + +/// Experimental API. +/// Set the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - the dash array. +/// dash_count - number of elements in |dash_array|. +/// phase - the line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, +double phase, +) { + return _FPDFPageObj_SetDashArray(page_object, +dash_array, +dash_count, +phase, +); +} + +late final _FPDFPageObj_SetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Float )>>('FPDFPageObj_SetDashArray'); +late final _FPDFPageObj_SetDashArray = _FPDFPageObj_SetDashArrayPtr.asFunction , int , double )>(); + +/// Get number of segments inside |path|. +/// +/// path - handle to a path. +/// +/// A segment is a command, created by e.g. FPDFPath_MoveTo(), +/// FPDFPath_LineTo() or FPDFPath_BezierTo(). +/// +/// Returns the number of objects in |path| or -1 on failure. +int FPDFPath_CountSegments(FPDF_PAGEOBJECT path, +) { + return _FPDFPath_CountSegments(path, +); +} + +late final _FPDFPath_CountSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFPath_CountSegments'); +late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); + +/// Get segment in |path| at |index|. +/// +/// path - handle to a path. +/// index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, +int index, +) { + return _FPDFPath_GetPathSegment(path, +index, +); +} + +late final _FPDFPath_GetPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFPath_GetPathSegment'); +late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction(); + +/// Get coordinates of |segment|. +/// +/// segment - handle to a segment. +/// x - the horizontal position of the segment. +/// y - the vertical position of the segment. +/// +/// Returns TRUE on success, otherwise |x| and |y| is not set. +int FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, +ffi.Pointer x, +ffi.Pointer y, +) { + return _FPDFPathSegment_GetPoint(segment, +x, +y, +); +} + +late final _FPDFPathSegment_GetPointPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPathSegment_GetPoint'); +late final _FPDFPathSegment_GetPoint = _FPDFPathSegment_GetPointPtr.asFunction , ffi.Pointer )>(); + +/// Get type of |segment|. +/// +/// segment - handle to a segment. +/// +/// Returns one of the FPDF_SEGMENT_* values on success, +/// FPDF_SEGMENT_UNKNOWN on error. +int FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment, +) { + return _FPDFPathSegment_GetType(segment, +); +} + +late final _FPDFPathSegment_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetType'); +late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); + +/// Gets if the |segment| closes the current subpath of a given path. +/// +/// segment - handle to a segment. +/// +/// Returns close flag for non-NULL segment, FALSE otherwise. +int FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment, +) { + return _FPDFPathSegment_GetClose(segment, +); +} + +late final _FPDFPathSegment_GetClosePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetClose'); +late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); + +/// Move a path's current point. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new current point. +/// y - the vertical position of the new current point. +/// +/// Note that no line will be created between the previous current point and the +/// new one. +/// +/// Returns TRUE on success +int FPDFPath_MoveTo(FPDF_PAGEOBJECT path, +double x, +double y, +) { + return _FPDFPath_MoveTo(path, +x, +y, +); +} + +late final _FPDFPath_MoveToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_MoveTo'); +late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction(); + +/// Add a line between the current point and a new point in the path. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new point. +/// y - the vertical position of the new point. +/// +/// The path's current point is changed to (x, y). +/// +/// Returns TRUE on success +int FPDFPath_LineTo(FPDF_PAGEOBJECT path, +double x, +double y, +) { + return _FPDFPath_LineTo(path, +x, +y, +); +} + +late final _FPDFPath_LineToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_LineTo'); +late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction(); + +/// Add a cubic Bezier curve to the given path, starting at the current point. +/// +/// path - the handle to the path object. +/// x1 - the horizontal position of the first Bezier control point. +/// y1 - the vertical position of the first Bezier control point. +/// x2 - the horizontal position of the second Bezier control point. +/// y2 - the vertical position of the second Bezier control point. +/// x3 - the horizontal position of the ending point of the Bezier curve. +/// y3 - the vertical position of the ending point of the Bezier curve. +/// +/// Returns TRUE on success +int FPDFPath_BezierTo(FPDF_PAGEOBJECT path, +double x1, +double y1, +double x2, +double y2, +double x3, +double y3, +) { + return _FPDFPath_BezierTo(path, +x1, +y1, +x2, +y2, +x3, +y3, +); +} + +late final _FPDFPath_BezierToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_BezierTo'); +late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction(); + +/// Close the current subpath of a given path. +/// +/// path - the handle to the path object. +/// +/// This will add a line between the current point and the initial point of the +/// subpath, thus terminating the current subpath. +/// +/// Returns TRUE on success +int FPDFPath_Close(FPDF_PAGEOBJECT path, +) { + return _FPDFPath_Close(path, +); +} + +late final _FPDFPath_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFPath_Close'); +late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); + +/// Set the drawing mode of a path. +/// +/// path - the handle to the path object. +/// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path should be stroked or not. +/// +/// Returns TRUE on success +int FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, +int fillmode, +int stroke, +) { + return _FPDFPath_SetDrawMode(path, +fillmode, +stroke, +); +} + +late final _FPDFPath_SetDrawModePtr = _lookup< + ffi.NativeFunction>('FPDFPath_SetDrawMode'); +late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction(); + +/// Get the drawing mode of a path. +/// +/// path - the handle to the path object. +/// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path is stroked or not. +/// +/// Returns TRUE on success +int FPDFPath_GetDrawMode(FPDF_PAGEOBJECT path, +ffi.Pointer fillmode, +ffi.Pointer stroke, +) { + return _FPDFPath_GetDrawMode(path, +fillmode, +stroke, +); +} + +late final _FPDFPath_GetDrawModePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPath_GetDrawMode'); +late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction , ffi.Pointer )>(); + +/// Create a new text object using one of the standard PDF fonts. +/// +/// document - handle to the document. +/// font - string containing the font name, without spaces. +/// font_size - the font size for the new text object. +/// +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, +double font_size, +) { + return _FPDFPageObj_NewTextObj(document, +font, +font_size, +); +} + +late final _FPDFPageObj_NewTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewTextObj'); +late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction(); + +/// Set the text for a text object. If it had text, it will be replaced. +/// +/// text_object - handle to the text object. +/// text - the UTF-16LE encoded string containing the text to be added. +/// +/// Returns TRUE on success +int FPDFText_SetText(FPDF_PAGEOBJECT text_object, +FPDF_WIDESTRING text, +) { + return _FPDFText_SetText(text_object, +text, +); +} + +late final _FPDFText_SetTextPtr = _lookup< + ffi.NativeFunction>('FPDFText_SetText'); +late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction(); + +/// Experimental API. +/// Set the text using charcodes for a text object. If it had text, it will be +/// replaced. +/// +/// text_object - handle to the text object. +/// charcodes - pointer to an array of charcodes to be added. +/// count - number of elements in |charcodes|. +/// +/// Returns TRUE on success +int FPDFText_SetCharcodes(FPDF_PAGEOBJECT text_object, +ffi.Pointer charcodes, +int count, +) { + return _FPDFText_SetCharcodes(text_object, +charcodes, +count, +); +} + +late final _FPDFText_SetCharcodesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFText_SetCharcodes'); +late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction , int )>(); + +/// Returns a font object loaded from a stream of data. The font is loaded +/// into the document. Various font data structures, such as the ToUnicode data, +/// are auto-generated based on the inputs. +/// +/// document - handle to the document. +/// data - the stream of font data, which will be copied by the font object. +/// size - the size of the font data, in bytes. +/// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. +/// cid - a boolean specifying if the font is a CID font or not. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure +FPDF_FONT FPDFText_LoadFont(FPDF_DOCUMENT document, +ffi.Pointer data, +int size, +int font_type, +int cid, +) { + return _FPDFText_LoadFont(document, +data, +size, +font_type, +cid, +); +} + +late final _FPDFText_LoadFontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , ffi.Int , FPDF_BOOL )>>('FPDFText_LoadFont'); +late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction , int , int , int )>(); + +/// Experimental API. +/// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred +/// way of using font style is using a dash to separate the name from the style, +/// for example 'Helvetica-BoldItalic'. +/// +/// document - handle to the document. +/// font - string containing the font name, without spaces. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadStandardFont(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, +) { + return _FPDFText_LoadStandardFont(document, +font, +); +} + +late final _FPDFText_LoadStandardFontPtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadStandardFont'); +late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr.asFunction(); + +/// Experimental API. +/// Returns a font object loaded from a stream of data for a type 2 CID font. The +/// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode +/// data and the CIDToGIDMap data are caller provided, instead of auto-generated. +/// +/// document - handle to the document. +/// font_data - the stream of font data, which will be copied by +/// the font object. +/// font_data_size - the size of the font data, in bytes. +/// to_unicode_cmap - the ToUnicode data. +/// cid_to_gid_map_data - the stream of CIDToGIDMap data. +/// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadCidType2Font(FPDF_DOCUMENT document, +ffi.Pointer font_data, +int font_data_size, +FPDF_BYTESTRING to_unicode_cmap, +ffi.Pointer cid_to_gid_map_data, +int cid_to_gid_map_data_size, +) { + return _FPDFText_LoadCidType2Font(document, +font_data, +font_data_size, +to_unicode_cmap, +cid_to_gid_map_data, +cid_to_gid_map_data_size, +); +} + +late final _FPDFText_LoadCidType2FontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , FPDF_BYTESTRING , ffi.Pointer , ffi.Uint32 )>>('FPDFText_LoadCidType2Font'); +late final _FPDFText_LoadCidType2Font = _FPDFText_LoadCidType2FontPtr.asFunction , int , FPDF_BYTESTRING , ffi.Pointer , int )>(); + +/// Get the font size of a text object. +/// +/// text - handle to a text. +/// size - pointer to the font size of the text object, measured in points +/// (about 1/72 inch) +/// +/// Returns TRUE on success. +int FPDFTextObj_GetFontSize(FPDF_PAGEOBJECT text, +ffi.Pointer size, +) { + return _FPDFTextObj_GetFontSize(text, +size, +); +} + +late final _FPDFTextObj_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFTextObj_GetFontSize'); +late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction )>(); + +/// Close a loaded PDF font. +/// +/// font - Handle to the loaded font. +void FPDFFont_Close(FPDF_FONT font, +) { + return _FPDFFont_Close(font, +); +} + +late final _FPDFFont_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFFont_Close'); +late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); + +/// Create a new text object using a loaded font. +/// +/// document - handle to the document. +/// font - handle to the font object. +/// font_size - the font size for the new text object. +/// +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, +FPDF_FONT font, +double font_size, +) { + return _FPDFPageObj_CreateTextObj(document, +font, +font_size, +); +} + +late final _FPDFPageObj_CreateTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateTextObj'); +late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr.asFunction(); + +/// Get the text rendering mode of a text object. +/// +/// text - the handle to the text object. +/// +/// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, +/// FPDF_TEXTRENDERMODE_UNKNOWN on error. +FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode(FPDF_PAGEOBJECT text, +) { + return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode(text, +)); +} + +late final _FPDFTextObj_GetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetTextRenderMode'); +late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr.asFunction(); + +/// Experimental API. +/// Set the text rendering mode of a text object. +/// +/// text - the handle to the text object. +/// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to +/// FPDF_TEXTRENDERMODE_UNKNOWN). +/// +/// Returns TRUE on success. +DartFPDF_BOOL FPDFTextObj_SetTextRenderMode(FPDF_PAGEOBJECT text, +FPDF_TEXT_RENDERMODE render_mode, +) { + return _FPDFTextObj_SetTextRenderMode(text, +render_mode.value, +); +} + +late final _FPDFTextObj_SetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_SetTextRenderMode'); +late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr.asFunction(); + +/// Get the text of a text object. +/// +/// text_object - the handle to the text object. +/// text_page - the handle to the text page. +/// buffer - the address of a buffer that receives the text. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the text (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFTextObj_GetText(FPDF_PAGEOBJECT text_object, +FPDF_TEXTPAGE text_page, +ffi.Pointer buffer, +int length, +) { + return _FPDFTextObj_GetText(text_object, +text_page, +buffer, +length, +); +} + +late final _FPDFTextObj_GetTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFTextObj_GetText'); +late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction , int )>(); + +/// Experimental API. +/// Get a bitmap rasterization of |text_object|. To render correctly, the caller +/// must provide the |document| associated with |text_object|. If there is a +/// |page| associated with |text_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// document - handle to a document associated with |text_object|. +/// page - handle to an optional page associated with |text_object|. +/// text_object - handle to a text object. +/// scale - the scaling factor, which must be greater than 0. +/// +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT text_object, +double scale, +) { + return _FPDFTextObj_GetRenderedBitmap(document, +page, +text_object, +scale, +); +} + +late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetRenderedBitmap'); +late final _FPDFTextObj_GetRenderedBitmap = _FPDFTextObj_GetRenderedBitmapPtr.asFunction(); + +/// Experimental API. +/// Get the font of a text object. +/// +/// text - the handle to the text object. +/// +/// Returns a handle to the font object held by |text| which retains ownership. +FPDF_FONT FPDFTextObj_GetFont(FPDF_PAGEOBJECT text, +) { + return _FPDFTextObj_GetFont(text, +); +} + +late final _FPDFTextObj_GetFontPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetFont'); +late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); + +/// Experimental API. +/// Get the base name of a font. +/// +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the base font name. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the base name (including the trailing NUL +/// character) on success, 0 on error. The base name is typically the font's +/// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetBaseFontName(FPDF_FONT font, +ffi.Pointer buffer, +int length, +) { + return _FPDFFont_GetBaseFontName(font, +buffer, +length, +); +} + +late final _FPDFFont_GetBaseFontNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetBaseFontName'); +late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the family name of a font. +/// +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the font name. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the family name (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetFamilyName(FPDF_FONT font, +ffi.Pointer buffer, +int length, +) { + return _FPDFFont_GetFamilyName(font, +buffer, +length, +); +} + +late final _FPDFFont_GetFamilyNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetFamilyName'); +late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the decoded data from the |font| object. +/// +/// font - The handle to the font object. (Required) +/// buffer - The address of a buffer that receives the font data. +/// buflen - Length of the buffer. +/// out_buflen - Pointer to variable that will receive the minimum buffer size +/// to contain the font data. Not filled if the return value is +/// FALSE. (Required) +/// +/// Returns TRUE on success. In which case, |out_buflen| will be filled, and +/// |buffer| will be filled if it is large enough. Returns FALSE if any of the +/// required parameters are null. +/// +/// The decoded data is the uncompressed font data. i.e. the raw font data after +/// having all stream filters applied, when the data is embedded. +/// +/// If the font is not embedded, then this API will instead return the data for +/// the substitution font it is using. +int FPDFFont_GetFontData(FPDF_FONT font, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFFont_GetFontData(font, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFFont_GetFontDataPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFFont_GetFontData'); +late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get whether |font| is embedded or not. +/// +/// font - the handle to the font object. +/// +/// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. +int FPDFFont_GetIsEmbedded(FPDF_FONT font, +) { + return _FPDFFont_GetIsEmbedded(font, +); +} + +late final _FPDFFont_GetIsEmbeddedPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetIsEmbedded'); +late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); + +/// Experimental API. +/// Get the descriptor flags of a font. +/// +/// font - the handle to the font object. +/// +/// Returns the bit flags specifying various characteristics of the font as +/// defined in ISO 32000-1:2008, table 123, -1 on failure. +int FPDFFont_GetFlags(FPDF_FONT font, +) { + return _FPDFFont_GetFlags(font, +); +} + +late final _FPDFFont_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetFlags'); +late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); + +/// Experimental API. +/// Get the font weight of a font. +/// +/// font - the handle to the font object. +/// +/// Returns the font weight, -1 on failure. +/// Typical values are 400 (normal) and 700 (bold). +int FPDFFont_GetWeight(FPDF_FONT font, +) { + return _FPDFFont_GetWeight(font, +); +} + +late final _FPDFFont_GetWeightPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetWeight'); +late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); + +/// Experimental API. +/// Get the italic angle of a font. +/// +/// font - the handle to the font object. +/// angle - pointer where the italic angle will be stored +/// +/// The italic angle of a |font| is defined as degrees counterclockwise +/// from vertical. For a font that slopes to the right, this will be negative. +/// +/// Returns TRUE on success; |angle| unmodified on failure. +int FPDFFont_GetItalicAngle(FPDF_FONT font, +ffi.Pointer angle, +) { + return _FPDFFont_GetItalicAngle(font, +angle, +); +} + +late final _FPDFFont_GetItalicAnglePtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetItalicAngle'); +late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction )>(); + +/// Experimental API. +/// Get ascent distance of a font. +/// +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// ascent - pointer where the font ascent will be stored +/// +/// Ascent is the maximum distance in points above the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |ascent| unmodified on failure. +int FPDFFont_GetAscent(FPDF_FONT font, +double font_size, +ffi.Pointer ascent, +) { + return _FPDFFont_GetAscent(font, +font_size, +ascent, +); +} + +late final _FPDFFont_GetAscentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetAscent'); +late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction )>(); + +/// Experimental API. +/// Get descent distance of a font. +/// +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// descent - pointer where the font descent will be stored +/// +/// Descent is the maximum distance in points below the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |descent| unmodified on failure. +int FPDFFont_GetDescent(FPDF_FONT font, +double font_size, +ffi.Pointer descent, +) { + return _FPDFFont_GetDescent(font, +font_size, +descent, +); +} + +late final _FPDFFont_GetDescentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetDescent'); +late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction )>(); + +/// Experimental API. +/// Get the width of a glyph in a font. +/// +/// font - the handle to the font object. +/// glyph - the glyph. +/// font_size - the size of the font. +/// width - pointer where the glyph width will be stored +/// +/// Glyph width is the distance from the end of the prior glyph to the next +/// glyph. This will be the vertical distance for vertical writing. +/// +/// Returns TRUE on success; |width| unmodified on failure. +int FPDFFont_GetGlyphWidth(FPDF_FONT font, +int glyph, +double font_size, +ffi.Pointer width, +) { + return _FPDFFont_GetGlyphWidth(font, +glyph, +font_size, +width, +); +} + +late final _FPDFFont_GetGlyphWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetGlyphWidth'); +late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction )>(); + +/// Experimental API. +/// Get the glyphpath describing how to draw a font glyph. +/// +/// font - the handle to the font object. +/// glyph - the glyph being drawn. +/// font_size - the size of the font. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_GLYPHPATH FPDFFont_GetGlyphPath(FPDF_FONT font, +int glyph, +double font_size, +) { + return _FPDFFont_GetGlyphPath(font, +glyph, +font_size, +); +} + +late final _FPDFFont_GetGlyphPathPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetGlyphPath'); +late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction(); + +/// Experimental API. +/// Get number of segments inside glyphpath. +/// +/// glyphpath - handle to a glyph path. +/// +/// Returns the number of objects in |glyphpath| or -1 on failure. +int FPDFGlyphPath_CountGlyphSegments(FPDF_GLYPHPATH glyphpath, +) { + return _FPDFGlyphPath_CountGlyphSegments(glyphpath, +); +} + +late final _FPDFGlyphPath_CountGlyphSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_CountGlyphSegments'); +late final _FPDFGlyphPath_CountGlyphSegments = _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction(); + +/// Experimental API. +/// Get segment in glyphpath at index. +/// +/// glyphpath - handle to a glyph path. +/// index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment(FPDF_GLYPHPATH glyphpath, +int index, +) { + return _FPDFGlyphPath_GetGlyphPathSegment(glyphpath, +index, +); +} + +late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_GetGlyphPathSegment'); +late final _FPDFGlyphPath_GetGlyphPathSegment = _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction(); + +/// Get number of page objects inside |form_object|. +/// +/// form_object - handle to a form object. +/// +/// Returns the number of objects in |form_object| on success, -1 on error. +int FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object, +) { + return _FPDFFormObj_CountObjects(form_object, +); +} + +late final _FPDFFormObj_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_CountObjects'); +late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); + +/// Get page object in |form_object| at |index|. +/// +/// form_object - handle to a form object. +/// index - the 0-based index of a page object. +/// +/// Returns the handle to the page object, or NULL on error. +FPDF_PAGEOBJECT FPDFFormObj_GetObject(FPDF_PAGEOBJECT form_object, +int index, +) { + return _FPDFFormObj_GetObject(form_object, +index, +); +} + +late final _FPDFFormObj_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_GetObject'); +late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction(); + +/// Experimental API. +/// +/// Remove |page_object| from |form_object|. +/// +/// form_object - handle to a form object. +/// page_object - handle to a page object to be removed from the form. +/// +/// Returns TRUE on success. +/// +/// Ownership of the removed |page_object| is transferred to the caller. +/// Call FPDFPageObj_Destroy() on the removed page_object to free it. +int FPDFFormObj_RemoveObject(FPDF_PAGEOBJECT form_object, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFFormObj_RemoveObject(form_object, +page_object, +); +} + +late final _FPDFFormObj_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_RemoveObject'); +late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); + +/// Function: FPDF_GetDefaultTTFMap +/// Returns a pointer to the default character set to TT Font name map. The +/// map is an array of FPDF_CharsetFontMap structs, with its end indicated +/// by a { -1, NULL } entry. +/// Parameters: +/// None. +/// Return Value: +/// Pointer to the Charset Font Map. +/// Note: +/// Once FPDF_GetDefaultTTFMapCount() and FPDF_GetDefaultTTFMapEntry() are no +/// longer experimental, this API will be marked as deprecated. +/// See https://crbug.com/348468114 +ffi.Pointer FPDF_GetDefaultTTFMap() { + return _FPDF_GetDefaultTTFMap(); +} + +late final _FPDF_GetDefaultTTFMapPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultTTFMap'); +late final _FPDF_GetDefaultTTFMap = _FPDF_GetDefaultTTFMapPtr.asFunction Function()>(); + +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapCount +/// Returns the number of entries in the default character set to TT Font name +/// map. +/// Parameters: +/// None. +/// Return Value: +/// The number of entries in the map. +int FPDF_GetDefaultTTFMapCount() { + return _FPDF_GetDefaultTTFMapCount(); +} + +late final _FPDF_GetDefaultTTFMapCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDefaultTTFMapCount'); +late final _FPDF_GetDefaultTTFMapCount = _FPDF_GetDefaultTTFMapCountPtr.asFunction(); + +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapEntry +/// Returns an entry in the default character set to TT Font name map. +/// Parameters: +/// index - The index to the entry in the map to retrieve. +/// Return Value: +/// A pointer to the entry, if it is in the map, or NULL if the index is out +/// of bounds. +ffi.Pointer FPDF_GetDefaultTTFMapEntry(int index, +) { + return _FPDF_GetDefaultTTFMapEntry(index, +); +} + +late final _FPDF_GetDefaultTTFMapEntryPtr = _lookup< + ffi.NativeFunction Function(ffi.Size )>>('FPDF_GetDefaultTTFMapEntry'); +late final _FPDF_GetDefaultTTFMapEntry = _FPDF_GetDefaultTTFMapEntryPtr.asFunction Function(int )>(); + +/// Function: FPDF_AddInstalledFont +/// Add a system font to the list in PDFium. +/// Comments: +/// This function is only called during the system font list building +/// process. +/// Parameters: +/// mapper - Opaque pointer to Foxit font mapper +/// face - The font face name +/// charset - Font character set. See above defined constants. +/// Return Value: +/// None. +void FPDF_AddInstalledFont(ffi.Pointer mapper, +ffi.Pointer face, +int charset, +) { + return _FPDF_AddInstalledFont(mapper, +face, +charset, +); +} + +late final _FPDF_AddInstalledFontPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_AddInstalledFont'); +late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction , ffi.Pointer , int )>(); + +/// Function: FPDF_SetSystemFontInfo +/// Set the system font info interface into PDFium +/// Parameters: +/// pFontInfo - Pointer to a FPDF_SYSFONTINFO structure +/// Return Value: +/// None +/// Comments: +/// Platform support implementation should implement required methods of +/// FFDF_SYSFONTINFO interface, then call this function during PDFium +/// initialization process. +/// +/// Call this with NULL to tell PDFium to stop using a previously set +/// |FPDF_SYSFONTINFO|. +void FPDF_SetSystemFontInfo(ffi.Pointer pFontInfo, +) { + return _FPDF_SetSystemFontInfo(pFontInfo, +); +} + +late final _FPDF_SetSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_SetSystemFontInfo'); +late final _FPDF_SetSystemFontInfo = _FPDF_SetSystemFontInfoPtr.asFunction )>(); + +/// Function: FPDF_GetDefaultSystemFontInfo +/// Get default system font info interface for current platform +/// Parameters: +/// None +/// Return Value: +/// Pointer to a FPDF_SYSFONTINFO structure describing the default +/// interface, or NULL if the platform doesn't have a default interface. +/// Application should call FPDF_FreeDefaultSystemFontInfo to free the +/// returned pointer. +/// Comments: +/// For some platforms, PDFium implements a default version of system +/// font info interface. The default implementation can be passed to +/// FPDF_SetSystemFontInfo(). +ffi.Pointer FPDF_GetDefaultSystemFontInfo() { + return _FPDF_GetDefaultSystemFontInfo(); +} + +late final _FPDF_GetDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultSystemFontInfo'); +late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.asFunction Function()>(); + +/// Function: FPDF_FreeDefaultSystemFontInfo +/// Free a default system font info interface +/// Parameters: +/// pFontInfo - Pointer to a FPDF_SYSFONTINFO structure +/// Return Value: +/// None +/// Comments: +/// This function should be called on the output from +/// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. +void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer pFontInfo, +) { + return _FPDF_FreeDefaultSystemFontInfo(pFontInfo, +); +} + +late final _FPDF_FreeDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_FreeDefaultSystemFontInfo'); +late final _FPDF_FreeDefaultSystemFontInfo = _FPDF_FreeDefaultSystemFontInfoPtr.asFunction )>(); - /// Function: FPDF_InitLibraryWithConfig - /// Initialize the PDFium library and allocate global resources for it. - /// Parameters: - /// config - configuration information as above. - /// Return value: - /// None. - /// Comments: - /// You have to call this function before you can call any PDF - /// processing functions. - void FPDF_InitLibraryWithConfig( - ffi.Pointer config, - ) { - return _FPDF_InitLibraryWithConfig( - config, - ); - } - - late final _FPDF_InitLibraryWithConfigPtr = _lookup< - ffi - .NativeFunction)>>( - 'FPDF_InitLibraryWithConfig'); - late final _FPDF_InitLibraryWithConfig = _FPDF_InitLibraryWithConfigPtr - .asFunction)>(); - - /// Function: FPDF_InitLibrary - /// Initialize the PDFium library (alternative form). - /// Parameters: - /// None - /// Return value: - /// None. - /// Comments: - /// Convenience function to call FPDF_InitLibraryWithConfig() with a - /// default configuration for backwards compatibility purposes. New - /// code should call FPDF_InitLibraryWithConfig() instead. This will - /// be deprecated in the future. - void FPDF_InitLibrary() { - return _FPDF_InitLibrary(); - } - - late final _FPDF_InitLibraryPtr = - _lookup>('FPDF_InitLibrary'); - late final _FPDF_InitLibrary = - _FPDF_InitLibraryPtr.asFunction(); - - /// Function: FPDF_DestroyLibrary - /// Release global resources allocated to the PDFium library by - /// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). - /// Parameters: - /// None. - /// Return value: - /// None. - /// Comments: - /// After this function is called, you must not call any PDF - /// processing functions. - /// - /// Calling this function does not automatically close other - /// objects. It is recommended to close other objects before - /// closing the library with this function. - void FPDF_DestroyLibrary() { - return _FPDF_DestroyLibrary(); - } - - late final _FPDF_DestroyLibraryPtr = - _lookup>('FPDF_DestroyLibrary'); - late final _FPDF_DestroyLibrary = - _FPDF_DestroyLibraryPtr.asFunction(); - - /// Function: FPDF_SetSandBoxPolicy - /// Set the policy for the sandbox environment. - /// Parameters: - /// policy - The specified policy for setting, for example: - /// FPDF_POLICY_MACHINETIME_ACCESS. - /// enable - True to enable, false to disable the policy. - /// Return value: - /// None. - void FPDF_SetSandBoxPolicy( - int policy, - int enable, - ) { - return _FPDF_SetSandBoxPolicy( - policy, - enable, - ); - } - - late final _FPDF_SetSandBoxPolicyPtr = - _lookup>( - 'FPDF_SetSandBoxPolicy'); - late final _FPDF_SetSandBoxPolicy = - _FPDF_SetSandBoxPolicyPtr.asFunction(); - - /// Function: FPDF_LoadDocument - /// Open and load a PDF document. - /// Parameters: - /// file_path - Path to the PDF file (including extension). - /// password - A string used as the password for the PDF file. - /// If no password is needed, empty or NULL can be used. - /// See comments below regarding the encoding. - /// Return value: - /// A handle to the loaded document, or NULL on failure. - /// Comments: - /// Loaded document can be closed by FPDF_CloseDocument(). - /// If this function fails, you can use FPDF_GetLastError() to retrieve - /// the reason why it failed. - /// - /// The encoding for |file_path| is UTF-8. - /// - /// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, - /// depending on the security handler revision, will only accept one or - /// the other encoding. If |password|'s encoding and the PDF's expected - /// encoding do not match, FPDF_LoadDocument() will automatically - /// convert |password| to the other encoding. - FPDF_DOCUMENT FPDF_LoadDocument( - FPDF_STRING file_path, - FPDF_BYTESTRING password, - ) { - return _FPDF_LoadDocument( - file_path, - password, - ); - } - - late final _FPDF_LoadDocumentPtr = _lookup< - ffi.NativeFunction< - FPDF_DOCUMENT Function( - FPDF_STRING, FPDF_BYTESTRING)>>('FPDF_LoadDocument'); - late final _FPDF_LoadDocument = _FPDF_LoadDocumentPtr.asFunction< - FPDF_DOCUMENT Function(FPDF_STRING, FPDF_BYTESTRING)>(); - - /// Function: FPDF_LoadMemDocument - /// Open and load a PDF document from memory. - /// Parameters: - /// data_buf - Pointer to a buffer containing the PDF document. - /// size - Number of bytes in the PDF document. - /// password - A string used as the password for the PDF file. - /// If no password is needed, empty or NULL can be used. - /// Return value: - /// A handle to the loaded document, or NULL on failure. - /// Comments: - /// The memory buffer must remain valid when the document is open. - /// The loaded document can be closed by FPDF_CloseDocument. - /// If this function fails, you can use FPDF_GetLastError() to retrieve - /// the reason why it failed. - /// - /// See the comments for FPDF_LoadDocument() regarding the encoding for - /// |password|. - /// Notes: - /// If PDFium is built with the XFA module, the application should call - /// FPDF_LoadXFA() function after the PDF document loaded to support XFA - /// fields defined in the fpdfformfill.h file. - FPDF_DOCUMENT FPDF_LoadMemDocument( - ffi.Pointer data_buf, - int size, - FPDF_BYTESTRING password, - ) { - return _FPDF_LoadMemDocument( - data_buf, - size, - password, - ); - } - - late final _FPDF_LoadMemDocumentPtr = _lookup< - ffi.NativeFunction< - FPDF_DOCUMENT Function(ffi.Pointer, ffi.Int, - FPDF_BYTESTRING)>>('FPDF_LoadMemDocument'); - late final _FPDF_LoadMemDocument = _FPDF_LoadMemDocumentPtr.asFunction< - FPDF_DOCUMENT Function(ffi.Pointer, int, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Function: FPDF_LoadMemDocument64 - /// Open and load a PDF document from memory. - /// Parameters: - /// data_buf - Pointer to a buffer containing the PDF document. - /// size - Number of bytes in the PDF document. - /// password - A string used as the password for the PDF file. - /// If no password is needed, empty or NULL can be used. - /// Return value: - /// A handle to the loaded document, or NULL on failure. - /// Comments: - /// The memory buffer must remain valid when the document is open. - /// The loaded document can be closed by FPDF_CloseDocument. - /// If this function fails, you can use FPDF_GetLastError() to retrieve - /// the reason why it failed. - /// - /// See the comments for FPDF_LoadDocument() regarding the encoding for - /// |password|. - /// Notes: - /// If PDFium is built with the XFA module, the application should call - /// FPDF_LoadXFA() function after the PDF document loaded to support XFA - /// fields defined in the fpdfformfill.h file. - FPDF_DOCUMENT FPDF_LoadMemDocument64( - ffi.Pointer data_buf, - int size, - FPDF_BYTESTRING password, - ) { - return _FPDF_LoadMemDocument64( - data_buf, - size, - password, - ); - } - - late final _FPDF_LoadMemDocument64Ptr = _lookup< - ffi.NativeFunction< - FPDF_DOCUMENT Function(ffi.Pointer, ffi.Size, - FPDF_BYTESTRING)>>('FPDF_LoadMemDocument64'); - late final _FPDF_LoadMemDocument64 = _FPDF_LoadMemDocument64Ptr.asFunction< - FPDF_DOCUMENT Function(ffi.Pointer, int, FPDF_BYTESTRING)>(); - - /// Function: FPDF_LoadCustomDocument - /// Load PDF document from a custom access descriptor. - /// Parameters: - /// pFileAccess - A structure for accessing the file. - /// password - Optional password for decrypting the PDF file. - /// Return value: - /// A handle to the loaded document, or NULL on failure. - /// Comments: - /// The application must keep the file resources |pFileAccess| points to - /// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| - /// itself does not need to outlive the FPDF_DOCUMENT. - /// - /// The loaded document can be closed with FPDF_CloseDocument(). - /// - /// See the comments for FPDF_LoadDocument() regarding the encoding for - /// |password|. - /// Notes: - /// If PDFium is built with the XFA module, the application should call - /// FPDF_LoadXFA() function after the PDF document loaded to support XFA - /// fields defined in the fpdfformfill.h file. - FPDF_DOCUMENT FPDF_LoadCustomDocument( - ffi.Pointer pFileAccess, - FPDF_BYTESTRING password, - ) { - return _FPDF_LoadCustomDocument( - pFileAccess, - password, - ); - } - - late final _FPDF_LoadCustomDocumentPtr = _lookup< - ffi.NativeFunction< - FPDF_DOCUMENT Function(ffi.Pointer, - FPDF_BYTESTRING)>>('FPDF_LoadCustomDocument'); - late final _FPDF_LoadCustomDocument = _FPDF_LoadCustomDocumentPtr.asFunction< - FPDF_DOCUMENT Function(ffi.Pointer, FPDF_BYTESTRING)>(); - - /// Function: FPDF_GetFileVersion - /// Get the file version of the given PDF document. - /// Parameters: - /// doc - Handle to a document. - /// fileVersion - The PDF file version. File version: 14 for 1.4, 15 - /// for 1.5, ... - /// Return value: - /// True if succeeds, false otherwise. - /// Comments: - /// If the document was created by FPDF_CreateNewDocument, - /// then this function will always fail. - int FPDF_GetFileVersion( - FPDF_DOCUMENT doc, - ffi.Pointer fileVersion, - ) { - return _FPDF_GetFileVersion( - doc, - fileVersion, - ); - } - - late final _FPDF_GetFileVersionPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DOCUMENT, ffi.Pointer)>>('FPDF_GetFileVersion'); - late final _FPDF_GetFileVersion = _FPDF_GetFileVersionPtr.asFunction< - int Function(FPDF_DOCUMENT, ffi.Pointer)>(); - - /// Function: FPDF_GetLastError - /// Get last error code when a function fails. - /// Parameters: - /// None. - /// Return value: - /// A 32-bit integer indicating error code as defined above. - /// Comments: - /// If the previous SDK call succeeded, the return value of this - /// function is not defined. This function only works in conjunction - /// with APIs that mention FPDF_GetLastError() in their documentation. - int FPDF_GetLastError() { - return _FPDF_GetLastError(); - } - - late final _FPDF_GetLastErrorPtr = - _lookup>( - 'FPDF_GetLastError'); - late final _FPDF_GetLastError = - _FPDF_GetLastErrorPtr.asFunction(); - - /// Experimental API. - /// Function: FPDF_DocumentHasValidCrossReferenceTable - /// Whether the document's cross reference table is valid or not. - /// Parameters: - /// document - Handle to a document. Returned by FPDF_LoadDocument. - /// Return value: - /// True if the PDF parser did not encounter problems parsing the cross - /// reference table. False if the parser could not parse the cross - /// reference table and the table had to be rebuild from other data - /// within the document. - /// Comments: - /// The return value can change over time as the PDF parser evolves. - int FPDF_DocumentHasValidCrossReferenceTable( - FPDF_DOCUMENT document, - ) { - return _FPDF_DocumentHasValidCrossReferenceTable( - document, - ); - } - - late final _FPDF_DocumentHasValidCrossReferenceTablePtr = - _lookup>( - 'FPDF_DocumentHasValidCrossReferenceTable'); - late final _FPDF_DocumentHasValidCrossReferenceTable = - _FPDF_DocumentHasValidCrossReferenceTablePtr.asFunction< - int Function(FPDF_DOCUMENT)>(); - - /// Experimental API. - /// Function: FPDF_GetTrailerEnds - /// Get the byte offsets of trailer ends. - /// Parameters: - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// buffer - The address of a buffer that receives the - /// byte offsets. - /// length - The size, in ints, of |buffer|. - /// Return value: - /// Returns the number of ints in the buffer on success, 0 on error. - /// - /// |buffer| is an array of integers that describes the exact byte offsets of the - /// trailer ends in the document. If |length| is less than the returned length, - /// or |document| or |buffer| is NULL, |buffer| will not be modified. - int FPDF_GetTrailerEnds( - FPDF_DOCUMENT document, - ffi.Pointer buffer, - int length, - ) { - return _FPDF_GetTrailerEnds( - document, - buffer, - length, - ); - } - - late final _FPDF_GetTrailerEndsPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_DOCUMENT, ffi.Pointer, - ffi.UnsignedLong)>>('FPDF_GetTrailerEnds'); - late final _FPDF_GetTrailerEnds = _FPDF_GetTrailerEndsPtr.asFunction< - int Function(FPDF_DOCUMENT, ffi.Pointer, int)>(); - - /// Function: FPDF_GetDocPermissions - /// Get file permission flags of the document. - /// Parameters: - /// document - Handle to a document. Returned by FPDF_LoadDocument. - /// Return value: - /// A 32-bit integer indicating permission flags. Please refer to the - /// PDF Reference for detailed descriptions. If the document is not - /// protected or was unlocked by the owner, 0xffffffff will be returned. - int FPDF_GetDocPermissions( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetDocPermissions( - document, - ); - } - - late final _FPDF_GetDocPermissionsPtr = - _lookup>( - 'FPDF_GetDocPermissions'); - late final _FPDF_GetDocPermissions = - _FPDF_GetDocPermissionsPtr.asFunction(); - - /// Function: FPDF_GetDocUserPermissions - /// Get user file permission flags of the document. - /// Parameters: - /// document - Handle to a document. Returned by FPDF_LoadDocument. - /// Return value: - /// A 32-bit integer indicating permission flags. Please refer to the - /// PDF Reference for detailed descriptions. If the document is not - /// protected, 0xffffffff will be returned. Always returns user - /// permissions, even if the document was unlocked by the owner. - int FPDF_GetDocUserPermissions( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetDocUserPermissions( - document, - ); - } - - late final _FPDF_GetDocUserPermissionsPtr = - _lookup>( - 'FPDF_GetDocUserPermissions'); - late final _FPDF_GetDocUserPermissions = - _FPDF_GetDocUserPermissionsPtr.asFunction(); - - /// Function: FPDF_GetSecurityHandlerRevision - /// Get the revision for the security handler. - /// Parameters: - /// document - Handle to a document. Returned by FPDF_LoadDocument. - /// Return value: - /// The security handler revision number. Please refer to the PDF - /// Reference for a detailed description. If the document is not - /// protected, -1 will be returned. - int FPDF_GetSecurityHandlerRevision( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetSecurityHandlerRevision( - document, - ); - } - - late final _FPDF_GetSecurityHandlerRevisionPtr = - _lookup>( - 'FPDF_GetSecurityHandlerRevision'); - late final _FPDF_GetSecurityHandlerRevision = - _FPDF_GetSecurityHandlerRevisionPtr.asFunction< - int Function(FPDF_DOCUMENT)>(); - - /// Function: FPDF_GetPageCount - /// Get total number of pages in the document. - /// Parameters: - /// document - Handle to document. Returned by FPDF_LoadDocument. - /// Return value: - /// Total number of pages in the document. - int FPDF_GetPageCount( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetPageCount( - document, - ); - } - - late final _FPDF_GetPageCountPtr = - _lookup>( - 'FPDF_GetPageCount'); - late final _FPDF_GetPageCount = - _FPDF_GetPageCountPtr.asFunction(); - - /// Function: FPDF_LoadPage - /// Load a page inside the document. - /// Parameters: - /// document - Handle to document. Returned by FPDF_LoadDocument - /// page_index - Index number of the page. 0 for the first page. - /// Return value: - /// A handle to the loaded page, or NULL if page load fails. - /// Comments: - /// The loaded page can be rendered to devices using FPDF_RenderPage. - /// The loaded page can be closed using FPDF_ClosePage. - FPDF_PAGE FPDF_LoadPage( - FPDF_DOCUMENT document, - int page_index, - ) { - return _FPDF_LoadPage( - document, - page_index, - ); - } - - late final _FPDF_LoadPagePtr = - _lookup>( - 'FPDF_LoadPage'); - late final _FPDF_LoadPage = - _FPDF_LoadPagePtr.asFunction(); - - /// Experimental API - /// Function: FPDF_GetPageWidthF - /// Get page width. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// Return value: - /// Page width (excluding non-displayable area) measured in points. - /// One point is 1/72 inch (around 0.3528 mm). - /// Comments: - /// Changing the rotation of |page| affects the return value. - double FPDF_GetPageWidthF( - FPDF_PAGE page, - ) { - return _FPDF_GetPageWidthF( - page, - ); - } - - late final _FPDF_GetPageWidthFPtr = - _lookup>( - 'FPDF_GetPageWidthF'); - late final _FPDF_GetPageWidthF = - _FPDF_GetPageWidthFPtr.asFunction(); - - /// Function: FPDF_GetPageWidth - /// Get page width. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// Return value: - /// Page width (excluding non-displayable area) measured in points. - /// One point is 1/72 inch (around 0.3528 mm). - /// Note: - /// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the - /// future. - /// Comments: - /// Changing the rotation of |page| affects the return value. - double FPDF_GetPageWidth( - FPDF_PAGE page, - ) { - return _FPDF_GetPageWidth( - page, - ); - } - - late final _FPDF_GetPageWidthPtr = - _lookup>( - 'FPDF_GetPageWidth'); - late final _FPDF_GetPageWidth = - _FPDF_GetPageWidthPtr.asFunction(); - - /// Experimental API - /// Function: FPDF_GetPageHeightF - /// Get page height. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// Return value: - /// Page height (excluding non-displayable area) measured in points. - /// One point is 1/72 inch (around 0.3528 mm) - /// Comments: - /// Changing the rotation of |page| affects the return value. - double FPDF_GetPageHeightF( - FPDF_PAGE page, - ) { - return _FPDF_GetPageHeightF( - page, - ); - } - - late final _FPDF_GetPageHeightFPtr = - _lookup>( - 'FPDF_GetPageHeightF'); - late final _FPDF_GetPageHeightF = - _FPDF_GetPageHeightFPtr.asFunction(); - - /// Function: FPDF_GetPageHeight - /// Get page height. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// Return value: - /// Page height (excluding non-displayable area) measured in points. - /// One point is 1/72 inch (around 0.3528 mm) - /// Note: - /// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the - /// future. - /// Comments: - /// Changing the rotation of |page| affects the return value. - double FPDF_GetPageHeight( - FPDF_PAGE page, - ) { - return _FPDF_GetPageHeight( - page, - ); - } - - late final _FPDF_GetPageHeightPtr = - _lookup>( - 'FPDF_GetPageHeight'); - late final _FPDF_GetPageHeight = - _FPDF_GetPageHeightPtr.asFunction(); - - /// Experimental API. - /// Function: FPDF_GetPageBoundingBox - /// Get the bounding box of the page. This is the intersection between - /// its media box and its crop box. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// rect - Pointer to a rect to receive the page bounding box. - /// On an error, |rect| won't be filled. - /// Return value: - /// True for success. - int FPDF_GetPageBoundingBox( - FPDF_PAGE page, - ffi.Pointer rect, - ) { - return _FPDF_GetPageBoundingBox( - page, - rect, - ); - } - - late final _FPDF_GetPageBoundingBoxPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGE, ffi.Pointer)>>('FPDF_GetPageBoundingBox'); - late final _FPDF_GetPageBoundingBox = _FPDF_GetPageBoundingBoxPtr.asFunction< - int Function(FPDF_PAGE, ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDF_GetPageSizeByIndexF - /// Get the size of the page at the given index. - /// Parameters: - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// page_index - Page index, zero for the first page. - /// size - Pointer to a FS_SIZEF to receive the page size. - /// (in points). - /// Return value: - /// Non-zero for success. 0 for error (document or page not found). - int FPDF_GetPageSizeByIndexF( - FPDF_DOCUMENT document, - int page_index, - ffi.Pointer size, - ) { - return _FPDF_GetPageSizeByIndexF( - document, - page_index, - size, - ); - } - - late final _FPDF_GetPageSizeByIndexFPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_DOCUMENT, ffi.Int, - ffi.Pointer)>>('FPDF_GetPageSizeByIndexF'); - late final _FPDF_GetPageSizeByIndexF = _FPDF_GetPageSizeByIndexFPtr - .asFunction)>(); - - /// Function: FPDF_GetPageSizeByIndex - /// Get the size of the page at the given index. - /// Parameters: - /// document - Handle to document. Returned by FPDF_LoadDocument. - /// page_index - Page index, zero for the first page. - /// width - Pointer to a double to receive the page width - /// (in points). - /// height - Pointer to a double to receive the page height - /// (in points). - /// Return value: - /// Non-zero for success. 0 for error (document or page not found). - /// Note: - /// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in - /// the future. - int FPDF_GetPageSizeByIndex( - FPDF_DOCUMENT document, - int page_index, - ffi.Pointer width, - ffi.Pointer height, - ) { - return _FPDF_GetPageSizeByIndex( - document, - page_index, - width, - height, - ); - } - - late final _FPDF_GetPageSizeByIndexPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_DOCUMENT, ffi.Int, ffi.Pointer, - ffi.Pointer)>>('FPDF_GetPageSizeByIndex'); - late final _FPDF_GetPageSizeByIndex = _FPDF_GetPageSizeByIndexPtr.asFunction< - int Function(FPDF_DOCUMENT, int, ffi.Pointer, - ffi.Pointer)>(); - - /// Function: FPDF_RenderPageBitmap - /// Render contents of a page to a device independent bitmap. - /// Parameters: - /// bitmap - Handle to the device independent bitmap (as the - /// output buffer). The bitmap handle can be created - /// by FPDFBitmap_Create or retrieved from an image - /// object by FPDFImageObj_GetBitmap. - /// page - Handle to the page. Returned by FPDF_LoadPage - /// start_x - Left pixel position of the display area in - /// bitmap coordinates. - /// start_y - Top pixel position of the display area in bitmap - /// coordinates. - /// size_x - Horizontal size (in pixels) for displaying the page. - /// size_y - Vertical size (in pixels) for displaying the page. - /// rotate - Page orientation: - /// 0 (normal) - /// 1 (rotated 90 degrees clockwise) - /// 2 (rotated 180 degrees) - /// 3 (rotated 90 degrees counter-clockwise) - /// flags - 0 for normal display, or combination of the Page - /// Rendering flags defined above. With the FPDF_ANNOT - /// flag, it renders all annotations that do not require - /// user-interaction, which are all annotations except - /// widget and popup annotations. - /// Return value: - /// None. - void FPDF_RenderPageBitmap( - FPDF_BITMAP bitmap, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags, - ) { - return _FPDF_RenderPageBitmap( - bitmap, - page, - start_x, - start_y, - size_x, - size_y, - rotate, - flags, - ); - } - - late final _FPDF_RenderPageBitmapPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_BITMAP, FPDF_PAGE, ffi.Int, ffi.Int, ffi.Int, - ffi.Int, ffi.Int, ffi.Int)>>('FPDF_RenderPageBitmap'); - late final _FPDF_RenderPageBitmap = _FPDF_RenderPageBitmapPtr.asFunction< - void Function(FPDF_BITMAP, FPDF_PAGE, int, int, int, int, int, int)>(); - - /// Function: FPDF_RenderPageBitmapWithMatrix - /// Render contents of a page to a device independent bitmap. - /// Parameters: - /// bitmap - Handle to the device independent bitmap (as the - /// output buffer). The bitmap handle can be created - /// by FPDFBitmap_Create or retrieved by - /// FPDFImageObj_GetBitmap. - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// matrix - The transform matrix, which must be invertible. - /// See PDF Reference 1.7, 4.2.2 Common Transformations. - /// clipping - The rect to clip to in device coords. - /// flags - 0 for normal display, or combination of the Page - /// Rendering flags defined above. With the FPDF_ANNOT - /// flag, it renders all annotations that do not require - /// user-interaction, which are all annotations except - /// widget and popup annotations. - /// Return value: - /// None. Note that behavior is undefined if det of |matrix| is 0. - void FPDF_RenderPageBitmapWithMatrix( - FPDF_BITMAP bitmap, - FPDF_PAGE page, - ffi.Pointer matrix, - ffi.Pointer clipping, - int flags, - ) { - return _FPDF_RenderPageBitmapWithMatrix( - bitmap, - page, - matrix, - clipping, - flags, - ); - } - - late final _FPDF_RenderPageBitmapWithMatrixPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - FPDF_BITMAP, - FPDF_PAGE, - ffi.Pointer, - ffi.Pointer, - ffi.Int)>>('FPDF_RenderPageBitmapWithMatrix'); - late final _FPDF_RenderPageBitmapWithMatrix = - _FPDF_RenderPageBitmapWithMatrixPtr.asFunction< - void Function(FPDF_BITMAP, FPDF_PAGE, ffi.Pointer, - ffi.Pointer, int)>(); - - /// Function: FPDF_ClosePage - /// Close a loaded PDF page. - /// Parameters: - /// page - Handle to the loaded page. - /// Return value: - /// None. - void FPDF_ClosePage( - FPDF_PAGE page, - ) { - return _FPDF_ClosePage( - page, - ); - } - - late final _FPDF_ClosePagePtr = - _lookup>( - 'FPDF_ClosePage'); - late final _FPDF_ClosePage = - _FPDF_ClosePagePtr.asFunction(); - - /// Function: FPDF_CloseDocument - /// Close a loaded PDF document. - /// Parameters: - /// document - Handle to the loaded document. - /// Return value: - /// None. - void FPDF_CloseDocument( - FPDF_DOCUMENT document, - ) { - return _FPDF_CloseDocument( - document, - ); - } - - late final _FPDF_CloseDocumentPtr = - _lookup>( - 'FPDF_CloseDocument'); - late final _FPDF_CloseDocument = - _FPDF_CloseDocumentPtr.asFunction(); - - /// Function: FPDF_DeviceToPage - /// Convert the screen coordinates of a point to page coordinates. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// start_x - Left pixel position of the display area in - /// device coordinates. - /// start_y - Top pixel position of the display area in device - /// coordinates. - /// size_x - Horizontal size (in pixels) for displaying the page. - /// size_y - Vertical size (in pixels) for displaying the page. - /// rotate - Page orientation: - /// 0 (normal) - /// 1 (rotated 90 degrees clockwise) - /// 2 (rotated 180 degrees) - /// 3 (rotated 90 degrees counter-clockwise) - /// device_x - X value in device coordinates to be converted. - /// device_y - Y value in device coordinates to be converted. - /// page_x - A pointer to a double receiving the converted X - /// value in page coordinates. - /// page_y - A pointer to a double receiving the converted Y - /// value in page coordinates. - /// Return value: - /// Returns true if the conversion succeeds, and |page_x| and |page_y| - /// successfully receives the converted coordinates. - /// Comments: - /// The page coordinate system has its origin at the left-bottom corner - /// of the page, with the X-axis on the bottom going to the right, and - /// the Y-axis on the left side going up. - /// - /// NOTE: this coordinate system can be altered when you zoom, scroll, - /// or rotate a page, however, a point on the page should always have - /// the same coordinate values in the page coordinate system. - /// - /// The device coordinate system is device dependent. For screen device, - /// its origin is at the left-top corner of the window. However this - /// origin can be altered by the Windows coordinate transformation - /// utilities. - /// - /// You must make sure the start_x, start_y, size_x, size_y - /// and rotate parameters have exactly same values as you used in - /// the FPDF_RenderPage() function call. - int FPDF_DeviceToPage( - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int device_x, - int device_y, - ffi.Pointer page_x, - ffi.Pointer page_y, - ) { - return _FPDF_DeviceToPage( - page, - start_x, - start_y, - size_x, - size_y, - rotate, - device_x, - device_y, - page_x, - page_y, - ); - } - - late final _FPDF_DeviceToPagePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGE, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Pointer, - ffi.Pointer)>>('FPDF_DeviceToPage'); - late final _FPDF_DeviceToPage = _FPDF_DeviceToPagePtr.asFunction< - int Function(FPDF_PAGE, int, int, int, int, int, int, int, - ffi.Pointer, ffi.Pointer)>(); - - /// Function: FPDF_PageToDevice - /// Convert the page coordinates of a point to screen coordinates. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage. - /// start_x - Left pixel position of the display area in - /// device coordinates. - /// start_y - Top pixel position of the display area in device - /// coordinates. - /// size_x - Horizontal size (in pixels) for displaying the page. - /// size_y - Vertical size (in pixels) for displaying the page. - /// rotate - Page orientation: - /// 0 (normal) - /// 1 (rotated 90 degrees clockwise) - /// 2 (rotated 180 degrees) - /// 3 (rotated 90 degrees counter-clockwise) - /// page_x - X value in page coordinates. - /// page_y - Y value in page coordinate. - /// device_x - A pointer to an integer receiving the result X - /// value in device coordinates. - /// device_y - A pointer to an integer receiving the result Y - /// value in device coordinates. - /// Return value: - /// Returns true if the conversion succeeds, and |device_x| and - /// |device_y| successfully receives the converted coordinates. - /// Comments: - /// See comments for FPDF_DeviceToPage(). - int FPDF_PageToDevice( - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - double page_x, - double page_y, - ffi.Pointer device_x, - ffi.Pointer device_y, - ) { - return _FPDF_PageToDevice( - page, - start_x, - start_y, - size_x, - size_y, - rotate, - page_x, - page_y, - device_x, - device_y, - ); - } - - late final _FPDF_PageToDevicePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGE, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Int, - ffi.Double, - ffi.Double, - ffi.Pointer, - ffi.Pointer)>>('FPDF_PageToDevice'); - late final _FPDF_PageToDevice = _FPDF_PageToDevicePtr.asFunction< - int Function(FPDF_PAGE, int, int, int, int, int, double, double, - ffi.Pointer, ffi.Pointer)>(); - - /// Function: FPDFBitmap_Create - /// Create a device independent bitmap (FXDIB). - /// Parameters: - /// width - The number of pixels in width for the bitmap. - /// Must be greater than 0. - /// height - The number of pixels in height for the bitmap. - /// Must be greater than 0. - /// alpha - A flag indicating whether the alpha channel is used. - /// Non-zero for using alpha, zero for not using. - /// Return value: - /// The created bitmap handle, or NULL if a parameter error or out of - /// memory. - /// Comments: - /// The bitmap always uses 4 bytes per pixel. The first byte is always - /// double word aligned. - /// - /// The byte order is BGRx (the last byte unused if no alpha channel) or - /// BGRA. - /// - /// The pixels in a horizontal line are stored side by side, with the - /// left most pixel stored first (with lower memory address). - /// Each line uses width * 4 bytes. - /// - /// Lines are stored one after another, with the top most line stored - /// first. There is no gap between adjacent lines. - /// - /// This function allocates enough memory for holding all pixels in the - /// bitmap, but it doesn't initialize the buffer. Applications can use - /// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS - /// allows it, this function can allocate up to 4 GB of memory. - FPDF_BITMAP FPDFBitmap_Create( - int width, - int height, - int alpha, - ) { - return _FPDFBitmap_Create( - width, - height, - alpha, - ); - } - - late final _FPDFBitmap_CreatePtr = _lookup< - ffi.NativeFunction>( - 'FPDFBitmap_Create'); - late final _FPDFBitmap_Create = - _FPDFBitmap_CreatePtr.asFunction(); - - /// Function: FPDFBitmap_CreateEx - /// Create a device independent bitmap (FXDIB) - /// Parameters: - /// width - The number of pixels in width for the bitmap. - /// Must be greater than 0. - /// height - The number of pixels in height for the bitmap. - /// Must be greater than 0. - /// format - A number indicating for bitmap format, as defined - /// above. - /// first_scan - A pointer to the first byte of the first line if - /// using an external buffer. If this parameter is NULL, - /// then a new buffer will be created. - /// stride - Number of bytes for each scan line. The value must - /// be 0 or greater. When the value is 0, - /// FPDFBitmap_CreateEx() will automatically calculate - /// the appropriate value using |width| and |format|. - /// When using an external buffer, it is recommended for - /// the caller to pass in the value. - /// When not using an external buffer, it is recommended - /// for the caller to pass in 0. - /// Return value: - /// The bitmap handle, or NULL if parameter error or out of memory. - /// Comments: - /// Similar to FPDFBitmap_Create function, but allows for more formats - /// and an external buffer is supported. The bitmap created by this - /// function can be used in any place that a FPDF_BITMAP handle is - /// required. - /// - /// If an external buffer is used, then the caller should destroy the - /// buffer. FPDFBitmap_Destroy() will not destroy the buffer. - /// - /// It is recommended to use FPDFBitmap_GetStride() to get the stride - /// value. - FPDF_BITMAP FPDFBitmap_CreateEx( - int width, - int height, - int format, - ffi.Pointer first_scan, - int stride, - ) { - return _FPDFBitmap_CreateEx( - width, - height, - format, - first_scan, - stride, - ); - } - - late final _FPDFBitmap_CreateExPtr = _lookup< - ffi.NativeFunction< - FPDF_BITMAP Function(ffi.Int, ffi.Int, ffi.Int, ffi.Pointer, - ffi.Int)>>('FPDFBitmap_CreateEx'); - late final _FPDFBitmap_CreateEx = _FPDFBitmap_CreateExPtr.asFunction< - FPDF_BITMAP Function(int, int, int, ffi.Pointer, int)>(); - - /// Function: FPDFBitmap_GetFormat - /// Get the format of the bitmap. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// The format of the bitmap. - /// Comments: - /// Only formats supported by FPDFBitmap_CreateEx are supported by this - /// function; see the list of such formats above. - int FPDFBitmap_GetFormat( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_GetFormat( - bitmap, - ); - } - - late final _FPDFBitmap_GetFormatPtr = - _lookup>( - 'FPDFBitmap_GetFormat'); - late final _FPDFBitmap_GetFormat = - _FPDFBitmap_GetFormatPtr.asFunction(); - - /// Function: FPDFBitmap_FillRect - /// Fill a rectangle in a bitmap. - /// Parameters: - /// bitmap - The handle to the bitmap. Returned by - /// FPDFBitmap_Create. - /// left - The left position. Starting from 0 at the - /// left-most pixel. - /// top - The top position. Starting from 0 at the - /// top-most line. - /// width - Width in pixels to be filled. - /// height - Height in pixels to be filled. - /// color - A 32-bit value specifing the color, in 8888 ARGB - /// format. - /// Return value: - /// Returns whether the operation succeeded or not. - /// Comments: - /// This function sets the color and (optionally) alpha value in the - /// specified region of the bitmap. - /// - /// NOTE: If the alpha channel is used, this function does NOT - /// composite the background with the source color, instead the - /// background will be replaced by the source color and the alpha. - /// - /// If the alpha channel is not used, the alpha parameter is ignored. - int FPDFBitmap_FillRect( - FPDF_BITMAP bitmap, - int left, - int top, - int width, - int height, - int color, - ) { - return _FPDFBitmap_FillRect( - bitmap, - left, - top, - width, - height, - color, - ); - } - - late final _FPDFBitmap_FillRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_BITMAP, ffi.Int, ffi.Int, ffi.Int, ffi.Int, - FPDF_DWORD)>>('FPDFBitmap_FillRect'); - late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction< - int Function(FPDF_BITMAP, int, int, int, int, int)>(); - - /// Function: FPDFBitmap_GetBuffer - /// Get data buffer of a bitmap. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// The pointer to the first byte of the bitmap buffer. - /// Comments: - /// The stride may be more than width * number of bytes per pixel - /// - /// Applications can use this function to get the bitmap buffer pointer, - /// then manipulate any color and/or alpha values for any pixels in the - /// bitmap. - /// - /// Use FPDFBitmap_GetFormat() to find out the format of the data. - ffi.Pointer FPDFBitmap_GetBuffer( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_GetBuffer( - bitmap, - ); - } - - late final _FPDFBitmap_GetBufferPtr = - _lookup Function(FPDF_BITMAP)>>( - 'FPDFBitmap_GetBuffer'); - late final _FPDFBitmap_GetBuffer = _FPDFBitmap_GetBufferPtr.asFunction< - ffi.Pointer Function(FPDF_BITMAP)>(); - - /// Function: FPDFBitmap_GetWidth - /// Get width of a bitmap. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// The width of the bitmap in pixels. - int FPDFBitmap_GetWidth( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_GetWidth( - bitmap, - ); - } - - late final _FPDFBitmap_GetWidthPtr = - _lookup>( - 'FPDFBitmap_GetWidth'); - late final _FPDFBitmap_GetWidth = - _FPDFBitmap_GetWidthPtr.asFunction(); - - /// Function: FPDFBitmap_GetHeight - /// Get height of a bitmap. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// The height of the bitmap in pixels. - int FPDFBitmap_GetHeight( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_GetHeight( - bitmap, - ); - } - - late final _FPDFBitmap_GetHeightPtr = - _lookup>( - 'FPDFBitmap_GetHeight'); - late final _FPDFBitmap_GetHeight = - _FPDFBitmap_GetHeightPtr.asFunction(); - - /// Function: FPDFBitmap_GetStride - /// Get number of bytes for each line in the bitmap buffer. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// The number of bytes for each line in the bitmap buffer. - /// Comments: - /// The stride may be more than width * number of bytes per pixel. - int FPDFBitmap_GetStride( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_GetStride( - bitmap, - ); - } - - late final _FPDFBitmap_GetStridePtr = - _lookup>( - 'FPDFBitmap_GetStride'); - late final _FPDFBitmap_GetStride = - _FPDFBitmap_GetStridePtr.asFunction(); - - /// Function: FPDFBitmap_Destroy - /// Destroy a bitmap and release all related buffers. - /// Parameters: - /// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create - /// or FPDFImageObj_GetBitmap. - /// Return value: - /// None. - /// Comments: - /// This function will not destroy any external buffers provided when - /// the bitmap was created. - void FPDFBitmap_Destroy( - FPDF_BITMAP bitmap, - ) { - return _FPDFBitmap_Destroy( - bitmap, - ); - } - - late final _FPDFBitmap_DestroyPtr = - _lookup>( - 'FPDFBitmap_Destroy'); - late final _FPDFBitmap_Destroy = - _FPDFBitmap_DestroyPtr.asFunction(); - - /// Function: FPDF_VIEWERREF_GetPrintScaling - /// Whether the PDF document prefers to be scaled or not. - /// Parameters: - /// document - Handle to the loaded document. - /// Return value: - /// None. - int FPDF_VIEWERREF_GetPrintScaling( - FPDF_DOCUMENT document, - ) { - return _FPDF_VIEWERREF_GetPrintScaling( - document, - ); - } - - late final _FPDF_VIEWERREF_GetPrintScalingPtr = - _lookup>( - 'FPDF_VIEWERREF_GetPrintScaling'); - late final _FPDF_VIEWERREF_GetPrintScaling = - _FPDF_VIEWERREF_GetPrintScalingPtr.asFunction< - int Function(FPDF_DOCUMENT)>(); - - /// Function: FPDF_VIEWERREF_GetNumCopies - /// Returns the number of copies to be printed. - /// Parameters: - /// document - Handle to the loaded document. - /// Return value: - /// The number of copies to be printed. - int FPDF_VIEWERREF_GetNumCopies( - FPDF_DOCUMENT document, - ) { - return _FPDF_VIEWERREF_GetNumCopies( - document, - ); - } - - late final _FPDF_VIEWERREF_GetNumCopiesPtr = - _lookup>( - 'FPDF_VIEWERREF_GetNumCopies'); - late final _FPDF_VIEWERREF_GetNumCopies = - _FPDF_VIEWERREF_GetNumCopiesPtr.asFunction(); - - /// Function: FPDF_VIEWERREF_GetPrintPageRange - /// Page numbers to initialize print dialog box when file is printed. - /// Parameters: - /// document - Handle to the loaded document. - /// Return value: - /// The print page range to be used for printing. - FPDF_PAGERANGE FPDF_VIEWERREF_GetPrintPageRange( - FPDF_DOCUMENT document, - ) { - return _FPDF_VIEWERREF_GetPrintPageRange( - document, - ); - } - - late final _FPDF_VIEWERREF_GetPrintPageRangePtr = - _lookup>( - 'FPDF_VIEWERREF_GetPrintPageRange'); - late final _FPDF_VIEWERREF_GetPrintPageRange = - _FPDF_VIEWERREF_GetPrintPageRangePtr.asFunction< - FPDF_PAGERANGE Function(FPDF_DOCUMENT)>(); - - /// Experimental API. - /// Function: FPDF_VIEWERREF_GetPrintPageRangeCount - /// Returns the number of elements in a FPDF_PAGERANGE. - /// Parameters: - /// pagerange - Handle to the page range. - /// Return value: - /// The number of elements in the page range. Returns 0 on error. - int FPDF_VIEWERREF_GetPrintPageRangeCount( - FPDF_PAGERANGE pagerange, - ) { - return _FPDF_VIEWERREF_GetPrintPageRangeCount( - pagerange, - ); - } - - late final _FPDF_VIEWERREF_GetPrintPageRangeCountPtr = - _lookup>( - 'FPDF_VIEWERREF_GetPrintPageRangeCount'); - late final _FPDF_VIEWERREF_GetPrintPageRangeCount = - _FPDF_VIEWERREF_GetPrintPageRangeCountPtr.asFunction< - int Function(FPDF_PAGERANGE)>(); - - /// Experimental API. - /// Function: FPDF_VIEWERREF_GetPrintPageRangeElement - /// Returns an element from a FPDF_PAGERANGE. - /// Parameters: - /// pagerange - Handle to the page range. - /// index - Index of the element. - /// Return value: - /// The value of the element in the page range at a given index. - /// Returns -1 on error. - int FPDF_VIEWERREF_GetPrintPageRangeElement( - FPDF_PAGERANGE pagerange, - int index, - ) { - return _FPDF_VIEWERREF_GetPrintPageRangeElement( - pagerange, - index, - ); - } - - late final _FPDF_VIEWERREF_GetPrintPageRangeElementPtr = - _lookup>( - 'FPDF_VIEWERREF_GetPrintPageRangeElement'); - late final _FPDF_VIEWERREF_GetPrintPageRangeElement = - _FPDF_VIEWERREF_GetPrintPageRangeElementPtr.asFunction< - int Function(FPDF_PAGERANGE, int)>(); - - /// Function: FPDF_VIEWERREF_GetDuplex - /// Returns the paper handling option to be used when printing from - /// the print dialog. - /// Parameters: - /// document - Handle to the loaded document. - /// Return value: - /// The paper handling option to be used when printing. - _FPDF_DUPLEXTYPE_ FPDF_VIEWERREF_GetDuplex( - FPDF_DOCUMENT document, - ) { - return _FPDF_DUPLEXTYPE_.fromValue(_FPDF_VIEWERREF_GetDuplex( - document, - )); - } - - late final _FPDF_VIEWERREF_GetDuplexPtr = - _lookup>( - 'FPDF_VIEWERREF_GetDuplex'); - late final _FPDF_VIEWERREF_GetDuplex = - _FPDF_VIEWERREF_GetDuplexPtr.asFunction(); - - /// Function: FPDF_VIEWERREF_GetName - /// Gets the contents for a viewer ref, with a given key. The value must - /// be of type "name". - /// Parameters: - /// document - Handle to the loaded document. - /// key - Name of the key in the viewer pref dictionary, - /// encoded in UTF-8. - /// buffer - Caller-allocate buffer to receive the key, or NULL - /// - to query the required length. - /// length - Length of the buffer. - /// Return value: - /// The number of bytes in the contents, including the NULL terminator. - /// Thus if the return value is 0, then that indicates an error, such - /// as when |document| is invalid. If |length| is less than the required - /// length, or |buffer| is NULL, |buffer| will not be modified. - int FPDF_VIEWERREF_GetName( - FPDF_DOCUMENT document, - FPDF_BYTESTRING key, - ffi.Pointer buffer, - int length, - ) { - return _FPDF_VIEWERREF_GetName( - document, - key, - buffer, - length, - ); - } - - late final _FPDF_VIEWERREF_GetNamePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_DOCUMENT, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDF_VIEWERREF_GetName'); - late final _FPDF_VIEWERREF_GetName = _FPDF_VIEWERREF_GetNamePtr.asFunction< - int Function( - FPDF_DOCUMENT, FPDF_BYTESTRING, ffi.Pointer, int)>(); - - /// Function: FPDF_CountNamedDests - /// Get the count of named destinations in the PDF document. - /// Parameters: - /// document - Handle to a document - /// Return value: - /// The count of named destinations. - int FPDF_CountNamedDests( - FPDF_DOCUMENT document, - ) { - return _FPDF_CountNamedDests( - document, - ); - } - - late final _FPDF_CountNamedDestsPtr = - _lookup>( - 'FPDF_CountNamedDests'); - late final _FPDF_CountNamedDests = - _FPDF_CountNamedDestsPtr.asFunction(); - - /// Function: FPDF_GetNamedDestByName - /// Get a the destination handle for the given name. - /// Parameters: - /// document - Handle to the loaded document. - /// name - The name of a destination. - /// Return value: - /// The handle to the destination. - FPDF_DEST FPDF_GetNamedDestByName( - FPDF_DOCUMENT document, - FPDF_BYTESTRING name, - ) { - return _FPDF_GetNamedDestByName( - document, - name, - ); - } - - late final _FPDF_GetNamedDestByNamePtr = _lookup< - ffi - .NativeFunction>( - 'FPDF_GetNamedDestByName'); - late final _FPDF_GetNamedDestByName = _FPDF_GetNamedDestByNamePtr.asFunction< - FPDF_DEST Function(FPDF_DOCUMENT, FPDF_BYTESTRING)>(); - - /// Function: FPDF_GetNamedDest - /// Get the named destination by index. - /// Parameters: - /// document - Handle to a document - /// index - The index of a named destination. - /// buffer - The buffer to store the destination name, - /// used as wchar_t*. - /// buflen [in/out] - Size of the buffer in bytes on input, - /// length of the result in bytes on output - /// or -1 if the buffer is too small. - /// Return value: - /// The destination handle for a given index, or NULL if there is no - /// named destination corresponding to |index|. - /// Comments: - /// Call this function twice to get the name of the named destination: - /// 1) First time pass in |buffer| as NULL and get buflen. - /// 2) Second time pass in allocated |buffer| and buflen to retrieve - /// |buffer|, which should be used as wchar_t*. - /// - /// If buflen is not sufficiently large, it will be set to -1 upon - /// return. - FPDF_DEST FPDF_GetNamedDest( - FPDF_DOCUMENT document, - int index, - ffi.Pointer buffer, - ffi.Pointer buflen, - ) { - return _FPDF_GetNamedDest( - document, - index, - buffer, - buflen, - ); - } - - late final _FPDF_GetNamedDestPtr = _lookup< - ffi.NativeFunction< - FPDF_DEST Function(FPDF_DOCUMENT, ffi.Int, ffi.Pointer, - ffi.Pointer)>>('FPDF_GetNamedDest'); - late final _FPDF_GetNamedDest = _FPDF_GetNamedDestPtr.asFunction< - FPDF_DEST Function( - FPDF_DOCUMENT, int, ffi.Pointer, ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDF_GetXFAPacketCount - /// Get the number of valid packets in the XFA entry. - /// Parameters: - /// document - Handle to the document. - /// Return value: - /// The number of valid packets, or -1 on error. - int FPDF_GetXFAPacketCount( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetXFAPacketCount( - document, - ); - } - - late final _FPDF_GetXFAPacketCountPtr = - _lookup>( - 'FPDF_GetXFAPacketCount'); - late final _FPDF_GetXFAPacketCount = - _FPDF_GetXFAPacketCountPtr.asFunction(); - - /// Experimental API. - /// Function: FPDF_GetXFAPacketName - /// Get the name of a packet in the XFA array. - /// Parameters: - /// document - Handle to the document. - /// index - Index number of the packet. 0 for the first packet. - /// buffer - Buffer for holding the name of the XFA packet. - /// buflen - Length of |buffer| in bytes. - /// Return value: - /// The length of the packet name in bytes, or 0 on error. - /// - /// |document| must be valid and |index| must be in the range [0, N), where N is - /// the value returned by FPDF_GetXFAPacketCount(). - /// |buffer| is only modified if it is non-NULL and |buflen| is greater than or - /// equal to the length of the packet name. The packet name includes a - /// terminating NUL character. |buffer| is unmodified on error. - int FPDF_GetXFAPacketName( - FPDF_DOCUMENT document, - int index, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDF_GetXFAPacketName( - document, - index, - buffer, - buflen, - ); - } - - late final _FPDF_GetXFAPacketNamePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_DOCUMENT, - ffi.Int, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDF_GetXFAPacketName'); - late final _FPDF_GetXFAPacketName = _FPDF_GetXFAPacketNamePtr.asFunction< - int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); - - /// Experimental API. - /// Function: FPDF_GetXFAPacketContent - /// Get the content of a packet in the XFA array. - /// Parameters: - /// document - Handle to the document. - /// index - Index number of the packet. 0 for the first packet. - /// buffer - Buffer for holding the content of the XFA packet. - /// buflen - Length of |buffer| in bytes. - /// out_buflen - Pointer to the variable that will receive the minimum - /// buffer size needed to contain the content of the XFA - /// packet. - /// Return value: - /// Whether the operation succeeded or not. - /// - /// |document| must be valid and |index| must be in the range [0, N), where N is - /// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be - /// NULL. When the aforementioned arguments are valid, the operation succeeds, - /// and |out_buflen| receives the content size. |buffer| is only modified if - /// |buffer| is non-null and long enough to contain the content. Callers must - /// check both the return value and the input |buflen| is no less than the - /// returned |out_buflen| before using the data in |buffer|. - int FPDF_GetXFAPacketContent( - FPDF_DOCUMENT document, - int index, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDF_GetXFAPacketContent( - document, - index, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDF_GetXFAPacketContentPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DOCUMENT, - ffi.Int, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>('FPDF_GetXFAPacketContent'); - late final _FPDF_GetXFAPacketContent = - _FPDF_GetXFAPacketContentPtr.asFunction< - int Function(FPDF_DOCUMENT, int, ffi.Pointer, int, - ffi.Pointer)>(); - - /// Function: FPDFDOC_InitFormFillEnvironment - /// Initialize form fill environment. - /// Parameters: - /// document - Handle to document from FPDF_LoadDocument(). - /// formInfo - Pointer to a FPDF_FORMFILLINFO structure. - /// Return Value: - /// Handle to the form fill module, or NULL on failure. - /// Comments: - /// This function should be called before any form fill operation. - /// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until - /// the returned FPDF_FORMHANDLE is closed. - FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment( - FPDF_DOCUMENT document, - ffi.Pointer formInfo, - ) { - return _FPDFDOC_InitFormFillEnvironment( - document, - formInfo, - ); - } - - late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< - ffi.NativeFunction< - FPDF_FORMHANDLE Function( - FPDF_DOCUMENT, ffi.Pointer)>>( - 'FPDFDOC_InitFormFillEnvironment'); - late final _FPDFDOC_InitFormFillEnvironment = - _FPDFDOC_InitFormFillEnvironmentPtr.asFunction< - FPDF_FORMHANDLE Function( - FPDF_DOCUMENT, ffi.Pointer)>(); - - /// Function: FPDFDOC_ExitFormFillEnvironment - /// Take ownership of |hHandle| and exit form fill environment. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - /// Comments: - /// This function is a no-op when |hHandle| is null. - void FPDFDOC_ExitFormFillEnvironment( - FPDF_FORMHANDLE hHandle, - ) { - return _FPDFDOC_ExitFormFillEnvironment( - hHandle, - ); - } - - late final _FPDFDOC_ExitFormFillEnvironmentPtr = - _lookup>( - 'FPDFDOC_ExitFormFillEnvironment'); - late final _FPDFDOC_ExitFormFillEnvironment = - _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction< - void Function(FPDF_FORMHANDLE)>(); - - /// Function: FORM_OnAfterLoadPage - /// This method is required for implementing all the form related - /// functions. Should be invoked after user successfully loaded a - /// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - void FORM_OnAfterLoadPage( - FPDF_PAGE page, - FPDF_FORMHANDLE hHandle, - ) { - return _FORM_OnAfterLoadPage( - page, - hHandle, - ); - } - - late final _FORM_OnAfterLoadPagePtr = _lookup< - ffi.NativeFunction>( - 'FORM_OnAfterLoadPage'); - late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction< - void Function(FPDF_PAGE, FPDF_FORMHANDLE)>(); - - /// Function: FORM_OnBeforeClosePage - /// This method is required for implementing all the form related - /// functions. Should be invoked before user closes the PDF page. - /// Parameters: - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - void FORM_OnBeforeClosePage( - FPDF_PAGE page, - FPDF_FORMHANDLE hHandle, - ) { - return _FORM_OnBeforeClosePage( - page, - hHandle, - ); - } - - late final _FORM_OnBeforeClosePagePtr = _lookup< - ffi.NativeFunction>( - 'FORM_OnBeforeClosePage'); - late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction< - void Function(FPDF_PAGE, FPDF_FORMHANDLE)>(); - - /// Function: FORM_DoDocumentJSAction - /// This method is required for performing document-level JavaScript - /// actions. It should be invoked after the PDF document has been loaded. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - /// Comments: - /// If there is document-level JavaScript action embedded in the - /// document, this method will execute the JavaScript action. Otherwise, - /// the method will do nothing. - void FORM_DoDocumentJSAction( - FPDF_FORMHANDLE hHandle, - ) { - return _FORM_DoDocumentJSAction( - hHandle, - ); - } - - late final _FORM_DoDocumentJSActionPtr = - _lookup>( - 'FORM_DoDocumentJSAction'); - late final _FORM_DoDocumentJSAction = - _FORM_DoDocumentJSActionPtr.asFunction(); - - /// Function: FORM_DoDocumentOpenAction - /// This method is required for performing open-action when the document - /// is opened. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - /// Comments: - /// This method will do nothing if there are no open-actions embedded - /// in the document. - void FORM_DoDocumentOpenAction( - FPDF_FORMHANDLE hHandle, - ) { - return _FORM_DoDocumentOpenAction( - hHandle, - ); - } - - late final _FORM_DoDocumentOpenActionPtr = - _lookup>( - 'FORM_DoDocumentOpenAction'); - late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr - .asFunction(); - - /// Function: FORM_DoDocumentAAction - /// This method is required for performing the document's - /// additional-action. - /// Parameters: - /// hHandle - Handle to the form fill module. Returned by - /// FPDFDOC_InitFormFillEnvironment. - /// aaType - The type of the additional-actions which defined - /// above. - /// Return Value: - /// None. - /// Comments: - /// This method will do nothing if there is no document - /// additional-action corresponding to the specified |aaType|. - void FORM_DoDocumentAAction( - FPDF_FORMHANDLE hHandle, - int aaType, - ) { - return _FORM_DoDocumentAAction( - hHandle, - aaType, - ); - } - - late final _FORM_DoDocumentAActionPtr = - _lookup>( - 'FORM_DoDocumentAAction'); - late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction< - void Function(FPDF_FORMHANDLE, int)>(); - - /// Function: FORM_DoPageAAction - /// This method is required for performing the page object's - /// additional-action when opened or closed. - /// Parameters: - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// aaType - The type of the page object's additional-actions - /// which defined above. - /// Return Value: - /// None. - /// Comments: - /// This method will do nothing if no additional-action corresponding - /// to the specified |aaType| exists. - void FORM_DoPageAAction( - FPDF_PAGE page, - FPDF_FORMHANDLE hHandle, - int aaType, - ) { - return _FORM_DoPageAAction( - page, - hHandle, - aaType, - ); - } - - late final _FORM_DoPageAActionPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - FPDF_PAGE, FPDF_FORMHANDLE, ffi.Int)>>('FORM_DoPageAAction'); - late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction< - void Function(FPDF_PAGE, FPDF_FORMHANDLE, int)>(); - - /// Function: FORM_OnMouseMove - /// Call this member function when the mouse cursor moves. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_x - Specifies the x-coordinate of the cursor in PDF user - /// space. - /// page_y - Specifies the y-coordinate of the cursor in PDF user - /// space. - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnMouseMove( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnMouseMove( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnMouseMovePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnMouseMove'); - late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Experimental API - /// Function: FORM_OnMouseWheel - /// Call this member function when the user scrolls the mouse wheel. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_coord - Specifies the coordinates of the cursor in PDF user - /// space. - /// delta_x - Specifies the amount of wheel movement on the x-axis, - /// in units of platform-agnostic wheel deltas. Negative - /// values mean left. - /// delta_y - Specifies the amount of wheel movement on the y-axis, - /// in units of platform-agnostic wheel deltas. Negative - /// values mean down. - /// Return Value: - /// True indicates success; otherwise false. - /// Comments: - /// For |delta_x| and |delta_y|, the caller must normalize - /// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 - /// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines - /// WHEEL_DELTA as 120. - int FORM_OnMouseWheel( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - ffi.Pointer page_coord, - int delta_x, - int delta_y, - ) { - return _FORM_OnMouseWheel( - hHandle, - page, - modifier, - page_coord, - delta_x, - delta_y, - ); - } - - late final _FORM_OnMouseWheelPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, - ffi.Pointer, ffi.Int, ffi.Int)>>('FORM_OnMouseWheel'); - late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction< - int Function( - FPDF_FORMHANDLE, FPDF_PAGE, int, ffi.Pointer, int, int)>(); - - /// Function: FORM_OnFocus - /// This function focuses the form annotation at a given point. If the - /// annotation at the point already has focus, nothing happens. If there - /// is no annotation at the point, removes form focus. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_x - Specifies the x-coordinate of the cursor in PDF user - /// space. - /// page_y - Specifies the y-coordinate of the cursor in PDF user - /// space. - /// Return Value: - /// True if there is an annotation at the given point and it has focus. - int FORM_OnFocus( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnFocus( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnFocusPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnFocus'); - late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnLButtonDown - /// Call this member function when the user presses the left - /// mouse button. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_x - Specifies the x-coordinate of the cursor in PDF user - /// space. - /// page_y - Specifies the y-coordinate of the cursor in PDF user - /// space. - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnLButtonDown( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnLButtonDown( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnLButtonDownPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnLButtonDown'); - late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnRButtonDown - /// Same as above, execpt for the right mouse button. - /// Comments: - /// At the present time, has no effect except in XFA builds, but is - /// included for the sake of symmetry. - int FORM_OnRButtonDown( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnRButtonDown( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnRButtonDownPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnRButtonDown'); - late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnLButtonUp - /// Call this member function when the user releases the left - /// mouse button. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_x - Specifies the x-coordinate of the cursor in device. - /// page_y - Specifies the y-coordinate of the cursor in device. - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnLButtonUp( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnLButtonUp( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnLButtonUpPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnLButtonUp'); - late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnRButtonUp - /// Same as above, execpt for the right mouse button. - /// Comments: - /// At the present time, has no effect except in XFA builds, but is - /// included for the sake of symmetry. - int FORM_OnRButtonUp( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnRButtonUp( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnRButtonUpPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnRButtonUp'); - late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnLButtonDoubleClick - /// Call this member function when the user double clicks the - /// left mouse button. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// modifier - Indicates whether various virtual keys are down. - /// page_x - Specifies the x-coordinate of the cursor in PDF user - /// space. - /// page_y - Specifies the y-coordinate of the cursor in PDF user - /// space. - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnLButtonDoubleClick( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y, - ) { - return _FORM_OnLButtonDoubleClick( - hHandle, - page, - modifier, - page_x, - page_y, - ); - } - - late final _FORM_OnLButtonDoubleClickPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Double, - ffi.Double)>>('FORM_OnLButtonDoubleClick'); - late final _FORM_OnLButtonDoubleClick = - _FORM_OnLButtonDoubleClickPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, double, double)>(); - - /// Function: FORM_OnKeyDown - /// Call this member function when a nonsystem key is pressed. - /// Parameters: - /// hHandle - Handle to the form fill module, aseturned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// nKeyCode - The virtual-key code of the given key (see - /// fpdf_fwlevent.h for virtual key codes). - /// modifier - Mask of key flags (see fpdf_fwlevent.h for key - /// flag values). - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnKeyDown( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nKeyCode, - int modifier, - ) { - return _FORM_OnKeyDown( - hHandle, - page, - nKeyCode, - modifier, - ); - } - - late final _FORM_OnKeyDownPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Int)>>('FORM_OnKeyDown'); - late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); - - /// Function: FORM_OnKeyUp - /// Call this member function when a nonsystem key is released. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// nKeyCode - The virtual-key code of the given key (see - /// fpdf_fwlevent.h for virtual key codes). - /// modifier - Mask of key flags (see fpdf_fwlevent.h for key - /// flag values). - /// Return Value: - /// True indicates success; otherwise false. - /// Comments: - /// Currently unimplemented and always returns false. PDFium reserves this - /// API and may implement it in the future on an as-needed basis. - int FORM_OnKeyUp( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nKeyCode, - int modifier, - ) { - return _FORM_OnKeyUp( - hHandle, - page, - nKeyCode, - modifier, - ); - } - - late final _FORM_OnKeyUpPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Int)>>('FORM_OnKeyUp'); - late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); - - /// Function: FORM_OnChar - /// Call this member function when a keystroke translates to a - /// nonsystem character. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// nChar - The character code value itself. - /// modifier - Mask of key flags (see fpdf_fwlevent.h for key - /// flag values). - /// Return Value: - /// True indicates success; otherwise false. - int FORM_OnChar( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nChar, - int modifier, - ) { - return _FORM_OnChar( - hHandle, - page, - nChar, - modifier, - ); - } - - late final _FORM_OnCharPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, ffi.Int)>>('FORM_OnChar'); - late final _FORM_OnChar = _FORM_OnCharPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); - - /// Experimental API - /// Function: FORM_GetFocusedText - /// Call this function to obtain the text within the current focused - /// field, if any. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// buffer - Buffer for holding the form text, encoded in - /// UTF-16LE. If NULL, |buffer| is not modified. - /// buflen - Length of |buffer| in bytes. If |buflen| is less - /// than the length of the form text string, |buffer| is - /// not modified. - /// Return Value: - /// Length in bytes for the text in the focused field. - int FORM_GetFocusedText( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ffi.Pointer buffer, - int buflen, - ) { - return _FORM_GetFocusedText( - hHandle, - page, - buffer, - buflen, - ); - } - - late final _FORM_GetFocusedTextPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_FORMHANDLE, FPDF_PAGE, - ffi.Pointer, ffi.UnsignedLong)>>('FORM_GetFocusedText'); - late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer, int)>(); - - /// Function: FORM_GetSelectedText - /// Call this function to obtain selected text within a form text - /// field or form combobox text field. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// buffer - Buffer for holding the selected text, encoded in - /// UTF-16LE. If NULL, |buffer| is not modified. - /// buflen - Length of |buffer| in bytes. If |buflen| is less - /// than the length of the selected text string, - /// |buffer| is not modified. - /// Return Value: - /// Length in bytes of selected text in form text field or form combobox - /// text field. - int FORM_GetSelectedText( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ffi.Pointer buffer, - int buflen, - ) { - return _FORM_GetSelectedText( - hHandle, - page, - buffer, - buflen, - ); - } - - late final _FORM_GetSelectedTextPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_PAGE, - ffi.Pointer, - ffi.UnsignedLong)>>('FORM_GetSelectedText'); - late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer, int)>(); - - /// Experimental API - /// Function: FORM_ReplaceAndKeepSelection - /// Call this function to replace the selected text in a form - /// text field or user-editable form combobox text field with another - /// text string (which can be empty or non-empty). If there is no - /// selected text, this function will append the replacement text after - /// the current caret position. After the insertion, the inserted text - /// will be selected. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as Returned by FPDF_LoadPage(). - /// wsText - The text to be inserted, in UTF-16LE format. - /// Return Value: - /// None. - void FORM_ReplaceAndKeepSelection( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - FPDF_WIDESTRING wsText, - ) { - return _FORM_ReplaceAndKeepSelection( - hHandle, - page, - wsText, - ); - } - - late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_FORMHANDLE, FPDF_PAGE, - FPDF_WIDESTRING)>>('FORM_ReplaceAndKeepSelection'); - late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr - .asFunction(); - - /// Function: FORM_ReplaceSelection - /// Call this function to replace the selected text in a form - /// text field or user-editable form combobox text field with another - /// text string (which can be empty or non-empty). If there is no - /// selected text, this function will append the replacement text after - /// the current caret position. After the insertion, the selection range - /// will be set to empty. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as Returned by FPDF_LoadPage(). - /// wsText - The text to be inserted, in UTF-16LE format. - /// Return Value: - /// None. - void FORM_ReplaceSelection( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - FPDF_WIDESTRING wsText, - ) { - return _FORM_ReplaceSelection( - hHandle, - page, - wsText, - ); - } - - late final _FORM_ReplaceSelectionPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_FORMHANDLE, FPDF_PAGE, - FPDF_WIDESTRING)>>('FORM_ReplaceSelection'); - late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction< - void Function(FPDF_FORMHANDLE, FPDF_PAGE, FPDF_WIDESTRING)>(); - - /// Experimental API - /// Function: FORM_SelectAllText - /// Call this function to select all the text within the currently focused - /// form text field or form combobox text field. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// Return Value: - /// Whether the operation succeeded or not. - int FORM_SelectAllText( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ) { - return _FORM_SelectAllText( - hHandle, - page, - ); - } - - late final _FORM_SelectAllTextPtr = _lookup< - ffi.NativeFunction>( - 'FORM_SelectAllText'); - late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE)>(); - - /// Function: FORM_CanUndo - /// Find out if it is possible for the current focused widget in a given - /// form to perform an undo operation. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// Return Value: - /// True if it is possible to undo. - int FORM_CanUndo( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ) { - return _FORM_CanUndo( - hHandle, - page, - ); - } - - late final _FORM_CanUndoPtr = _lookup< - ffi.NativeFunction>( - 'FORM_CanUndo'); - late final _FORM_CanUndo = - _FORM_CanUndoPtr.asFunction(); - - /// Function: FORM_CanRedo - /// Find out if it is possible for the current focused widget in a given - /// form to perform a redo operation. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// Return Value: - /// True if it is possible to redo. - int FORM_CanRedo( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ) { - return _FORM_CanRedo( - hHandle, - page, - ); - } - - late final _FORM_CanRedoPtr = _lookup< - ffi.NativeFunction>( - 'FORM_CanRedo'); - late final _FORM_CanRedo = - _FORM_CanRedoPtr.asFunction(); - - /// Function: FORM_Undo - /// Make the current focused widget perform an undo operation. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// Return Value: - /// True if the undo operation succeeded. - int FORM_Undo( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ) { - return _FORM_Undo( - hHandle, - page, - ); - } - - late final _FORM_UndoPtr = _lookup< - ffi.NativeFunction>( - 'FORM_Undo'); - late final _FORM_Undo = - _FORM_UndoPtr.asFunction(); - - /// Function: FORM_Redo - /// Make the current focused widget perform a redo operation. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// Return Value: - /// True if the redo operation succeeded. - int FORM_Redo( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ) { - return _FORM_Redo( - hHandle, - page, - ); - } - - late final _FORM_RedoPtr = _lookup< - ffi.NativeFunction>( - 'FORM_Redo'); - late final _FORM_Redo = - _FORM_RedoPtr.asFunction(); - - /// Function: FORM_ForceToKillFocus. - /// Call this member function to force to kill the focus of the form - /// field which has focus. If it would kill the focus of a form field, - /// save the value of form field if was changed by theuser. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// True indicates success; otherwise false. - int FORM_ForceToKillFocus( - FPDF_FORMHANDLE hHandle, - ) { - return _FORM_ForceToKillFocus( - hHandle, - ); - } - - late final _FORM_ForceToKillFocusPtr = - _lookup>( - 'FORM_ForceToKillFocus'); - late final _FORM_ForceToKillFocus = - _FORM_ForceToKillFocusPtr.asFunction(); - - /// Experimental API. - /// Function: FORM_GetFocusedAnnot. - /// Call this member function to get the currently focused annotation. - /// Parameters: - /// handle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page_index - Buffer to hold the index number of the page which - /// contains the focused annotation. 0 for the first page. - /// Can't be NULL. - /// annot - Buffer to hold the focused annotation. Can't be NULL. - /// Return Value: - /// On success, return true and write to the out parameters. Otherwise - /// return false and leave the out parameters unmodified. - /// Comments: - /// Not currently supported for XFA forms - will report no focused - /// annotation. - /// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| - /// by this function is no longer needed. - /// This will return true and set |page_index| to -1 and |annot| to NULL, - /// if there is no focused annotation. - int FORM_GetFocusedAnnot( - FPDF_FORMHANDLE handle, - ffi.Pointer page_index, - ffi.Pointer annot, - ) { - return _FORM_GetFocusedAnnot( - handle, - page_index, - annot, - ); - } - - late final _FORM_GetFocusedAnnotPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, ffi.Pointer, - ffi.Pointer)>>('FORM_GetFocusedAnnot'); - late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction< - int Function(FPDF_FORMHANDLE, ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FORM_SetFocusedAnnot. - /// Call this member function to set the currently focused annotation. - /// Parameters: - /// handle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - Handle to an annotation. - /// Return Value: - /// True indicates success; otherwise false. - /// Comments: - /// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() - /// instead. - int FORM_SetFocusedAnnot( - FPDF_FORMHANDLE handle, - FPDF_ANNOTATION annot, - ) { - return _FORM_SetFocusedAnnot( - handle, - annot, - ); - } - - late final _FORM_SetFocusedAnnotPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_ANNOTATION)>>('FORM_SetFocusedAnnot'); - late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION)>(); - - /// Function: FPDFPage_HasFormFieldAtPoint - /// Get the form field type by point. - /// Parameters: - /// hHandle - Handle to the form fill module. Returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// page_x - X position in PDF "user space". - /// page_y - Y position in PDF "user space". - /// Return Value: - /// Return the type of the form field; -1 indicates no field. - /// See field types above. - int FPDFPage_HasFormFieldAtPoint( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - double page_x, - double page_y, - ) { - return _FPDFPage_HasFormFieldAtPoint( - hHandle, - page, - page_x, - page_y, - ); - } - - late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Double, - ffi.Double)>>('FPDFPage_HasFormFieldAtPoint'); - late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr - .asFunction(); - - /// Function: FPDFPage_FormFieldZOrderAtPoint - /// Get the form field z-order by point. - /// Parameters: - /// hHandle - Handle to the form fill module. Returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// page_x - X position in PDF "user space". - /// page_y - Y position in PDF "user space". - /// Return Value: - /// Return the z-order of the form field; -1 indicates no field. - /// Higher numbers are closer to the front. - int FPDFPage_FormFieldZOrderAtPoint( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - double page_x, - double page_y, - ) { - return _FPDFPage_FormFieldZOrderAtPoint( - hHandle, - page, - page_x, - page_y, - ); - } - - late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Double, - ffi.Double)>>('FPDFPage_FormFieldZOrderAtPoint'); - late final _FPDFPage_FormFieldZOrderAtPoint = - _FPDFPage_FormFieldZOrderAtPointPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, double, double)>(); - - /// Function: FPDF_SetFormFieldHighlightColor - /// Set the highlight color of the specified (or all) form fields - /// in the document. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// doc - Handle to the document, as returned by - /// FPDF_LoadDocument(). - /// fieldType - A 32-bit integer indicating the type of a form - /// field (defined above). - /// color - The highlight color of the form field. Constructed by - /// 0xxxrrggbb. - /// Return Value: - /// None. - /// Comments: - /// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the - /// highlight color will be applied to all the form fields in the - /// document. - /// Please refresh the client window to show the highlight immediately - /// if necessary. - void FPDF_SetFormFieldHighlightColor( - FPDF_FORMHANDLE hHandle, - int fieldType, - int color, - ) { - return _FPDF_SetFormFieldHighlightColor( - hHandle, - fieldType, - color, - ); - } - - late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_FORMHANDLE, ffi.Int, - ffi.UnsignedLong)>>('FPDF_SetFormFieldHighlightColor'); - late final _FPDF_SetFormFieldHighlightColor = - _FPDF_SetFormFieldHighlightColorPtr.asFunction< - void Function(FPDF_FORMHANDLE, int, int)>(); - - /// Function: FPDF_SetFormFieldHighlightAlpha - /// Set the transparency of the form field highlight color in the - /// document. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// doc - Handle to the document, as returaned by - /// FPDF_LoadDocument(). - /// alpha - The transparency of the form field highlight color, - /// between 0-255. - /// Return Value: - /// None. - void FPDF_SetFormFieldHighlightAlpha( - FPDF_FORMHANDLE hHandle, - int alpha, - ) { - return _FPDF_SetFormFieldHighlightAlpha( - hHandle, - alpha, - ); - } - - late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_FORMHANDLE, - ffi.UnsignedChar)>>('FPDF_SetFormFieldHighlightAlpha'); - late final _FPDF_SetFormFieldHighlightAlpha = - _FPDF_SetFormFieldHighlightAlphaPtr.asFunction< - void Function(FPDF_FORMHANDLE, int)>(); - - /// Function: FPDF_RemoveFormFieldHighlight - /// Remove the form field highlight color in the document. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// Return Value: - /// None. - /// Comments: - /// Please refresh the client window to remove the highlight immediately - /// if necessary. - void FPDF_RemoveFormFieldHighlight( - FPDF_FORMHANDLE hHandle, - ) { - return _FPDF_RemoveFormFieldHighlight( - hHandle, - ); - } - - late final _FPDF_RemoveFormFieldHighlightPtr = - _lookup>( - 'FPDF_RemoveFormFieldHighlight'); - late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr - .asFunction(); - - /// Function: FPDF_FFLDraw - /// Render FormFields and popup window on a page to a device independent - /// bitmap. - /// Parameters: - /// hHandle - Handle to the form fill module, as returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// bitmap - Handle to the device independent bitmap (as the - /// output buffer). Bitmap handles can be created by - /// FPDFBitmap_Create(). - /// page - Handle to the page, as returned by FPDF_LoadPage(). - /// start_x - Left pixel position of the display area in the - /// device coordinates. - /// start_y - Top pixel position of the display area in the device - /// coordinates. - /// size_x - Horizontal size (in pixels) for displaying the page. - /// size_y - Vertical size (in pixels) for displaying the page. - /// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees - /// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 - /// degrees counter-clockwise). - /// flags - 0 for normal display, or combination of flags - /// defined above. - /// Return Value: - /// None. - /// Comments: - /// This function is designed to render annotations that are - /// user-interactive, which are widget annotations (for FormFields) and - /// popup annotations. - /// With the FPDF_ANNOT flag, this function will render a popup annotation - /// when users mouse-hover on a non-widget annotation. Regardless of - /// FPDF_ANNOT flag, this function will always render widget annotations - /// for FormFields. - /// In order to implement the FormFill functions, implementation should - /// call this function after rendering functions, such as - /// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have - /// finished rendering the page contents. - void FPDF_FFLDraw( - FPDF_FORMHANDLE hHandle, - FPDF_BITMAP bitmap, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags, - ) { - return _FPDF_FFLDraw( - hHandle, - bitmap, - page, - start_x, - start_y, - size_x, - size_y, - rotate, - flags, - ); - } - - late final _FPDF_FFLDrawPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_FORMHANDLE, FPDF_BITMAP, FPDF_PAGE, ffi.Int, - ffi.Int, ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('FPDF_FFLDraw'); - late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction< - void Function(FPDF_FORMHANDLE, FPDF_BITMAP, FPDF_PAGE, int, int, int, int, - int, int)>(); - - /// Experimental API - /// Function: FPDF_GetFormType - /// Returns the type of form contained in the PDF document. - /// Parameters: - /// document - Handle to document. - /// Return Value: - /// Integer value representing one of the FORMTYPE_ values. - /// Comments: - /// If |document| is NULL, then the return value is FORMTYPE_NONE. - int FPDF_GetFormType( - FPDF_DOCUMENT document, - ) { - return _FPDF_GetFormType( - document, - ); - } - - late final _FPDF_GetFormTypePtr = - _lookup>( - 'FPDF_GetFormType'); - late final _FPDF_GetFormType = - _FPDF_GetFormTypePtr.asFunction(); - - /// Experimental API - /// Function: FORM_SetIndexSelected - /// Selects/deselects the value at the given |index| of the focused - /// annotation. - /// Parameters: - /// hHandle - Handle to the form fill module. Returned by - /// FPDFDOC_InitFormFillEnvironment. - /// page - Handle to the page. Returned by FPDF_LoadPage - /// index - 0-based index of value to be set as - /// selected/unselected - /// selected - true to select, false to deselect - /// Return Value: - /// TRUE if the operation succeeded. - /// FALSE if the operation failed or widget is not a supported type. - /// Comments: - /// Intended for use with listbox/combobox widget types. Comboboxes - /// have at most a single value selected at a time which cannot be - /// deselected. Deselect on a combobox is a no-op that returns false. - /// Default implementation is a no-op that will return false for - /// other types. - /// Not currently supported for XFA forms - will return false. - int FORM_SetIndexSelected( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int index, - int selected, - ) { - return _FORM_SetIndexSelected( - hHandle, - page, - index, - selected, - ); - } - - late final _FORM_SetIndexSelectedPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int, - FPDF_BOOL)>>('FORM_SetIndexSelected'); - late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int, int)>(); - - /// Experimental API - /// Function: FORM_IsIndexSelected - /// Returns whether or not the value at |index| of the focused - /// annotation is currently selected. - /// Parameters: - /// hHandle - Handle to the form fill module. Returned by - /// FPDFDOC_InitFormFillEnvironment. - /// page - Handle to the page. Returned by FPDF_LoadPage - /// index - 0-based Index of value to check - /// Return Value: - /// TRUE if value at |index| is currently selected. - /// FALSE if value at |index| is not selected or widget is not a - /// supported type. - /// Comments: - /// Intended for use with listbox/combobox widget types. Default - /// implementation is a no-op that will return false for other types. - /// Not currently supported for XFA forms - will return false. - int FORM_IsIndexSelected( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int index, - ) { - return _FORM_IsIndexSelected( - hHandle, - page, - index, - ); - } - - late final _FORM_IsIndexSelectedPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_PAGE, ffi.Int)>>('FORM_IsIndexSelected'); - late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_PAGE, int)>(); - - /// Function: FPDF_LoadXFA - /// If the document consists of XFA fields, call this method to - /// attempt to load XFA fields. - /// Parameters: - /// document - Handle to document from FPDF_LoadDocument(). - /// Return Value: - /// TRUE upon success, otherwise FALSE. If XFA support is not built - /// into PDFium, performs no action and always returns FALSE. - int FPDF_LoadXFA( - FPDF_DOCUMENT document, - ) { - return _FPDF_LoadXFA( - document, - ); - } - - late final _FPDF_LoadXFAPtr = - _lookup>( - 'FPDF_LoadXFA'); - late final _FPDF_LoadXFA = - _FPDF_LoadXFAPtr.asFunction(); - - /// Experimental API. - /// Check if an annotation subtype is currently supported for creation. - /// Currently supported subtypes: - /// - circle - /// - fileattachment - /// - freetext - /// - highlight - /// - ink - /// - link - /// - popup - /// - square, - /// - squiggly - /// - stamp - /// - strikeout - /// - text - /// - underline - /// - /// subtype - the subtype to be checked. - /// - /// Returns true if this subtype supported. - int FPDFAnnot_IsSupportedSubtype( - int subtype, - ) { - return _FPDFAnnot_IsSupportedSubtype( - subtype, - ); - } - - late final _FPDFAnnot_IsSupportedSubtypePtr = - _lookup>( - 'FPDFAnnot_IsSupportedSubtype'); - late final _FPDFAnnot_IsSupportedSubtype = - _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); - - /// Experimental API. - /// Create an annotation in |page| of the subtype |subtype|. If the specified - /// subtype is illegal or unsupported, then a new annotation will not be created. - /// Must call FPDFPage_CloseAnnot() when the annotation returned by this - /// function is no longer needed. - /// - /// page - handle to a page. - /// subtype - the subtype of the new annotation. - /// - /// Returns a handle to the new annotation object, or NULL on failure. - FPDF_ANNOTATION FPDFPage_CreateAnnot( - FPDF_PAGE page, - int subtype, - ) { - return _FPDFPage_CreateAnnot( - page, - subtype, - ); - } - - late final _FPDFPage_CreateAnnotPtr = _lookup< - ffi.NativeFunction< - FPDF_ANNOTATION Function( - FPDF_PAGE, FPDF_ANNOTATION_SUBTYPE)>>('FPDFPage_CreateAnnot'); - late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction< - FPDF_ANNOTATION Function(FPDF_PAGE, int)>(); - - /// Experimental API. - /// Get the number of annotations in |page|. - /// - /// page - handle to a page. - /// - /// Returns the number of annotations in |page|. - int FPDFPage_GetAnnotCount( - FPDF_PAGE page, - ) { - return _FPDFPage_GetAnnotCount( - page, - ); - } - - late final _FPDFPage_GetAnnotCountPtr = - _lookup>( - 'FPDFPage_GetAnnotCount'); - late final _FPDFPage_GetAnnotCount = - _FPDFPage_GetAnnotCountPtr.asFunction(); - - /// Experimental API. - /// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the - /// annotation returned by this function is no longer needed. - /// - /// page - handle to a page. - /// index - the index of the annotation. - /// - /// Returns a handle to the annotation object, or NULL on failure. - FPDF_ANNOTATION FPDFPage_GetAnnot( - FPDF_PAGE page, - int index, - ) { - return _FPDFPage_GetAnnot( - page, - index, - ); - } - - late final _FPDFPage_GetAnnotPtr = - _lookup>( - 'FPDFPage_GetAnnot'); - late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction< - FPDF_ANNOTATION Function(FPDF_PAGE, int)>(); - - /// Experimental API. - /// Get the index of |annot| in |page|. This is the opposite of - /// FPDFPage_GetAnnot(). - /// - /// page - handle to the page that the annotation is on. - /// annot - handle to an annotation. - /// - /// Returns the index of |annot|, or -1 on failure. - int FPDFPage_GetAnnotIndex( - FPDF_PAGE page, - FPDF_ANNOTATION annot, - ) { - return _FPDFPage_GetAnnotIndex( - page, - annot, - ); - } - - late final _FPDFPage_GetAnnotIndexPtr = - _lookup>( - 'FPDFPage_GetAnnotIndex'); - late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction< - int Function(FPDF_PAGE, FPDF_ANNOTATION)>(); - - /// Experimental API. - /// Close an annotation. Must be called when the annotation returned by - /// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This - /// function does not remove the annotation from the document. - /// - /// annot - handle to an annotation. - void FPDFPage_CloseAnnot( - FPDF_ANNOTATION annot, - ) { - return _FPDFPage_CloseAnnot( - annot, - ); - } - - late final _FPDFPage_CloseAnnotPtr = - _lookup>( - 'FPDFPage_CloseAnnot'); - late final _FPDFPage_CloseAnnot = - _FPDFPage_CloseAnnotPtr.asFunction(); - - /// Experimental API. - /// Remove the annotation in |page| at |index|. - /// - /// page - handle to a page. - /// index - the index of the annotation. - /// - /// Returns true if successful. - int FPDFPage_RemoveAnnot( - FPDF_PAGE page, - int index, - ) { - return _FPDFPage_RemoveAnnot( - page, - index, - ); - } - - late final _FPDFPage_RemoveAnnotPtr = - _lookup>( - 'FPDFPage_RemoveAnnot'); - late final _FPDFPage_RemoveAnnot = - _FPDFPage_RemoveAnnotPtr.asFunction(); - - /// Experimental API. - /// Get the subtype of an annotation. - /// - /// annot - handle to an annotation. - /// - /// Returns the annotation subtype. - int FPDFAnnot_GetSubtype( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetSubtype( - annot, - ); - } - - late final _FPDFAnnot_GetSubtypePtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetSubtype'); - late final _FPDFAnnot_GetSubtype = - _FPDFAnnot_GetSubtypePtr.asFunction(); - - /// Experimental API. - /// Check if an annotation subtype is currently supported for object extraction, - /// update, and removal. - /// Currently supported subtypes: ink and stamp. - /// - /// subtype - the subtype to be checked. - /// - /// Returns true if this subtype supported. - int FPDFAnnot_IsObjectSupportedSubtype( - int subtype, - ) { - return _FPDFAnnot_IsObjectSupportedSubtype( - subtype, - ); - } - - late final _FPDFAnnot_IsObjectSupportedSubtypePtr = - _lookup>( - 'FPDFAnnot_IsObjectSupportedSubtype'); - late final _FPDFAnnot_IsObjectSupportedSubtype = - _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); - - /// Experimental API. - /// Update |obj| in |annot|. |obj| must be in |annot| already and must have - /// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp - /// annotations are supported by this API. Also note that only path, image, and - /// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and - /// FPDFImageObj_*(). - /// - /// annot - handle to an annotation. - /// obj - handle to the object that |annot| needs to update. - /// - /// Return true if successful. - int FPDFAnnot_UpdateObject( - FPDF_ANNOTATION annot, - FPDF_PAGEOBJECT obj, - ) { - return _FPDFAnnot_UpdateObject( - annot, - obj, - ); - } - - late final _FPDFAnnot_UpdateObjectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, FPDF_PAGEOBJECT)>>('FPDFAnnot_UpdateObject'); - late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_PAGEOBJECT)>(); - - /// Experimental API. - /// Add a new InkStroke, represented by an array of points, to the InkList of - /// |annot|. The API creates an InkList if one doesn't already exist in |annot|. - /// This API works only for ink annotations. Please refer to ISO 32000-1:2008 - /// spec, section 12.5.6.13. - /// - /// annot - handle to an annotation. - /// points - pointer to a FS_POINTF array representing input points. - /// point_count - number of elements in |points| array. This should not exceed - /// the maximum value that can be represented by an int32_t). - /// - /// Returns the 0-based index at which the new InkStroke is added in the InkList - /// of the |annot|. Returns -1 on failure. - int FPDFAnnot_AddInkStroke( - FPDF_ANNOTATION annot, - ffi.Pointer points, - int point_count, - ) { - return _FPDFAnnot_AddInkStroke( - annot, - points, - point_count, - ); - } - - late final _FPDFAnnot_AddInkStrokePtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_ANNOTATION, ffi.Pointer, - ffi.Size)>>('FPDFAnnot_AddInkStroke'); - late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer, int)>(); - - /// Experimental API. - /// Removes an InkList in |annot|. - /// This API works only for ink annotations. - /// - /// annot - handle to an annotation. - /// - /// Return true on successful removal of /InkList entry from context of the - /// non-null ink |annot|. Returns false on failure. - int FPDFAnnot_RemoveInkList( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_RemoveInkList( - annot, - ); - } - - late final _FPDFAnnot_RemoveInkListPtr = - _lookup>( - 'FPDFAnnot_RemoveInkList'); - late final _FPDFAnnot_RemoveInkList = - _FPDFAnnot_RemoveInkListPtr.asFunction(); - - /// Experimental API. - /// Add |obj| to |annot|. |obj| must have been created by - /// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and - /// will be owned by |annot|. Note that an |obj| cannot belong to more than one - /// |annot|. Currently, only ink and stamp annotations are supported by this API. - /// Also note that only path, image, and text objects have APIs for creation. - /// - /// annot - handle to an annotation. - /// obj - handle to the object that is to be added to |annot|. - /// - /// Return true if successful. - int FPDFAnnot_AppendObject( - FPDF_ANNOTATION annot, - FPDF_PAGEOBJECT obj, - ) { - return _FPDFAnnot_AppendObject( - annot, - obj, - ); - } - - late final _FPDFAnnot_AppendObjectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, FPDF_PAGEOBJECT)>>('FPDFAnnot_AppendObject'); - late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_PAGEOBJECT)>(); - - /// Experimental API. - /// Get the total number of objects in |annot|, including path objects, text - /// objects, external objects, image objects, and shading objects. - /// - /// annot - handle to an annotation. - /// - /// Returns the number of objects in |annot|. - int FPDFAnnot_GetObjectCount( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetObjectCount( - annot, - ); - } - - late final _FPDFAnnot_GetObjectCountPtr = - _lookup>( - 'FPDFAnnot_GetObjectCount'); - late final _FPDFAnnot_GetObjectCount = - _FPDFAnnot_GetObjectCountPtr.asFunction(); - - /// Experimental API. - /// Get the object in |annot| at |index|. - /// - /// annot - handle to an annotation. - /// index - the index of the object. - /// - /// Return a handle to the object, or NULL on failure. - FPDF_PAGEOBJECT FPDFAnnot_GetObject( - FPDF_ANNOTATION annot, - int index, - ) { - return _FPDFAnnot_GetObject( - annot, - index, - ); - } - - late final _FPDFAnnot_GetObjectPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetObject'); - late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_ANNOTATION, int)>(); - - /// Experimental API. - /// Remove the object in |annot| at |index|. - /// - /// annot - handle to an annotation. - /// index - the index of the object to be removed. - /// - /// Return true if successful. - int FPDFAnnot_RemoveObject( - FPDF_ANNOTATION annot, - int index, - ) { - return _FPDFAnnot_RemoveObject( - annot, - index, - ); - } - - late final _FPDFAnnot_RemoveObjectPtr = - _lookup>( - 'FPDFAnnot_RemoveObject'); - late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction< - int Function(FPDF_ANNOTATION, int)>(); - - /// Experimental API. - /// Set the color of an annotation. Fails when called on annotations with - /// appearance streams already defined; instead use - /// FPDFPageObj_Set{Stroke|Fill}Color(). - /// - /// annot - handle to an annotation. - /// type - type of the color to be set. - /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. - /// A - buffer to hold the opacity. Ranges from 0 to 255. - /// - /// Returns true if successful. - DartFPDF_BOOL FPDFAnnot_SetColor( - FPDF_ANNOTATION annot, - FPDFANNOT_COLORTYPE type, - int R, - int G, - int B, - int A, - ) { - return _FPDFAnnot_SetColor( - annot, - type.value, - R, - G, - B, - A, - ); - } - - late final _FPDFAnnot_SetColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, - ffi.UnsignedInt, - ffi.UnsignedInt, - ffi.UnsignedInt, - ffi.UnsignedInt, - ffi.UnsignedInt)>>('FPDFAnnot_SetColor'); - late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction< - int Function(FPDF_ANNOTATION, int, int, int, int, int)>(); - - /// Experimental API. - /// Get the color of an annotation. If no color is specified, default to yellow - /// for highlight annotation, black for all else. Fails when called on - /// annotations with appearance streams already defined; instead use - /// FPDFPageObj_Get{Stroke|Fill}Color(). - /// - /// annot - handle to an annotation. - /// type - type of the color requested. - /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. - /// A - buffer to hold the opacity. Ranges from 0 to 255. - /// - /// Returns true if successful. - DartFPDF_BOOL FPDFAnnot_GetColor( - FPDF_ANNOTATION annot, - FPDFANNOT_COLORTYPE type, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ffi.Pointer A, - ) { - return _FPDFAnnot_GetColor( - annot, - type.value, - R, - G, - B, - A, - ); - } - - late final _FPDFAnnot_GetColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, - ffi.UnsignedInt, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFAnnot_GetColor'); - late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction< - int Function( - FPDF_ANNOTATION, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Check if the annotation is of a type that has attachment points - /// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that - /// encompasses the texts affected by the annotation. They provide the - /// coordinates in the page where the annotation is attached. Only text markup - /// annotations (i.e. highlight, strikeout, squiggly, and underline) and link - /// annotations have quadpoints. - /// - /// annot - handle to an annotation. - /// - /// Returns true if the annotation is of a type that has quadpoints, false - /// otherwise. - int FPDFAnnot_HasAttachmentPoints( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_HasAttachmentPoints( - annot, - ); - } - - late final _FPDFAnnot_HasAttachmentPointsPtr = - _lookup>( - 'FPDFAnnot_HasAttachmentPoints'); - late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr - .asFunction(); - - /// Experimental API. - /// Replace the attachment points (i.e. quadpoints) set of an annotation at - /// |quad_index|. This index needs to be within the result of - /// FPDFAnnot_CountAttachmentPoints(). - /// If the annotation's appearance stream is defined and this annotation is of a - /// type with quadpoints, then update the bounding box too if the new quadpoints - /// define a bigger one. - /// - /// annot - handle to an annotation. - /// quad_index - index of the set of quadpoints. - /// quad_points - the quadpoints to be set. - /// - /// Returns true if successful. - int FPDFAnnot_SetAttachmentPoints( - FPDF_ANNOTATION annot, - int quad_index, - ffi.Pointer quad_points, - ) { - return _FPDFAnnot_SetAttachmentPoints( - annot, - quad_index, - quad_points, - ); - } - - late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, ffi.Size, - ffi.Pointer)>>('FPDFAnnot_SetAttachmentPoints'); - late final _FPDFAnnot_SetAttachmentPoints = - _FPDFAnnot_SetAttachmentPointsPtr.asFunction< - int Function(FPDF_ANNOTATION, int, ffi.Pointer)>(); - - /// Experimental API. - /// Append to the list of attachment points (i.e. quadpoints) of an annotation. - /// If the annotation's appearance stream is defined and this annotation is of a - /// type with quadpoints, then update the bounding box too if the new quadpoints - /// define a bigger one. - /// - /// annot - handle to an annotation. - /// quad_points - the quadpoints to be set. - /// - /// Returns true if successful. - int FPDFAnnot_AppendAttachmentPoints( - FPDF_ANNOTATION annot, - ffi.Pointer quad_points, - ) { - return _FPDFAnnot_AppendAttachmentPoints( - annot, - quad_points, - ); - } - - late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, ffi.Pointer)>>( - 'FPDFAnnot_AppendAttachmentPoints'); - late final _FPDFAnnot_AppendAttachmentPoints = - _FPDFAnnot_AppendAttachmentPointsPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer)>(); - - /// Experimental API. - /// Get the number of sets of quadpoints of an annotation. - /// - /// annot - handle to an annotation. - /// - /// Returns the number of sets of quadpoints, or 0 on failure. - int FPDFAnnot_CountAttachmentPoints( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_CountAttachmentPoints( - annot, - ); - } - - late final _FPDFAnnot_CountAttachmentPointsPtr = - _lookup>( - 'FPDFAnnot_CountAttachmentPoints'); - late final _FPDFAnnot_CountAttachmentPoints = - _FPDFAnnot_CountAttachmentPointsPtr.asFunction< - int Function(FPDF_ANNOTATION)>(); - - /// Experimental API. - /// Get the attachment points (i.e. quadpoints) of an annotation. - /// - /// annot - handle to an annotation. - /// quad_index - index of the set of quadpoints. - /// quad_points - receives the quadpoints; must not be NULL. - /// - /// Returns true if successful. - int FPDFAnnot_GetAttachmentPoints( - FPDF_ANNOTATION annot, - int quad_index, - ffi.Pointer quad_points, - ) { - return _FPDFAnnot_GetAttachmentPoints( - annot, - quad_index, - quad_points, - ); - } - - late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, ffi.Size, - ffi.Pointer)>>('FPDFAnnot_GetAttachmentPoints'); - late final _FPDFAnnot_GetAttachmentPoints = - _FPDFAnnot_GetAttachmentPointsPtr.asFunction< - int Function(FPDF_ANNOTATION, int, ffi.Pointer)>(); - - /// Experimental API. - /// Set the annotation rectangle defining the location of the annotation. If the - /// annotation's appearance stream is defined and this annotation is of a type - /// without quadpoints, then update the bounding box too if the new rectangle - /// defines a bigger one. - /// - /// annot - handle to an annotation. - /// rect - the annotation rectangle to be set. - /// - /// Returns true if successful. - int FPDFAnnot_SetRect( - FPDF_ANNOTATION annot, - ffi.Pointer rect, - ) { - return _FPDFAnnot_SetRect( - annot, - rect, - ); - } - - late final _FPDFAnnot_SetRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, ffi.Pointer)>>('FPDFAnnot_SetRect'); - late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer)>(); - - /// Experimental API. - /// Get the annotation rectangle defining the location of the annotation. - /// - /// annot - handle to an annotation. - /// rect - receives the rectangle; must not be NULL. - /// - /// Returns true if successful. - int FPDFAnnot_GetRect( - FPDF_ANNOTATION annot, - ffi.Pointer rect, - ) { - return _FPDFAnnot_GetRect( - annot, - rect, - ); - } - - late final _FPDFAnnot_GetRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, ffi.Pointer)>>('FPDFAnnot_GetRect'); - late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer)>(); - - /// Experimental API. - /// Get the vertices of a polygon or polyline annotation. |buffer| is an array of - /// points of the annotation. If |length| is less than the returned length, or - /// |annot| or |buffer| is NULL, |buffer| will not be modified. - /// - /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() - /// buffer - buffer for holding the points. - /// length - length of the buffer in points. - /// - /// Returns the number of points if the annotation is of type polygon or - /// polyline, 0 otherwise. - int FPDFAnnot_GetVertices( - FPDF_ANNOTATION annot, - ffi.Pointer buffer, - int length, - ) { - return _FPDFAnnot_GetVertices( - annot, - buffer, - length, - ); - } - - late final _FPDFAnnot_GetVerticesPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_ANNOTATION, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetVertices'); - late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the number of paths in the ink list of an ink annotation. - /// - /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() - /// - /// Returns the number of paths in the ink list if the annotation is of type ink, - /// 0 otherwise. - int FPDFAnnot_GetInkListCount( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetInkListCount( - annot, - ); - } - - late final _FPDFAnnot_GetInkListCountPtr = - _lookup>( - 'FPDFAnnot_GetInkListCount'); - late final _FPDFAnnot_GetInkListCount = - _FPDFAnnot_GetInkListCountPtr.asFunction(); - - /// Experimental API. - /// Get a path in the ink list of an ink annotation. |buffer| is an array of - /// points of the path. If |length| is less than the returned length, or |annot| - /// or |buffer| is NULL, |buffer| will not be modified. - /// - /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() - /// path_index - index of the path - /// buffer - buffer for holding the points. - /// length - length of the buffer in points. - /// - /// Returns the number of points of the path if the annotation is of type ink, 0 - /// otherwise. - int FPDFAnnot_GetInkListPath( - FPDF_ANNOTATION annot, - int path_index, - ffi.Pointer buffer, - int length, - ) { - return _FPDFAnnot_GetInkListPath( - annot, - path_index, - buffer, - length, - ); - } - - late final _FPDFAnnot_GetInkListPathPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_ANNOTATION, - ffi.UnsignedLong, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetInkListPath'); - late final _FPDFAnnot_GetInkListPath = - _FPDFAnnot_GetInkListPathPtr.asFunction< - int Function(FPDF_ANNOTATION, int, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the starting and ending coordinates of a line annotation. - /// - /// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() - /// start - starting point - /// end - ending point - /// - /// Returns true if the annotation is of type line, |start| and |end| are not - /// NULL, false otherwise. - int FPDFAnnot_GetLine( - FPDF_ANNOTATION annot, - ffi.Pointer start, - ffi.Pointer end, - ) { - return _FPDFAnnot_GetLine( - annot, - start, - end, - ); - } - - late final _FPDFAnnot_GetLinePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, ffi.Pointer, - ffi.Pointer)>>('FPDFAnnot_GetLine'); - late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction< - int Function( - FPDF_ANNOTATION, ffi.Pointer, ffi.Pointer)>(); - - /// Experimental API. - /// Set the characteristics of the annotation's border (rounded rectangle). - /// - /// annot - handle to an annotation - /// horizontal_radius - horizontal corner radius, in default user space units - /// vertical_radius - vertical corner radius, in default user space units - /// border_width - border width, in default user space units - /// - /// Returns true if setting the border for |annot| succeeds, false otherwise. - /// - /// If |annot| contains an appearance stream that overrides the border values, - /// then the appearance stream will be removed on success. - int FPDFAnnot_SetBorder( - FPDF_ANNOTATION annot, - double horizontal_radius, - double vertical_radius, - double border_width, - ) { - return _FPDFAnnot_SetBorder( - annot, - horizontal_radius, - vertical_radius, - border_width, - ); - } - - late final _FPDFAnnot_SetBorderPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, ffi.Float, ffi.Float, - ffi.Float)>>('FPDFAnnot_SetBorder'); - late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction< - int Function(FPDF_ANNOTATION, double, double, double)>(); - - /// Experimental API. - /// Get the characteristics of the annotation's border (rounded rectangle). - /// - /// annot - handle to an annotation - /// horizontal_radius - horizontal corner radius, in default user space units - /// vertical_radius - vertical corner radius, in default user space units - /// border_width - border width, in default user space units - /// - /// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are - /// not NULL, false otherwise. - int FPDFAnnot_GetBorder( - FPDF_ANNOTATION annot, - ffi.Pointer horizontal_radius, - ffi.Pointer vertical_radius, - ffi.Pointer border_width, - ) { - return _FPDFAnnot_GetBorder( - annot, - horizontal_radius, - vertical_radius, - border_width, - ); - } - - late final _FPDFAnnot_GetBorderPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFAnnot_GetBorder'); - late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer, - ffi.Pointer, ffi.Pointer)>(); - - /// Experimental API. - /// Get the JavaScript of an event of the annotation's additional actions. - /// |buffer| is only modified if |buflen| is large enough to hold the whole - /// JavaScript string. If |buflen| is smaller, the total size of the JavaScript - /// is still returned, but nothing is copied. If there is no JavaScript for - /// |event| in |annot|, an empty string is written to |buf| and 2 is returned, - /// denoting the size of the null terminator in the buffer. On other errors, - /// nothing is written to |buffer| and 0 is returned. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// event - event type, one of the FPDF_ANNOT_AACTION_* values. - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes, including the 2-byte - /// null terminator. - int FPDFAnnot_GetFormAdditionalActionJavaScript( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - int event, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetFormAdditionalActionJavaScript( - hHandle, - annot, - event, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Int, ffi.Pointer, ffi.UnsignedLong)>>( - 'FPDFAnnot_GetFormAdditionalActionJavaScript'); - late final _FPDFAnnot_GetFormAdditionalActionJavaScript = - _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Check if |annot|'s dictionary has |key| as a key. - /// - /// annot - handle to an annotation. - /// key - the key to look for, encoded in UTF-8. - /// - /// Returns true if |key| exists. - int FPDFAnnot_HasKey( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - ) { - return _FPDFAnnot_HasKey( - annot, - key, - ); - } - - late final _FPDFAnnot_HasKeyPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, FPDF_BYTESTRING)>>('FPDFAnnot_HasKey'); - late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Get the type of the value corresponding to |key| in |annot|'s dictionary. - /// - /// annot - handle to an annotation. - /// key - the key to look for, encoded in UTF-8. - /// - /// Returns the type of the dictionary value. - int FPDFAnnot_GetValueType( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - ) { - return _FPDFAnnot_GetValueType( - annot, - key, - ); - } - - late final _FPDFAnnot_GetValueTypePtr = _lookup< - ffi.NativeFunction< - FPDF_OBJECT_TYPE Function( - FPDF_ANNOTATION, FPDF_BYTESTRING)>>('FPDFAnnot_GetValueType'); - late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Set the string value corresponding to |key| in |annot|'s dictionary, - /// overwriting the existing value if any. The value type would be - /// FPDF_OBJECT_STRING after this function call succeeds. - /// - /// annot - handle to an annotation. - /// key - the key to the dictionary entry to be set, encoded in UTF-8. - /// value - the string value to be set, encoded in UTF-16LE. - /// - /// Returns true if successful. - int FPDFAnnot_SetStringValue( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - FPDF_WIDESTRING value, - ) { - return _FPDFAnnot_SetStringValue( - annot, - key, - value, - ); - } - - late final _FPDFAnnot_SetStringValuePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, FPDF_BYTESTRING, - FPDF_WIDESTRING)>>('FPDFAnnot_SetStringValue'); - late final _FPDFAnnot_SetStringValue = - _FPDFAnnot_SetStringValuePtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_BYTESTRING, FPDF_WIDESTRING)>(); - - /// Experimental API. - /// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| - /// is only modified if |buflen| is longer than the length of contents. Note that - /// if |key| does not exist in the dictionary or if |key|'s corresponding value - /// in the dictionary is not a string (i.e. the value is not of type - /// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied - /// to |buffer| and the return value would be 2. On other errors, nothing would - /// be added to |buffer| and the return value would be 0. - /// - /// annot - handle to an annotation. - /// key - the key to the requested dictionary entry, encoded in UTF-8. - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetStringValue( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetStringValue( - annot, - key, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetStringValuePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_ANNOTATION, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetStringValue'); - late final _FPDFAnnot_GetStringValue = - _FPDFAnnot_GetStringValuePtr.asFunction< - int Function(FPDF_ANNOTATION, FPDF_BYTESTRING, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the float value corresponding to |key| in |annot|'s dictionary. Writes - /// value to |value| and returns True if |key| exists in the dictionary and - /// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False - /// otherwise. - /// - /// annot - handle to an annotation. - /// key - the key to the requested dictionary entry, encoded in UTF-8. - /// value - receives the value, must not be NULL. - /// - /// Returns True if value found, False otherwise. - int FPDFAnnot_GetNumberValue( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - ffi.Pointer value, - ) { - return _FPDFAnnot_GetNumberValue( - annot, - key, - value, - ); - } - - late final _FPDFAnnot_GetNumberValuePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, FPDF_BYTESTRING, - ffi.Pointer)>>('FPDFAnnot_GetNumberValue'); - late final _FPDFAnnot_GetNumberValue = - _FPDFAnnot_GetNumberValuePtr.asFunction< - int Function( - FPDF_ANNOTATION, FPDF_BYTESTRING, ffi.Pointer)>(); - - /// Experimental API. - /// Set the AP (appearance string) in |annot|'s dictionary for a given - /// |appearanceMode|. - /// - /// annot - handle to an annotation. - /// appearanceMode - the appearance mode (normal, rollover or down) for which - /// to get the AP. - /// value - the string value to be set, encoded in UTF-16LE. If - /// nullptr is passed, the AP is cleared for that mode. If the - /// mode is Normal, APs for all modes are cleared. - /// - /// Returns true if successful. - int FPDFAnnot_SetAP( - FPDF_ANNOTATION annot, - int appearanceMode, - FPDF_WIDESTRING value, - ) { - return _FPDFAnnot_SetAP( - annot, - appearanceMode, - value, - ); - } - - late final _FPDFAnnot_SetAPPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_ANNOTATION, FPDF_ANNOT_APPEARANCEMODE, - FPDF_WIDESTRING)>>('FPDFAnnot_SetAP'); - late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction< - int Function(FPDF_ANNOTATION, int, FPDF_WIDESTRING)>(); - - /// Experimental API. - /// Get the AP (appearance string) from |annot|'s dictionary for a given - /// |appearanceMode|. - /// |buffer| is only modified if |buflen| is large enough to hold the whole AP - /// string. If |buflen| is smaller, the total size of the AP is still returned, - /// but nothing is copied. - /// If there is no appearance stream for |annot| in |appearanceMode|, an empty - /// string is written to |buf| and 2 is returned. - /// On other errors, nothing is written to |buffer| and 0 is returned. - /// - /// annot - handle to an annotation. - /// appearanceMode - the appearance mode (normal, rollover or down) for which - /// to get the AP. - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetAP( - FPDF_ANNOTATION annot, - int appearanceMode, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetAP( - annot, - appearanceMode, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetAPPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_ANNOTATION, FPDF_ANNOT_APPEARANCEMODE, - ffi.Pointer, ffi.UnsignedLong)>>('FPDFAnnot_GetAP'); - late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction< - int Function(FPDF_ANNOTATION, int, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the annotation corresponding to |key| in |annot|'s dictionary. Common - /// keys for linking annotations include "IRT" and "Popup". Must call - /// FPDFPage_CloseAnnot() when the annotation returned by this function is no - /// longer needed. - /// - /// annot - handle to an annotation. - /// key - the key to the requested dictionary entry, encoded in UTF-8. - /// - /// Returns a handle to the linked annotation object, or NULL on failure. - FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot( - FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - ) { - return _FPDFAnnot_GetLinkedAnnot( - annot, - key, - ); - } - - late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< - ffi.NativeFunction< - FPDF_ANNOTATION Function( - FPDF_ANNOTATION, FPDF_BYTESTRING)>>('FPDFAnnot_GetLinkedAnnot'); - late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr - .asFunction(); - - /// Experimental API. - /// Get the annotation flags of |annot|. - /// - /// annot - handle to an annotation. - /// - /// Returns the annotation flags. - int FPDFAnnot_GetFlags( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFlags( - annot, - ); - } - - late final _FPDFAnnot_GetFlagsPtr = - _lookup>( - 'FPDFAnnot_GetFlags'); - late final _FPDFAnnot_GetFlags = - _FPDFAnnot_GetFlagsPtr.asFunction(); - - /// Experimental API. - /// Set the |annot|'s flags to be of the value |flags|. - /// - /// annot - handle to an annotation. - /// flags - the flag values to be set. - /// - /// Returns true if successful. - int FPDFAnnot_SetFlags( - FPDF_ANNOTATION annot, - int flags, - ) { - return _FPDFAnnot_SetFlags( - annot, - flags, - ); - } - - late final _FPDFAnnot_SetFlagsPtr = - _lookup>( - 'FPDFAnnot_SetFlags'); - late final _FPDFAnnot_SetFlags = - _FPDFAnnot_SetFlagsPtr.asFunction(); - - /// Experimental API. - /// Get the annotation flags of |annot|. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// - /// Returns the annotation flags specific to interactive forms. - int FPDFAnnot_GetFormFieldFlags( - FPDF_FORMHANDLE handle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFormFieldFlags( - handle, - annot, - ); - } - - late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetFormFieldFlags'); - late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr - .asFunction(); - - /// Experimental API. - /// Sets the form field flags for an interactive form annotation. - /// - /// handle - the handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// flags - the form field flags to be set. - /// - /// Returns true if successful. - int FPDFAnnot_SetFormFieldFlags( - FPDF_FORMHANDLE handle, - FPDF_ANNOTATION annot, - int flags, - ) { - return _FPDFAnnot_SetFormFieldFlags( - handle, - annot, - flags, - ); - } - - late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Int)>>('FPDFAnnot_SetFormFieldFlags'); - late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr - .asFunction(); - - /// Experimental API. - /// Retrieves an interactive form annotation whose rectangle contains a given - /// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned - /// is no longer needed. - /// - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// page - handle to the page, returned by FPDF_LoadPage function. - /// point - position in PDF "user space". - /// - /// Returns the interactive form annotation whose rectangle contains the given - /// coordinates on the page. If there is no such annotation, return NULL. - FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint( - FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - ffi.Pointer point, - ) { - return _FPDFAnnot_GetFormFieldAtPoint( - hHandle, - page, - point, - ); - } - - late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< - ffi.NativeFunction< - FPDF_ANNOTATION Function(FPDF_FORMHANDLE, FPDF_PAGE, - ffi.Pointer)>>('FPDFAnnot_GetFormFieldAtPoint'); - late final _FPDFAnnot_GetFormFieldAtPoint = - _FPDFAnnot_GetFormFieldAtPointPtr.asFunction< - FPDF_ANNOTATION Function( - FPDF_FORMHANDLE, FPDF_PAGE, ffi.Pointer)>(); - - /// Experimental API. - /// Gets the name of |annot|, which is an interactive form annotation. - /// |buffer| is only modified if |buflen| is longer than the length of contents. - /// In case of error, nothing will be added to |buffer| and the return value will - /// be 0. Note that return value of empty string is 2 for "\0\0". - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// buffer - buffer for holding the name string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetFormFieldName( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetFormFieldName( - hHandle, - annot, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetFormFieldName'); - late final _FPDFAnnot_GetFormFieldName = - _FPDFAnnot_GetFormFieldNamePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Gets the alternate name of |annot|, which is an interactive form annotation. - /// |buffer| is only modified if |buflen| is longer than the length of contents. - /// In case of error, nothing will be added to |buffer| and the return value will - /// be 0. Note that return value of empty string is 2 for "\0\0". - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// buffer - buffer for holding the alternate name string, encoded in - /// UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetFormFieldAlternateName( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetFormFieldAlternateName( - hHandle, - annot, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetFormFieldAlternateName'); - late final _FPDFAnnot_GetFormFieldAlternateName = - _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Gets the form field type of |annot|, which is an interactive form annotation. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// - /// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on - /// success. Returns -1 on error. - /// See field types in fpdf_formfill.h. - int FPDFAnnot_GetFormFieldType( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFormFieldType( - hHandle, - annot, - ); - } - - late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetFormFieldType'); - late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr - .asFunction(); - - /// Experimental API. - /// Gets the value of |annot|, which is an interactive form annotation. - /// |buffer| is only modified if |buflen| is longer than the length of contents. - /// In case of error, nothing will be added to |buffer| and the return value will - /// be 0. Note that return value of empty string is 2 for "\0\0". - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetFormFieldValue( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetFormFieldValue( - hHandle, - annot, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetFormFieldValue'); - late final _FPDFAnnot_GetFormFieldValue = - _FPDFAnnot_GetFormFieldValuePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the number of options in the |annot|'s "Opt" dictionary. Intended for - /// use with listbox and combobox widget annotations. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// - /// Returns the number of options in "Opt" dictionary on success. Return value - /// will be -1 if annotation does not have an "Opt" dictionary or other error. - int FPDFAnnot_GetOptionCount( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetOptionCount( - hHandle, - annot, - ); - } - - late final _FPDFAnnot_GetOptionCountPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetOptionCount'); - late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr - .asFunction(); - - /// Experimental API. - /// Get the string value for the label of the option at |index| in |annot|'s - /// "Opt" dictionary. Intended for use with listbox and combobox widget - /// annotations. |buffer| is only modified if |buflen| is longer than the length - /// of contents. If index is out of range or in case of other error, nothing - /// will be added to |buffer| and the return value will be 0. Note that - /// return value of empty string is 2 for "\0\0". - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// index - numeric index of the option in the "Opt" array - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - /// If |annot| does not have an "Opt" array, |index| is out of range or if any - /// other error occurs, returns 0. - int FPDFAnnot_GetOptionLabel( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - int index, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetOptionLabel( - hHandle, - annot, - index, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetOptionLabelPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Int, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetOptionLabel'); - late final _FPDFAnnot_GetOptionLabel = - _FPDFAnnot_GetOptionLabelPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary - /// is selected. Intended for use with listbox and combobox widget annotations. - /// - /// handle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// index - numeric index of the option in the "Opt" array. - /// - /// Returns true if the option at |index| in |annot|'s "Opt" dictionary is - /// selected, false otherwise. - int FPDFAnnot_IsOptionSelected( - FPDF_FORMHANDLE handle, - FPDF_ANNOTATION annot, - int index, - ) { - return _FPDFAnnot_IsOptionSelected( - handle, - annot, - index, - ); - } - - late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Int)>>('FPDFAnnot_IsOptionSelected'); - late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr - .asFunction(); - - /// Experimental API. - /// Get the float value of the font size for an |annot| with variable text. - /// If 0, the font is to be auto-sized: its size is computed as a function of - /// the height of the annotation rectangle. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// value - Required. Float which will be set to font size on success. - /// - /// Returns true if the font size was set in |value|, false on error or if - /// |value| not provided. - int FPDFAnnot_GetFontSize( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer value, - ) { - return _FPDFAnnot_GetFontSize( - hHandle, - annot, - value, - ); - } - - late final _FPDFAnnot_GetFontSizePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Pointer)>>('FPDFAnnot_GetFontSize'); - late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.Pointer)>(); - - /// Experimental API. - /// Set the text color of an annotation. - /// - /// handle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// R - the red component for the text color. - /// G - the green component for the text color. - /// B - the blue component for the text color. - /// - /// Returns true if successful. - /// - /// Currently supported subtypes: freetext. - /// The range for the color components is 0 to 255. - int FPDFAnnot_SetFontColor( - FPDF_FORMHANDLE handle, - FPDF_ANNOTATION annot, - int R, - int G, - int B, - ) { - return _FPDFAnnot_SetFontColor( - handle, - annot, - R, - G, - B, - ); - } - - late final _FPDFAnnot_SetFontColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, ffi.UnsignedInt, - ffi.UnsignedInt, ffi.UnsignedInt)>>('FPDFAnnot_SetFontColor'); - late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, int, int, int)>(); - - /// Experimental API. - /// Get the RGB value of the font color for an |annot| with variable text. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. - /// - /// Returns true if the font color was set, false on error or if the font - /// color was not provided. - int FPDFAnnot_GetFontColor( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ) { - return _FPDFAnnot_GetFontColor( - hHandle, - annot, - R, - G, - B, - ); - } - - late final _FPDFAnnot_GetFontColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFAnnot_GetFontColor'); - late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction< - int Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Determine if |annot| is a form widget that is checked. Intended for use with - /// checkbox and radio button widgets. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// - /// Returns true if |annot| is a form widget and is checked, false otherwise. - int FPDFAnnot_IsChecked( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_IsChecked( - hHandle, - annot, - ); - } - - late final _FPDFAnnot_IsCheckedPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, FPDF_ANNOTATION)>>('FPDFAnnot_IsChecked'); - late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION)>(); - - /// Experimental API. - /// Set the list of focusable annotation subtypes. Annotations of subtype - /// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API - /// will override the existing subtypes. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// subtypes - list of annotation subtype which can be tabbed over. - /// count - total number of annotation subtype in list. - /// Returns true if list of annotation subtype is set successfully, false - /// otherwise. - int FPDFAnnot_SetFocusableSubtypes( - FPDF_FORMHANDLE hHandle, - ffi.Pointer subtypes, - int count, - ) { - return _FPDFAnnot_SetFocusableSubtypes( - hHandle, - subtypes, - count, - ); - } - - late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, - ffi.Pointer, - ffi.Size)>>('FPDFAnnot_SetFocusableSubtypes'); - late final _FPDFAnnot_SetFocusableSubtypes = - _FPDFAnnot_SetFocusableSubtypesPtr.asFunction< - int Function( - FPDF_FORMHANDLE, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the count of focusable annotation subtypes as set by host - /// for a |hHandle|. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// Returns the count of focusable annotation subtypes or -1 on error. - /// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. - int FPDFAnnot_GetFocusableSubtypesCount( - FPDF_FORMHANDLE hHandle, - ) { - return _FPDFAnnot_GetFocusableSubtypesCount( - hHandle, - ); - } - - late final _FPDFAnnot_GetFocusableSubtypesCountPtr = - _lookup>( - 'FPDFAnnot_GetFocusableSubtypesCount'); - late final _FPDFAnnot_GetFocusableSubtypesCount = - _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction< - int Function(FPDF_FORMHANDLE)>(); - - /// Experimental API. - /// Get the list of focusable annotation subtype as set by host. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// subtypes - receives the list of annotation subtype which can be tabbed - /// over. Caller must have allocated |subtypes| more than or - /// equal to the count obtained from - /// FPDFAnnot_GetFocusableSubtypesCount() API. - /// count - size of |subtypes|. - /// Returns true on success and set list of annotation subtype to |subtypes|, - /// false otherwise. - /// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. - int FPDFAnnot_GetFocusableSubtypes( - FPDF_FORMHANDLE hHandle, - ffi.Pointer subtypes, - int count, - ) { - return _FPDFAnnot_GetFocusableSubtypes( - hHandle, - subtypes, - count, - ); - } - - late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_FORMHANDLE, - ffi.Pointer, - ffi.Size)>>('FPDFAnnot_GetFocusableSubtypes'); - late final _FPDFAnnot_GetFocusableSubtypes = - _FPDFAnnot_GetFocusableSubtypesPtr.asFunction< - int Function( - FPDF_FORMHANDLE, ffi.Pointer, int)>(); - - /// Experimental API. - /// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. - /// - /// annot - handle to an annotation. - /// - /// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, - /// if the input annot is NULL or input annot's subtype is not link. - FPDF_LINK FPDFAnnot_GetLink( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetLink( - annot, - ); - } - - late final _FPDFAnnot_GetLinkPtr = - _lookup>( - 'FPDFAnnot_GetLink'); - late final _FPDFAnnot_GetLink = - _FPDFAnnot_GetLinkPtr.asFunction(); - - /// Experimental API. - /// Gets the count of annotations in the |annot|'s control group. - /// A group of interactive form annotations is collectively called a form - /// control group. Here, |annot|, an interactive form annotation, should be - /// either a radio button or a checkbox. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// - /// Returns number of controls in its control group or -1 on error. - int FPDFAnnot_GetFormControlCount( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFormControlCount( - hHandle, - annot, - ); - } - - late final _FPDFAnnot_GetFormControlCountPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetFormControlCount'); - late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr - .asFunction(); - - /// Experimental API. - /// Gets the index of |annot| in |annot|'s control group. - /// A group of interactive form annotations is collectively called a form - /// control group. Here, |annot|, an interactive form annotation, should be - /// either a radio button or a checkbox. - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment. - /// annot - handle to an annotation. - /// - /// Returns index of a given |annot| in its control group or -1 on error. - int FPDFAnnot_GetFormControlIndex( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFormControlIndex( - hHandle, - annot, - ); - } - - late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFAnnot_GetFormControlIndex'); - late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr - .asFunction(); - - /// Experimental API. - /// Gets the export value of |annot| which is an interactive form annotation. - /// Intended for use with radio button and checkbox widget annotations. - /// |buffer| is only modified if |buflen| is longer than the length of contents. - /// In case of error, nothing will be added to |buffer| and the return value - /// will be 0. Note that return value of empty string is 2 for "\0\0". - /// - /// hHandle - handle to the form fill module, returned by - /// FPDFDOC_InitFormFillEnvironment(). - /// annot - handle to an interactive form annotation. - /// buffer - buffer for holding the value string, encoded in UTF-16LE. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the string value in bytes. - int FPDFAnnot_GetFormFieldExportValue( - FPDF_FORMHANDLE hHandle, - FPDF_ANNOTATION annot, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAnnot_GetFormFieldExportValue( - hHandle, - annot, - buffer, - buflen, - ); - } - - late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_FORMHANDLE, - FPDF_ANNOTATION, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAnnot_GetFormFieldExportValue'); - late final _FPDFAnnot_GetFormFieldExportValue = - _FPDFAnnot_GetFormFieldExportValuePtr.asFunction< - int Function(FPDF_FORMHANDLE, FPDF_ANNOTATION, - ffi.Pointer, int)>(); - - /// Experimental API. - /// Add a URI action to |annot|, overwriting the existing action, if any. - /// - /// annot - handle to a link annotation. - /// uri - the URI to be set, encoded in 7-bit ASCII. - /// - /// Returns true if successful. - int FPDFAnnot_SetURI( - FPDF_ANNOTATION annot, - ffi.Pointer uri, - ) { - return _FPDFAnnot_SetURI( - annot, - uri, - ); - } - - late final _FPDFAnnot_SetURIPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_ANNOTATION, ffi.Pointer)>>('FPDFAnnot_SetURI'); - late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction< - int Function(FPDF_ANNOTATION, ffi.Pointer)>(); - - /// Experimental API. - /// Get the attachment from |annot|. - /// - /// annot - handle to a file annotation. - /// - /// Returns the handle to the attachment object, or NULL on failure. - FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment( - FPDF_ANNOTATION annot, - ) { - return _FPDFAnnot_GetFileAttachment( - annot, - ); - } - - late final _FPDFAnnot_GetFileAttachmentPtr = - _lookup>( - 'FPDFAnnot_GetFileAttachment'); - late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr - .asFunction(); - - /// Experimental API. - /// Add an embedded file with |name| to |annot|. - /// - /// annot - handle to a file annotation. - /// name - name of the new attachment. - /// - /// Returns a handle to the new attachment object, or NULL on failure. - FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment( - FPDF_ANNOTATION annot, - FPDF_WIDESTRING name, - ) { - return _FPDFAnnot_AddFileAttachment( - annot, - name, - ); - } - - late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< - ffi.NativeFunction< - FPDF_ATTACHMENT Function(FPDF_ANNOTATION, - FPDF_WIDESTRING)>>('FPDFAnnot_AddFileAttachment'); - late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr - .asFunction(); - - /// Function: FPDFText_LoadPage - /// Prepare information about all characters in a page. - /// Parameters: - /// page - Handle to the page. Returned by FPDF_LoadPage function - /// (in FPDFVIEW module). - /// Return value: - /// A handle to the text page information structure. - /// NULL if something goes wrong. - /// Comments: - /// Application must call FPDFText_ClosePage to release the text page - /// information. - FPDF_TEXTPAGE FPDFText_LoadPage( - FPDF_PAGE page, - ) { - return _FPDFText_LoadPage( - page, - ); - } - - late final _FPDFText_LoadPagePtr = - _lookup>( - 'FPDFText_LoadPage'); - late final _FPDFText_LoadPage = - _FPDFText_LoadPagePtr.asFunction(); - - /// Function: FPDFText_ClosePage - /// Release all resources allocated for a text page information - /// structure. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// Return Value: - /// None. - void FPDFText_ClosePage( - FPDF_TEXTPAGE text_page, - ) { - return _FPDFText_ClosePage( - text_page, - ); - } - - late final _FPDFText_ClosePagePtr = - _lookup>( - 'FPDFText_ClosePage'); - late final _FPDFText_ClosePage = - _FPDFText_ClosePagePtr.asFunction(); - - /// Function: FPDFText_CountChars - /// Get number of characters in a page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// Return value: - /// Number of characters in the page. Return -1 for error. - /// Generated characters, like additional space characters, new line - /// characters, are also counted. - /// Comments: - /// Characters in a page form a "stream", inside the stream, each - /// character has an index. - /// We will use the index parameters in many of FPDFTEXT functions. The - /// first character in the page - /// has an index value of zero. - int FPDFText_CountChars( - FPDF_TEXTPAGE text_page, - ) { - return _FPDFText_CountChars( - text_page, - ); - } - - late final _FPDFText_CountCharsPtr = - _lookup>( - 'FPDFText_CountChars'); - late final _FPDFText_CountChars = - _FPDFText_CountCharsPtr.asFunction(); - - /// Function: FPDFText_GetUnicode - /// Get Unicode of a character in a page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// The Unicode of the particular character. - /// If a character is not encoded in Unicode and Foxit engine can't - /// convert to Unicode, - /// the return value will be zero. - int FPDFText_GetUnicode( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_GetUnicode( - text_page, - index, - ); - } - - late final _FPDFText_GetUnicodePtr = _lookup< - ffi.NativeFunction>( - 'FPDFText_GetUnicode'); - late final _FPDFText_GetUnicode = - _FPDFText_GetUnicodePtr.asFunction(); - - /// Experimental API. - /// Function: FPDFText_GetTextObject - /// Get the FPDF_PAGEOBJECT associated with a given character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// The associated text object for the character at |index|, or NULL on - /// error. The returned text object, if non-null, is of type - /// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. - FPDF_PAGEOBJECT FPDFText_GetTextObject( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_GetTextObject( - text_page, - index, - ); - } - - late final _FPDFText_GetTextObjectPtr = _lookup< - ffi.NativeFunction>( - 'FPDFText_GetTextObject'); - late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_TEXTPAGE, int)>(); - - /// Experimental API. - /// Function: FPDFText_IsGenerated - /// Get if a character in a page is generated by PDFium. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// 1 if the character is generated by PDFium. - /// 0 if the character is not generated by PDFium. - /// -1 if there was an error. - int FPDFText_IsGenerated( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_IsGenerated( - text_page, - index, - ); - } - - late final _FPDFText_IsGeneratedPtr = - _lookup>( - 'FPDFText_IsGenerated'); - late final _FPDFText_IsGenerated = - _FPDFText_IsGeneratedPtr.asFunction(); - - /// Experimental API. - /// Function: FPDFText_IsHyphen - /// Get if a character in a page is a hyphen. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// 1 if the character is a hyphen. - /// 0 if the character is not a hyphen. - /// -1 if there was an error. - int FPDFText_IsHyphen( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_IsHyphen( - text_page, - index, - ); - } - - late final _FPDFText_IsHyphenPtr = - _lookup>( - 'FPDFText_IsHyphen'); - late final _FPDFText_IsHyphen = - _FPDFText_IsHyphenPtr.asFunction(); - - /// Experimental API. - /// Function: FPDFText_HasUnicodeMapError - /// Get if a character in a page has an invalid unicode mapping. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// 1 if the character has an invalid unicode mapping. - /// 0 if the character has no known unicode mapping issues. - /// -1 if there was an error. - int FPDFText_HasUnicodeMapError( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_HasUnicodeMapError( - text_page, - index, - ); - } - - late final _FPDFText_HasUnicodeMapErrorPtr = - _lookup>( - 'FPDFText_HasUnicodeMapError'); - late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr - .asFunction(); - - /// Function: FPDFText_GetFontSize - /// Get the font size of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// The font size of the particular character, measured in points (about - /// 1/72 inch). This is the typographic size of the font (so called - /// "em size"). - double FPDFText_GetFontSize( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_GetFontSize( - text_page, - index, - ); - } - - late final _FPDFText_GetFontSizePtr = - _lookup>( - 'FPDFText_GetFontSize'); - late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction< - double Function(FPDF_TEXTPAGE, int)>(); - - /// Experimental API. - /// Function: FPDFText_GetFontInfo - /// Get the font name and flags of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// buffer - A buffer receiving the font name. - /// buflen - The length of |buffer| in bytes. - /// flags - Optional pointer to an int receiving the font flags. - /// These flags should be interpreted per PDF spec 1.7 - /// Section 5.7.1 Font Descriptor Flags. - /// Return value: - /// On success, return the length of the font name, including the - /// trailing NUL character, in bytes. If this length is less than or - /// equal to |length|, |buffer| is set to the font name, |flags| is - /// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on - /// failure. - int FPDFText_GetFontInfo( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer buffer, - int buflen, - ffi.Pointer flags, - ) { - return _FPDFText_GetFontInfo( - text_page, - index, - buffer, - buflen, - flags, - ); - } - - late final _FPDFText_GetFontInfoPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_TEXTPAGE, - ffi.Int, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>('FPDFText_GetFontInfo'); - late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction< - int Function(FPDF_TEXTPAGE, int, ffi.Pointer, int, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDFText_GetFontWeight - /// Get the font weight of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return value: - /// On success, return the font weight of the particular character. If - /// |text_page| is invalid, if |index| is out of bounds, or if the - /// character's text object is undefined, return -1. - int FPDFText_GetFontWeight( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_GetFontWeight( - text_page, - index, - ); - } - - late final _FPDFText_GetFontWeightPtr = - _lookup>( - 'FPDFText_GetFontWeight'); - late final _FPDFText_GetFontWeight = - _FPDFText_GetFontWeightPtr.asFunction(); - - /// Experimental API. - /// Function: FPDFText_GetFillColor - /// Get the fill color of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// R - Pointer to an unsigned int number receiving the - /// red value of the fill color. - /// G - Pointer to an unsigned int number receiving the - /// green value of the fill color. - /// B - Pointer to an unsigned int number receiving the - /// blue value of the fill color. - /// A - Pointer to an unsigned int number receiving the - /// alpha value of the fill color. - /// Return value: - /// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are - /// unchanged. - int FPDFText_GetFillColor( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ffi.Pointer A, - ) { - return _FPDFText_GetFillColor( - text_page, - index, - R, - G, - B, - A, - ); - } - - late final _FPDFText_GetFillColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_TEXTPAGE, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFText_GetFillColor'); - late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction< - int Function( - FPDF_TEXTPAGE, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDFText_GetStrokeColor - /// Get the stroke color of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// R - Pointer to an unsigned int number receiving the - /// red value of the stroke color. - /// G - Pointer to an unsigned int number receiving the - /// green value of the stroke color. - /// B - Pointer to an unsigned int number receiving the - /// blue value of the stroke color. - /// A - Pointer to an unsigned int number receiving the - /// alpha value of the stroke color. - /// Return value: - /// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are - /// unchanged. - int FPDFText_GetStrokeColor( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ffi.Pointer A, - ) { - return _FPDFText_GetStrokeColor( - text_page, - index, - R, - G, - B, - A, - ); - } - - late final _FPDFText_GetStrokeColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_TEXTPAGE, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFText_GetStrokeColor'); - late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction< - int Function( - FPDF_TEXTPAGE, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDFText_GetCharAngle - /// Get character rotation angle. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// Return Value: - /// On success, return the angle value in radian. Value will always be - /// greater or equal to 0. If |text_page| is invalid, or if |index| is - /// out of bounds, then return -1. - double FPDFText_GetCharAngle( - FPDF_TEXTPAGE text_page, - int index, - ) { - return _FPDFText_GetCharAngle( - text_page, - index, - ); - } - - late final _FPDFText_GetCharAnglePtr = - _lookup>( - 'FPDFText_GetCharAngle'); - late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction< - double Function(FPDF_TEXTPAGE, int)>(); - - /// Function: FPDFText_GetCharBox - /// Get bounding box of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// left - Pointer to a double number receiving left position - /// of the character box. - /// right - Pointer to a double number receiving right position - /// of the character box. - /// bottom - Pointer to a double number receiving bottom position - /// of the character box. - /// top - Pointer to a double number receiving top position of - /// the character box. - /// Return Value: - /// On success, return TRUE and fill in |left|, |right|, |bottom|, and - /// |top|. If |text_page| is invalid, or if |index| is out of bounds, - /// then return FALSE, and the out parameters remain unmodified. - /// Comments: - /// All positions are measured in PDF "user space". - int FPDFText_GetCharBox( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer left, - ffi.Pointer right, - ffi.Pointer bottom, - ffi.Pointer top, - ) { - return _FPDFText_GetCharBox( - text_page, - index, - left, - right, - bottom, - top, - ); - } - - late final _FPDFText_GetCharBoxPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_TEXTPAGE, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFText_GetCharBox'); - late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction< - int Function( - FPDF_TEXTPAGE, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDFText_GetLooseCharBox - /// Get a "loose" bounding box of a particular character, i.e., covering - /// the entire glyph bounds, without taking the actual glyph shape into - /// account. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// rect - Pointer to a FS_RECTF receiving the character box. - /// Return Value: - /// On success, return TRUE and fill in |rect|. If |text_page| is - /// invalid, or if |index| is out of bounds, then return FALSE, and the - /// |rect| out parameter remains unmodified. - /// Comments: - /// All positions are measured in PDF "user space". - int FPDFText_GetLooseCharBox( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer rect, - ) { - return _FPDFText_GetLooseCharBox( - text_page, - index, - rect, - ); - } - - late final _FPDFText_GetLooseCharBoxPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_TEXTPAGE, ffi.Int, - ffi.Pointer)>>('FPDFText_GetLooseCharBox'); - late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr - .asFunction)>(); - - /// Experimental API. - /// Function: FPDFText_GetMatrix - /// Get the effective transformation matrix for a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage(). - /// index - Zero-based index of the character. - /// matrix - Pointer to a FS_MATRIX receiving the transformation - /// matrix. - /// Return Value: - /// On success, return TRUE and fill in |matrix|. If |text_page| is - /// invalid, or if |index| is out of bounds, or if |matrix| is NULL, - /// then return FALSE, and |matrix| remains unmodified. - int FPDFText_GetMatrix( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer matrix, - ) { - return _FPDFText_GetMatrix( - text_page, - index, - matrix, - ); - } - - late final _FPDFText_GetMatrixPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_TEXTPAGE, ffi.Int, - ffi.Pointer)>>('FPDFText_GetMatrix'); - late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction< - int Function(FPDF_TEXTPAGE, int, ffi.Pointer)>(); - - /// Function: FPDFText_GetCharOrigin - /// Get origin of a particular character. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// index - Zero-based index of the character. - /// x - Pointer to a double number receiving x coordinate of - /// the character origin. - /// y - Pointer to a double number receiving y coordinate of - /// the character origin. - /// Return Value: - /// Whether the call succeeded. If false, x and y are unchanged. - /// Comments: - /// All positions are measured in PDF "user space". - int FPDFText_GetCharOrigin( - FPDF_TEXTPAGE text_page, - int index, - ffi.Pointer x, - ffi.Pointer y, - ) { - return _FPDFText_GetCharOrigin( - text_page, - index, - x, - y, - ); - } - - late final _FPDFText_GetCharOriginPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_TEXTPAGE, ffi.Int, ffi.Pointer, - ffi.Pointer)>>('FPDFText_GetCharOrigin'); - late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction< - int Function(FPDF_TEXTPAGE, int, ffi.Pointer, - ffi.Pointer)>(); - - /// Function: FPDFText_GetCharIndexAtPos - /// Get the index of a character at or nearby a certain position on the - /// page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// x - X position in PDF "user space". - /// y - Y position in PDF "user space". - /// xTolerance - An x-axis tolerance value for character hit - /// detection, in point units. - /// yTolerance - A y-axis tolerance value for character hit - /// detection, in point units. - /// Return Value: - /// The zero-based index of the character at, or nearby the point (x,y). - /// If there is no character at or nearby the point, return value will - /// be -1. If an error occurs, -3 will be returned. - int FPDFText_GetCharIndexAtPos( - FPDF_TEXTPAGE text_page, - double x, - double y, - double xTolerance, - double yTolerance, - ) { - return _FPDFText_GetCharIndexAtPos( - text_page, - x, - y, - xTolerance, - yTolerance, - ); - } - - late final _FPDFText_GetCharIndexAtPosPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_TEXTPAGE, ffi.Double, ffi.Double, ffi.Double, - ffi.Double)>>('FPDFText_GetCharIndexAtPos'); - late final _FPDFText_GetCharIndexAtPos = - _FPDFText_GetCharIndexAtPosPtr.asFunction< - int Function(FPDF_TEXTPAGE, double, double, double, double)>(); - - /// Function: FPDFText_GetText - /// Extract unicode text string from the page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// start_index - Index for the start characters. - /// count - Number of UCS-2 values to be extracted. - /// result - A buffer (allocated by application) receiving the - /// extracted UCS-2 values. The buffer must be able to - /// hold `count` UCS-2 values plus a terminator. - /// Return Value: - /// Number of characters written into the result buffer, including the - /// trailing terminator. - /// Comments: - /// This function ignores characters without UCS-2 representations. - /// It considers all characters on the page, even those that are not - /// visible when the page has a cropbox. To filter out the characters - /// outside of the cropbox, use FPDF_GetPageBoundingBox() and - /// FPDFText_GetCharBox(). - int FPDFText_GetText( - FPDF_TEXTPAGE text_page, - int start_index, - int count, - ffi.Pointer result, - ) { - return _FPDFText_GetText( - text_page, - start_index, - count, - result, - ); - } - - late final _FPDFText_GetTextPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_TEXTPAGE, ffi.Int, ffi.Int, - ffi.Pointer)>>('FPDFText_GetText'); - late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction< - int Function(FPDF_TEXTPAGE, int, int, ffi.Pointer)>(); - - /// Function: FPDFText_CountRects - /// Counts number of rectangular areas occupied by a segment of text, - /// and caches the result for subsequent FPDFText_GetRect() calls. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// start_index - Index for the start character. - /// count - Number of characters, or -1 for all remaining. - /// Return value: - /// Number of rectangles, 0 if text_page is null, or -1 on bad - /// start_index. - /// Comments: - /// This function, along with FPDFText_GetRect can be used by - /// applications to detect the position on the page for a text segment, - /// so proper areas can be highlighted. The FPDFText_* functions will - /// automatically merge small character boxes into bigger one if those - /// characters are on the same line and use same font settings. - int FPDFText_CountRects( - FPDF_TEXTPAGE text_page, - int start_index, - int count, - ) { - return _FPDFText_CountRects( - text_page, - start_index, - count, - ); - } - - late final _FPDFText_CountRectsPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFText_CountRects'); - late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction< - int Function(FPDF_TEXTPAGE, int, int)>(); - - /// Function: FPDFText_GetRect - /// Get a rectangular area from the result generated by - /// FPDFText_CountRects. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// rect_index - Zero-based index for the rectangle. - /// left - Pointer to a double value receiving the rectangle - /// left boundary. - /// top - Pointer to a double value receiving the rectangle - /// top boundary. - /// right - Pointer to a double value receiving the rectangle - /// right boundary. - /// bottom - Pointer to a double value receiving the rectangle - /// bottom boundary. - /// Return Value: - /// On success, return TRUE and fill in |left|, |top|, |right|, and - /// |bottom|. If |text_page| is invalid then return FALSE, and the out - /// parameters remain unmodified. If |text_page| is valid but - /// |rect_index| is out of bounds, then return FALSE and set the out - /// parameters to 0. - int FPDFText_GetRect( - FPDF_TEXTPAGE text_page, - int rect_index, - ffi.Pointer left, - ffi.Pointer top, - ffi.Pointer right, - ffi.Pointer bottom, - ) { - return _FPDFText_GetRect( - text_page, - rect_index, - left, - top, - right, - bottom, - ); - } - - late final _FPDFText_GetRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_TEXTPAGE, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFText_GetRect'); - late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction< - int Function( - FPDF_TEXTPAGE, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Function: FPDFText_GetBoundedText - /// Extract unicode text within a rectangular boundary on the page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// left - Left boundary. - /// top - Top boundary. - /// right - Right boundary. - /// bottom - Bottom boundary. - /// buffer - Caller-allocated buffer to receive UTF-16 values. - /// buflen - Number of UTF-16 values (not bytes) that `buffer` - /// is capable of holding. - /// Return Value: - /// If buffer is NULL or buflen is zero, return number of UTF-16 - /// values (not bytes) of text present within the rectangle, excluding - /// a terminating NUL. Generally you should pass a buffer at least one - /// larger than this if you want a terminating NUL, which will be - /// provided if space is available. Otherwise, return number of UTF-16 - /// values copied into the buffer, including the terminating NUL when - /// space for it is available. - /// Comment: - /// If the buffer is too small, as much text as will fit is copied into - /// it. May return a split surrogate in that case. - int FPDFText_GetBoundedText( - FPDF_TEXTPAGE text_page, - double left, - double top, - double right, - double bottom, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFText_GetBoundedText( - text_page, - left, - top, - right, - bottom, - buffer, - buflen, - ); - } - - late final _FPDFText_GetBoundedTextPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function( - FPDF_TEXTPAGE, - ffi.Double, - ffi.Double, - ffi.Double, - ffi.Double, - ffi.Pointer, - ffi.Int)>>('FPDFText_GetBoundedText'); - late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction< - int Function(FPDF_TEXTPAGE, double, double, double, double, - ffi.Pointer, int)>(); - - /// Function: FPDFText_FindStart - /// Start a search. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// findwhat - A unicode match pattern. - /// flags - Option flags. - /// start_index - Start from this character. -1 for end of the page. - /// Return Value: - /// A handle for the search context. FPDFText_FindClose must be called - /// to release this handle. - FPDF_SCHHANDLE FPDFText_FindStart( - FPDF_TEXTPAGE text_page, - FPDF_WIDESTRING findwhat, - int flags, - int start_index, - ) { - return _FPDFText_FindStart( - text_page, - findwhat, - flags, - start_index, - ); - } - - late final _FPDFText_FindStartPtr = _lookup< - ffi.NativeFunction< - FPDF_SCHHANDLE Function(FPDF_TEXTPAGE, FPDF_WIDESTRING, - ffi.UnsignedLong, ffi.Int)>>('FPDFText_FindStart'); - late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction< - FPDF_SCHHANDLE Function(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int)>(); - - /// Function: FPDFText_FindNext - /// Search in the direction from page start to end. - /// Parameters: - /// handle - A search context handle returned by - /// FPDFText_FindStart. - /// Return Value: - /// Whether a match is found. - int FPDFText_FindNext( - FPDF_SCHHANDLE handle, - ) { - return _FPDFText_FindNext( - handle, - ); - } - - late final _FPDFText_FindNextPtr = - _lookup>( - 'FPDFText_FindNext'); - late final _FPDFText_FindNext = - _FPDFText_FindNextPtr.asFunction(); - - /// Function: FPDFText_FindPrev - /// Search in the direction from page end to start. - /// Parameters: - /// handle - A search context handle returned by - /// FPDFText_FindStart. - /// Return Value: - /// Whether a match is found. - int FPDFText_FindPrev( - FPDF_SCHHANDLE handle, - ) { - return _FPDFText_FindPrev( - handle, - ); - } - - late final _FPDFText_FindPrevPtr = - _lookup>( - 'FPDFText_FindPrev'); - late final _FPDFText_FindPrev = - _FPDFText_FindPrevPtr.asFunction(); - - /// Function: FPDFText_GetSchResultIndex - /// Get the starting character index of the search result. - /// Parameters: - /// handle - A search context handle returned by - /// FPDFText_FindStart. - /// Return Value: - /// Index for the starting character. - int FPDFText_GetSchResultIndex( - FPDF_SCHHANDLE handle, - ) { - return _FPDFText_GetSchResultIndex( - handle, - ); - } - - late final _FPDFText_GetSchResultIndexPtr = - _lookup>( - 'FPDFText_GetSchResultIndex'); - late final _FPDFText_GetSchResultIndex = - _FPDFText_GetSchResultIndexPtr.asFunction(); - - /// Function: FPDFText_GetSchCount - /// Get the number of matched characters in the search result. - /// Parameters: - /// handle - A search context handle returned by - /// FPDFText_FindStart. - /// Return Value: - /// Number of matched characters. - int FPDFText_GetSchCount( - FPDF_SCHHANDLE handle, - ) { - return _FPDFText_GetSchCount( - handle, - ); - } - - late final _FPDFText_GetSchCountPtr = - _lookup>( - 'FPDFText_GetSchCount'); - late final _FPDFText_GetSchCount = - _FPDFText_GetSchCountPtr.asFunction(); - - /// Function: FPDFText_FindClose - /// Release a search context. - /// Parameters: - /// handle - A search context handle returned by - /// FPDFText_FindStart. - /// Return Value: - /// None. - void FPDFText_FindClose( - FPDF_SCHHANDLE handle, - ) { - return _FPDFText_FindClose( - handle, - ); - } - - late final _FPDFText_FindClosePtr = - _lookup>( - 'FPDFText_FindClose'); - late final _FPDFText_FindClose = - _FPDFText_FindClosePtr.asFunction(); - - /// Function: FPDFLink_LoadWebLinks - /// Prepare information about weblinks in a page. - /// Parameters: - /// text_page - Handle to a text page information structure. - /// Returned by FPDFText_LoadPage function. - /// Return Value: - /// A handle to the page's links information structure, or - /// NULL if something goes wrong. - /// Comments: - /// Weblinks are those links implicitly embedded in PDF pages. PDF also - /// has a type of annotation called "link" (FPDFTEXT doesn't deal with - /// that kind of link). FPDFTEXT weblink feature is useful for - /// automatically detecting links in the page contents. For example, - /// things like "https://www.example.com" will be detected, so - /// applications can allow user to click on those characters to activate - /// the link, even the PDF doesn't come with link annotations. - /// - /// FPDFLink_CloseWebLinks must be called to release resources. - FPDF_PAGELINK FPDFLink_LoadWebLinks( - FPDF_TEXTPAGE text_page, - ) { - return _FPDFLink_LoadWebLinks( - text_page, - ); - } - - late final _FPDFLink_LoadWebLinksPtr = - _lookup>( - 'FPDFLink_LoadWebLinks'); - late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction< - FPDF_PAGELINK Function(FPDF_TEXTPAGE)>(); - - /// Function: FPDFLink_CountWebLinks - /// Count number of detected web links. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// Return Value: - /// Number of detected web links. - int FPDFLink_CountWebLinks( - FPDF_PAGELINK link_page, - ) { - return _FPDFLink_CountWebLinks( - link_page, - ); - } - - late final _FPDFLink_CountWebLinksPtr = - _lookup>( - 'FPDFLink_CountWebLinks'); - late final _FPDFLink_CountWebLinks = - _FPDFLink_CountWebLinksPtr.asFunction(); - - /// Function: FPDFLink_GetURL - /// Fetch the URL information for a detected web link. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// link_index - Zero-based index for the link. - /// buffer - A unicode buffer for the result. - /// buflen - Number of 16-bit code units (not bytes) for the - /// buffer, including an additional terminator. - /// Return Value: - /// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit - /// code units (not bytes) needed to buffer the result (an additional - /// terminator is included in this count). - /// Otherwise, copy the result into |buffer|, truncating at |buflen| if - /// the result is too large to fit, and return the number of 16-bit code - /// units actually copied into the buffer (the additional terminator is - /// also included in this count). - /// If |link_index| does not correspond to a valid link, then the result - /// is an empty string. - int FPDFLink_GetURL( - FPDF_PAGELINK link_page, - int link_index, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFLink_GetURL( - link_page, - link_index, - buffer, - buflen, - ); - } - - late final _FPDFLink_GetURLPtr = _lookup< - ffi.NativeFunction< - ffi.Int Function(FPDF_PAGELINK, ffi.Int, - ffi.Pointer, ffi.Int)>>('FPDFLink_GetURL'); - late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction< - int Function(FPDF_PAGELINK, int, ffi.Pointer, int)>(); - - /// Function: FPDFLink_CountRects - /// Count number of rectangular areas for the link. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// link_index - Zero-based index for the link. - /// Return Value: - /// Number of rectangular areas for the link. If |link_index| does - /// not correspond to a valid link, then 0 is returned. - int FPDFLink_CountRects( - FPDF_PAGELINK link_page, - int link_index, - ) { - return _FPDFLink_CountRects( - link_page, - link_index, - ); - } - - late final _FPDFLink_CountRectsPtr = - _lookup>( - 'FPDFLink_CountRects'); - late final _FPDFLink_CountRects = - _FPDFLink_CountRectsPtr.asFunction(); - - /// Function: FPDFLink_GetRect - /// Fetch the boundaries of a rectangle for a link. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// link_index - Zero-based index for the link. - /// rect_index - Zero-based index for a rectangle. - /// left - Pointer to a double value receiving the rectangle - /// left boundary. - /// top - Pointer to a double value receiving the rectangle - /// top boundary. - /// right - Pointer to a double value receiving the rectangle - /// right boundary. - /// bottom - Pointer to a double value receiving the rectangle - /// bottom boundary. - /// Return Value: - /// On success, return TRUE and fill in |left|, |top|, |right|, and - /// |bottom|. If |link_page| is invalid or if |link_index| does not - /// correspond to a valid link, then return FALSE, and the out - /// parameters remain unmodified. - int FPDFLink_GetRect( - FPDF_PAGELINK link_page, - int link_index, - int rect_index, - ffi.Pointer left, - ffi.Pointer top, - ffi.Pointer right, - ffi.Pointer bottom, - ) { - return _FPDFLink_GetRect( - link_page, - link_index, - rect_index, - left, - top, - right, - bottom, - ); - } - - late final _FPDFLink_GetRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGELINK, - ffi.Int, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFLink_GetRect'); - late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction< - int Function( - FPDF_PAGELINK, - int, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Function: FPDFLink_GetTextRange - /// Fetch the start char index and char count for a link. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// link_index - Zero-based index for the link. - /// start_char_index - pointer to int receiving the start char index - /// char_count - pointer to int receiving the char count - /// Return Value: - /// On success, return TRUE and fill in |start_char_index| and - /// |char_count|. if |link_page| is invalid or if |link_index| does - /// not correspond to a valid link, then return FALSE and the out - /// parameters remain unmodified. - int FPDFLink_GetTextRange( - FPDF_PAGELINK link_page, - int link_index, - ffi.Pointer start_char_index, - ffi.Pointer char_count, - ) { - return _FPDFLink_GetTextRange( - link_page, - link_index, - start_char_index, - char_count, - ); - } - - late final _FPDFLink_GetTextRangePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGELINK, ffi.Int, ffi.Pointer, - ffi.Pointer)>>('FPDFLink_GetTextRange'); - late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction< - int Function( - FPDF_PAGELINK, int, ffi.Pointer, ffi.Pointer)>(); - - /// Function: FPDFLink_CloseWebLinks - /// Release resources used by weblink feature. - /// Parameters: - /// link_page - Handle returned by FPDFLink_LoadWebLinks. - /// Return Value: - /// None. - void FPDFLink_CloseWebLinks( - FPDF_PAGELINK link_page, - ) { - return _FPDFLink_CloseWebLinks( - link_page, - ); - } - - late final _FPDFLink_CloseWebLinksPtr = - _lookup>( - 'FPDFLink_CloseWebLinks'); - late final _FPDFLink_CloseWebLinks = - _FPDFLink_CloseWebLinksPtr.asFunction(); - - /// Get the first child of |bookmark|, or the first top-level bookmark item. - /// - /// document - handle to the document. - /// bookmark - handle to the current bookmark. Pass NULL for the first top - /// level item. - /// - /// Returns a handle to the first child of |bookmark| or the first top-level - /// bookmark item. NULL if no child or top-level bookmark found. - /// Note that another name for the bookmarks is the document outline, as - /// described in ISO 32000-1:2008, section 12.3.3. - FPDF_BOOKMARK FPDFBookmark_GetFirstChild( - FPDF_DOCUMENT document, - FPDF_BOOKMARK bookmark, - ) { - return _FPDFBookmark_GetFirstChild( - document, - bookmark, - ); - } - - late final _FPDFBookmark_GetFirstChildPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOKMARK Function( - FPDF_DOCUMENT, FPDF_BOOKMARK)>>('FPDFBookmark_GetFirstChild'); - late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr - .asFunction(); - - /// Get the next sibling of |bookmark|. - /// - /// document - handle to the document. - /// bookmark - handle to the current bookmark. - /// - /// Returns a handle to the next sibling of |bookmark|, or NULL if this is the - /// last bookmark at this level. - /// - /// Note that the caller is responsible for handling circular bookmark - /// references, as may arise from malformed documents. - FPDF_BOOKMARK FPDFBookmark_GetNextSibling( - FPDF_DOCUMENT document, - FPDF_BOOKMARK bookmark, - ) { - return _FPDFBookmark_GetNextSibling( - document, - bookmark, - ); - } - - late final _FPDFBookmark_GetNextSiblingPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOKMARK Function( - FPDF_DOCUMENT, FPDF_BOOKMARK)>>('FPDFBookmark_GetNextSibling'); - late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr - .asFunction(); - - /// Get the title of |bookmark|. - /// - /// bookmark - handle to the bookmark. - /// buffer - buffer for the title. May be NULL. - /// buflen - the length of the buffer in bytes. May be 0. - /// - /// Returns the number of bytes in the title, including the terminating NUL - /// character. The number of bytes is returned regardless of the |buffer| and - /// |buflen| parameters. - /// - /// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The - /// string is terminated by a UTF16 NUL character. If |buflen| is less than the - /// required length, or |buffer| is NULL, |buffer| will not be modified. - int FPDFBookmark_GetTitle( - FPDF_BOOKMARK bookmark, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFBookmark_GetTitle( - bookmark, - buffer, - buflen, - ); - } - - late final _FPDFBookmark_GetTitlePtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_BOOKMARK, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFBookmark_GetTitle'); - late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction< - int Function(FPDF_BOOKMARK, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the number of chlidren of |bookmark|. - /// - /// bookmark - handle to the bookmark. - /// - /// Returns a signed integer that represents the number of sub-items the given - /// bookmark has. If the value is positive, child items shall be shown by default - /// (open state). If the value is negative, child items shall be hidden by - /// default (closed state). Please refer to PDF 32000-1:2008, Table 153. - /// Returns 0 if the bookmark has no children or is invalid. - int FPDFBookmark_GetCount( - FPDF_BOOKMARK bookmark, - ) { - return _FPDFBookmark_GetCount( - bookmark, - ); - } - - late final _FPDFBookmark_GetCountPtr = - _lookup>( - 'FPDFBookmark_GetCount'); - late final _FPDFBookmark_GetCount = - _FPDFBookmark_GetCountPtr.asFunction(); - - /// Find the bookmark with |title| in |document|. - /// - /// document - handle to the document. - /// title - the UTF-16LE encoded Unicode title for which to search. - /// - /// Returns the handle to the bookmark, or NULL if |title| can't be found. - /// - /// FPDFBookmark_Find() will always return the first bookmark found even if - /// multiple bookmarks have the same |title|. - FPDF_BOOKMARK FPDFBookmark_Find( - FPDF_DOCUMENT document, - FPDF_WIDESTRING title, - ) { - return _FPDFBookmark_Find( - document, - title, - ); - } - - late final _FPDFBookmark_FindPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOKMARK Function( - FPDF_DOCUMENT, FPDF_WIDESTRING)>>('FPDFBookmark_Find'); - late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction< - FPDF_BOOKMARK Function(FPDF_DOCUMENT, FPDF_WIDESTRING)>(); - - /// Get the destination associated with |bookmark|. - /// - /// document - handle to the document. - /// bookmark - handle to the bookmark. - /// - /// Returns the handle to the destination data, or NULL if no destination is - /// associated with |bookmark|. - FPDF_DEST FPDFBookmark_GetDest( - FPDF_DOCUMENT document, - FPDF_BOOKMARK bookmark, - ) { - return _FPDFBookmark_GetDest( - document, - bookmark, - ); - } - - late final _FPDFBookmark_GetDestPtr = _lookup< - ffi.NativeFunction>( - 'FPDFBookmark_GetDest'); - late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction< - FPDF_DEST Function(FPDF_DOCUMENT, FPDF_BOOKMARK)>(); - - /// Get the action associated with |bookmark|. - /// - /// bookmark - handle to the bookmark. - /// - /// Returns the handle to the action data, or NULL if no action is associated - /// with |bookmark|. - /// If this function returns a valid handle, it is valid as long as |bookmark| is - /// valid. - /// If this function returns NULL, FPDFBookmark_GetDest() should be called to get - /// the |bookmark| destination data. - FPDF_ACTION FPDFBookmark_GetAction( - FPDF_BOOKMARK bookmark, - ) { - return _FPDFBookmark_GetAction( - bookmark, - ); - } - - late final _FPDFBookmark_GetActionPtr = - _lookup>( - 'FPDFBookmark_GetAction'); - late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction< - FPDF_ACTION Function(FPDF_BOOKMARK)>(); - - /// Get the type of |action|. - /// - /// action - handle to the action. - /// - /// Returns one of: - /// PDFACTION_UNSUPPORTED - /// PDFACTION_GOTO - /// PDFACTION_REMOTEGOTO - /// PDFACTION_URI - /// PDFACTION_LAUNCH - int FPDFAction_GetType( - FPDF_ACTION action, - ) { - return _FPDFAction_GetType( - action, - ); - } - - late final _FPDFAction_GetTypePtr = - _lookup>( - 'FPDFAction_GetType'); - late final _FPDFAction_GetType = - _FPDFAction_GetTypePtr.asFunction(); - - /// Get the destination of |action|. - /// - /// document - handle to the document. - /// action - handle to the action. |action| must be a |PDFACTION_GOTO| or - /// |PDFACTION_REMOTEGOTO|. - /// - /// Returns a handle to the destination data, or NULL on error, typically - /// because the arguments were bad or the action was of the wrong type. - /// - /// In the case of |PDFACTION_REMOTEGOTO|, you must first call - /// FPDFAction_GetFilePath(), then load the document at that path, then pass - /// the document handle from that document as |document| to FPDFAction_GetDest(). - FPDF_DEST FPDFAction_GetDest( - FPDF_DOCUMENT document, - FPDF_ACTION action, - ) { - return _FPDFAction_GetDest( - document, - action, - ); - } - - late final _FPDFAction_GetDestPtr = _lookup< - ffi.NativeFunction>( - 'FPDFAction_GetDest'); - late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction< - FPDF_DEST Function(FPDF_DOCUMENT, FPDF_ACTION)>(); - - /// Get the file path of |action|. - /// - /// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or - /// |PDFACTION_REMOTEGOTO|. - /// buffer - a buffer for output the path string. May be NULL. - /// buflen - the length of the buffer, in bytes. May be 0. - /// - /// Returns the number of bytes in the file path, including the trailing NUL - /// character, or 0 on error, typically because the arguments were bad or the - /// action was of the wrong type. - /// - /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. - /// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| - /// will not be modified. - int FPDFAction_GetFilePath( - FPDF_ACTION action, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAction_GetFilePath( - action, - buffer, - buflen, - ); - } - - late final _FPDFAction_GetFilePathPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_ACTION, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAction_GetFilePath'); - late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction< - int Function(FPDF_ACTION, ffi.Pointer, int)>(); - - /// Get the URI path of |action|. - /// - /// document - handle to the document. - /// action - handle to the action. Must be a |PDFACTION_URI|. - /// buffer - a buffer for the path string. May be NULL. - /// buflen - the length of the buffer, in bytes. May be 0. - /// - /// Returns the number of bytes in the URI path, including the trailing NUL - /// character, or 0 on error, typically because the arguments were bad or the - /// action was of the wrong type. - /// - /// The |buffer| may contain badly encoded data. The caller should validate the - /// output. e.g. Check to see if it is UTF-8. - /// - /// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| - /// will not be modified. - /// - /// Historically, the documentation for this API claimed |buffer| is always - /// encoded in 7-bit ASCII, but did not actually enforce it. - /// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 - /// added that enforcement, but that did not work well for real world PDFs that - /// used UTF-8. As of this writing, this API reverted back to its original - /// behavior prior to commit d609e84cee. - int FPDFAction_GetURIPath( - FPDF_DOCUMENT document, - FPDF_ACTION action, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFAction_GetURIPath( - document, - action, - buffer, - buflen, - ); - } - - late final _FPDFAction_GetURIPathPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_DOCUMENT, - FPDF_ACTION, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFAction_GetURIPath'); - late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction< - int Function(FPDF_DOCUMENT, FPDF_ACTION, ffi.Pointer, int)>(); - - /// Get the page index of |dest|. - /// - /// document - handle to the document. - /// dest - handle to the destination. - /// - /// Returns the 0-based page index containing |dest|. Returns -1 on error. - int FPDFDest_GetDestPageIndex( - FPDF_DOCUMENT document, - FPDF_DEST dest, - ) { - return _FPDFDest_GetDestPageIndex( - document, - dest, - ); - } - - late final _FPDFDest_GetDestPageIndexPtr = - _lookup>( - 'FPDFDest_GetDestPageIndex'); - late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr - .asFunction(); - - /// Experimental API. - /// Get the view (fit type) specified by |dest|. - /// - /// dest - handle to the destination. - /// pNumParams - receives the number of view parameters, which is at most 4. - /// pParams - buffer to write the view parameters. Must be at least 4 - /// FS_FLOATs long. - /// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if - /// |dest| does not specify a view. - int FPDFDest_GetView( - FPDF_DEST dest, - ffi.Pointer pNumParams, - ffi.Pointer pParams, - ) { - return _FPDFDest_GetView( - dest, - pNumParams, - pParams, - ); - } - - late final _FPDFDest_GetViewPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_DEST, ffi.Pointer, - ffi.Pointer)>>('FPDFDest_GetView'); - late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction< - int Function( - FPDF_DEST, ffi.Pointer, ffi.Pointer)>(); - - /// Get the (x, y, zoom) location of |dest| in the destination page, if the - /// destination is in [page /XYZ x y zoom] syntax. - /// - /// dest - handle to the destination. - /// hasXVal - out parameter; true if the x value is not null - /// hasYVal - out parameter; true if the y value is not null - /// hasZoomVal - out parameter; true if the zoom value is not null - /// x - out parameter; the x coordinate, in page coordinates. - /// y - out parameter; the y coordinate, in page coordinates. - /// zoom - out parameter; the zoom value. - /// Returns TRUE on successfully reading the /XYZ value. - /// - /// Note the [x, y, zoom] values are only set if the corresponding hasXVal, - /// hasYVal or hasZoomVal flags are true. - int FPDFDest_GetLocationInPage( - FPDF_DEST dest, - ffi.Pointer hasXVal, - ffi.Pointer hasYVal, - ffi.Pointer hasZoomVal, - ffi.Pointer x, - ffi.Pointer y, - ffi.Pointer zoom, - ) { - return _FPDFDest_GetLocationInPage( - dest, - hasXVal, - hasYVal, - hasZoomVal, - x, - y, - zoom, - ); - } - - late final _FPDFDest_GetLocationInPagePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DEST, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFDest_GetLocationInPage'); - late final _FPDFDest_GetLocationInPage = - _FPDFDest_GetLocationInPagePtr.asFunction< - int Function( - FPDF_DEST, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Find a link at point (|x|,|y|) on |page|. - /// - /// page - handle to the document page. - /// x - the x coordinate, in the page coordinate system. - /// y - the y coordinate, in the page coordinate system. - /// - /// Returns a handle to the link, or NULL if no link found at the given point. - /// - /// You can convert coordinates from screen coordinates to page coordinates using - /// FPDF_DeviceToPage(). - FPDF_LINK FPDFLink_GetLinkAtPoint( - FPDF_PAGE page, - double x, - double y, - ) { - return _FPDFLink_GetLinkAtPoint( - page, - x, - y, - ); - } - - late final _FPDFLink_GetLinkAtPointPtr = _lookup< - ffi.NativeFunction< - FPDF_LINK Function( - FPDF_PAGE, ffi.Double, ffi.Double)>>('FPDFLink_GetLinkAtPoint'); - late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction< - FPDF_LINK Function(FPDF_PAGE, double, double)>(); - - /// Find the Z-order of link at point (|x|,|y|) on |page|. - /// - /// page - handle to the document page. - /// x - the x coordinate, in the page coordinate system. - /// y - the y coordinate, in the page coordinate system. - /// - /// Returns the Z-order of the link, or -1 if no link found at the given point. - /// Larger Z-order numbers are closer to the front. - /// - /// You can convert coordinates from screen coordinates to page coordinates using - /// FPDF_DeviceToPage(). - int FPDFLink_GetLinkZOrderAtPoint( - FPDF_PAGE page, - double x, - double y, - ) { - return _FPDFLink_GetLinkZOrderAtPoint( - page, - x, - y, - ); - } - - late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFLink_GetLinkZOrderAtPoint'); - late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr - .asFunction(); - - /// Get destination info for |link|. - /// - /// document - handle to the document. - /// link - handle to the link. - /// - /// Returns a handle to the destination, or NULL if there is no destination - /// associated with the link. In this case, you should call FPDFLink_GetAction() - /// to retrieve the action associated with |link|. - FPDF_DEST FPDFLink_GetDest( - FPDF_DOCUMENT document, - FPDF_LINK link, - ) { - return _FPDFLink_GetDest( - document, - link, - ); - } - - late final _FPDFLink_GetDestPtr = - _lookup>( - 'FPDFLink_GetDest'); - late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction< - FPDF_DEST Function(FPDF_DOCUMENT, FPDF_LINK)>(); - - /// Get action info for |link|. - /// - /// link - handle to the link. - /// - /// Returns a handle to the action associated to |link|, or NULL if no action. - /// If this function returns a valid handle, it is valid as long as |link| is - /// valid. - FPDF_ACTION FPDFLink_GetAction( - FPDF_LINK link, - ) { - return _FPDFLink_GetAction( - link, - ); - } - - late final _FPDFLink_GetActionPtr = - _lookup>( - 'FPDFLink_GetAction'); - late final _FPDFLink_GetAction = - _FPDFLink_GetActionPtr.asFunction(); - - /// Enumerates all the link annotations in |page|. - /// - /// page - handle to the page. - /// start_pos - the start position, should initially be 0 and is updated with - /// the next start position on return. - /// link_annot - the link handle for |startPos|. - /// - /// Returns TRUE on success. - int FPDFLink_Enumerate( - FPDF_PAGE page, - ffi.Pointer start_pos, - ffi.Pointer link_annot, - ) { - return _FPDFLink_Enumerate( - page, - start_pos, - link_annot, - ); - } - - late final _FPDFLink_EnumeratePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGE, ffi.Pointer, - ffi.Pointer)>>('FPDFLink_Enumerate'); - late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction< - int Function(FPDF_PAGE, ffi.Pointer, ffi.Pointer)>(); - - /// Experimental API. - /// Gets FPDF_ANNOTATION object for |link_annot|. - /// - /// page - handle to the page in which FPDF_LINK object is present. - /// link_annot - handle to link annotation. - /// - /// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, - /// if the input link annot or page is NULL. - FPDF_ANNOTATION FPDFLink_GetAnnot( - FPDF_PAGE page, - FPDF_LINK link_annot, - ) { - return _FPDFLink_GetAnnot( - page, - link_annot, - ); - } - - late final _FPDFLink_GetAnnotPtr = _lookup< - ffi.NativeFunction>( - 'FPDFLink_GetAnnot'); - late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction< - FPDF_ANNOTATION Function(FPDF_PAGE, FPDF_LINK)>(); - - /// Get the rectangle for |link_annot|. - /// - /// link_annot - handle to the link annotation. - /// rect - the annotation rectangle. - /// - /// Returns true on success. - int FPDFLink_GetAnnotRect( - FPDF_LINK link_annot, - ffi.Pointer rect, - ) { - return _FPDFLink_GetAnnotRect( - link_annot, - rect, - ); - } - - late final _FPDFLink_GetAnnotRectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_LINK, ffi.Pointer)>>('FPDFLink_GetAnnotRect'); - late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction< - int Function(FPDF_LINK, ffi.Pointer)>(); - - /// Get the count of quadrilateral points to the |link_annot|. - /// - /// link_annot - handle to the link annotation. - /// - /// Returns the count of quadrilateral points. - int FPDFLink_CountQuadPoints( - FPDF_LINK link_annot, - ) { - return _FPDFLink_CountQuadPoints( - link_annot, - ); - } - - late final _FPDFLink_CountQuadPointsPtr = - _lookup>( - 'FPDFLink_CountQuadPoints'); - late final _FPDFLink_CountQuadPoints = - _FPDFLink_CountQuadPointsPtr.asFunction(); - - /// Get the quadrilateral points for the specified |quad_index| in |link_annot|. - /// - /// link_annot - handle to the link annotation. - /// quad_index - the specified quad point index. - /// quad_points - receives the quadrilateral points. - /// - /// Returns true on success. - int FPDFLink_GetQuadPoints( - FPDF_LINK link_annot, - int quad_index, - ffi.Pointer quad_points, - ) { - return _FPDFLink_GetQuadPoints( - link_annot, - quad_index, - quad_points, - ); - } - - late final _FPDFLink_GetQuadPointsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_LINK, ffi.Int, - ffi.Pointer)>>('FPDFLink_GetQuadPoints'); - late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction< - int Function(FPDF_LINK, int, ffi.Pointer)>(); - - /// Experimental API - /// Gets an additional-action from |page|. - /// - /// page - handle to the page, as returned by FPDF_LoadPage(). - /// aa_type - the type of the page object's addtional-action, defined - /// in public/fpdf_formfill.h - /// - /// Returns the handle to the action data, or NULL if there is no - /// additional-action of type |aa_type|. - /// If this function returns a valid handle, it is valid as long as |page| is - /// valid. - FPDF_ACTION FPDF_GetPageAAction( - FPDF_PAGE page, - int aa_type, - ) { - return _FPDF_GetPageAAction( - page, - aa_type, - ); - } - - late final _FPDF_GetPageAActionPtr = - _lookup>( - 'FPDF_GetPageAAction'); - late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction< - FPDF_ACTION Function(FPDF_PAGE, int)>(); - - /// Experimental API. - /// Get the file identifer defined in the trailer of |document|. - /// - /// document - handle to the document. - /// id_type - the file identifier type to retrieve. - /// buffer - a buffer for the file identifier. May be NULL. - /// buflen - the length of the buffer, in bytes. May be 0. - /// - /// Returns the number of bytes in the file identifier, including the NUL - /// terminator. - /// - /// The |buffer| is always a byte string. The |buffer| is followed by a NUL - /// terminator. If |buflen| is less than the returned length, or |buffer| is - /// NULL, |buffer| will not be modified. - int FPDF_GetFileIdentifier( - FPDF_DOCUMENT document, - FPDF_FILEIDTYPE id_type, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDF_GetFileIdentifier( - document, - id_type.value, - buffer, - buflen, - ); - } - - late final _FPDF_GetFileIdentifierPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_DOCUMENT, - ffi.UnsignedInt, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDF_GetFileIdentifier'); - late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction< - int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); - - /// Get meta-data |tag| content from |document|. - /// - /// document - handle to the document. - /// tag - the tag to retrieve. The tag can be one of: - /// Title, Author, Subject, Keywords, Creator, Producer, - /// CreationDate, or ModDate. - /// For detailed explanations of these tags and their respective - /// values, please refer to PDF Reference 1.6, section 10.2.1, - /// 'Document Information Dictionary'. - /// buffer - a buffer for the tag. May be NULL. - /// buflen - the length of the buffer, in bytes. May be 0. - /// - /// Returns the number of bytes in the tag, including trailing zeros. - /// - /// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two - /// bytes of zeros indicating the end of the string. If |buflen| is less than - /// the returned length, or |buffer| is NULL, |buffer| will not be modified. - /// - /// For linearized files, FPDFAvail_IsFormAvail must be called before this, and - /// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there - /// is no guarantee the metadata has been loaded. - int FPDF_GetMetaText( - FPDF_DOCUMENT document, - FPDF_BYTESTRING tag, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDF_GetMetaText( - document, - tag, - buffer, - buflen, - ); - } - - late final _FPDF_GetMetaTextPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_DOCUMENT, FPDF_BYTESTRING, - ffi.Pointer, ffi.UnsignedLong)>>('FPDF_GetMetaText'); - late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction< - int Function( - FPDF_DOCUMENT, FPDF_BYTESTRING, ffi.Pointer, int)>(); - - /// Get the page label for |page_index| from |document|. - /// - /// document - handle to the document. - /// page_index - the 0-based index of the page. - /// buffer - a buffer for the page label. May be NULL. - /// buflen - the length of the buffer, in bytes. May be 0. - /// - /// Returns the number of bytes in the page label, including trailing zeros. - /// - /// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two - /// bytes of zeros indicating the end of the string. If |buflen| is less than - /// the returned length, or |buffer| is NULL, |buffer| will not be modified. - int FPDF_GetPageLabel( - FPDF_DOCUMENT document, - int page_index, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDF_GetPageLabel( - document, - page_index, - buffer, - buflen, - ); - } - - late final _FPDF_GetPageLabelPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_DOCUMENT, ffi.Int, - ffi.Pointer, ffi.UnsignedLong)>>('FPDF_GetPageLabel'); - late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction< - int Function(FPDF_DOCUMENT, int, ffi.Pointer, int)>(); - - /// Create a new PDF document. - /// - /// Returns a handle to a new document, or NULL on failure. - FPDF_DOCUMENT FPDF_CreateNewDocument() { - return _FPDF_CreateNewDocument(); - } - - late final _FPDF_CreateNewDocumentPtr = - _lookup>( - 'FPDF_CreateNewDocument'); - late final _FPDF_CreateNewDocument = - _FPDF_CreateNewDocumentPtr.asFunction(); - - /// Create a new PDF page. - /// - /// document - handle to document. - /// page_index - suggested 0-based index of the page to create. If it is larger - /// than document's current last index(L), the created page index - /// is the next available index -- L+1. - /// width - the page width in points. - /// height - the page height in points. - /// - /// Returns the handle to the new page or NULL on failure. - /// - /// The page should be closed with FPDF_ClosePage() when finished as - /// with any other page in the document. - FPDF_PAGE FPDFPage_New( - FPDF_DOCUMENT document, - int page_index, - double width, - double height, - ) { - return _FPDFPage_New( - document, - page_index, - width, - height, - ); - } - - late final _FPDFPage_NewPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGE Function( - FPDF_DOCUMENT, ffi.Int, ffi.Double, ffi.Double)>>('FPDFPage_New'); - late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction< - FPDF_PAGE Function(FPDF_DOCUMENT, int, double, double)>(); - - /// Delete the page at |page_index|. - /// - /// document - handle to document. - /// page_index - the index of the page to delete. - void FPDFPage_Delete( - FPDF_DOCUMENT document, - int page_index, - ) { - return _FPDFPage_Delete( - document, - page_index, - ); - } - - late final _FPDFPage_DeletePtr = - _lookup>( - 'FPDFPage_Delete'); - late final _FPDFPage_Delete = - _FPDFPage_DeletePtr.asFunction(); - - /// Experimental API. - /// Move the given pages to a new index position. - /// - /// page_indices - the ordered list of pages to move. No duplicates allowed. - /// page_indices_len - the number of elements in |page_indices| - /// dest_page_index - the new index position to which the pages in - /// |page_indices| are moved. - /// - /// Returns TRUE on success. If it returns FALSE, the document may be left in an - /// indeterminate state. - /// - /// Example: The PDF document starts out with pages [A, B, C, D], with indices - /// [0, 1, 2, 3]. - /// - /// > Move(doc, [3, 2], 2, 1); // returns true - /// > // The document has pages [A, D, C, B]. - /// > - /// > Move(doc, [0, 4, 3], 3, 1); // returns false - /// > // Returned false because index 4 is out of range. - /// > - /// > Move(doc, [0, 3, 1], 3, 2); // returns false - /// > // Returned false because index 2 is out of range for 3 page indices. - /// > - /// > Move(doc, [2, 2], 2, 0); // returns false - /// > // Returned false because [2, 2] contains duplicates. - int FPDF_MovePages( - FPDF_DOCUMENT document, - ffi.Pointer page_indices, - int page_indices_len, - int dest_page_index, - ) { - return _FPDF_MovePages( - document, - page_indices, - page_indices_len, - dest_page_index, - ); - } - - late final _FPDF_MovePagesPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_DOCUMENT, ffi.Pointer, - ffi.UnsignedLong, ffi.Int)>>('FPDF_MovePages'); - late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction< - int Function(FPDF_DOCUMENT, ffi.Pointer, int, int)>(); - - /// Get the rotation of |page|. - /// - /// page - handle to a page - /// - /// Returns one of the following indicating the page rotation: - /// 0 - No rotation. - /// 1 - Rotated 90 degrees clockwise. - /// 2 - Rotated 180 degrees clockwise. - /// 3 - Rotated 270 degrees clockwise. - int FPDFPage_GetRotation( - FPDF_PAGE page, - ) { - return _FPDFPage_GetRotation( - page, - ); - } - - late final _FPDFPage_GetRotationPtr = - _lookup>( - 'FPDFPage_GetRotation'); - late final _FPDFPage_GetRotation = - _FPDFPage_GetRotationPtr.asFunction(); - - /// Set rotation for |page|. - /// - /// page - handle to a page. - /// rotate - the rotation value, one of: - /// 0 - No rotation. - /// 1 - Rotated 90 degrees clockwise. - /// 2 - Rotated 180 degrees clockwise. - /// 3 - Rotated 270 degrees clockwise. - void FPDFPage_SetRotation( - FPDF_PAGE page, - int rotate, - ) { - return _FPDFPage_SetRotation( - page, - rotate, - ); - } - - late final _FPDFPage_SetRotationPtr = - _lookup>( - 'FPDFPage_SetRotation'); - late final _FPDFPage_SetRotation = - _FPDFPage_SetRotationPtr.asFunction(); - - /// Insert |page_object| into |page|. - /// - /// page - handle to a page - /// page_object - handle to a page object. The |page_object| will be - /// automatically freed. - void FPDFPage_InsertObject( - FPDF_PAGE page, - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPage_InsertObject( - page, - page_object, - ); - } - - late final _FPDFPage_InsertObjectPtr = _lookup< - ffi.NativeFunction>( - 'FPDFPage_InsertObject'); - late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction< - void Function(FPDF_PAGE, FPDF_PAGEOBJECT)>(); - - /// Experimental API. - /// Remove |page_object| from |page|. - /// - /// page - handle to a page - /// page_object - handle to a page object to be removed. - /// - /// Returns TRUE on success. - /// - /// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free - /// it. - /// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all - /// FPDF_TEXTPAGE handles for |page| are no longer valid. - int FPDFPage_RemoveObject( - FPDF_PAGE page, - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPage_RemoveObject( - page, - page_object, - ); - } - - late final _FPDFPage_RemoveObjectPtr = _lookup< - ffi.NativeFunction>( - 'FPDFPage_RemoveObject'); - late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction< - int Function(FPDF_PAGE, FPDF_PAGEOBJECT)>(); - - /// Get number of page objects inside |page|. - /// - /// page - handle to a page. - /// - /// Returns the number of objects in |page|. - int FPDFPage_CountObjects( - FPDF_PAGE page, - ) { - return _FPDFPage_CountObjects( - page, - ); - } - - late final _FPDFPage_CountObjectsPtr = - _lookup>( - 'FPDFPage_CountObjects'); - late final _FPDFPage_CountObjects = - _FPDFPage_CountObjectsPtr.asFunction(); - - /// Get object in |page| at |index|. - /// - /// page - handle to a page. - /// index - the index of a page object. - /// - /// Returns the handle to the page object, or NULL on failed. - FPDF_PAGEOBJECT FPDFPage_GetObject( - FPDF_PAGE page, - int index, - ) { - return _FPDFPage_GetObject( - page, - index, - ); - } - - late final _FPDFPage_GetObjectPtr = - _lookup>( - 'FPDFPage_GetObject'); - late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_PAGE, int)>(); - - /// Checks if |page| contains transparency. - /// - /// page - handle to a page. - /// - /// Returns TRUE if |page| contains transparency. - int FPDFPage_HasTransparency( - FPDF_PAGE page, - ) { - return _FPDFPage_HasTransparency( - page, - ); - } - - late final _FPDFPage_HasTransparencyPtr = - _lookup>( - 'FPDFPage_HasTransparency'); - late final _FPDFPage_HasTransparency = - _FPDFPage_HasTransparencyPtr.asFunction(); - - /// Generate the content of |page|. - /// - /// page - handle to a page. - /// - /// Returns TRUE on success. - /// - /// Before you save the page to a file, or reload the page, you must call - /// |FPDFPage_GenerateContent| or any changes to |page| will be lost. - int FPDFPage_GenerateContent( - FPDF_PAGE page, - ) { - return _FPDFPage_GenerateContent( - page, - ); - } - - late final _FPDFPage_GenerateContentPtr = - _lookup>( - 'FPDFPage_GenerateContent'); - late final _FPDFPage_GenerateContent = - _FPDFPage_GenerateContentPtr.asFunction(); - - /// Destroy |page_object| by releasing its resources. |page_object| must have - /// been created by FPDFPageObj_CreateNew{Path|Rect}() or - /// FPDFPageObj_New{Text|Image}Obj(). This function must be called on - /// newly-created objects if they are not added to a page through - /// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). - /// - /// page_object - handle to a page object. - void FPDFPageObj_Destroy( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_Destroy( - page_object, - ); - } - - late final _FPDFPageObj_DestroyPtr = - _lookup>( - 'FPDFPageObj_Destroy'); - late final _FPDFPageObj_Destroy = - _FPDFPageObj_DestroyPtr.asFunction(); - - /// Checks if |page_object| contains transparency. - /// - /// page_object - handle to a page object. - /// - /// Returns TRUE if |page_object| contains transparency. - int FPDFPageObj_HasTransparency( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_HasTransparency( - page_object, - ); - } - - late final _FPDFPageObj_HasTransparencyPtr = - _lookup>( - 'FPDFPageObj_HasTransparency'); - late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr - .asFunction(); - - /// Get type of |page_object|. - /// - /// page_object - handle to a page object. - /// - /// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on - /// error. - int FPDFPageObj_GetType( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_GetType( - page_object, - ); - } - - late final _FPDFPageObj_GetTypePtr = - _lookup>( - 'FPDFPageObj_GetType'); - late final _FPDFPageObj_GetType = - _FPDFPageObj_GetTypePtr.asFunction(); - - /// Experimental API. - /// Gets active state for |page_object| within page. - /// - /// page_object - handle to a page object. - /// active - pointer to variable that will receive if the page object is - /// active. This is a required parameter. Not filled if FALSE - /// is returned. - /// - /// For page objects where |active| is filled with FALSE, the |page_object| is - /// treated as if it wasn't in the document even though it is still held - /// internally. - /// - /// Returns TRUE if the operation succeeded, FALSE if it failed. - int FPDFPageObj_GetIsActive( - FPDF_PAGEOBJECT page_object, - ffi.Pointer active, - ) { - return _FPDFPageObj_GetIsActive( - page_object, - active, - ); - } - - late final _FPDFPageObj_GetIsActivePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_GetIsActive'); - late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); - - /// Experimental API. - /// Sets if |page_object| is active within page. - /// - /// page_object - handle to a page object. - /// active - a boolean specifying if the object is active. - /// - /// Returns TRUE on success. - /// - /// Page objects all start in the active state by default, and remain in that - /// state unless this function is called. - /// - /// When |active| is false, this makes the |page_object| be treated as if it - /// wasn't in the document even though it is still held internally. - int FPDFPageObj_SetIsActive( - FPDF_PAGEOBJECT page_object, - int active, - ) { - return _FPDFPageObj_SetIsActive( - page_object, - active, - ); - } - - late final _FPDFPageObj_SetIsActivePtr = _lookup< - ffi.NativeFunction>( - 'FPDFPageObj_SetIsActive'); - late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction< - int Function(FPDF_PAGEOBJECT, int)>(); - - /// Transform |page_object| by the given matrix. - /// - /// page_object - handle to a page object. - /// a - matrix value. - /// b - matrix value. - /// c - matrix value. - /// d - matrix value. - /// e - matrix value. - /// f - matrix value. - /// - /// The matrix is composed as: - /// |a c e| - /// |b d f| - /// and can be used to scale, rotate, shear and translate the |page_object|. - void FPDFPageObj_Transform( - FPDF_PAGEOBJECT page_object, - double a, - double b, - double c, - double d, - double e, - double f, - ) { - return _FPDFPageObj_Transform( - page_object, - a, - b, - c, - d, - e, - f, - ); - } - - late final _FPDFPageObj_TransformPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_PAGEOBJECT, ffi.Double, ffi.Double, ffi.Double, - ffi.Double, ffi.Double, ffi.Double)>>('FPDFPageObj_Transform'); - late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction< - void Function( - FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); - - /// Experimental API. - /// Transform |page_object| by the given matrix. - /// - /// page_object - handle to a page object. - /// matrix - the transform matrix. - /// - /// Returns TRUE on success. - /// - /// This can be used to scale, rotate, shear and translate the |page_object|. - /// It is an improved version of FPDFPageObj_Transform() that does not do - /// unnecessary double to float conversions, and only uses 1 parameter for the - /// matrix. It also returns whether the operation succeeded or not. - int FPDFPageObj_TransformF( - FPDF_PAGEOBJECT page_object, - ffi.Pointer matrix, - ) { - return _FPDFPageObj_TransformF( - page_object, - matrix, - ); - } - - late final _FPDFPageObj_TransformFPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_TransformF'); - late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); - - /// Experimental API. - /// Get the transform matrix of a page object. - /// - /// page_object - handle to a page object. - /// matrix - pointer to struct to receive the matrix value. - /// - /// The matrix is composed as: - /// |a c e| - /// |b d f| - /// and used to scale, rotate, shear and translate the page object. - /// - /// For page objects outside form objects, the matrix values are relative to the - /// page that contains it. - /// For page objects inside form objects, the matrix values are relative to the - /// form that contains it. - /// - /// Returns TRUE on success. - int FPDFPageObj_GetMatrix( - FPDF_PAGEOBJECT page_object, - ffi.Pointer matrix, - ) { - return _FPDFPageObj_GetMatrix( - page_object, - matrix, - ); - } - - late final _FPDFPageObj_GetMatrixPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_GetMatrix'); - late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); - - /// Experimental API. - /// Set the transform matrix of a page object. - /// - /// page_object - handle to a page object. - /// matrix - pointer to struct with the matrix value. - /// - /// The matrix is composed as: - /// |a c e| - /// |b d f| - /// and can be used to scale, rotate, shear and translate the page object. - /// - /// Returns TRUE on success. - int FPDFPageObj_SetMatrix( - FPDF_PAGEOBJECT page_object, - ffi.Pointer matrix, - ) { - return _FPDFPageObj_SetMatrix( - page_object, - matrix, - ); - } - - late final _FPDFPageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_SetMatrix'); - late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); - - /// Transform all annotations in |page|. - /// - /// page - handle to a page. - /// a - matrix value. - /// b - matrix value. - /// c - matrix value. - /// d - matrix value. - /// e - matrix value. - /// f - matrix value. - /// - /// The matrix is composed as: - /// |a c e| - /// |b d f| - /// and can be used to scale, rotate, shear and translate the |page| annotations. - void FPDFPage_TransformAnnots( - FPDF_PAGE page, - double a, - double b, - double c, - double d, - double e, - double f, - ) { - return _FPDFPage_TransformAnnots( - page, - a, - b, - c, - d, - e, - f, - ); - } - - late final _FPDFPage_TransformAnnotsPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(FPDF_PAGE, ffi.Double, ffi.Double, ffi.Double, - ffi.Double, ffi.Double, ffi.Double)>>('FPDFPage_TransformAnnots'); - late final _FPDFPage_TransformAnnots = - _FPDFPage_TransformAnnotsPtr.asFunction< - void Function( - FPDF_PAGE, double, double, double, double, double, double)>(); - - /// Create a new image object. - /// - /// document - handle to a document. - /// - /// Returns a handle to a new image object. - FPDF_PAGEOBJECT FPDFPageObj_NewImageObj( - FPDF_DOCUMENT document, - ) { - return _FPDFPageObj_NewImageObj( - document, - ); - } - - late final _FPDFPageObj_NewImageObjPtr = - _lookup>( - 'FPDFPageObj_NewImageObj'); - late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_DOCUMENT)>(); - - /// Experimental API. - /// Get the marked content ID for the object. - /// - /// page_object - handle to a page object. - /// - /// Returns the page object's marked content ID, or -1 on error. - int FPDFPageObj_GetMarkedContentID( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_GetMarkedContentID( - page_object, - ); - } - - late final _FPDFPageObj_GetMarkedContentIDPtr = - _lookup>( - 'FPDFPageObj_GetMarkedContentID'); - late final _FPDFPageObj_GetMarkedContentID = - _FPDFPageObj_GetMarkedContentIDPtr.asFunction< - int Function(FPDF_PAGEOBJECT)>(); - - /// Experimental API. - /// Get number of content marks in |page_object|. - /// - /// page_object - handle to a page object. - /// - /// Returns the number of content marks in |page_object|, or -1 in case of - /// failure. - int FPDFPageObj_CountMarks( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_CountMarks( - page_object, - ); - } - - late final _FPDFPageObj_CountMarksPtr = - _lookup>( - 'FPDFPageObj_CountMarks'); - late final _FPDFPageObj_CountMarks = - _FPDFPageObj_CountMarksPtr.asFunction(); - - /// Experimental API. - /// Get content mark in |page_object| at |index|. - /// - /// page_object - handle to a page object. - /// index - the index of a page object. - /// - /// Returns the handle to the content mark, or NULL on failure. The handle is - /// still owned by the library, and it should not be freed directly. It becomes - /// invalid if the page object is destroyed, either directly or indirectly by - /// unloading the page. - FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark( - FPDF_PAGEOBJECT page_object, - int index, - ) { - return _FPDFPageObj_GetMark( - page_object, - index, - ); - } - - late final _FPDFPageObj_GetMarkPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECTMARK Function( - FPDF_PAGEOBJECT, ffi.UnsignedLong)>>('FPDFPageObj_GetMark'); - late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction< - FPDF_PAGEOBJECTMARK Function(FPDF_PAGEOBJECT, int)>(); - - /// Experimental API. - /// Add a new content mark to a |page_object|. - /// - /// page_object - handle to a page object. - /// name - the name (tag) of the mark. - /// - /// Returns the handle to the content mark, or NULL on failure. The handle is - /// still owned by the library, and it should not be freed directly. It becomes - /// invalid if the page object is destroyed, either directly or indirectly by - /// unloading the page. - FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark( - FPDF_PAGEOBJECT page_object, - FPDF_BYTESTRING name, - ) { - return _FPDFPageObj_AddMark( - page_object, - name, - ); - } - - late final _FPDFPageObj_AddMarkPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECTMARK Function( - FPDF_PAGEOBJECT, FPDF_BYTESTRING)>>('FPDFPageObj_AddMark'); - late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction< - FPDF_PAGEOBJECTMARK Function(FPDF_PAGEOBJECT, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Removes a content |mark| from a |page_object|. - /// The mark handle will be invalid after the removal. - /// - /// page_object - handle to a page object. - /// mark - handle to a content mark in that object to remove. - /// - /// Returns TRUE if the operation succeeded, FALSE if it failed. - int FPDFPageObj_RemoveMark( - FPDF_PAGEOBJECT page_object, - FPDF_PAGEOBJECTMARK mark, - ) { - return _FPDFPageObj_RemoveMark( - page_object, - mark, - ); - } - - late final _FPDFPageObj_RemoveMarkPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK)>>('FPDFPageObj_RemoveMark'); - late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction< - int Function(FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK)>(); - - /// Experimental API. - /// Get the name of a content mark. - /// - /// mark - handle to a content mark. - /// buffer - buffer for holding the returned name in UTF-16LE. This is only - /// modified if |buflen| is large enough to store the name. - /// Optional, pass null to just retrieve the size of the buffer - /// needed. - /// buflen - length of the buffer in bytes. - /// out_buflen - pointer to variable that will receive the minimum buffer size - /// in bytes to contain the name. This is a required parameter. - /// Not filled if FALSE is returned. - /// - /// Returns TRUE if the operation succeeded, FALSE if it failed. - int FPDFPageObjMark_GetName( - FPDF_PAGEOBJECTMARK mark, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFPageObjMark_GetName( - mark, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFPageObjMark_GetNamePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECTMARK, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>('FPDFPageObjMark_GetName'); - late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, ffi.Pointer, int, - ffi.Pointer)>(); - - /// Experimental API. - /// Get the number of key/value pair parameters in |mark|. - /// - /// mark - handle to a content mark. - /// - /// Returns the number of key/value pair parameters |mark|, or -1 in case of - /// failure. - int FPDFPageObjMark_CountParams( - FPDF_PAGEOBJECTMARK mark, - ) { - return _FPDFPageObjMark_CountParams( - mark, - ); - } - - late final _FPDFPageObjMark_CountParamsPtr = - _lookup>( - 'FPDFPageObjMark_CountParams'); - late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr - .asFunction(); - - /// Experimental API. - /// Get the key of a property in a content mark. - /// - /// mark - handle to a content mark. - /// index - index of the property. - /// buffer - buffer for holding the returned key in UTF-16LE. This is only - /// modified if |buflen| is large enough to store the key. - /// Optional, pass null to just retrieve the size of the buffer - /// needed. - /// buflen - length of the buffer in bytes. - /// out_buflen - pointer to variable that will receive the minimum buffer size - /// in bytes to contain the name. This is a required parameter. - /// Not filled if FALSE is returned. - /// - /// Returns TRUE if the operation was successful, FALSE otherwise. - int FPDFPageObjMark_GetParamKey( - FPDF_PAGEOBJECTMARK mark, - int index, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFPageObjMark_GetParamKey( - mark, - index, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECTMARK, - ffi.UnsignedLong, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>('FPDFPageObjMark_GetParamKey'); - late final _FPDFPageObjMark_GetParamKey = - _FPDFPageObjMark_GetParamKeyPtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, int, ffi.Pointer, int, - ffi.Pointer)>(); - - /// Experimental API. - /// Get the type of the value of a property in a content mark by key. - /// - /// mark - handle to a content mark. - /// key - string key of the property. - /// - /// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. - int FPDFPageObjMark_GetParamValueType( - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ) { - return _FPDFPageObjMark_GetParamValueType( - mark, - key, - ); - } - - late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< - ffi.NativeFunction< - FPDF_OBJECT_TYPE Function(FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING)>>('FPDFPageObjMark_GetParamValueType'); - late final _FPDFPageObjMark_GetParamValueType = - _FPDFPageObjMark_GetParamValueTypePtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Get the value of a number property in a content mark by key as int. - /// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER - /// for this property. - /// - /// mark - handle to a content mark. - /// key - string key of the property. - /// out_value - pointer to variable that will receive the value. Not filled if - /// false is returned. - /// - /// Returns TRUE if the key maps to a number value, FALSE otherwise. - int FPDFPageObjMark_GetParamIntValue( - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ffi.Pointer out_value, - ) { - return _FPDFPageObjMark_GetParamIntValue( - mark, - key, - out_value, - ); - } - - late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer)>>('FPDFPageObjMark_GetParamIntValue'); - late final _FPDFPageObjMark_GetParamIntValue = - _FPDFPageObjMark_GetParamIntValuePtr.asFunction< - int Function( - FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, ffi.Pointer)>(); - - /// Experimental API. - /// Get the value of a string property in a content mark by key. - /// - /// mark - handle to a content mark. - /// key - string key of the property. - /// buffer - buffer for holding the returned value in UTF-16LE. This is - /// only modified if |buflen| is large enough to store the value. - /// Optional, pass null to just retrieve the size of the buffer - /// needed. - /// buflen - length of the buffer in bytes. - /// out_buflen - pointer to variable that will receive the minimum buffer size - /// in bytes to contain the name. This is a required parameter. - /// Not filled if FALSE is returned. - /// - /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. - int FPDFPageObjMark_GetParamStringValue( - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFPageObjMark_GetParamStringValue( - mark, - key, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>( - 'FPDFPageObjMark_GetParamStringValue'); - late final _FPDFPageObjMark_GetParamStringValue = - _FPDFPageObjMark_GetParamStringValuePtr.asFunction< - int Function(FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, - ffi.Pointer, int, ffi.Pointer)>(); - - /// Experimental API. - /// Get the value of a blob property in a content mark by key. - /// - /// mark - handle to a content mark. - /// key - string key of the property. - /// buffer - buffer for holding the returned value. This is only modified - /// if |buflen| is large enough to store the value. - /// Optional, pass null to just retrieve the size of the buffer - /// needed. - /// buflen - length of the buffer in bytes. - /// out_buflen - pointer to variable that will receive the minimum buffer size - /// in bytes to contain the name. This is a required parameter. - /// Not filled if FALSE is returned. - /// - /// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. - int FPDFPageObjMark_GetParamBlobValue( - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFPageObjMark_GetParamBlobValue( - mark, - key, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.UnsignedLong, - ffi.Pointer)>>( - 'FPDFPageObjMark_GetParamBlobValue'); - late final _FPDFPageObjMark_GetParamBlobValue = - _FPDFPageObjMark_GetParamBlobValuePtr.asFunction< - int Function( - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - ffi.Pointer, - int, - ffi.Pointer)>(); - - /// Experimental API. - /// Set the value of an int property in a content mark by key. If a parameter - /// with key |key| exists, its value is set to |value|. Otherwise, it is added as - /// a new parameter. - /// - /// document - handle to the document. - /// page_object - handle to the page object with the mark. - /// mark - handle to a content mark. - /// key - string key of the property. - /// value - int value to set. - /// - /// Returns TRUE if the operation succeeded, FALSE otherwise. - int FPDFPageObjMark_SetIntParam( - FPDF_DOCUMENT document, - FPDF_PAGEOBJECT page_object, - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - int value, - ) { - return _FPDFPageObjMark_SetIntParam( - document, - page_object, - mark, - key, - value, - ); - } - - late final _FPDFPageObjMark_SetIntParamPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DOCUMENT, - FPDF_PAGEOBJECT, - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - ffi.Int)>>('FPDFPageObjMark_SetIntParam'); - late final _FPDFPageObjMark_SetIntParam = - _FPDFPageObjMark_SetIntParamPtr.asFunction< - int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, int)>(); - - /// Experimental API. - /// Set the value of a string property in a content mark by key. If a parameter - /// with key |key| exists, its value is set to |value|. Otherwise, it is added as - /// a new parameter. - /// - /// document - handle to the document. - /// page_object - handle to the page object with the mark. - /// mark - handle to a content mark. - /// key - string key of the property. - /// value - string value to set. - /// - /// Returns TRUE if the operation succeeded, FALSE otherwise. - int FPDFPageObjMark_SetStringParam( - FPDF_DOCUMENT document, - FPDF_PAGEOBJECT page_object, - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - FPDF_BYTESTRING value, - ) { - return _FPDFPageObjMark_SetStringParam( - document, - page_object, - mark, - key, - value, - ); - } - - late final _FPDFPageObjMark_SetStringParamPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DOCUMENT, - FPDF_PAGEOBJECT, - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - FPDF_BYTESTRING)>>('FPDFPageObjMark_SetStringParam'); - late final _FPDFPageObjMark_SetStringParam = - _FPDFPageObjMark_SetStringParamPtr.asFunction< - int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, FPDF_BYTESTRING)>(); - - /// Experimental API. - /// Set the value of a blob property in a content mark by key. If a parameter - /// with key |key| exists, its value is set to |value|. Otherwise, it is added as - /// a new parameter. - /// - /// document - handle to the document. - /// page_object - handle to the page object with the mark. - /// mark - handle to a content mark. - /// key - string key of the property. - /// value - pointer to blob value to set. - /// value_len - size in bytes of |value|. - /// - /// Returns TRUE if the operation succeeded, FALSE otherwise. - int FPDFPageObjMark_SetBlobParam( - FPDF_DOCUMENT document, - FPDF_PAGEOBJECT page_object, - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ffi.Pointer value, - int value_len, - ) { - return _FPDFPageObjMark_SetBlobParam( - document, - page_object, - mark, - key, - value, - value_len, - ); - } - - late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_DOCUMENT, - FPDF_PAGEOBJECT, - FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFPageObjMark_SetBlobParam'); - late final _FPDFPageObjMark_SetBlobParam = - _FPDFPageObjMark_SetBlobParamPtr.asFunction< - int Function(FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING, ffi.Pointer, int)>(); - - /// Experimental API. - /// Removes a property from a content mark by key. - /// - /// page_object - handle to the page object with the mark. - /// mark - handle to a content mark. - /// key - string key of the property. - /// - /// Returns TRUE if the operation succeeded, FALSE otherwise. - int FPDFPageObjMark_RemoveParam( - FPDF_PAGEOBJECT page_object, - FPDF_PAGEOBJECTMARK mark, - FPDF_BYTESTRING key, - ) { - return _FPDFPageObjMark_RemoveParam( - page_object, - mark, - key, - ); - } - - late final _FPDFPageObjMark_RemoveParamPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, - FPDF_BYTESTRING)>>('FPDFPageObjMark_RemoveParam'); - late final _FPDFPageObjMark_RemoveParam = - _FPDFPageObjMark_RemoveParamPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING)>(); - - /// Load an image from a JPEG image file and then set it into |image_object|. - /// - /// pages - pointer to the start of all loaded pages, may be NULL. - /// count - number of |pages|, may be 0. - /// image_object - handle to an image object. - /// file_access - file access handler which specifies the JPEG image file. - /// - /// Returns TRUE on success. - /// - /// The image object might already have an associated image, which is shared and - /// cached by the loaded pages. In that case, we need to clear the cached image - /// for all the loaded pages. Pass |pages| and page count (|count|) to this API - /// to clear the image cache. If the image is not previously shared, or NULL is a - /// valid |pages| value. - int FPDFImageObj_LoadJpegFile( - ffi.Pointer pages, - int count, - FPDF_PAGEOBJECT image_object, - ffi.Pointer file_access, - ) { - return _FPDFImageObj_LoadJpegFile( - pages, - count, - image_object, - file_access, - ); - } - - late final _FPDFImageObj_LoadJpegFilePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(ffi.Pointer, ffi.Int, FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFImageObj_LoadJpegFile'); - late final _FPDFImageObj_LoadJpegFile = - _FPDFImageObj_LoadJpegFilePtr.asFunction< - int Function(ffi.Pointer, int, FPDF_PAGEOBJECT, - ffi.Pointer)>(); - - /// Load an image from a JPEG image file and then set it into |image_object|. - /// - /// pages - pointer to the start of all loaded pages, may be NULL. - /// count - number of |pages|, may be 0. - /// image_object - handle to an image object. - /// file_access - file access handler which specifies the JPEG image file. - /// - /// Returns TRUE on success. - /// - /// The image object might already have an associated image, which is shared and - /// cached by the loaded pages. In that case, we need to clear the cached image - /// for all the loaded pages. Pass |pages| and page count (|count|) to this API - /// to clear the image cache. If the image is not previously shared, or NULL is a - /// valid |pages| value. This function loads the JPEG image inline, so the image - /// content is copied to the file. This allows |file_access| and its associated - /// data to be deleted after this function returns. - int FPDFImageObj_LoadJpegFileInline( - ffi.Pointer pages, - int count, - FPDF_PAGEOBJECT image_object, - ffi.Pointer file_access, - ) { - return _FPDFImageObj_LoadJpegFileInline( - pages, - count, - image_object, - file_access, - ); - } - - late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(ffi.Pointer, ffi.Int, - FPDF_PAGEOBJECT, ffi.Pointer)>>( - 'FPDFImageObj_LoadJpegFileInline'); - late final _FPDFImageObj_LoadJpegFileInline = - _FPDFImageObj_LoadJpegFileInlinePtr.asFunction< - int Function(ffi.Pointer, int, FPDF_PAGEOBJECT, - ffi.Pointer)>(); - - /// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. - /// - /// Set the transform matrix of |image_object|. - /// - /// image_object - handle to an image object. - /// a - matrix value. - /// b - matrix value. - /// c - matrix value. - /// d - matrix value. - /// e - matrix value. - /// f - matrix value. - /// - /// The matrix is composed as: - /// |a c e| - /// |b d f| - /// and can be used to scale, rotate, shear and translate the |image_object|. - /// - /// Returns TRUE on success. - int FPDFImageObj_SetMatrix( - FPDF_PAGEOBJECT image_object, - double a, - double b, - double c, - double d, - double e, - double f, - ) { - return _FPDFImageObj_SetMatrix( - image_object, - a, - b, - c, - d, - e, - f, - ); - } - - late final _FPDFImageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, - ffi.Double, - ffi.Double, - ffi.Double, - ffi.Double, - ffi.Double, - ffi.Double)>>('FPDFImageObj_SetMatrix'); - late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); - - /// Set |bitmap| to |image_object|. - /// - /// pages - pointer to the start of all loaded pages, may be NULL. - /// count - number of |pages|, may be 0. - /// image_object - handle to an image object. - /// bitmap - handle of the bitmap. - /// - /// Returns TRUE on success. - int FPDFImageObj_SetBitmap( - ffi.Pointer pages, - int count, - FPDF_PAGEOBJECT image_object, - FPDF_BITMAP bitmap, - ) { - return _FPDFImageObj_SetBitmap( - pages, - count, - image_object, - bitmap, - ); - } - - late final _FPDFImageObj_SetBitmapPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(ffi.Pointer, ffi.Int, FPDF_PAGEOBJECT, - FPDF_BITMAP)>>('FPDFImageObj_SetBitmap'); - late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction< - int Function( - ffi.Pointer, int, FPDF_PAGEOBJECT, FPDF_BITMAP)>(); - - /// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only - /// operates on |image_object| and does not take the associated image mask into - /// account. It also ignores the matrix for |image_object|. - /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() - /// must be called on the returned bitmap when it is no longer needed. - /// - /// image_object - handle to an image object. - /// - /// Returns the bitmap. - FPDF_BITMAP FPDFImageObj_GetBitmap( - FPDF_PAGEOBJECT image_object, - ) { - return _FPDFImageObj_GetBitmap( - image_object, - ); - } - - late final _FPDFImageObj_GetBitmapPtr = - _lookup>( - 'FPDFImageObj_GetBitmap'); - late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction< - FPDF_BITMAP Function(FPDF_PAGEOBJECT)>(); - - /// Experimental API. - /// Get a bitmap rasterization of |image_object| that takes the image mask and - /// image matrix into account. To render correctly, the caller must provide the - /// |document| associated with |image_object|. If there is a |page| associated - /// with |image_object|, the caller should provide that as well. - /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() - /// must be called on the returned bitmap when it is no longer needed. - /// - /// document - handle to a document associated with |image_object|. - /// page - handle to an optional page associated with |image_object|. - /// image_object - handle to an image object. - /// - /// Returns the bitmap or NULL on failure. - FPDF_BITMAP FPDFImageObj_GetRenderedBitmap( - FPDF_DOCUMENT document, - FPDF_PAGE page, - FPDF_PAGEOBJECT image_object, - ) { - return _FPDFImageObj_GetRenderedBitmap( - document, - page, - image_object, - ); - } - - late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction< - FPDF_BITMAP Function(FPDF_DOCUMENT, FPDF_PAGE, - FPDF_PAGEOBJECT)>>('FPDFImageObj_GetRenderedBitmap'); - late final _FPDFImageObj_GetRenderedBitmap = - _FPDFImageObj_GetRenderedBitmapPtr.asFunction< - FPDF_BITMAP Function(FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT)>(); - - /// Get the decoded image data of |image_object|. The decoded data is the - /// uncompressed image data, i.e. the raw image data after having all filters - /// applied. |buffer| is only modified if |buflen| is longer than the length of - /// the decoded image data. - /// - /// image_object - handle to an image object. - /// buffer - buffer for holding the decoded image data. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the decoded image data. - int FPDFImageObj_GetImageDataDecoded( - FPDF_PAGEOBJECT image_object, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFImageObj_GetImageDataDecoded( - image_object, - buffer, - buflen, - ); - } - - late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFImageObj_GetImageDataDecoded'); - late final _FPDFImageObj_GetImageDataDecoded = - _FPDFImageObj_GetImageDataDecodedPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer, int)>(); - - /// Get the raw image data of |image_object|. The raw data is the image data as - /// stored in the PDF without applying any filters. |buffer| is only modified if - /// |buflen| is longer than the length of the raw image data. - /// - /// image_object - handle to an image object. - /// buffer - buffer for holding the raw image data. - /// buflen - length of the buffer in bytes. - /// - /// Returns the length of the raw image data. - int FPDFImageObj_GetImageDataRaw( - FPDF_PAGEOBJECT image_object, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFImageObj_GetImageDataRaw( - image_object, - buffer, - buflen, - ); - } - - late final _FPDFImageObj_GetImageDataRawPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.UnsignedLong)>>('FPDFImageObj_GetImageDataRaw'); - late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr - .asFunction, int)>(); - - /// Get the number of filters (i.e. decoders) of the image in |image_object|. - /// - /// image_object - handle to an image object. - /// - /// Returns the number of |image_object|'s filters. - int FPDFImageObj_GetImageFilterCount( - FPDF_PAGEOBJECT image_object, - ) { - return _FPDFImageObj_GetImageFilterCount( - image_object, - ); - } - - late final _FPDFImageObj_GetImageFilterCountPtr = - _lookup>( - 'FPDFImageObj_GetImageFilterCount'); - late final _FPDFImageObj_GetImageFilterCount = - _FPDFImageObj_GetImageFilterCountPtr.asFunction< - int Function(FPDF_PAGEOBJECT)>(); - - /// Get the filter at |index| of |image_object|'s list of filters. Note that the - /// filters need to be applied in order, i.e. the first filter should be applied - /// first, then the second, etc. |buffer| is only modified if |buflen| is longer - /// than the length of the filter string. - /// - /// image_object - handle to an image object. - /// index - the index of the filter requested. - /// buffer - buffer for holding filter string, encoded in UTF-8. - /// buflen - length of the buffer. - /// - /// Returns the length of the filter string. - int FPDFImageObj_GetImageFilter( - FPDF_PAGEOBJECT image_object, - int index, - ffi.Pointer buffer, - int buflen, - ) { - return _FPDFImageObj_GetImageFilter( - image_object, - index, - buffer, - buflen, - ); - } - - late final _FPDFImageObj_GetImageFilterPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_PAGEOBJECT, - ffi.Int, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFImageObj_GetImageFilter'); - late final _FPDFImageObj_GetImageFilter = - _FPDFImageObj_GetImageFilterPtr.asFunction< - int Function(FPDF_PAGEOBJECT, int, ffi.Pointer, int)>(); - - /// Get the image metadata of |image_object|, including dimension, DPI, bits per - /// pixel, and colorspace. If the |image_object| is not an image object or if it - /// does not have an image, then the return value will be false. Otherwise, - /// failure to retrieve any specific parameter would result in its value being 0. - /// - /// image_object - handle to an image object. - /// page - handle to the page that |image_object| is on. Required for - /// retrieving the image's bits per pixel and colorspace. - /// metadata - receives the image metadata; must not be NULL. - /// - /// Returns true if successful. - int FPDFImageObj_GetImageMetadata( - FPDF_PAGEOBJECT image_object, - FPDF_PAGE page, - ffi.Pointer metadata, - ) { - return _FPDFImageObj_GetImageMetadata( - image_object, - page, - metadata, - ); - } - - late final _FPDFImageObj_GetImageMetadataPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, FPDF_PAGE, - ffi.Pointer)>>( - 'FPDFImageObj_GetImageMetadata'); - late final _FPDFImageObj_GetImageMetadata = - _FPDFImageObj_GetImageMetadataPtr.asFunction< - int Function(FPDF_PAGEOBJECT, FPDF_PAGE, - ffi.Pointer)>(); - - /// Experimental API. - /// Get the image size in pixels. Faster method to get only image size. - /// - /// image_object - handle to an image object. - /// width - receives the image width in pixels; must not be NULL. - /// height - receives the image height in pixels; must not be NULL. - /// - /// Returns true if successful. - int FPDFImageObj_GetImagePixelSize( - FPDF_PAGEOBJECT image_object, - ffi.Pointer width, - ffi.Pointer height, - ) { - return _FPDFImageObj_GetImagePixelSize( - image_object, - width, - height, - ); - } - - late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.Pointer)>>('FPDFImageObj_GetImagePixelSize'); - late final _FPDFImageObj_GetImagePixelSize = - _FPDFImageObj_GetImagePixelSizePtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Get ICC profile decoded data of |image_object|. If the |image_object| is not - /// an image object or if it does not have an image, then the return value will - /// be false. It also returns false if the |image_object| has no ICC profile. - /// |buffer| is only modified if ICC profile exists and |buflen| is longer than - /// the length of the ICC profile decoded data. - /// - /// image_object - handle to an image object; must not be NULL. - /// page - handle to the page containing |image_object|; must not be - /// NULL. Required for retrieving the image's colorspace. - /// buffer - Buffer to receive ICC profile data; may be NULL if querying - /// required size via |out_buflen|. - /// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. - /// out_buflen - Pointer to receive the ICC profile data size in bytes; must - /// not be NULL. Will be set if this API returns true. - /// - /// Returns true if |out_buflen| is not null and an ICC profile exists for the - /// given |image_object|. - int FPDFImageObj_GetIccProfileDataDecoded( - FPDF_PAGEOBJECT image_object, - FPDF_PAGE page, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFImageObj_GetIccProfileDataDecoded( - image_object, - page, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, - FPDF_PAGE, - ffi.Pointer, - ffi.Size, - ffi.Pointer)>>('FPDFImageObj_GetIccProfileDataDecoded'); - late final _FPDFImageObj_GetIccProfileDataDecoded = - _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction< - int Function(FPDF_PAGEOBJECT, FPDF_PAGE, ffi.Pointer, int, - ffi.Pointer)>(); - - /// Create a new path object at an initial position. - /// - /// x - initial horizontal position. - /// y - initial vertical position. - /// - /// Returns a handle to a new path object. - FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath( - double x, - double y, - ) { - return _FPDFPageObj_CreateNewPath( - x, - y, - ); - } - - late final _FPDFPageObj_CreateNewPathPtr = _lookup< - ffi.NativeFunction>( - 'FPDFPageObj_CreateNewPath'); - late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr - .asFunction(); - - /// Create a closed path consisting of a rectangle. - /// - /// x - horizontal position for the left boundary of the rectangle. - /// y - vertical position for the bottom boundary of the rectangle. - /// w - width of the rectangle. - /// h - height of the rectangle. - /// - /// Returns a handle to the new path object. - FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect( - double x, - double y, - double w, - double h, - ) { - return _FPDFPageObj_CreateNewRect( - x, - y, - w, - h, - ); - } - - late final _FPDFPageObj_CreateNewRectPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECT Function(ffi.Float, ffi.Float, ffi.Float, - ffi.Float)>>('FPDFPageObj_CreateNewRect'); - late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr - .asFunction(); - - /// Get the bounding box of |page_object|. - /// - /// page_object - handle to a page object. - /// left - pointer where the left coordinate will be stored - /// bottom - pointer where the bottom coordinate will be stored - /// right - pointer where the right coordinate will be stored - /// top - pointer where the top coordinate will be stored - /// - /// On success, returns TRUE and fills in the 4 coordinates. - int FPDFPageObj_GetBounds( - FPDF_PAGEOBJECT page_object, - ffi.Pointer left, - ffi.Pointer bottom, - ffi.Pointer right, - ffi.Pointer top, - ) { - return _FPDFPageObj_GetBounds( - page_object, - left, - bottom, - right, - top, - ); - } - - late final _FPDFPageObj_GetBoundsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFPageObj_GetBounds'); - late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Get the quad points that bounds |page_object|. - /// - /// page_object - handle to a page object. - /// quad_points - pointer where the quadrilateral points will be stored. - /// - /// On success, returns TRUE and fills in |quad_points|. - /// - /// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page - /// object. When the object is rotated by a non-multiple of 90 degrees, this API - /// returns a tighter bound that cannot be represented with just the 4 sides of - /// a rectangle. - /// - /// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and - /// FPDF_PAGEOBJ_IMAGE. - int FPDFPageObj_GetRotatedBounds( - FPDF_PAGEOBJECT page_object, - ffi.Pointer quad_points, - ) { - return _FPDFPageObj_GetRotatedBounds( - page_object, - quad_points, - ); - } - - late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_GetRotatedBounds'); - late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr - .asFunction)>(); - - /// Set the blend mode of |page_object|. - /// - /// page_object - handle to a page object. - /// blend_mode - string containing the blend mode. - /// - /// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, - /// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, - /// Overlay, Saturation, Screen, SoftLight - void FPDFPageObj_SetBlendMode( - FPDF_PAGEOBJECT page_object, - FPDF_BYTESTRING blend_mode, - ) { - return _FPDFPageObj_SetBlendMode( - page_object, - blend_mode, - ); - } - - late final _FPDFPageObj_SetBlendModePtr = _lookup< - ffi - .NativeFunction>( - 'FPDFPageObj_SetBlendMode'); - late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr - .asFunction(); - - /// Set the stroke RGBA of a page object. Range of values: 0 - 255. - /// - /// page_object - the handle to the page object. - /// R - the red component for the object's stroke color. - /// G - the green component for the object's stroke color. - /// B - the blue component for the object's stroke color. - /// A - the stroke alpha for the object. - /// - /// Returns TRUE on success. - int FPDFPageObj_SetStrokeColor( - FPDF_PAGEOBJECT page_object, - int R, - int G, - int B, - int A, - ) { - return _FPDFPageObj_SetStrokeColor( - page_object, - R, - G, - B, - A, - ); - } - - late final _FPDFPageObj_SetStrokeColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.UnsignedInt, ffi.UnsignedInt, - ffi.UnsignedInt, ffi.UnsignedInt)>>('FPDFPageObj_SetStrokeColor'); - late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr - .asFunction(); - - /// Get the stroke RGBA of a page object. Range of values: 0 - 255. - /// - /// page_object - the handle to the page object. - /// R - the red component of the path stroke color. - /// G - the green component of the object's stroke color. - /// B - the blue component of the object's stroke color. - /// A - the stroke alpha of the object. - /// - /// Returns TRUE on success. - int FPDFPageObj_GetStrokeColor( - FPDF_PAGEOBJECT page_object, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ffi.Pointer A, - ) { - return _FPDFPageObj_GetStrokeColor( - page_object, - R, - G, - B, - A, - ); - } - - late final _FPDFPageObj_GetStrokeColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFPageObj_GetStrokeColor'); - late final _FPDFPageObj_GetStrokeColor = - _FPDFPageObj_GetStrokeColorPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Set the stroke width of a page object. - /// - /// path - the handle to the page object. - /// width - the width of the stroke. - /// - /// Returns TRUE on success - int FPDFPageObj_SetStrokeWidth( - FPDF_PAGEOBJECT page_object, - double width, - ) { - return _FPDFPageObj_SetStrokeWidth( - page_object, - width, - ); - } - - late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< - ffi.NativeFunction>( - 'FPDFPageObj_SetStrokeWidth'); - late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr - .asFunction(); - - /// Get the stroke width of a page object. - /// - /// path - the handle to the page object. - /// width - the width of the stroke. - /// - /// Returns TRUE on success - int FPDFPageObj_GetStrokeWidth( - FPDF_PAGEOBJECT page_object, - ffi.Pointer width, - ) { - return _FPDFPageObj_GetStrokeWidth( - page_object, - width, - ); - } - - late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_GetStrokeWidth'); - late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr - .asFunction)>(); - - /// Get the line join of |page_object|. - /// - /// page_object - handle to a page object. - /// - /// Returns the line join, or -1 on failure. - /// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, - /// FPDF_LINEJOIN_BEVEL - int FPDFPageObj_GetLineJoin( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_GetLineJoin( - page_object, - ); - } - - late final _FPDFPageObj_GetLineJoinPtr = - _lookup>( - 'FPDFPageObj_GetLineJoin'); - late final _FPDFPageObj_GetLineJoin = - _FPDFPageObj_GetLineJoinPtr.asFunction(); - - /// Set the line join of |page_object|. - /// - /// page_object - handle to a page object. - /// line_join - line join - /// - /// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, - /// FPDF_LINEJOIN_BEVEL - int FPDFPageObj_SetLineJoin( - FPDF_PAGEOBJECT page_object, - int line_join, - ) { - return _FPDFPageObj_SetLineJoin( - page_object, - line_join, - ); - } - - late final _FPDFPageObj_SetLineJoinPtr = - _lookup>( - 'FPDFPageObj_SetLineJoin'); - late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction< - int Function(FPDF_PAGEOBJECT, int)>(); - - /// Get the line cap of |page_object|. - /// - /// page_object - handle to a page object. - /// - /// Returns the line cap, or -1 on failure. - /// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, - /// FPDF_LINECAP_PROJECTING_SQUARE - int FPDFPageObj_GetLineCap( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_GetLineCap( - page_object, - ); - } - - late final _FPDFPageObj_GetLineCapPtr = - _lookup>( - 'FPDFPageObj_GetLineCap'); - late final _FPDFPageObj_GetLineCap = - _FPDFPageObj_GetLineCapPtr.asFunction(); - - /// Set the line cap of |page_object|. - /// - /// page_object - handle to a page object. - /// line_cap - line cap - /// - /// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, - /// FPDF_LINECAP_PROJECTING_SQUARE - int FPDFPageObj_SetLineCap( - FPDF_PAGEOBJECT page_object, - int line_cap, - ) { - return _FPDFPageObj_SetLineCap( - page_object, - line_cap, - ); - } - - late final _FPDFPageObj_SetLineCapPtr = - _lookup>( - 'FPDFPageObj_SetLineCap'); - late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction< - int Function(FPDF_PAGEOBJECT, int)>(); - - /// Set the fill RGBA of a page object. Range of values: 0 - 255. - /// - /// page_object - the handle to the page object. - /// R - the red component for the object's fill color. - /// G - the green component for the object's fill color. - /// B - the blue component for the object's fill color. - /// A - the fill alpha for the object. - /// - /// Returns TRUE on success. - int FPDFPageObj_SetFillColor( - FPDF_PAGEOBJECT page_object, - int R, - int G, - int B, - int A, - ) { - return _FPDFPageObj_SetFillColor( - page_object, - R, - G, - B, - A, - ); - } - - late final _FPDFPageObj_SetFillColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.UnsignedInt, ffi.UnsignedInt, - ffi.UnsignedInt, ffi.UnsignedInt)>>('FPDFPageObj_SetFillColor'); - late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr - .asFunction(); - - /// Get the fill RGBA of a page object. Range of values: 0 - 255. - /// - /// page_object - the handle to the page object. - /// R - the red component of the object's fill color. - /// G - the green component of the object's fill color. - /// B - the blue component of the object's fill color. - /// A - the fill alpha of the object. - /// - /// Returns TRUE on success. - int FPDFPageObj_GetFillColor( - FPDF_PAGEOBJECT page_object, - ffi.Pointer R, - ffi.Pointer G, - ffi.Pointer B, - ffi.Pointer A, - ) { - return _FPDFPageObj_GetFillColor( - page_object, - R, - G, - B, - A, - ); - } - - late final _FPDFPageObj_GetFillColorPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>('FPDFPageObj_GetFillColor'); - late final _FPDFPageObj_GetFillColor = - _FPDFPageObj_GetFillColorPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - - /// Experimental API. - /// Get the line dash |phase| of |page_object|. - /// - /// page_object - handle to a page object. - /// phase - pointer where the dashing phase will be stored. - /// - /// Returns TRUE on success. - int FPDFPageObj_GetDashPhase( - FPDF_PAGEOBJECT page_object, - ffi.Pointer phase, - ) { - return _FPDFPageObj_GetDashPhase( - page_object, - phase, - ); - } - - late final _FPDFPageObj_GetDashPhasePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFPageObj_GetDashPhase'); - late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr - .asFunction)>(); - - /// Experimental API. - /// Set the line dash phase of |page_object|. - /// - /// page_object - handle to a page object. - /// phase - line dash phase. - /// - /// Returns TRUE on success. - int FPDFPageObj_SetDashPhase( - FPDF_PAGEOBJECT page_object, - double phase, - ) { - return _FPDFPageObj_SetDashPhase( - page_object, - phase, - ); - } - - late final _FPDFPageObj_SetDashPhasePtr = _lookup< - ffi.NativeFunction>( - 'FPDFPageObj_SetDashPhase'); - late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr - .asFunction(); - - /// Experimental API. - /// Get the line dash array of |page_object|. - /// - /// page_object - handle to a page object. - /// - /// Returns the line dash array size or -1 on failure. - int FPDFPageObj_GetDashCount( - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFPageObj_GetDashCount( - page_object, - ); - } - - late final _FPDFPageObj_GetDashCountPtr = - _lookup>( - 'FPDFPageObj_GetDashCount'); - late final _FPDFPageObj_GetDashCount = - _FPDFPageObj_GetDashCountPtr.asFunction(); - - /// Experimental API. - /// Get the line dash array of |page_object|. - /// - /// page_object - handle to a page object. - /// dash_array - pointer where the dashing array will be stored. - /// dash_count - number of elements in |dash_array|. - /// - /// Returns TRUE on success. - int FPDFPageObj_GetDashArray( - FPDF_PAGEOBJECT page_object, - ffi.Pointer dash_array, - int dash_count, - ) { - return _FPDFPageObj_GetDashArray( - page_object, - dash_array, - dash_count, - ); - } - - late final _FPDFPageObj_GetDashArrayPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.Size)>>('FPDFPageObj_GetDashArray'); - late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr - .asFunction, int)>(); - - /// Experimental API. - /// Set the line dash array of |page_object|. - /// - /// page_object - handle to a page object. - /// dash_array - the dash array. - /// dash_count - number of elements in |dash_array|. - /// phase - the line dash phase. - /// - /// Returns TRUE on success. - int FPDFPageObj_SetDashArray( - FPDF_PAGEOBJECT page_object, - ffi.Pointer dash_array, - int dash_count, - double phase, - ) { - return _FPDFPageObj_SetDashArray( - page_object, - dash_array, - dash_count, - phase, - ); - } - - late final _FPDFPageObj_SetDashArrayPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Pointer, ffi.Size, - ffi.Float)>>('FPDFPageObj_SetDashArray'); - late final _FPDFPageObj_SetDashArray = - _FPDFPageObj_SetDashArrayPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer, int, double)>(); - - /// Get number of segments inside |path|. - /// - /// path - handle to a path. - /// - /// A segment is a command, created by e.g. FPDFPath_MoveTo(), - /// FPDFPath_LineTo() or FPDFPath_BezierTo(). - /// - /// Returns the number of objects in |path| or -1 on failure. - int FPDFPath_CountSegments( - FPDF_PAGEOBJECT path, - ) { - return _FPDFPath_CountSegments( - path, - ); - } - - late final _FPDFPath_CountSegmentsPtr = - _lookup>( - 'FPDFPath_CountSegments'); - late final _FPDFPath_CountSegments = - _FPDFPath_CountSegmentsPtr.asFunction(); - - /// Get segment in |path| at |index|. - /// - /// path - handle to a path. - /// index - the index of a segment. - /// - /// Returns the handle to the segment, or NULL on faiure. - FPDF_PATHSEGMENT FPDFPath_GetPathSegment( - FPDF_PAGEOBJECT path, - int index, - ) { - return _FPDFPath_GetPathSegment( - path, - index, - ); - } - - late final _FPDFPath_GetPathSegmentPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFPath_GetPathSegment'); - late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction< - FPDF_PATHSEGMENT Function(FPDF_PAGEOBJECT, int)>(); - - /// Get coordinates of |segment|. - /// - /// segment - handle to a segment. - /// x - the horizontal position of the segment. - /// y - the vertical position of the segment. - /// - /// Returns TRUE on success, otherwise |x| and |y| is not set. - int FPDFPathSegment_GetPoint( - FPDF_PATHSEGMENT segment, - ffi.Pointer x, - ffi.Pointer y, - ) { - return _FPDFPathSegment_GetPoint( - segment, - x, - y, - ); - } - - late final _FPDFPathSegment_GetPointPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PATHSEGMENT, ffi.Pointer, - ffi.Pointer)>>('FPDFPathSegment_GetPoint'); - late final _FPDFPathSegment_GetPoint = - _FPDFPathSegment_GetPointPtr.asFunction< - int Function(FPDF_PATHSEGMENT, ffi.Pointer, - ffi.Pointer)>(); - - /// Get type of |segment|. - /// - /// segment - handle to a segment. - /// - /// Returns one of the FPDF_SEGMENT_* values on success, - /// FPDF_SEGMENT_UNKNOWN on error. - int FPDFPathSegment_GetType( - FPDF_PATHSEGMENT segment, - ) { - return _FPDFPathSegment_GetType( - segment, - ); - } - - late final _FPDFPathSegment_GetTypePtr = - _lookup>( - 'FPDFPathSegment_GetType'); - late final _FPDFPathSegment_GetType = - _FPDFPathSegment_GetTypePtr.asFunction(); - - /// Gets if the |segment| closes the current subpath of a given path. - /// - /// segment - handle to a segment. - /// - /// Returns close flag for non-NULL segment, FALSE otherwise. - int FPDFPathSegment_GetClose( - FPDF_PATHSEGMENT segment, - ) { - return _FPDFPathSegment_GetClose( - segment, - ); - } - - late final _FPDFPathSegment_GetClosePtr = - _lookup>( - 'FPDFPathSegment_GetClose'); - late final _FPDFPathSegment_GetClose = - _FPDFPathSegment_GetClosePtr.asFunction(); - - /// Move a path's current point. - /// - /// path - the handle to the path object. - /// x - the horizontal position of the new current point. - /// y - the vertical position of the new current point. - /// - /// Note that no line will be created between the previous current point and the - /// new one. - /// - /// Returns TRUE on success - int FPDFPath_MoveTo( - FPDF_PAGEOBJECT path, - double x, - double y, - ) { - return _FPDFPath_MoveTo( - path, - x, - y, - ); - } - - late final _FPDFPath_MoveToPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, ffi.Float, ffi.Float)>>('FPDFPath_MoveTo'); - late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction< - int Function(FPDF_PAGEOBJECT, double, double)>(); - - /// Add a line between the current point and a new point in the path. - /// - /// path - the handle to the path object. - /// x - the horizontal position of the new point. - /// y - the vertical position of the new point. - /// - /// The path's current point is changed to (x, y). - /// - /// Returns TRUE on success - int FPDFPath_LineTo( - FPDF_PAGEOBJECT path, - double x, - double y, - ) { - return _FPDFPath_LineTo( - path, - x, - y, - ); - } - - late final _FPDFPath_LineToPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, ffi.Float, ffi.Float)>>('FPDFPath_LineTo'); - late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction< - int Function(FPDF_PAGEOBJECT, double, double)>(); - - /// Add a cubic Bezier curve to the given path, starting at the current point. - /// - /// path - the handle to the path object. - /// x1 - the horizontal position of the first Bezier control point. - /// y1 - the vertical position of the first Bezier control point. - /// x2 - the horizontal position of the second Bezier control point. - /// y2 - the vertical position of the second Bezier control point. - /// x3 - the horizontal position of the ending point of the Bezier curve. - /// y3 - the vertical position of the ending point of the Bezier curve. - /// - /// Returns TRUE on success - int FPDFPath_BezierTo( - FPDF_PAGEOBJECT path, - double x1, - double y1, - double x2, - double y2, - double x3, - double y3, - ) { - return _FPDFPath_BezierTo( - path, - x1, - y1, - x2, - y2, - x3, - y3, - ); - } - - late final _FPDFPath_BezierToPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Float, ffi.Float, ffi.Float, - ffi.Float, ffi.Float, ffi.Float)>>('FPDFPath_BezierTo'); - late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, double, double, double, double, double, double)>(); - - /// Close the current subpath of a given path. - /// - /// path - the handle to the path object. - /// - /// This will add a line between the current point and the initial point of the - /// subpath, thus terminating the current subpath. - /// - /// Returns TRUE on success - int FPDFPath_Close( - FPDF_PAGEOBJECT path, - ) { - return _FPDFPath_Close( - path, - ); - } - - late final _FPDFPath_ClosePtr = - _lookup>( - 'FPDFPath_Close'); - late final _FPDFPath_Close = - _FPDFPath_ClosePtr.asFunction(); - - /// Set the drawing mode of a path. - /// - /// path - the handle to the path object. - /// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. - /// stroke - a boolean specifying if the path should be stroked or not. - /// - /// Returns TRUE on success - int FPDFPath_SetDrawMode( - FPDF_PAGEOBJECT path, - int fillmode, - int stroke, - ) { - return _FPDFPath_SetDrawMode( - path, - fillmode, - stroke, - ); - } - - late final _FPDFPath_SetDrawModePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, ffi.Int, FPDF_BOOL)>>('FPDFPath_SetDrawMode'); - late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction< - int Function(FPDF_PAGEOBJECT, int, int)>(); - - /// Get the drawing mode of a path. - /// - /// path - the handle to the path object. - /// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. - /// stroke - a boolean specifying if the path is stroked or not. - /// - /// Returns TRUE on success - int FPDFPath_GetDrawMode( - FPDF_PAGEOBJECT path, - ffi.Pointer fillmode, - ffi.Pointer stroke, - ) { - return _FPDFPath_GetDrawMode( - path, - fillmode, - stroke, - ); - } - - late final _FPDFPath_GetDrawModePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.Pointer)>>('FPDFPath_GetDrawMode'); - late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction< - int Function( - FPDF_PAGEOBJECT, ffi.Pointer, ffi.Pointer)>(); - - /// Create a new text object using one of the standard PDF fonts. - /// - /// document - handle to the document. - /// font - string containing the font name, without spaces. - /// font_size - the font size for the new text object. - /// - /// Returns a handle to a new text object, or NULL on failure - FPDF_PAGEOBJECT FPDFPageObj_NewTextObj( - FPDF_DOCUMENT document, - FPDF_BYTESTRING font, - double font_size, - ) { - return _FPDFPageObj_NewTextObj( - document, - font, - font_size, - ); - } - - late final _FPDFPageObj_NewTextObjPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECT Function(FPDF_DOCUMENT, FPDF_BYTESTRING, - ffi.Float)>>('FPDFPageObj_NewTextObj'); - late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_DOCUMENT, FPDF_BYTESTRING, double)>(); - - /// Set the text for a text object. If it had text, it will be replaced. - /// - /// text_object - handle to the text object. - /// text - the UTF-16LE encoded string containing the text to be added. - /// - /// Returns TRUE on success - int FPDFText_SetText( - FPDF_PAGEOBJECT text_object, - FPDF_WIDESTRING text, - ) { - return _FPDFText_SetText( - text_object, - text, - ); - } - - late final _FPDFText_SetTextPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, FPDF_WIDESTRING)>>('FPDFText_SetText'); - late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction< - int Function(FPDF_PAGEOBJECT, FPDF_WIDESTRING)>(); - - /// Experimental API. - /// Set the text using charcodes for a text object. If it had text, it will be - /// replaced. - /// - /// text_object - handle to the text object. - /// charcodes - pointer to an array of charcodes to be added. - /// count - number of elements in |charcodes|. - /// - /// Returns TRUE on success - int FPDFText_SetCharcodes( - FPDF_PAGEOBJECT text_object, - ffi.Pointer charcodes, - int count, - ) { - return _FPDFText_SetCharcodes( - text_object, - charcodes, - count, - ); - } - - late final _FPDFText_SetCharcodesPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, ffi.Pointer, - ffi.Size)>>('FPDFText_SetCharcodes'); - late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer, int)>(); - - /// Returns a font object loaded from a stream of data. The font is loaded - /// into the document. Various font data structures, such as the ToUnicode data, - /// are auto-generated based on the inputs. - /// - /// document - handle to the document. - /// data - the stream of font data, which will be copied by the font object. - /// size - the size of the font data, in bytes. - /// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. - /// cid - a boolean specifying if the font is a CID font or not. - /// - /// The loaded font can be closed using FPDFFont_Close(). - /// - /// Returns NULL on failure - FPDF_FONT FPDFText_LoadFont( - FPDF_DOCUMENT document, - ffi.Pointer data, - int size, - int font_type, - int cid, - ) { - return _FPDFText_LoadFont( - document, - data, - size, - font_type, - cid, - ); - } - - late final _FPDFText_LoadFontPtr = _lookup< - ffi.NativeFunction< - FPDF_FONT Function(FPDF_DOCUMENT, ffi.Pointer, ffi.Uint32, - ffi.Int, FPDF_BOOL)>>('FPDFText_LoadFont'); - late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction< - FPDF_FONT Function( - FPDF_DOCUMENT, ffi.Pointer, int, int, int)>(); - - /// Experimental API. - /// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred - /// way of using font style is using a dash to separate the name from the style, - /// for example 'Helvetica-BoldItalic'. - /// - /// document - handle to the document. - /// font - string containing the font name, without spaces. - /// - /// The loaded font can be closed using FPDFFont_Close(). - /// - /// Returns NULL on failure. - FPDF_FONT FPDFText_LoadStandardFont( - FPDF_DOCUMENT document, - FPDF_BYTESTRING font, - ) { - return _FPDFText_LoadStandardFont( - document, - font, - ); - } - - late final _FPDFText_LoadStandardFontPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFText_LoadStandardFont'); - late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr - .asFunction(); - - /// Experimental API. - /// Returns a font object loaded from a stream of data for a type 2 CID font. The - /// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode - /// data and the CIDToGIDMap data are caller provided, instead of auto-generated. - /// - /// document - handle to the document. - /// font_data - the stream of font data, which will be copied by - /// the font object. - /// font_data_size - the size of the font data, in bytes. - /// to_unicode_cmap - the ToUnicode data. - /// cid_to_gid_map_data - the stream of CIDToGIDMap data. - /// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. - /// - /// The loaded font can be closed using FPDFFont_Close(). - /// - /// Returns NULL on failure. - FPDF_FONT FPDFText_LoadCidType2Font( - FPDF_DOCUMENT document, - ffi.Pointer font_data, - int font_data_size, - FPDF_BYTESTRING to_unicode_cmap, - ffi.Pointer cid_to_gid_map_data, - int cid_to_gid_map_data_size, - ) { - return _FPDFText_LoadCidType2Font( - document, - font_data, - font_data_size, - to_unicode_cmap, - cid_to_gid_map_data, - cid_to_gid_map_data_size, - ); - } - - late final _FPDFText_LoadCidType2FontPtr = _lookup< - ffi.NativeFunction< - FPDF_FONT Function( - FPDF_DOCUMENT, - ffi.Pointer, - ffi.Uint32, - FPDF_BYTESTRING, - ffi.Pointer, - ffi.Uint32)>>('FPDFText_LoadCidType2Font'); - late final _FPDFText_LoadCidType2Font = - _FPDFText_LoadCidType2FontPtr.asFunction< - FPDF_FONT Function(FPDF_DOCUMENT, ffi.Pointer, int, - FPDF_BYTESTRING, ffi.Pointer, int)>(); - - /// Get the font size of a text object. - /// - /// text - handle to a text. - /// size - pointer to the font size of the text object, measured in points - /// (about 1/72 inch) - /// - /// Returns TRUE on success. - int FPDFTextObj_GetFontSize( - FPDF_PAGEOBJECT text, - ffi.Pointer size, - ) { - return _FPDFTextObj_GetFontSize( - text, - size, - ); - } - - late final _FPDFTextObj_GetFontSizePtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_PAGEOBJECT, - ffi.Pointer)>>('FPDFTextObj_GetFontSize'); - late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction< - int Function(FPDF_PAGEOBJECT, ffi.Pointer)>(); - - /// Close a loaded PDF font. - /// - /// font - Handle to the loaded font. - void FPDFFont_Close( - FPDF_FONT font, - ) { - return _FPDFFont_Close( - font, - ); - } - - late final _FPDFFont_ClosePtr = - _lookup>( - 'FPDFFont_Close'); - late final _FPDFFont_Close = - _FPDFFont_ClosePtr.asFunction(); - - /// Create a new text object using a loaded font. - /// - /// document - handle to the document. - /// font - handle to the font object. - /// font_size - the font size for the new text object. - /// - /// Returns a handle to a new text object, or NULL on failure - FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj( - FPDF_DOCUMENT document, - FPDF_FONT font, - double font_size, - ) { - return _FPDFPageObj_CreateTextObj( - document, - font, - font_size, - ); - } - - late final _FPDFPageObj_CreateTextObjPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECT Function(FPDF_DOCUMENT, FPDF_FONT, - ffi.Float)>>('FPDFPageObj_CreateTextObj'); - late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr - .asFunction(); - - /// Get the text rendering mode of a text object. - /// - /// text - the handle to the text object. - /// - /// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, - /// FPDF_TEXTRENDERMODE_UNKNOWN on error. - FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode( - FPDF_PAGEOBJECT text, - ) { - return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode( - text, - )); - } - - late final _FPDFTextObj_GetTextRenderModePtr = - _lookup>( - 'FPDFTextObj_GetTextRenderMode'); - late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr - .asFunction(); - - /// Experimental API. - /// Set the text rendering mode of a text object. - /// - /// text - the handle to the text object. - /// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to - /// FPDF_TEXTRENDERMODE_UNKNOWN). - /// - /// Returns TRUE on success. - DartFPDF_BOOL FPDFTextObj_SetTextRenderMode( - FPDF_PAGEOBJECT text, - FPDF_TEXT_RENDERMODE render_mode, - ) { - return _FPDFTextObj_SetTextRenderMode( - text, - render_mode.value, - ); - } - - late final _FPDFTextObj_SetTextRenderModePtr = - _lookup>( - 'FPDFTextObj_SetTextRenderMode'); - late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr - .asFunction(); - - /// Get the text of a text object. - /// - /// text_object - the handle to the text object. - /// text_page - the handle to the text page. - /// buffer - the address of a buffer that receives the text. - /// length - the size, in bytes, of |buffer|. - /// - /// Returns the number of bytes in the text (including the trailing NUL - /// character) on success, 0 on error. - /// - /// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. - /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| - /// will not be modified. - int FPDFTextObj_GetText( - FPDF_PAGEOBJECT text_object, - FPDF_TEXTPAGE text_page, - ffi.Pointer buffer, - int length, - ) { - return _FPDFTextObj_GetText( - text_object, - text_page, - buffer, - length, - ); - } - - late final _FPDFTextObj_GetTextPtr = _lookup< - ffi.NativeFunction< - ffi.UnsignedLong Function( - FPDF_PAGEOBJECT, - FPDF_TEXTPAGE, - ffi.Pointer, - ffi.UnsignedLong)>>('FPDFTextObj_GetText'); - late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction< - int Function( - FPDF_PAGEOBJECT, FPDF_TEXTPAGE, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get a bitmap rasterization of |text_object|. To render correctly, the caller - /// must provide the |document| associated with |text_object|. If there is a - /// |page| associated with |text_object|, the caller should provide that as well. - /// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() - /// must be called on the returned bitmap when it is no longer needed. - /// - /// document - handle to a document associated with |text_object|. - /// page - handle to an optional page associated with |text_object|. - /// text_object - handle to a text object. - /// scale - the scaling factor, which must be greater than 0. - /// - /// Returns the bitmap or NULL on failure. - FPDF_BITMAP FPDFTextObj_GetRenderedBitmap( - FPDF_DOCUMENT document, - FPDF_PAGE page, - FPDF_PAGEOBJECT text_object, - double scale, - ) { - return _FPDFTextObj_GetRenderedBitmap( - document, - page, - text_object, - scale, - ); - } - - late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction< - FPDF_BITMAP Function(FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT, - ffi.Float)>>('FPDFTextObj_GetRenderedBitmap'); - late final _FPDFTextObj_GetRenderedBitmap = - _FPDFTextObj_GetRenderedBitmapPtr.asFunction< - FPDF_BITMAP Function( - FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT, double)>(); - - /// Experimental API. - /// Get the font of a text object. - /// - /// text - the handle to the text object. - /// - /// Returns a handle to the font object held by |text| which retains ownership. - FPDF_FONT FPDFTextObj_GetFont( - FPDF_PAGEOBJECT text, - ) { - return _FPDFTextObj_GetFont( - text, - ); - } - - late final _FPDFTextObj_GetFontPtr = - _lookup>( - 'FPDFTextObj_GetFont'); - late final _FPDFTextObj_GetFont = - _FPDFTextObj_GetFontPtr.asFunction(); - - /// Experimental API. - /// Get the base name of a font. - /// - /// font - the handle to the font object. - /// buffer - the address of a buffer that receives the base font name. - /// length - the size, in bytes, of |buffer|. - /// - /// Returns the number of bytes in the base name (including the trailing NUL - /// character) on success, 0 on error. The base name is typically the font's - /// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. - /// - /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. - /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| - /// will not be modified. - int FPDFFont_GetBaseFontName( - FPDF_FONT font, - ffi.Pointer buffer, - int length, - ) { - return _FPDFFont_GetBaseFontName( - font, - buffer, - length, - ); - } - - late final _FPDFFont_GetBaseFontNamePtr = _lookup< - ffi.NativeFunction< - ffi.Size Function(FPDF_FONT, ffi.Pointer, - ffi.Size)>>('FPDFFont_GetBaseFontName'); - late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr - .asFunction, int)>(); - - /// Experimental API. - /// Get the family name of a font. - /// - /// font - the handle to the font object. - /// buffer - the address of a buffer that receives the font name. - /// length - the size, in bytes, of |buffer|. - /// - /// Returns the number of bytes in the family name (including the trailing NUL - /// character) on success, 0 on error. - /// - /// Regardless of the platform, the |buffer| is always in UTF-8 encoding. - /// If |length| is less than the returned length, or |buffer| is NULL, |buffer| - /// will not be modified. - int FPDFFont_GetFamilyName( - FPDF_FONT font, - ffi.Pointer buffer, - int length, - ) { - return _FPDFFont_GetFamilyName( - font, - buffer, - length, - ); - } - - late final _FPDFFont_GetFamilyNamePtr = _lookup< - ffi.NativeFunction< - ffi.Size Function(FPDF_FONT, ffi.Pointer, - ffi.Size)>>('FPDFFont_GetFamilyName'); - late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction< - int Function(FPDF_FONT, ffi.Pointer, int)>(); - - /// Experimental API. - /// Get the decoded data from the |font| object. - /// - /// font - The handle to the font object. (Required) - /// buffer - The address of a buffer that receives the font data. - /// buflen - Length of the buffer. - /// out_buflen - Pointer to variable that will receive the minimum buffer size - /// to contain the font data. Not filled if the return value is - /// FALSE. (Required) - /// - /// Returns TRUE on success. In which case, |out_buflen| will be filled, and - /// |buffer| will be filled if it is large enough. Returns FALSE if any of the - /// required parameters are null. - /// - /// The decoded data is the uncompressed font data. i.e. the raw font data after - /// having all stream filters applied, when the data is embedded. - /// - /// If the font is not embedded, then this API will instead return the data for - /// the substitution font it is using. - int FPDFFont_GetFontData( - FPDF_FONT font, - ffi.Pointer buffer, - int buflen, - ffi.Pointer out_buflen, - ) { - return _FPDFFont_GetFontData( - font, - buffer, - buflen, - out_buflen, - ); - } - - late final _FPDFFont_GetFontDataPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FONT, ffi.Pointer, ffi.Size, - ffi.Pointer)>>('FPDFFont_GetFontData'); - late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction< - int Function( - FPDF_FONT, ffi.Pointer, int, ffi.Pointer)>(); - - /// Experimental API. - /// Get whether |font| is embedded or not. - /// - /// font - the handle to the font object. - /// - /// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. - int FPDFFont_GetIsEmbedded( - FPDF_FONT font, - ) { - return _FPDFFont_GetIsEmbedded( - font, - ); - } - - late final _FPDFFont_GetIsEmbeddedPtr = - _lookup>( - 'FPDFFont_GetIsEmbedded'); - late final _FPDFFont_GetIsEmbedded = - _FPDFFont_GetIsEmbeddedPtr.asFunction(); - - /// Experimental API. - /// Get the descriptor flags of a font. - /// - /// font - the handle to the font object. - /// - /// Returns the bit flags specifying various characteristics of the font as - /// defined in ISO 32000-1:2008, table 123, -1 on failure. - int FPDFFont_GetFlags( - FPDF_FONT font, - ) { - return _FPDFFont_GetFlags( - font, - ); - } - - late final _FPDFFont_GetFlagsPtr = - _lookup>( - 'FPDFFont_GetFlags'); - late final _FPDFFont_GetFlags = - _FPDFFont_GetFlagsPtr.asFunction(); - - /// Experimental API. - /// Get the font weight of a font. - /// - /// font - the handle to the font object. - /// - /// Returns the font weight, -1 on failure. - /// Typical values are 400 (normal) and 700 (bold). - int FPDFFont_GetWeight( - FPDF_FONT font, - ) { - return _FPDFFont_GetWeight( - font, - ); - } - - late final _FPDFFont_GetWeightPtr = - _lookup>( - 'FPDFFont_GetWeight'); - late final _FPDFFont_GetWeight = - _FPDFFont_GetWeightPtr.asFunction(); - - /// Experimental API. - /// Get the italic angle of a font. - /// - /// font - the handle to the font object. - /// angle - pointer where the italic angle will be stored - /// - /// The italic angle of a |font| is defined as degrees counterclockwise - /// from vertical. For a font that slopes to the right, this will be negative. - /// - /// Returns TRUE on success; |angle| unmodified on failure. - int FPDFFont_GetItalicAngle( - FPDF_FONT font, - ffi.Pointer angle, - ) { - return _FPDFFont_GetItalicAngle( - font, - angle, - ); - } - - late final _FPDFFont_GetItalicAnglePtr = _lookup< - ffi - .NativeFunction)>>( - 'FPDFFont_GetItalicAngle'); - late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction< - int Function(FPDF_FONT, ffi.Pointer)>(); - - /// Experimental API. - /// Get ascent distance of a font. - /// - /// font - the handle to the font object. - /// font_size - the size of the |font|. - /// ascent - pointer where the font ascent will be stored - /// - /// Ascent is the maximum distance in points above the baseline reached by the - /// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). - /// - /// Returns TRUE on success; |ascent| unmodified on failure. - int FPDFFont_GetAscent( - FPDF_FONT font, - double font_size, - ffi.Pointer ascent, - ) { - return _FPDFFont_GetAscent( - font, - font_size, - ascent, - ); - } - - late final _FPDFFont_GetAscentPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FONT, ffi.Float, - ffi.Pointer)>>('FPDFFont_GetAscent'); - late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction< - int Function(FPDF_FONT, double, ffi.Pointer)>(); - - /// Experimental API. - /// Get descent distance of a font. - /// - /// font - the handle to the font object. - /// font_size - the size of the |font|. - /// descent - pointer where the font descent will be stored - /// - /// Descent is the maximum distance in points below the baseline reached by the - /// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). - /// - /// Returns TRUE on success; |descent| unmodified on failure. - int FPDFFont_GetDescent( - FPDF_FONT font, - double font_size, - ffi.Pointer descent, - ) { - return _FPDFFont_GetDescent( - font, - font_size, - descent, - ); - } - - late final _FPDFFont_GetDescentPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FONT, ffi.Float, - ffi.Pointer)>>('FPDFFont_GetDescent'); - late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction< - int Function(FPDF_FONT, double, ffi.Pointer)>(); - - /// Experimental API. - /// Get the width of a glyph in a font. - /// - /// font - the handle to the font object. - /// glyph - the glyph. - /// font_size - the size of the font. - /// width - pointer where the glyph width will be stored - /// - /// Glyph width is the distance from the end of the prior glyph to the next - /// glyph. This will be the vertical distance for vertical writing. - /// - /// Returns TRUE on success; |width| unmodified on failure. - int FPDFFont_GetGlyphWidth( - FPDF_FONT font, - int glyph, - double font_size, - ffi.Pointer width, - ) { - return _FPDFFont_GetGlyphWidth( - font, - glyph, - font_size, - width, - ); - } - - late final _FPDFFont_GetGlyphWidthPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function(FPDF_FONT, ffi.Uint32, ffi.Float, - ffi.Pointer)>>('FPDFFont_GetGlyphWidth'); - late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction< - int Function(FPDF_FONT, int, double, ffi.Pointer)>(); - - /// Experimental API. - /// Get the glyphpath describing how to draw a font glyph. - /// - /// font - the handle to the font object. - /// glyph - the glyph being drawn. - /// font_size - the size of the font. - /// - /// Returns the handle to the segment, or NULL on faiure. - FPDF_GLYPHPATH FPDFFont_GetGlyphPath( - FPDF_FONT font, - int glyph, - double font_size, - ) { - return _FPDFFont_GetGlyphPath( - font, - glyph, - font_size, - ); - } - - late final _FPDFFont_GetGlyphPathPtr = _lookup< - ffi.NativeFunction< - FPDF_GLYPHPATH Function( - FPDF_FONT, ffi.Uint32, ffi.Float)>>('FPDFFont_GetGlyphPath'); - late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction< - FPDF_GLYPHPATH Function(FPDF_FONT, int, double)>(); - - /// Experimental API. - /// Get number of segments inside glyphpath. - /// - /// glyphpath - handle to a glyph path. - /// - /// Returns the number of objects in |glyphpath| or -1 on failure. - int FPDFGlyphPath_CountGlyphSegments( - FPDF_GLYPHPATH glyphpath, - ) { - return _FPDFGlyphPath_CountGlyphSegments( - glyphpath, - ); - } - - late final _FPDFGlyphPath_CountGlyphSegmentsPtr = - _lookup>( - 'FPDFGlyphPath_CountGlyphSegments'); - late final _FPDFGlyphPath_CountGlyphSegments = - _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction< - int Function(FPDF_GLYPHPATH)>(); - - /// Experimental API. - /// Get segment in glyphpath at index. - /// - /// glyphpath - handle to a glyph path. - /// index - the index of a segment. - /// - /// Returns the handle to the segment, or NULL on faiure. - FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment( - FPDF_GLYPHPATH glyphpath, - int index, - ) { - return _FPDFGlyphPath_GetGlyphPathSegment( - glyphpath, - index, - ); - } - - late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< - ffi - .NativeFunction>( - 'FPDFGlyphPath_GetGlyphPathSegment'); - late final _FPDFGlyphPath_GetGlyphPathSegment = - _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction< - FPDF_PATHSEGMENT Function(FPDF_GLYPHPATH, int)>(); - - /// Get number of page objects inside |form_object|. - /// - /// form_object - handle to a form object. - /// - /// Returns the number of objects in |form_object| on success, -1 on error. - int FPDFFormObj_CountObjects( - FPDF_PAGEOBJECT form_object, - ) { - return _FPDFFormObj_CountObjects( - form_object, - ); - } - - late final _FPDFFormObj_CountObjectsPtr = - _lookup>( - 'FPDFFormObj_CountObjects'); - late final _FPDFFormObj_CountObjects = - _FPDFFormObj_CountObjectsPtr.asFunction(); - - /// Get page object in |form_object| at |index|. - /// - /// form_object - handle to a form object. - /// index - the 0-based index of a page object. - /// - /// Returns the handle to the page object, or NULL on error. - FPDF_PAGEOBJECT FPDFFormObj_GetObject( - FPDF_PAGEOBJECT form_object, - int index, - ) { - return _FPDFFormObj_GetObject( - form_object, - index, - ); - } - - late final _FPDFFormObj_GetObjectPtr = _lookup< - ffi.NativeFunction< - FPDF_PAGEOBJECT Function( - FPDF_PAGEOBJECT, ffi.UnsignedLong)>>('FPDFFormObj_GetObject'); - late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction< - FPDF_PAGEOBJECT Function(FPDF_PAGEOBJECT, int)>(); - - /// Experimental API. - /// - /// Remove |page_object| from |form_object|. - /// - /// form_object - handle to a form object. - /// page_object - handle to a page object to be removed from the form. - /// - /// Returns TRUE on success. - /// - /// Ownership of the removed |page_object| is transferred to the caller. - /// Call FPDFPageObj_Destroy() on the removed page_object to free it. - int FPDFFormObj_RemoveObject( - FPDF_PAGEOBJECT form_object, - FPDF_PAGEOBJECT page_object, - ) { - return _FPDFFormObj_RemoveObject( - form_object, - page_object, - ); - } - - late final _FPDFFormObj_RemoveObjectPtr = _lookup< - ffi.NativeFunction< - FPDF_BOOL Function( - FPDF_PAGEOBJECT, FPDF_PAGEOBJECT)>>('FPDFFormObj_RemoveObject'); - late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr - .asFunction(); } /// PDF text rendering modes @@ -10140,146 +8371,142 @@ enum FPDF_TEXT_RENDERMODE { const FPDF_TEXT_RENDERMODE(this.value); static FPDF_TEXT_RENDERMODE fromValue(int value) => switch (value) { - -1 => FPDF_TEXTRENDERMODE_UNKNOWN, - 0 => FPDF_TEXTRENDERMODE_FILL, - 1 => FPDF_TEXTRENDERMODE_STROKE, - 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, - 3 => FPDF_TEXTRENDERMODE_INVISIBLE, - 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, - 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, - 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, - 7 => FPDF_TEXTRENDERMODE_CLIP, - _ => - throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), - }; + -1 => FPDF_TEXTRENDERMODE_UNKNOWN, + 0 => FPDF_TEXTRENDERMODE_FILL, + 1 => FPDF_TEXTRENDERMODE_STROKE, + 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, + 3 => FPDF_TEXTRENDERMODE_INVISIBLE, + 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, + 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, + 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, + 7 => FPDF_TEXTRENDERMODE_CLIP, + _ => throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), + }; @override String toString() { - if (this == FPDF_TEXTRENDERMODE_CLIP) - return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; + if (this == FPDF_TEXTRENDERMODE_CLIP) return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; return super.toString(); - } -} + }} -final class fpdf_action_t__ extends ffi.Opaque {} +final class fpdf_action_t__ extends ffi.Opaque{ +} /// PDF types - use incomplete types (never completed) to force API type safety. typedef FPDF_ACTION = ffi.Pointer; - -final class fpdf_annotation_t__ extends ffi.Opaque {} +final class fpdf_annotation_t__ extends ffi.Opaque{ +} typedef FPDF_ANNOTATION = ffi.Pointer; - -final class fpdf_attachment_t__ extends ffi.Opaque {} +final class fpdf_attachment_t__ extends ffi.Opaque{ +} typedef FPDF_ATTACHMENT = ffi.Pointer; - -final class fpdf_avail_t__ extends ffi.Opaque {} +final class fpdf_avail_t__ extends ffi.Opaque{ +} typedef FPDF_AVAIL = ffi.Pointer; - -final class fpdf_bitmap_t__ extends ffi.Opaque {} +final class fpdf_bitmap_t__ extends ffi.Opaque{ +} typedef FPDF_BITMAP = ffi.Pointer; - -final class fpdf_bookmark_t__ extends ffi.Opaque {} +final class fpdf_bookmark_t__ extends ffi.Opaque{ +} typedef FPDF_BOOKMARK = ffi.Pointer; - -final class fpdf_clippath_t__ extends ffi.Opaque {} +final class fpdf_clippath_t__ extends ffi.Opaque{ +} typedef FPDF_CLIPPATH = ffi.Pointer; - -final class fpdf_dest_t__ extends ffi.Opaque {} +final class fpdf_dest_t__ extends ffi.Opaque{ +} typedef FPDF_DEST = ffi.Pointer; - -final class fpdf_document_t__ extends ffi.Opaque {} +final class fpdf_document_t__ extends ffi.Opaque{ +} typedef FPDF_DOCUMENT = ffi.Pointer; - -final class fpdf_font_t__ extends ffi.Opaque {} +final class fpdf_font_t__ extends ffi.Opaque{ +} typedef FPDF_FONT = ffi.Pointer; - -final class fpdf_form_handle_t__ extends ffi.Opaque {} +final class fpdf_form_handle_t__ extends ffi.Opaque{ +} typedef FPDF_FORMHANDLE = ffi.Pointer; - -final class fpdf_glyphpath_t__ extends ffi.Opaque {} +final class fpdf_glyphpath_t__ extends ffi.Opaque{ +} typedef FPDF_GLYPHPATH = ffi.Pointer; - -final class fpdf_javascript_action_t extends ffi.Opaque {} +final class fpdf_javascript_action_t extends ffi.Opaque{ +} typedef FPDF_JAVASCRIPT_ACTION = ffi.Pointer; - -final class fpdf_link_t__ extends ffi.Opaque {} +final class fpdf_link_t__ extends ffi.Opaque{ +} typedef FPDF_LINK = ffi.Pointer; - -final class fpdf_page_t__ extends ffi.Opaque {} +final class fpdf_page_t__ extends ffi.Opaque{ +} typedef FPDF_PAGE = ffi.Pointer; - -final class fpdf_pagelink_t__ extends ffi.Opaque {} +final class fpdf_pagelink_t__ extends ffi.Opaque{ +} typedef FPDF_PAGELINK = ffi.Pointer; - -final class fpdf_pageobject_t__ extends ffi.Opaque {} +final class fpdf_pageobject_t__ extends ffi.Opaque{ +} typedef FPDF_PAGEOBJECT = ffi.Pointer; - -final class fpdf_pageobjectmark_t__ extends ffi.Opaque {} +final class fpdf_pageobjectmark_t__ extends ffi.Opaque{ +} typedef FPDF_PAGEOBJECTMARK = ffi.Pointer; - -final class fpdf_pagerange_t__ extends ffi.Opaque {} +final class fpdf_pagerange_t__ extends ffi.Opaque{ +} typedef FPDF_PAGERANGE = ffi.Pointer; - -final class fpdf_pathsegment_t extends ffi.Opaque {} +final class fpdf_pathsegment_t extends ffi.Opaque{ +} typedef FPDF_PATHSEGMENT = ffi.Pointer; - -final class fpdf_schhandle_t__ extends ffi.Opaque {} +final class fpdf_schhandle_t__ extends ffi.Opaque{ +} typedef FPDF_SCHHANDLE = ffi.Pointer; - -final class fpdf_signature_t__ extends ffi.Opaque {} +final class fpdf_signature_t__ extends ffi.Opaque{ +} typedef FPDF_SIGNATURE = ffi.Pointer; typedef FPDF_SKIA_CANVAS = ffi.Pointer; - -final class fpdf_structelement_t__ extends ffi.Opaque {} +final class fpdf_structelement_t__ extends ffi.Opaque{ +} typedef FPDF_STRUCTELEMENT = ffi.Pointer; - -final class fpdf_structelement_attr_t__ extends ffi.Opaque {} +final class fpdf_structelement_attr_t__ extends ffi.Opaque{ +} typedef FPDF_STRUCTELEMENT_ATTR = ffi.Pointer; +final class fpdf_structelement_attr_value_t__ extends ffi.Opaque{ +} -final class fpdf_structelement_attr_value_t__ extends ffi.Opaque {} - -typedef FPDF_STRUCTELEMENT_ATTR_VALUE - = ffi.Pointer; - -final class fpdf_structtree_t__ extends ffi.Opaque {} +typedef FPDF_STRUCTELEMENT_ATTR_VALUE = ffi.Pointer; +final class fpdf_structtree_t__ extends ffi.Opaque{ +} typedef FPDF_STRUCTTREE = ffi.Pointer; - -final class fpdf_textpage_t__ extends ffi.Opaque {} +final class fpdf_textpage_t__ extends ffi.Opaque{ +} typedef FPDF_TEXTPAGE = ffi.Pointer; - -final class fpdf_widget_t__ extends ffi.Opaque {} +final class fpdf_widget_t__ extends ffi.Opaque{ +} typedef FPDF_WIDGET = ffi.Pointer; - -final class fpdf_xobject_t__ extends ffi.Opaque {} +final class fpdf_xobject_t__ extends ffi.Opaque{ +} typedef FPDF_XOBJECT = ffi.Pointer; - /// Basic data types typedef FPDF_BOOL = ffi.Int; typedef DartFPDF_BOOL = int; @@ -10289,7 +8516,6 @@ typedef FPDF_DWORD = ffi.UnsignedLong; typedef DartFPDF_DWORD = int; typedef FS_FLOAT = ffi.Float; typedef DartFS_FLOAT = double; - /// Duplex types enum _FPDF_DUPLEXTYPE_ { DuplexUndefined(0), @@ -10297,64 +8523,62 @@ enum _FPDF_DUPLEXTYPE_ { DuplexFlipShortEdge(2), DuplexFlipLongEdge(3); + final int value; const _FPDF_DUPLEXTYPE_(this.value); static _FPDF_DUPLEXTYPE_ fromValue(int value) => switch (value) { - 0 => DuplexUndefined, - 1 => Simplex, - 2 => DuplexFlipShortEdge, - 3 => DuplexFlipLongEdge, - _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), - }; + 0 => DuplexUndefined, + 1 => Simplex, + 2 => DuplexFlipShortEdge, + 3 => DuplexFlipLongEdge, + _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), + }; + } /// String types typedef FPDF_WCHAR = ffi.UnsignedShort; typedef DartFPDF_WCHAR = int; - /// Public PDFium API type for byte strings. typedef FPDF_BYTESTRING = ffi.Pointer; - /// The public PDFium API always uses UTF-16LE encoded wide strings, each /// character uses 2 bytes (except surrogation), with the low byte first. typedef FPDF_WIDESTRING = ffi.Pointer; - /// Structure for persisting a string beyond the duration of a callback. /// Note: although represented as a char*, string may be interpreted as /// a UTF-16LE formated string. Used only by XFA callbacks. -final class FPDF_BSTR_ extends ffi.Struct { +final class FPDF_BSTR_ extends ffi.Struct{ /// String buffer, manipulate only with FPDF_BStr_* methods. external ffi.Pointer str; /// Length of the string, in bytes. @ffi.Int() external int len; + } /// Structure for persisting a string beyond the duration of a callback. /// Note: although represented as a char*, string may be interpreted as /// a UTF-16LE formated string. Used only by XFA callbacks. typedef FPDF_BSTR = FPDF_BSTR_; - /// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a /// Windows unicode string, however, special care needs to be taken if you /// expect to process Unicode larger than 0xffff. -/// +/// /// For Linux/Unix programmers: most compiler/library environments use 4 bytes /// for a Unicode character, and you have to convert between FPDF_WIDESTRING and /// system wide string by yourself. typedef FPDF_STRING = ffi.Pointer; - /// Matrix for transformation, in the form [a b c d e f], equivalent to: /// | a b 0 | /// | c d 0 | /// | e f 1 | -/// +/// /// Translation is performed with [1 0 0 1 tx ty]. /// Scaling is performed with [sx 0 0 sy 0 0]. /// See PDF Reference 1.7, 4.2.2 Common Transformations for more. -final class _FS_MATRIX_ extends ffi.Struct { +final class _FS_MATRIX_ extends ffi.Struct{ @ffi.Float() external double a; @@ -10372,20 +8596,20 @@ final class _FS_MATRIX_ extends ffi.Struct { @ffi.Float() external double f; + } /// Matrix for transformation, in the form [a b c d e f], equivalent to: /// | a b 0 | /// | c d 0 | /// | e f 1 | -/// +/// /// Translation is performed with [1 0 0 1 tx ty]. /// Scaling is performed with [sx 0 0 sy 0 0]. /// See PDF Reference 1.7, 4.2.2 Common Transformations for more. typedef FS_MATRIX = _FS_MATRIX_; - /// Rectangle area(float) in device or page coordinate system. -final class _FS_RECTF_ extends ffi.Struct { +final class _FS_RECTF_ extends ffi.Struct{ /// The x-coordinate of the left-top corner. @ffi.Float() external double left; @@ -10401,48 +8625,45 @@ final class _FS_RECTF_ extends ffi.Struct { /// The y-coordinate of the right-bottom corner. @ffi.Float() external double bottom; + } /// Rectangle area(float) in device or page coordinate system. typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; typedef FS_RECTF = _FS_RECTF_; - /// Const Pointer to FS_RECTF structure. typedef FS_LPCRECTF = ffi.Pointer; - /// Rectangle size. Coordinate system agnostic. -final class FS_SIZEF_ extends ffi.Struct { +final class FS_SIZEF_ extends ffi.Struct{ @ffi.Float() external double width; @ffi.Float() external double height; + } /// Rectangle size. Coordinate system agnostic. typedef FS_LPSIZEF = ffi.Pointer; typedef FS_SIZEF = FS_SIZEF_; - /// Const Pointer to FS_SIZEF structure. typedef FS_LPCSIZEF = ffi.Pointer; - /// 2D Point. Coordinate system agnostic. -final class FS_POINTF_ extends ffi.Struct { +final class FS_POINTF_ extends ffi.Struct{ @ffi.Float() external double x; @ffi.Float() external double y; + } /// 2D Point. Coordinate system agnostic. typedef FS_LPPOINTF = ffi.Pointer; typedef FS_POINTF = FS_POINTF_; - /// Const Pointer to FS_POINTF structure. typedef FS_LPCPOINTF = ffi.Pointer; - -final class _FS_QUADPOINTSF extends ffi.Struct { +final class _FS_QUADPOINTSF extends ffi.Struct{ @FS_FLOAT() external double x1; @@ -10466,42 +8687,40 @@ final class _FS_QUADPOINTSF extends ffi.Struct { @FS_FLOAT() external double y4; + } typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; - /// Annotation enums. typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; typedef DartFPDF_ANNOTATION_SUBTYPE = int; typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; typedef DartFPDF_ANNOT_APPEARANCEMODE = int; - /// Dictionary value types. typedef FPDF_OBJECT_TYPE = ffi.Int; typedef DartFPDF_OBJECT_TYPE = int; - /// PDF renderer types - Experimental. /// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. enum FPDF_RENDERER_TYPE { /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ FPDF_RENDERERTYPE_AGG(0), - /// Skia - https://skia.org/ FPDF_RENDERERTYPE_SKIA(1); + final int value; const FPDF_RENDERER_TYPE(this.value); static FPDF_RENDERER_TYPE fromValue(int value) => switch (value) { - 0 => FPDF_RENDERERTYPE_AGG, - 1 => FPDF_RENDERERTYPE_SKIA, - _ => - throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), - }; + 0 => FPDF_RENDERERTYPE_AGG, + 1 => FPDF_RENDERERTYPE_SKIA, + _ => throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), + }; + } /// Process-wide options for initializing the library. -final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct { +final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct{ /// Version number of the interface. Currently must be 2. /// Support for version 1 will be deprecated in the future. @ffi.Int() @@ -10535,15 +8754,14 @@ final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct { @ffi.UnsignedInt() external int m_RendererTypeAsInt; - FPDF_RENDERER_TYPE get m_RendererType => - FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); +FPDF_RENDERER_TYPE get m_RendererType => FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); + } /// Process-wide options for initializing the library. typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; - /// Structure for custom file access. -final class FPDF_FILEACCESS extends ffi.Struct { +final class FPDF_FILEACCESS extends ffi.Struct{ /// File length, in bytes. @ffi.UnsignedLong() external int m_FileLen; @@ -10555,51 +8773,41 @@ final class FPDF_FILEACCESS extends ffi.Struct { /// It may be possible for PDFium to call this function multiple times for /// the same position. /// Return value: should be non-zero if successful, zero for error. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer param, - ffi.UnsignedLong position, - ffi.Pointer pBuf, - ffi.UnsignedLong size)>> m_GetBlock; + external ffi.Pointer param, ffi.UnsignedLong position, ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; /// A custom pointer for all implementation specific data. This pointer will /// be used as the first parameter to the m_GetBlock callback. external ffi.Pointer m_Param; + } /// Structure for file reading or writing (I/O). -/// +/// /// Note: This is a handler and should be implemented by callers, /// and is only used from XFA. -final class FPDF_FILEHANDLER_ extends ffi.Struct { +final class FPDF_FILEHANDLER_ extends ffi.Struct{ /// User-defined data. /// Note: Callers can use this field to track controls. external ffi.Pointer clientData; /// Callback function to release the current file stream object. - /// + /// /// Parameters: /// clientData - Pointer to user-defined data. /// Returns: /// None. - external ffi.Pointer< - ffi - .NativeFunction clientData)>> - Release; + external ffi.Pointer clientData)>> Release; /// Callback function to retrieve the current file stream size. - /// + /// /// Parameters: /// clientData - Pointer to user-defined data. /// Returns: /// Size of file stream. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_DWORD Function(ffi.Pointer clientData)>> GetSize; + external ffi.Pointer clientData)>> GetSize; /// Callback function to read data from the current file stream. - /// + /// /// Parameters: /// clientData - Pointer to user-defined data. /// offset - Offset position starts from the beginning of file @@ -10611,16 +8819,10 @@ final class FPDF_FILEHANDLER_ extends ffi.Struct { /// large enough to store specified data. /// Returns: /// 0 for success, other value for failure. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_RESULT Function( - ffi.Pointer clientData, - FPDF_DWORD offset, - ffi.Pointer buffer, - FPDF_DWORD size)>> ReadBlock; + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; /// Callback function to write data into the current file stream. - /// + /// /// Parameters: /// clientData - Pointer to user-defined data. /// offset - Offset position starts from the beginning of file @@ -10631,26 +8833,18 @@ final class FPDF_FILEHANDLER_ extends ffi.Struct { /// stream, in bytes. /// Returns: /// 0 for success, other value for failure. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_RESULT Function( - ffi.Pointer clientData, - FPDF_DWORD offset, - ffi.Pointer buffer, - FPDF_DWORD size)>> WriteBlock; + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; /// Callback function to flush all internal accessing buffers. - /// + /// /// Parameters: /// clientData - Pointer to user-defined data. /// Returns: /// 0 for success, other value for failure. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_RESULT Function(ffi.Pointer clientData)>> Flush; + external ffi.Pointer clientData)>> Flush; /// Callback function to change file size. - /// + /// /// Description: /// This function is called under writing mode usually. Implementer /// can determine whether to realize it based on application requests. @@ -10659,21 +8853,18 @@ final class FPDF_FILEHANDLER_ extends ffi.Struct { /// size - New size of file stream, in bytes. /// Returns: /// 0 for success, other value for failure. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_RESULT Function( - ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; + external ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; + } /// Structure for file reading or writing (I/O). -/// +/// /// Note: This is a handler and should be implemented by callers, /// and is only used from XFA. typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; - /// Struct for color scheme. /// Each should be a 32-bit value specifying the color, in 8888 ARGB format. -final class FPDF_COLORSCHEME_ extends ffi.Struct { +final class FPDF_COLORSCHEME_ extends ffi.Struct{ @FPDF_DWORD() external int path_fill_color; @@ -10685,13 +8876,13 @@ final class FPDF_COLORSCHEME_ extends ffi.Struct { @FPDF_DWORD() external int text_stroke_color; + } /// Struct for color scheme. /// Each should be a 32-bit value specifying the color, in 8888 ARGB format. typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; - -final class _IPDF_JsPlatform extends ffi.Struct { +final class _IPDF_JsPlatform extends ffi.Struct{ /// Version number of the interface. Currently must be 2. @ffi.Int() external int version; @@ -10713,14 +8904,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// Return Value: /// Option selected by user in dialogue, one of the /// JSPLATFORM_ALERT_RETURN_* values above. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, - FPDF_WIDESTRING Msg, - FPDF_WIDESTRING Title, - ffi.Int Type, - ffi.Int Icon)>> app_alert; + external ffi.Pointer pThis, FPDF_WIDESTRING Msg, FPDF_WIDESTRING Title, ffi.Int Type, ffi.Int Icon)>> app_alert; /// Method: app_beep /// Causes the system to play a sound. @@ -10734,10 +8918,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// above. /// Return Value: /// None - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Int nType)>> app_beep; + external ffi.Pointer pThis, ffi.Int nType)>> app_beep; /// Method: app_response /// Displays a dialog box containing a question and an entry field for @@ -10772,17 +8953,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// buffer as specified by the length parameter, only the /// first "length" bytes of the user input are to be written to the /// buffer. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, - FPDF_WIDESTRING Question, - FPDF_WIDESTRING Title, - FPDF_WIDESTRING Default, - FPDF_WIDESTRING cLabel, - FPDF_BOOL bPassword, - ffi.Pointer response, - ffi.Int length)>> app_response; + external ffi.Pointer pThis, FPDF_WIDESTRING Question, FPDF_WIDESTRING Title, FPDF_WIDESTRING Default, FPDF_WIDESTRING cLabel, FPDF_BOOL bPassword, ffi.Pointer response, ffi.Int length)>> app_response; /// Method: Doc_getFilePath /// Get the file path of the current document. @@ -10804,10 +8975,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// the buffer, even when there is no buffer specified, or the buffer /// size is less than required. In this case, the buffer will not /// be modified. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_IPDF_JsPlatform> pThis, - ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; /// Method: Doc_mail /// Mails the data buffer as an attachment to all recipients, with or @@ -10840,18 +9008,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// Comments: /// If the parameter mailData is NULL or length is 0, the current /// document will be mailed as an attachment to all recipients. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, - ffi.Pointer mailData, - ffi.Int length, - FPDF_BOOL bUI, - FPDF_WIDESTRING To, - FPDF_WIDESTRING Subject, - FPDF_WIDESTRING CC, - FPDF_WIDESTRING BCC, - FPDF_WIDESTRING Msg)>> Doc_mail; + external ffi.Pointer pThis, ffi.Pointer mailData, ffi.Int length, FPDF_BOOL bUI, FPDF_WIDESTRING To, FPDF_WIDESTRING Subject, FPDF_WIDESTRING CC, FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; /// Method: Doc_print /// Prints all or a specific number of pages of the document. @@ -10878,18 +9035,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// printed. /// Return Value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, - FPDF_BOOL bUI, - ffi.Int nStart, - ffi.Int nEnd, - FPDF_BOOL bSilent, - FPDF_BOOL bShrinkToFit, - FPDF_BOOL bPrintAsImage, - FPDF_BOOL bReverse, - FPDF_BOOL bAnnotations)>> Doc_print; + external ffi.Pointer pThis, FPDF_BOOL bUI, ffi.Int nStart, ffi.Int nEnd, FPDF_BOOL bSilent, FPDF_BOOL bShrinkToFit, FPDF_BOOL bPrintAsImage, FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; /// Method: Doc_submitForm /// Send the form data to a specified URL. @@ -10905,13 +9051,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// URL - The URL to send to. /// Return Value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, - ffi.Pointer formData, - ffi.Int length, - FPDF_WIDESTRING URL)>> Doc_submitForm; + external ffi.Pointer pThis, ffi.Pointer formData, ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; /// Method: Doc_gotoPage /// Jump to a specified page. @@ -10924,11 +9064,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// nPageNum - The specified page number, zero for the first page. /// Return Value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_IPDF_JsPlatform> pThis, ffi.Int nPageNum)>> - Doc_gotoPage; + external ffi.Pointer pThis, ffi.Int nPageNum)>> Doc_gotoPage; /// Method: Field_browse /// Show a file selection dialog, and return the selected file path. @@ -10945,10 +9081,7 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// Number of bytes the filePath consumes, including trailing zeros. /// Comments: /// The filePath should always be provided in local encoding. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_IPDF_JsPlatform> pThis, - ffi.Pointer filePath, ffi.Int length)>> Field_browse; + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; /// Pointer for embedder-specific data. Unused by PDFium, and despite /// its name, can be any data the embedder desires, though traditionally @@ -10961,12 +9094,12 @@ final class _IPDF_JsPlatform extends ffi.Struct { /// Unused in v3, retain for compatibility. @ffi.UnsignedInt() external int m_v8EmbedderSlot; + } typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); typedef DartTimerCallbackFunction = void Function(int idEvent); - /// Function signature for the callback function passed to the FFI_SetTimer /// method. /// Parameters: @@ -10974,9 +9107,8 @@ typedef DartTimerCallbackFunction = void Function(int idEvent); /// Return value: /// None. typedef TimerCallback = ffi.Pointer>; - /// Declares of a struct type to the local system time. -final class _FPDF_SYSTEMTIME extends ffi.Struct { +final class _FPDF_SYSTEMTIME extends ffi.Struct{ /// years since 1900 @ffi.UnsignedShort() external int wYear; @@ -11008,12 +9140,12 @@ final class _FPDF_SYSTEMTIME extends ffi.Struct { /// milliseconds after the second - [0,999] @ffi.UnsignedShort() external int wMilliseconds; + } /// Declares of a struct type to the local system time. typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; - -final class _FPDF_FORMFILLINFO extends ffi.Struct { +final class _FPDF_FORMFILLINFO extends ffi.Struct{ /// Version number of the interface. /// Version 1 contains stable interfaces. Version 2 has additional /// experimental interfaces. @@ -11041,9 +9173,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// pThis - Pointer to the interface structure itself /// Return Value: /// None - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> Release; + external ffi.Pointer pThis)>> Release; /// Method: FFI_Invalidate /// Invalidate the client area within the specified rectangle. @@ -11068,15 +9198,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// All positions are measured in PDF "user space". /// Implementation should call FPDF_RenderPageBitmap() for repainting /// the specified page area. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_PAGE page, - ffi.Double left, - ffi.Double top, - ffi.Double right, - ffi.Double bottom)>> FFI_Invalidate; + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; /// Method: FFI_OutputSelectedRect /// When the user selects text in form fields with the mouse, this @@ -11104,15 +9226,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// returned rectangles, then draw them one by one during the next /// painting period. Lastly, it should remove all the recorded /// rectangles when finished painting. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_PAGE page, - ffi.Double left, - ffi.Double top, - ffi.Double right, - ffi.Double bottom)>> FFI_OutputSelectedRect; + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; /// Method: FFI_SetCursor /// Set the Cursor shape. @@ -11125,11 +9239,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// nCursorType - Cursor type, see Flags for Cursor type for details. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int nCursorType)>> - FFI_SetCursor; + external ffi.Pointer pThis, ffi.Int nCursorType)>> FFI_SetCursor; /// Method: FFI_SetTimer /// This method installs a system timer. An interval value is specified, @@ -11147,10 +9257,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// The timer identifier of the new timer if the function is successful. /// An application passes this value to the FFI_KillTimer method to kill /// the timer. Nonzero if it is successful; otherwise, it is zero. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; + external ffi.Pointer pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; /// Method: FFI_KillTimer /// This method uninstalls a system timer, as set by an earlier call to @@ -11164,11 +9271,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// nTimerID - The timer ID returned by FFI_SetTimer function. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int nTimerID)>> - FFI_KillTimer; + external ffi.Pointer pThis, ffi.Int nTimerID)>> FFI_KillTimer; /// Method: FFI_GetLocalTime /// This method receives the current local time on the system. @@ -11181,10 +9284,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// Return value: /// The local time. See FPDF_SYSTEMTIME above for details. /// Note: Unused. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_SYSTEMTIME Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> - FFI_GetLocalTime; + external ffi.Pointer pThis)>> FFI_GetLocalTime; /// Method: FFI_OnChange /// This method will be invoked to notify the implementation when the @@ -11197,10 +9297,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// pThis - Pointer to the interface structure itself. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis)>> - FFI_OnChange; + external ffi.Pointer pThis)>> FFI_OnChange; /// Method: FFI_GetPage /// This method receives the page handle associated with a specified @@ -11222,10 +9319,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// cases, the document-level JavaScript action may refer to a page /// which hadn't been loaded yet. To successfully run the Javascript /// action, the implementation needs to load the page. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_PAGE Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; /// Method: FFI_GetCurrentPage /// This method receives the handle to the current page. @@ -11242,10 +9336,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// PDFium doesn't keep keep track of the "current page" (e.g. the one /// that is most visible on screen), so it must ask the embedder for /// this information. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_PAGE Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_DOCUMENT document)>> FFI_GetCurrentPage; + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; /// Method: FFI_GetRotation /// This method receives currently rotation of the page view. @@ -11264,11 +9355,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// 2 - 180 degrees /// 3 - 270 degrees /// Note: Unused. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_PAGE page)>> - FFI_GetRotation; + external ffi.Pointer pThis, FPDF_PAGE page)>> FFI_GetRotation; /// Method: FFI_ExecuteNamedAction /// This method will execute a named action. @@ -11286,10 +9373,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the /// standard named actions, but note that a document may supply any /// name of its choosing. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; + external ffi.Pointer pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; /// Method: FFI_SetTextFieldFocus /// Called when a text field is getting or losing focus. @@ -11309,13 +9393,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// None. /// Comments: /// Only supports text fields and combobox fields. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_WIDESTRING value, - FPDF_DWORD valueLen, - FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; + external ffi.Pointer pThis, FPDF_WIDESTRING value, FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; /// Method: FFI_DoURIAction /// Ask the implementation to navigate to a uniform resource identifier. @@ -11336,10 +9414,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// FFI_DoURIAction. /// See the URI actions description of <> /// for more details. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; + external ffi.Pointer pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; /// Method: FFI_DoGoToAction /// This action changes the view to a specified destination. @@ -11367,14 +9442,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// Comments: /// See the Destinations description of <> /// in 8.2.1 for more details. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Int nPageIndex, - ffi.Int zoomMode, - ffi.Pointer fPosArray, - ffi.Int sizeofArray)>> FFI_DoGoToAction; + external ffi.Pointer pThis, ffi.Int nPageIndex, ffi.Int zoomMode, ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; /// Pointer to IPDF_JSPLATFORM interface. /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then @@ -11406,16 +9474,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// coordinates. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_PAGE page, - FPDF_BOOL bVisible, - ffi.Double left, - ffi.Double top, - ffi.Double right, - ffi.Double bottom)>> FFI_DisplayCaret; + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_BOOL bVisible, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; /// Method: FFI_GetCurrentPageIndex /// This method will get the current page index. @@ -11428,10 +9487,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// document - Handle to document from FPDF_LoadDocument(). /// Return value: /// The index of current page. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; /// Method: FFI_SetCurrentPage /// This method will set the current page. @@ -11445,10 +9501,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// iCurPage - The index of the PDF page. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; /// Method: FFI_GotoURL /// This method will navigate to the specified URL. @@ -11462,10 +9515,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// wsURL - The string value of the URL, in UTF-16LE format. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; + external ffi.Pointer pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; /// Method: FFI_GetPageViewRect /// This method will get the current page view rectangle. @@ -11486,15 +9536,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// page view area in PDF page coordinates. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_PAGE page, - ffi.Pointer left, - ffi.Pointer top, - ffi.Pointer right, - ffi.Pointer bottom)>> FFI_GetPageViewRect; + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Pointer left, ffi.Pointer top, ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; /// Method: FFI_PageEvent /// This method fires when pages have been added to or deleted from @@ -11516,10 +9558,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// appended to the tail of document; If page_count is 2 and /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages /// have been deleted. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; + external ffi.Pointer pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; /// Method: FFI_PopupMenu /// This method will track the right context menu for XFA fields. @@ -11540,15 +9579,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// coordinates. /// Return value: /// TRUE indicates success; otherwise false. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_BOOL Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_PAGE page, - FPDF_WIDGET hWidget, - ffi.Int menuFlag, - ffi.Float x, - ffi.Float y)>> FFI_PopupMenu; + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_WIDGET hWidget, ffi.Int menuFlag, ffi.Float x, ffi.Float y)>> FFI_PopupMenu; /// Method: FFI_OpenFile /// This method will open the specified file with the specified mode. @@ -11565,13 +9596,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// mode - The mode for open file, e.g. "rb" or "wb". /// Return value: /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Int fileFlag, - FPDF_WIDESTRING wsURL, - ffi.Pointer mode)>> FFI_OpenFile; + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int fileFlag, FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; /// Method: FFI_EmailTo /// This method will email the specified file stream to the specified @@ -11594,16 +9619,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// NULL,in UTF-16LE format. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Pointer fileHandler, - FPDF_WIDESTRING pTo, - FPDF_WIDESTRING pSubject, - FPDF_WIDESTRING pCC, - FPDF_WIDESTRING pBcc, - FPDF_WIDESTRING pMsg)>> FFI_EmailTo; + external ffi.Pointer pThis, ffi.Pointer fileHandler, FPDF_WIDESTRING pTo, FPDF_WIDESTRING pSubject, FPDF_WIDESTRING pCC, FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; /// Method: FFI_UploadTo /// This method will upload the specified file stream to the @@ -11620,13 +9636,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// uploadTo - Pointer to the URL path, in UTF-16LE format. /// Return value: /// None. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Pointer fileHandler, - ffi.Int fileFlag, - FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; + external ffi.Pointer pThis, ffi.Pointer fileHandler, ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; /// Method: FFI_GetPlatform /// This method will get the current platform. @@ -11642,10 +9652,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// 0 to query the required size. /// Return value: /// The length of the buffer, number of bytes. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; + external ffi.Pointer pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; /// Method: FFI_GetLanguage /// This method will get the current language. @@ -11661,10 +9668,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// 0 to query the required size. /// Return value: /// The length of the buffer, number of bytes. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; + external ffi.Pointer pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; /// Method: FFI_DownloadFromURL /// This method will download the specified file from the URL. @@ -11678,11 +9682,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// format. /// Return value: /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> - FFI_DownloadFromURL; + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; /// Method: FFI_PostRequestURL /// This method will post the request to the server URL. @@ -11703,16 +9703,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// data from the server, in UTF-16LE format. /// Return value: /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_BOOL Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_WIDESTRING wsURL, - FPDF_WIDESTRING wsData, - FPDF_WIDESTRING wsContentType, - FPDF_WIDESTRING wsEncode, - FPDF_WIDESTRING wsHeader, - ffi.Pointer response)>> FFI_PostRequestURL; + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsContentType, FPDF_WIDESTRING wsEncode, FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; /// Method: FFI_PutRequestURL /// This method will put the request to the server URL. @@ -11728,13 +9719,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// wsEncode - The encode type, in UTR-16LE format. /// Return value: /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer< - ffi.NativeFunction< - FPDF_BOOL Function( - ffi.Pointer<_FPDF_FORMFILLINFO> pThis, - FPDF_WIDESTRING wsURL, - FPDF_WIDESTRING wsData, - FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; /// Method: FFI_OnFocusChange /// Called when the focused annotation is updated. @@ -11754,10 +9739,7 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// action such as scrolling the annotation rect into view. The /// embedder should not copy and store the annot as its scope is /// limited to this call only. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer<_FPDF_FORMFILLINFO> param, - FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; + external ffi.Pointer param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; /// Method: FFI_DoURIActionWithKeyboardModifier /// Ask the implementation to navigate to a uniform resource identifier @@ -11779,29 +9761,25 @@ final class _FPDF_FORMFILLINFO extends ffi.Struct { /// then a call will be redirected to FFI_DoURIAction. /// See the URI actions description of <> /// for more details. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer<_FPDF_FORMFILLINFO> param, - FPDF_BYTESTRING uri, - ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; + external ffi.Pointer param, FPDF_BYTESTRING uri, ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; + } typedef FPDF_FORMFILLINFO = _FPDF_FORMFILLINFO; - enum FPDFANNOT_COLORTYPE { FPDFANNOT_COLORTYPE_Color(0), FPDFANNOT_COLORTYPE_InteriorColor(1); + final int value; const FPDFANNOT_COLORTYPE(this.value); static FPDFANNOT_COLORTYPE fromValue(int value) => switch (value) { - 0 => FPDFANNOT_COLORTYPE_Color, - 1 => FPDFANNOT_COLORTYPE_InteriorColor, - _ => - throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), - }; + 0 => FPDFANNOT_COLORTYPE_Color, + 1 => FPDFANNOT_COLORTYPE_InteriorColor, + _ => throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), + }; + } /// The file identifier entry type. See section 14.4 "File Identifiers" of the @@ -11810,17 +9788,19 @@ enum FPDF_FILEIDTYPE { FILEIDTYPE_PERMANENT(0), FILEIDTYPE_CHANGING(1); + final int value; const FPDF_FILEIDTYPE(this.value); static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { - 0 => FILEIDTYPE_PERMANENT, - 1 => FILEIDTYPE_CHANGING, - _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), - }; + 0 => FILEIDTYPE_PERMANENT, + 1 => FILEIDTYPE_CHANGING, + _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), + }; + } -final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct { +final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ /// The image width in pixels. @ffi.UnsignedInt() external int width; @@ -11837,418 +9817,850 @@ final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct { @ffi.Float() external double vertical_dpi; - /// The number of bits used to represent each pixel. - @ffi.UnsignedInt() - external int bits_per_pixel; + /// The number of bits used to represent each pixel. + @ffi.UnsignedInt() + external int bits_per_pixel; + + /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. + @ffi.Int() + external int colorspace; + + /// The image's marked content ID. Useful for pairing with associated alt-text. + /// A value of -1 indicates no ID. + @ffi.Int() + external int marked_content_id; + +} + +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +final class _FPDF_SYSFONTINFO extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: Release + /// Give implementation a chance to release any data after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None + /// Comments: + /// Called by PDFium during the final cleanup process. + external ffi.Pointer pThis)>> Release; + + /// Method: EnumFonts + /// Enumerate all fonts installed on the system + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// pMapper - An opaque pointer to internal font mapper, used + /// when calling FPDF_AddInstalledFont(). + /// Return Value: + /// None + /// Comments: + /// Implementations should call FPDF_AddInstalledFont() function for + /// each font found. Only TrueType/OpenType and Type1 fonts are + /// accepted by PDFium. + external ffi.Pointer pThis, ffi.Pointer pMapper)>> EnumFonts; + + /// Method: MapFont + /// Use the system font mapper to get a font handle from requested + /// parameters. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if GetFont method is not implemented. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// weight - Weight of the requested font. 400 is normal and + /// 700 is bold. + /// bItalic - Italic option of the requested font, TRUE or + /// FALSE. + /// charset - Character set identifier for the requested font. + /// See above defined constants. + /// pitch_family - A combination of flags. See above defined + /// constants. + /// face - Typeface name. Currently use system local encoding + /// only. + /// bExact - Obsolete: this parameter is now ignored. + /// Return Value: + /// An opaque pointer for font handle, or NULL if system mapping is + /// not supported. + /// Comments: + /// If the system supports native font mapper (like Windows), + /// implementation can implement this method to get a font handle. + /// Otherwise, PDFium will do the mapping and then call GetFont + /// method. Only TrueType/OpenType and Type1 fonts are accepted + /// by PDFium. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Int weight, FPDF_BOOL bItalic, ffi.Int charset, ffi.Int pitch_family, ffi.Pointer face, ffi.Pointer bExact)>> MapFont; + + /// Method: GetFont + /// Get a handle to a particular font by its internal ID + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if MapFont method is not implemented. + /// Return Value: + /// An opaque pointer for font handle. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// face - Typeface name in system local encoding. + /// Comments: + /// If the system mapping not supported, PDFium will do the font + /// mapping and use this method to get a font handle. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Pointer face)>> GetFont; + + /// Method: GetFontData + /// Get font data from a font + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// table - TrueType/OpenType table identifier (refer to + /// TrueType specification), or 0 for the whole file. + /// buffer - The buffer receiving the font data. Can be NULL if + /// not provided. + /// buf_size - Buffer size, can be zero if not provided. + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + /// Comments: + /// Can read either the full font file, or a particular + /// TrueType/OpenType table. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.UnsignedInt table, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFontData; + + /// Method: GetFaceName + /// Get face name from a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// buffer - The buffer receiving the face name. Can be NULL if + /// not provided + /// buf_size - Buffer size, can be zero if not provided + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFaceName; + + /// Method: GetFontCharset + /// Get character set information for a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// Character set identifier. See defined constants above. + external ffi.Pointer pThis, ffi.Pointer hFont)>> GetFontCharset; + + /// Method: DeleteFont + /// Delete a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// None + external ffi.Pointer pThis, ffi.Pointer hFont)>> DeleteFont; + +} - /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +typedef FPDF_SYSFONTINFO = _FPDF_SYSFONTINFO; +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +final class FPDF_CharsetFontMap_ extends ffi.Struct{ + /// Character Set Enum value, see FXFONT_*_CHARSET above. @ffi.Int() - external int colorspace; + external int charset; + + /// Name of default font to use with that charset. + external ffi.Pointer fontname; - /// The image's marked content ID. Useful for pairing with associated alt-text. - /// A value of -1 indicates no ID. - @ffi.Int() - external int marked_content_id; } +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +typedef FPDF_CharsetFontMap = FPDF_CharsetFontMap_; + const int FPDF_OBJECT_UNKNOWN = 0; + const int FPDF_OBJECT_BOOLEAN = 1; + const int FPDF_OBJECT_NUMBER = 2; + const int FPDF_OBJECT_STRING = 3; + const int FPDF_OBJECT_NAME = 4; + const int FPDF_OBJECT_ARRAY = 5; + const int FPDF_OBJECT_DICTIONARY = 6; + const int FPDF_OBJECT_STREAM = 7; + const int FPDF_OBJECT_NULLOBJ = 8; + const int FPDF_OBJECT_REFERENCE = 9; + const int FPDF_POLICY_MACHINETIME_ACCESS = 0; + const int FPDF_ERR_SUCCESS = 0; + const int FPDF_ERR_UNKNOWN = 1; + const int FPDF_ERR_FILE = 2; + const int FPDF_ERR_FORMAT = 3; + const int FPDF_ERR_PASSWORD = 4; + const int FPDF_ERR_SECURITY = 5; + const int FPDF_ERR_PAGE = 6; + const int FPDF_ANNOT = 1; + const int FPDF_LCD_TEXT = 2; + const int FPDF_NO_NATIVETEXT = 4; + const int FPDF_GRAYSCALE = 8; + const int FPDF_DEBUG_INFO = 128; + const int FPDF_NO_CATCH = 256; + const int FPDF_RENDER_LIMITEDIMAGECACHE = 512; + const int FPDF_RENDER_FORCEHALFTONE = 1024; + const int FPDF_PRINTING = 2048; + const int FPDF_RENDER_NO_SMOOTHTEXT = 4096; + const int FPDF_RENDER_NO_SMOOTHIMAGE = 8192; + const int FPDF_RENDER_NO_SMOOTHPATH = 16384; + const int FPDF_REVERSE_BYTE_ORDER = 16; + const int FPDF_CONVERT_FILL_TO_STROKE = 32; + const int FPDFBitmap_Unknown = 0; + const int FPDFBitmap_Gray = 1; + const int FPDFBitmap_BGR = 2; + const int FPDFBitmap_BGRx = 3; + const int FPDFBitmap_BGRA = 4; + const int FPDFBitmap_BGRA_Premul = 5; + const int FORMTYPE_NONE = 0; + const int FORMTYPE_ACRO_FORM = 1; + const int FORMTYPE_XFA_FULL = 2; + const int FORMTYPE_XFA_FOREGROUND = 3; + const int FORMTYPE_COUNT = 4; + const int JSPLATFORM_ALERT_BUTTON_OK = 0; + const int JSPLATFORM_ALERT_BUTTON_OKCANCEL = 1; + const int JSPLATFORM_ALERT_BUTTON_YESNO = 2; + const int JSPLATFORM_ALERT_BUTTON_YESNOCANCEL = 3; + const int JSPLATFORM_ALERT_BUTTON_DEFAULT = 0; + const int JSPLATFORM_ALERT_ICON_ERROR = 0; + const int JSPLATFORM_ALERT_ICON_WARNING = 1; + const int JSPLATFORM_ALERT_ICON_QUESTION = 2; + const int JSPLATFORM_ALERT_ICON_STATUS = 3; + const int JSPLATFORM_ALERT_ICON_ASTERISK = 4; + const int JSPLATFORM_ALERT_ICON_DEFAULT = 0; + const int JSPLATFORM_ALERT_RETURN_OK = 1; + const int JSPLATFORM_ALERT_RETURN_CANCEL = 2; + const int JSPLATFORM_ALERT_RETURN_NO = 3; + const int JSPLATFORM_ALERT_RETURN_YES = 4; + const int JSPLATFORM_BEEP_ERROR = 0; + const int JSPLATFORM_BEEP_WARNING = 1; + const int JSPLATFORM_BEEP_QUESTION = 2; + const int JSPLATFORM_BEEP_STATUS = 3; + const int JSPLATFORM_BEEP_DEFAULT = 4; + const int FXCT_ARROW = 0; + const int FXCT_NESW = 1; + const int FXCT_NWSE = 2; + const int FXCT_VBEAM = 3; + const int FXCT_HBEAM = 4; + const int FXCT_HAND = 5; + const int FPDFDOC_AACTION_WC = 16; + const int FPDFDOC_AACTION_WS = 17; + const int FPDFDOC_AACTION_DS = 18; + const int FPDFDOC_AACTION_WP = 19; + const int FPDFDOC_AACTION_DP = 20; + const int FPDFPAGE_AACTION_OPEN = 0; + const int FPDFPAGE_AACTION_CLOSE = 1; + const int FPDF_FORMFIELD_UNKNOWN = 0; + const int FPDF_FORMFIELD_PUSHBUTTON = 1; + const int FPDF_FORMFIELD_CHECKBOX = 2; + const int FPDF_FORMFIELD_RADIOBUTTON = 3; + const int FPDF_FORMFIELD_COMBOBOX = 4; + const int FPDF_FORMFIELD_LISTBOX = 5; + const int FPDF_FORMFIELD_TEXTFIELD = 6; + const int FPDF_FORMFIELD_SIGNATURE = 7; + const int FPDF_FORMFIELD_COUNT = 8; + const int FPDF_ANNOT_UNKNOWN = 0; + const int FPDF_ANNOT_TEXT = 1; + const int FPDF_ANNOT_LINK = 2; + const int FPDF_ANNOT_FREETEXT = 3; + const int FPDF_ANNOT_LINE = 4; + const int FPDF_ANNOT_SQUARE = 5; + const int FPDF_ANNOT_CIRCLE = 6; + const int FPDF_ANNOT_POLYGON = 7; + const int FPDF_ANNOT_POLYLINE = 8; + const int FPDF_ANNOT_HIGHLIGHT = 9; + const int FPDF_ANNOT_UNDERLINE = 10; + const int FPDF_ANNOT_SQUIGGLY = 11; + const int FPDF_ANNOT_STRIKEOUT = 12; + const int FPDF_ANNOT_STAMP = 13; + const int FPDF_ANNOT_CARET = 14; + const int FPDF_ANNOT_INK = 15; + const int FPDF_ANNOT_POPUP = 16; + const int FPDF_ANNOT_FILEATTACHMENT = 17; + const int FPDF_ANNOT_SOUND = 18; + const int FPDF_ANNOT_MOVIE = 19; + const int FPDF_ANNOT_WIDGET = 20; + const int FPDF_ANNOT_SCREEN = 21; + const int FPDF_ANNOT_PRINTERMARK = 22; + const int FPDF_ANNOT_TRAPNET = 23; + const int FPDF_ANNOT_WATERMARK = 24; + const int FPDF_ANNOT_THREED = 25; + const int FPDF_ANNOT_RICHMEDIA = 26; + const int FPDF_ANNOT_XFAWIDGET = 27; + const int FPDF_ANNOT_REDACT = 28; + const int FPDF_ANNOT_FLAG_NONE = 0; + const int FPDF_ANNOT_FLAG_INVISIBLE = 1; + const int FPDF_ANNOT_FLAG_HIDDEN = 2; + const int FPDF_ANNOT_FLAG_PRINT = 4; + const int FPDF_ANNOT_FLAG_NOZOOM = 8; + const int FPDF_ANNOT_FLAG_NOROTATE = 16; + const int FPDF_ANNOT_FLAG_NOVIEW = 32; + const int FPDF_ANNOT_FLAG_READONLY = 64; + const int FPDF_ANNOT_FLAG_LOCKED = 128; + const int FPDF_ANNOT_FLAG_TOGGLENOVIEW = 256; + const int FPDF_ANNOT_APPEARANCEMODE_NORMAL = 0; + const int FPDF_ANNOT_APPEARANCEMODE_ROLLOVER = 1; + const int FPDF_ANNOT_APPEARANCEMODE_DOWN = 2; + const int FPDF_ANNOT_APPEARANCEMODE_COUNT = 3; + const int FPDF_FORMFLAG_NONE = 0; + const int FPDF_FORMFLAG_READONLY = 1; + const int FPDF_FORMFLAG_REQUIRED = 2; + const int FPDF_FORMFLAG_NOEXPORT = 4; + const int FPDF_FORMFLAG_TEXT_MULTILINE = 4096; + const int FPDF_FORMFLAG_TEXT_PASSWORD = 8192; + const int FPDF_FORMFLAG_CHOICE_COMBO = 131072; + const int FPDF_FORMFLAG_CHOICE_EDIT = 262144; + const int FPDF_FORMFLAG_CHOICE_MULTI_SELECT = 2097152; + const int FPDF_ANNOT_AACTION_KEY_STROKE = 12; + const int FPDF_ANNOT_AACTION_FORMAT = 13; + const int FPDF_ANNOT_AACTION_VALIDATE = 14; + const int FPDF_ANNOT_AACTION_CALCULATE = 15; + const int FPDF_MATCHCASE = 1; + const int FPDF_MATCHWHOLEWORD = 2; + const int FPDF_CONSECUTIVE = 4; + const int PDFACTION_UNSUPPORTED = 0; + const int PDFACTION_GOTO = 1; + const int PDFACTION_REMOTEGOTO = 2; + const int PDFACTION_URI = 3; + const int PDFACTION_LAUNCH = 4; + const int PDFACTION_EMBEDDEDGOTO = 5; + const int PDFDEST_VIEW_UNKNOWN_MODE = 0; + const int PDFDEST_VIEW_XYZ = 1; + const int PDFDEST_VIEW_FIT = 2; + const int PDFDEST_VIEW_FITH = 3; + const int PDFDEST_VIEW_FITV = 4; + const int PDFDEST_VIEW_FITR = 5; + const int PDFDEST_VIEW_FITB = 6; + const int PDFDEST_VIEW_FITBH = 7; + const int PDFDEST_VIEW_FITBV = 8; + const int FPDF_COLORSPACE_UNKNOWN = 0; + const int FPDF_COLORSPACE_DEVICEGRAY = 1; + const int FPDF_COLORSPACE_DEVICERGB = 2; + const int FPDF_COLORSPACE_DEVICECMYK = 3; + const int FPDF_COLORSPACE_CALGRAY = 4; + const int FPDF_COLORSPACE_CALRGB = 5; + const int FPDF_COLORSPACE_LAB = 6; + const int FPDF_COLORSPACE_ICCBASED = 7; + const int FPDF_COLORSPACE_SEPARATION = 8; + const int FPDF_COLORSPACE_DEVICEN = 9; + const int FPDF_COLORSPACE_INDEXED = 10; + const int FPDF_COLORSPACE_PATTERN = 11; + const int FPDF_PAGEOBJ_UNKNOWN = 0; + const int FPDF_PAGEOBJ_TEXT = 1; + const int FPDF_PAGEOBJ_PATH = 2; + const int FPDF_PAGEOBJ_IMAGE = 3; + const int FPDF_PAGEOBJ_SHADING = 4; + const int FPDF_PAGEOBJ_FORM = 5; + const int FPDF_SEGMENT_UNKNOWN = -1; + const int FPDF_SEGMENT_LINETO = 0; + const int FPDF_SEGMENT_BEZIERTO = 1; + const int FPDF_SEGMENT_MOVETO = 2; + const int FPDF_FILLMODE_NONE = 0; + const int FPDF_FILLMODE_ALTERNATE = 1; + const int FPDF_FILLMODE_WINDING = 2; + const int FPDF_FONT_TYPE1 = 1; + const int FPDF_FONT_TRUETYPE = 2; + const int FPDF_LINECAP_BUTT = 0; + const int FPDF_LINECAP_ROUND = 1; + const int FPDF_LINECAP_PROJECTING_SQUARE = 2; + const int FPDF_LINEJOIN_MITER = 0; + const int FPDF_LINEJOIN_ROUND = 1; + const int FPDF_LINEJOIN_BEVEL = 2; + const int FPDF_PRINTMODE_EMF = 0; + const int FPDF_PRINTMODE_TEXTONLY = 1; + const int FPDF_PRINTMODE_POSTSCRIPT2 = 2; + const int FPDF_PRINTMODE_POSTSCRIPT3 = 3; + const int FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4; + const int FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5; + const int FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6; + const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; + const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; + + +const int FXFONT_ANSI_CHARSET = 0; + + +const int FXFONT_DEFAULT_CHARSET = 1; + + +const int FXFONT_SYMBOL_CHARSET = 2; + + +const int FXFONT_SHIFTJIS_CHARSET = 128; + + +const int FXFONT_HANGEUL_CHARSET = 129; + + +const int FXFONT_GB2312_CHARSET = 134; + + +const int FXFONT_CHINESEBIG5_CHARSET = 136; + + +const int FXFONT_GREEK_CHARSET = 161; + + +const int FXFONT_VIETNAMESE_CHARSET = 163; + + +const int FXFONT_HEBREW_CHARSET = 177; + + +const int FXFONT_ARABIC_CHARSET = 178; + + +const int FXFONT_CYRILLIC_CHARSET = 204; + + +const int FXFONT_THAI_CHARSET = 222; + + +const int FXFONT_EASTERNEUROPEAN_CHARSET = 238; + + +const int FXFONT_FF_FIXEDPITCH = 1; + + +const int FXFONT_FF_ROMAN = 16; + + +const int FXFONT_FF_SCRIPT = 64; + + +const int FXFONT_FW_NORMAL = 400; + + +const int FXFONT_FW_BOLD = 700; + diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 7709bd3d..78409978 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -27,9 +27,10 @@ dev_dependencies: lints: ^5.0.0 test: ^1.24.0 -# To generate the bindings, firstly you must build example/viewer on x64 linux and +# To generate the bindings, firstly you must run the test once to download PDFium headers: +# dart run test # then run the following command: -# dart run ffigen +# dart run ffigen ffigen: output: bindings: "lib/src/native/pdfium_bindings.dart" @@ -41,6 +42,7 @@ ffigen: - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_edit.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_formfill.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_sysfontinfo.h" include-directives: - "/tmp/pdfrx.cache/pdfium/linux-x64/include/**" preamble: | From f6f982a5dc8009b9f6c575de7293c3f709a148b2 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Fri, 5 Sep 2025 03:37:15 +0900 Subject: [PATCH 290/663] #456: experimental missing font support on native platforms --- .vscode/settings.json | 4 +- packages/pdfrx/example/viewer/lib/main.dart | 25 +- .../example/viewer/lib/noto_google_fonts.dart | 663 ++++++++++++++++++ packages/pdfrx/example/viewer/pubspec.yaml | 1 + .../lib/src/native/pdfrx_pdfium.dart | 154 +++- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 10 + 6 files changed, 846 insertions(+), 11 deletions(-) create mode 100644 packages/pdfrx/example/viewer/lib/noto_google_fonts.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d91cd1b..a297c1e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -130,6 +130,7 @@ "newfstatat", "NOEXPORT", "NOROTATE", + "noto", "NOVIEW", "NOZOOM", "NULLOBJ", @@ -213,7 +214,8 @@ "XFAWIDGET", "xobject", "YESNO", - "YESNOCANCEL" + "YESNOCANCEL", + "ZapfDingbats" ], "editor.codeActionsOnSave": { "source.fixAll": "always", diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 505bf4aa..137af90d 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -3,10 +3,12 @@ import 'dart:math'; import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; import 'package:pdfrx/pdfrx.dart'; import 'package:url_launcher/url_launcher.dart'; import 'markers_view.dart'; +import 'noto_google_fonts.dart'; import 'outline_view.dart'; import 'password_dialog.dart'; import 'search_view.dart'; @@ -417,7 +419,28 @@ class _MainPageState extends State with WidgetsBindingObserver { controller.requestFocus(); controller.document.events.listen((event) { if (event is PdfDocumentMissingFontsEvent) { - debugPrint('Missing fonts: ${event.missingFonts.map((f) => f.face).join(', ')}'); + Future.microtask(() async { + // NOTE: This is just an example of downloading missing fonts from Google Fonts. + // In real-world use cases, you might want to have a more sophisticated + // mechanism to manage the fonts. + debugPrint('Missing fonts: ${event.missingFonts.map((f) => f.toString()).join(', ')}'); + int count = 0; + for (final font in event.missingFonts) { + final gf = getGoogleFontsUriFromFontQuery(font); + if (gf != null) { + debugPrint('Downloading font: ${gf.faceName}, weight: ${gf.weight}'); + final downloaded = (await http.get(gf.uri)).bodyBytes; + debugPrint(' Downloaded ${downloaded.length} bytes'); + await PdfrxEntryFunctions.instance.addFontData(face: font.face, data: downloaded); + count++; + } + } + if (count > 0) { + await PdfrxEntryFunctions.instance.reloadFonts(); + await controller.documentRef.resolveListenable().load(forceReload: true); + //controller.forceRepaintAllPageImages(); + } + }); } }); }, diff --git a/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart new file mode 100644 index 00000000..76dc2e03 --- /dev/null +++ b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart @@ -0,0 +1,663 @@ +// The code is based on the following code from Google Fonts: +// https://github.com/material-foundation/flutter-packages/blob/main/packages/google_fonts/lib/src/google_fonts_parts/part_n.dart +// +// Noto Sans/Serif is licensed under the SIL Open Font License, Version 1.1 . +// https://fonts.google.com/noto/specimen/Noto+Sans/license + +import 'package:pdfrx/pdfrx.dart'; + +class GoogleFontsFile { + GoogleFontsFile(this.faceName, this.weight, this.expectedFileHash, this.expectedLength); + final String faceName; + final int weight; + final String expectedFileHash; + final int expectedLength; + + Uri get uri => Uri.parse('https://fonts.gstatic.com/s/a/$expectedFileHash.ttf'); +} + +GoogleFontsFile? _getNearestWeight(Map fonts, int weight) { + final weights = fonts.keys.toList(); + weights.sort((a, b) => (a - weight).abs().compareTo((b - weight).abs())); + return fonts[weights.first]; +} + +GoogleFontsFile? getGoogleFontsUriFromFontQuery(PdfFontQuery query) { + final fontTable = switch (query.isRoman) { + true => switch (query.charset) { + PdfFontCharset.gb2312 => _notoSerifSc, + PdfFontCharset.chineseBig5 => _notoSerifTc, + PdfFontCharset.shiftJis => _notoSerifJp, + PdfFontCharset.hangul => _notoSerifKr, + PdfFontCharset.thai => _notoSerifThai, + PdfFontCharset.hebrew => _notoSerifHebrew, + PdfFontCharset.arabic => _notoNaskhArabic, + PdfFontCharset.arabic => _notoSansArabic, + PdfFontCharset.greek || + PdfFontCharset.vietnamese || + PdfFontCharset.cyrillic || + PdfFontCharset.easternEuropean => query.isItalic ? _notoSerifItalic : _notoSerif, + PdfFontCharset.ansi || PdfFontCharset.default_ || PdfFontCharset.symbol => null, + }, + false => switch (query.charset) { + PdfFontCharset.gb2312 => _notoSansSc, + PdfFontCharset.chineseBig5 => _notoSansTc, + PdfFontCharset.shiftJis => _notoSansJp, + PdfFontCharset.hangul => _notoSansKr, + PdfFontCharset.thai => _notoSansThai, + PdfFontCharset.hebrew => _notoSansHebrew, + PdfFontCharset.arabic => _notoSansArabic, + PdfFontCharset.greek || + PdfFontCharset.vietnamese || + PdfFontCharset.cyrillic || + PdfFontCharset.easternEuropean => query.isItalic ? _notoSansItalic : _notoSans, + PdfFontCharset.ansi || PdfFontCharset.default_ || PdfFontCharset.symbol => null, + }, + }; + if (fontTable == null) return null; + return _getNearestWeight(fontTable, query.weight); +} + +/// Noto Sans (Latin, Greek, Cyrillic, Vietnamese, and more) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans +final _notoSans = { + 100: GoogleFontsFile('NotoSans', 100, 'bc6ceb177561b27cfb9123c0dd372a54774cb6bcebe4ce18c12706bbb7ee902c', 523812), + 200: GoogleFontsFile('NotoSans', 200, '807ad06b65dbbaf657e4a7dcb6d2b0734c8831cd21a1f9172387ad0411cc396f', 524708), + 300: GoogleFontsFile('NotoSans', 300, '4e3e9bb50c6e6ade7e4a491bf0033d6b6ec3326a2621834201e735691cec4968', 524492), + 400: GoogleFontsFile('NotoSans', 400, '725edd9b341324f91a3859e24824c455d43c31be72ca6e710acd0f95920d61ee', 523940), + 500: GoogleFontsFile('NotoSans', 500, 'a77c7c7a4d75c23c5e68bcff3d44f71eb1ec0f80fe245457053ea43a4ce61bd4', 524252), + 600: GoogleFontsFile('NotoSans', 600, 'fc5b5ba2d400f44b0686c46db557e6b8067a97ade7337f14f823f524675c038c', 524444), + 700: GoogleFontsFile('NotoSans', 700, '222685dcf83610e3e88a0ecd4c602efde7a7b832832502649bfe2dcf1aa0bf15', 523772), + 800: GoogleFontsFile('NotoSans', 800, 'c6e87f6834db59a2a64ce43dce2fdc1aa3441f2a23afb0bfd667621403ed688c', 524672), + 900: GoogleFontsFile('NotoSans', 900, '7ead4fec44c3271cf7dc5d9f74795eb05fa9fb3cedc7bde3232eb10573d5f6cd', 524708), +}; + +/// Noto Sans Italic (Latin, Greek, Cyrillic, Vietnamese, and more) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans +final _notoSansItalic = { + 100: GoogleFontsFile( + 'NotoSans-Italic', + 100, + '8b32677abe42a47cdade4998d4124a3e1b44efa656c5badf27de546768c82f0d', + 541316, + ), + 200: GoogleFontsFile( + 'NotoSans-Italic', + 200, + 'd64c291d542bb1211538aa1448a7f6bbaca4dbd170e78b8b8242be5c9ff28959', + 541752, + ), + 300: GoogleFontsFile( + 'NotoSans-Italic', + 300, + '3a902e6bbe1ffba43428cb2981f1185ef529505836c311af5f6e5690bf9b44c8', + 541688, + ), + 400: GoogleFontsFile( + 'NotoSans-Italic', + 400, + '3d23478749575c0febb6169fc3dba6cb8cdb4202e8fb47ae1867c71a21792295', + 539972, + ), + 500: GoogleFontsFile( + 'NotoSans-Italic', + 500, + '085819a42ab67069f29329ae066ff8206a4b518bf6496dbf1193284f891fdbd1', + 540456, + ), + 600: GoogleFontsFile( + 'NotoSans-Italic', + 600, + 'ecb66a73df07fac622c73fdc0e4972bd51f50165367807433d7fc620378f9577', + 540608, + ), + 700: GoogleFontsFile( + 'NotoSans-Italic', + 700, + 'f72d0f7c9c7279b2762017fbafa2bcd9aaccdf7a79b8cf686f874e2eeb0e51ce', + 540016, + ), + 800: GoogleFontsFile( + 'NotoSans-Italic', + 800, + '0ef3e94eb6875007204e41604898141fa5104f7e20b87cb5640509a8f10430b5', + 540812, + ), + 900: GoogleFontsFile( + 'NotoSans-Italic', + 900, + 'b0e0148ef878a4ca6a295b6b56b1bfb4773400ff8ee0a31a1338285725dd514f', + 540396, + ), +}; + +/// Noto Sans SC (Simplified Chinese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+SC +final _notoSansSc = { + 100: GoogleFontsFile('NotoSansSC', 100, 'f1b8c2a287d23095abd470376c60519c9ff650ae8744b82bf76434ac5438982a', 10538940), + 200: GoogleFontsFile('NotoSansSC', 200, 'cba9bb657b61103aeb3cd0f360e8d3958c66febf59fbf58a4762f61e52015d36', 10544320), + 300: GoogleFontsFile('NotoSansSC', 300, '4cdbb86a1d6eca92c7bcaa0c759593bc2600a153600532584a8016c24eaca56c', 10545812), + 400: GoogleFontsFile('NotoSansSC', 400, 'eacedb2999b6cd30457f3820f277842f0dfbb28152a246fca8161779a8945425', 10540772), + 500: GoogleFontsFile('NotoSansSC', 500, '5383032c8e54fc5fa09773ce16483f64d9cdb7d1f8e87073a556051eb60f8529', 10533968), + 600: GoogleFontsFile('NotoSansSC', 600, '85c00dac0627c2c0184c24669735fad5adbb4f150bcb320c05620d46ed086381', 10530476), + 700: GoogleFontsFile('NotoSansSC', 700, 'a7a29b6d611205bb39b9a1a5c2be5a48416fbcbcfd7e6de98976e73ecb48720b', 10530536), + 800: GoogleFontsFile('NotoSansSC', 800, '038de57b1dc5f6428317a8b0fc11984789c25f49a9c24d47d33d2c03e3491d28', 10525556), + 900: GoogleFontsFile('NotoSansSC', 900, '501582a5e956ab1f4d9f9b2d683cf1646463eea291b21f928419da5e0c5a26eb', 10521812), +}; + +/// Noto Sans TC (Traditional Chinese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+TC +final _notoSansTc = { + 100: GoogleFontsFile('NotoSansTC', 100, '53debc0456f3a7d4bdb00e14704fc29ea129d38bd8a9f6565cf656ddc27abb91', 7089040), + 200: GoogleFontsFile('NotoSansTC', 200, '5ef06c341be841ab9e166a9cc7ebc0e39cfe695da81d819672f3d14b3fca56a8', 7092508), + 300: GoogleFontsFile('NotoSansTC', 300, '9e50ec0d5779016c848855daa73f8d866ef323f0431d5770f53b60a1506f1c4a', 7092872), + 400: GoogleFontsFile('NotoSansTC', 400, 'b4f9cfdee95b77d72fe945347c0b7457f1ffc0d5d05eaf6ff688e60a86067c95', 7090948), + 500: GoogleFontsFile('NotoSansTC', 500, '2011294f66de6692639ee00a9e74d67bc9134f251100feb5448ab6322a4a2a75', 7087068), + 600: GoogleFontsFile('NotoSansTC', 600, '440471acbbc2a3b33bf11befde184b2cafe5b0fcde243e2b832357044baa4aa1', 7084432), + 700: GoogleFontsFile('NotoSansTC', 700, '22779de66d31884014b0530df89e69d596018a486a84a57994209dff1dcb97cf', 7085728), + 800: GoogleFontsFile('NotoSansTC', 800, 'f5e8e3e746319570b0979bfa3a90b6ec6a84ec38fe9e41c45a395724c31db7b4', 7082400), + 900: GoogleFontsFile('NotoSansTC', 900, '2b1ab3d7db76aa94006fa19dc38b61e93578833d2e3f268a0a3b0b1321852af6', 7079980), +}; + +/// Noto Sans JP (Japanese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+JP +final _notoSansJp = { + 100: GoogleFontsFile('NotoSansJP', 100, '78a1fa1d16c437fe5d97df787782b6098a750350b5913b9f80089dc81f512417', 5706804), + 200: GoogleFontsFile('NotoSansJP', 200, 'c0532e4abf0ca438ea0e56749a3106a5badb2f10a89c8ba217b43dae4ec6e590', 5708144), + 300: GoogleFontsFile('NotoSansJP', 300, '64f10b3b9e06c99b76b16e1441174fba6adf994fcd6b8036cef2fbfa38535a84', 5707688), + 400: GoogleFontsFile('NotoSansJP', 400, '209c70f533554d512ef0a417b70dfe2997aeec080d2fe41695c55b361643f9ba', 5703748), + 500: GoogleFontsFile('NotoSansJP', 500, 'c5233cdc5a2901be5503f0d95ff48b4b5170afff6a39f95a076520cb73f17860', 5700280), + 600: GoogleFontsFile('NotoSansJP', 600, '852ad9268beb7d467374ec5ff0d416a22102c52d984ec21913f6d886409b85c4', 5697576), + 700: GoogleFontsFile('NotoSansJP', 700, 'eee16e4913b766be0eb7b9a02cd6ec3daf27292ca0ddf194cae01279aac1c9d0', 5698756), + 800: GoogleFontsFile('NotoSansJP', 800, '68d3c7136501158a6cf7d15c1c13e4af995aa164e34d1c250c3eef259cda74dd', 5696016), + 900: GoogleFontsFile('NotoSansJP', 900, '6ff9b55a270592e78670f98a2f866f621d05b6e1c3a18a14301da455a36f6561', 5693644), +}; + +/// Noto Sans KR (Korean) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+KR +final _notoSansKr = { + 100: GoogleFontsFile('NotoSansKR', 100, '302d55d333b15473a5b4909964ad17885a53cb41c34e3b434471f22ea55faea1', 6177560), + 200: GoogleFontsFile('NotoSansKR', 200, '1b03f89eccef4f2931d49db437091de1b15ced57186990749350a2cec1f4feb8', 6177360), + 300: GoogleFontsFile('NotoSansKR', 300, 'f8ed45f767a44de83d969ea276c3b4419c41a291d8460c32379e95930eae878e', 6175264), + 400: GoogleFontsFile('NotoSansKR', 400, '82547e25c2011910dae0116ba57d3ab9abd63f4865405677bd6f79c64487ae31', 6169044), + 500: GoogleFontsFile('NotoSansKR', 500, 'f67bdb1581dbb91b1ce92bdf89a0f3a4ca2545d821d204b17c5443bcda6b3677', 6166588), + 600: GoogleFontsFile('NotoSansKR', 600, '922e269443119b1ffa72c9631d4c7dcb365ab29ba1587b96e715d29c9a66d1b4', 6165240), + 700: GoogleFontsFile('NotoSansKR', 700, 'ed93ef6659b28599d47e40d020b9f55d18a01d94fdd43c9c171e44a66ddc1d66', 6165036), + 800: GoogleFontsFile('NotoSansKR', 800, 'e7088e3dfcc13f400aa9433a4042fce57b3dbe41038040073e9b5909a9390048', 6164096), + 900: GoogleFontsFile('NotoSansKR', 900, '14c5cfe30331277d21fa0086e66e11a7c414d4a5ce403229bdb0f384d3376888', 6163040), +}; + +/// Noto Sans Thai (Thai) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+Thai +final _notoSansThai = { + 100: GoogleFontsFile('NotoSansThai', 100, '77e781c33ba38f872109864fcf2f7bab58c7f85d73baf213fbcf7df2a7ea6b3f', 45684), + 200: GoogleFontsFile('NotoSansThai', 200, 'c8dc3faea7ead6f573771d50e3d2cc84b49431295bde43af0bd5f6356a628f72', 45792), + 300: GoogleFontsFile('NotoSansThai', 300, '9a1ba366a64ee23d486f48f0a276d75baef6432da4db5efb92f7c9b35dd5198d', 45728), + 400: GoogleFontsFile('NotoSansThai', 400, '5f71b18a03432951e2bce4e74497752958bd8c9976be06201af5390d47922be3', 45636), + 500: GoogleFontsFile('NotoSansThai', 500, '4c82507facc222df924a0272cda2bfdddc629de12b5684816aea0eb5851a61a7', 45720), + 600: GoogleFontsFile('NotoSansThai', 600, 'e81c6d83f8a625690b1ecc5de4f6b7b66a4d2ee9cbaf5b4f9ede73359c1db064', 45732), + 700: GoogleFontsFile('NotoSansThai', 700, '81bba197f8c779233db14166526e226f68e60cd9e33f2046b80f8075158cb433', 45640), + 800: GoogleFontsFile('NotoSansThai', 800, '7ae7ca1dae7a3df8e839ae08364e14e8e015337bab7dc2842abfc3315e477404', 45704), + 900: GoogleFontsFile('NotoSansThai', 900, '689d439d52c795a225c7fe4657a1072151407a86cc2910a51280337b8b1f57a3', 45584), +}; + +/// Noto Sans Hebrew (Hebrew) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+Hebrew +final _notoSansHebrew = { + 100: GoogleFontsFile( + 'NotoSansHebrew', + 100, + '724a57dd8003a31bad4428c37d10b2777cec5b5bfd20c6ed1be44d265989b599', + 46472, + ), + 200: GoogleFontsFile( + 'NotoSansHebrew', + 200, + 'ee40f0088e4408bd36620fd1fa7290fa145bf8964d2368aa181794e5b17ad819', + 46532, + ), + 300: GoogleFontsFile( + 'NotoSansHebrew', + 300, + '5686c511d470cd4e52afd09f7e1f004efe33549ff0d38cb23fe3621de1969cc9', + 46488, + ), + 400: GoogleFontsFile( + 'NotoSansHebrew', + 400, + '95e23e29b8422a9a461300a8b8e97630d8a2b8de319a9decbf53dc51e880ac41', + 46476, + ), + 500: GoogleFontsFile( + 'NotoSansHebrew', + 500, + '7fa6696c1d7d0d7f4ac63f1c5dafdc52bf0035a3d5b63a181b58e5515af338f6', + 46652, + ), + 600: GoogleFontsFile( + 'NotoSansHebrew', + 600, + 'cc6deb0701c8034e8ca4eb52ad13770cbe6e494a2bedb91238ad5cb7c591f0ae', + 46648, + ), + 700: GoogleFontsFile( + 'NotoSansHebrew', + 700, + 'fbb2c56fd00f54b81ecb4da7033e1729f1c3fd2b14f19a15db35d3f3dd5aadf9', + 46440, + ), + 800: GoogleFontsFile( + 'NotoSansHebrew', + 800, + '0fb06ecce97f71320c91adf9be6369c8c12979ac65d229fa7fb123f2476726a1', + 46472, + ), + 900: GoogleFontsFile( + 'NotoSansHebrew', + 900, + '8638b2f26a6e16bacf0b34c34d5b8a62efa912a3a90bfb93f0eb25a7b3f8705e', + 46372, + ), +}; + +/// Noto Sans Arabic (Arabic) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Sans+Arabic +final _notoSansArabic = { + 100: GoogleFontsFile( + 'NotoSansArabic', + 100, + '6cf2614bfc2885011fd9d47b2bcc7e5a576b3e35d379d4301d8247683a680245', + 162152, + ), + 200: GoogleFontsFile( + 'NotoSansArabic', + 200, + 'cecf509869241973813ea04cf6c437ff1e571722fcd54e329880185baf750b19', + 162412, + ), + 300: GoogleFontsFile( + 'NotoSansArabic', + 300, + 'c5219bd6425340861eb21a05d40d54da31875cb534dd128d5799b6b83674b9d1', + 162324, + ), + 400: GoogleFontsFile( + 'NotoSansArabic', + 400, + '25c2bf5bc8222800e2d8887c3af985f61d5803177bd92b355cb8bffa09c48862', + 161592, + ), + 500: GoogleFontsFile( + 'NotoSansArabic', + 500, + '47f226b1505792703ac273600be1dbce8c3cc83cd1981b3db5ef15e0f09bdd8a', + 162156, + ), + 600: GoogleFontsFile( + 'NotoSansArabic', + 600, + '332c2d597ed4d1f4d1ed84ed493a341cf81515f5e4d392789a4764e084ff4f1f', + 162512, + ), + 700: GoogleFontsFile( + 'NotoSansArabic', + 700, + '9235e0a73b449ef9a790df7bf5933644ede59c06099f7e96d8cda26c999641cd', + 162268, + ), + 800: GoogleFontsFile( + 'NotoSansArabic', + 800, + '3614725eeafdb55d8eeabb81fb6fb294a807327fa01c2230b4e074f56922d0b5', + 162896, + ), + 900: GoogleFontsFile( + 'NotoSansArabic', + 900, + 'cdbb85b809be063fb065f55b7226dc5161f4804795be56e007d7d3ce70208446', + 162668, + ), +}; + +/// Noto Serif (Serif) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif +final _notoSerif = { + 100: GoogleFontsFile('NotoSerif', 100, '7fd15a02691cfb99c193341bbb082778b1f3ca27e15fdcb7076816591994b7c7', 452700), + 200: GoogleFontsFile('NotoSerif', 200, '9446cf19cd57af964054d0afd385b76f9dec5e3b927c74a2d955041f97fad39b', 453240), + 300: GoogleFontsFile('NotoSerif', 300, '384650b173fced05061be4249607b7caedbc6ba463724075c3ede879ee78d456', 453240), + 400: GoogleFontsFile('NotoSerif', 400, 'b7373b9f9dab0875961c5d214edef00a9384ab593cde30c6462d7b29935ef8b2', 452276), + 500: GoogleFontsFile('NotoSerif', 500, '105a9e9c9bb80bcf8f8c408ed3473f1d9baad881686ea4602ecebebf22bbed50', 453160), + 600: GoogleFontsFile('NotoSerif', 600, '30257a49c70dd2e8abe6cc6a904df863dbc6f9ccf85f4b28a5c858aaa258eab6', 453104), + 700: GoogleFontsFile('NotoSerif', 700, 'dad0f53be4da04bfb608c81cfb72441fba851b336b2bd867592698cfaa2a0c3c', 452576), + 800: GoogleFontsFile('NotoSerif', 800, '12c5c47e6810fc5ea4291b6948adfba87c366eb3c081d66c99f989efd2b55975', 454040), + 900: GoogleFontsFile('NotoSerif', 900, '16f59df53d64f8a896e3dcacadc5b78e8b5fb503318bf01d9ddbe00e90dcceea', 453924), +}; + +/// Noto Serif (Italic) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif +final _notoSerifItalic = { + 100: GoogleFontsFile( + 'NotoSerif-Italic', + 100, + '98c7bc89a0eca32e9045076dd4557dadf866820b3faf5dffe946614cd59bdbb8', + 479008, + ), + 200: GoogleFontsFile( + 'NotoSerif-Italic', + 200, + '24a3e4603729024047e3af2a77e85fd3064c604b193add5b5ecb48fdeb630f4e', + 479532, + ), + 300: GoogleFontsFile( + 'NotoSerif-Italic', + 300, + '940fb65bf51f2a2306bc12343c9661aa4309634ea15bf2b1a0c8da2d23e9e9f3', + 479180, + ), + 400: GoogleFontsFile( + 'NotoSerif-Italic', + 400, + '65aae32ed0a63e3f6ce0fcde1cd5d04cd179699f7e1fef0d36a24948a3b17ce3', + 477448, + ), + 500: GoogleFontsFile( + 'NotoSerif-Italic', + 500, + '322ec18ea04041aabc9f9b3529ff23e7d4e4e18d4330d39d4d422058c66ddded', + 478256, + ), + 600: GoogleFontsFile( + 'NotoSerif-Italic', + 600, + '77e9996939afbc0723270879a0754de4374091b9b856f19790c098292992859c', + 478316, + ), + 700: GoogleFontsFile( + 'NotoSerif-Italic', + 700, + 'b4cf981f0033c2e3d72585d84de3980bdfb87eaa4fe1d95392025ecd0fe0b83c', + 477644, + ), + 800: GoogleFontsFile( + 'NotoSerif-Italic', + 800, + 'a9d0052ceaeea5a1962b7b1a23d995e39dd299ae59cfc288d3e9a68f1bf002e7', + 478924, + ), + 900: GoogleFontsFile( + 'NotoSerif-Italic', + 900, + '99f429bfa3aea82cc9620a6242992534d8c7b10f75d0ec7ca15e1790ca315de7', + 478760, + ), +}; + +/// Noto Serif SC (Simplified Chinese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif+SC +final _notoSerifSc = { + 200: GoogleFontsFile( + 'NotoSerifSC', + 200, + '288d1ce3098084328c59b62c0ee3ae79a41f2c29eef8c0b2ba9384c2c18f41ed', + 14778664, + ), + 300: GoogleFontsFile( + 'NotoSerifSC', + 300, + '7725ad7c403a2d10fd0fe29ae5d50445057a3559c348d67f129d0c9b8521bce8', + 14780440, + ), + 400: GoogleFontsFile( + 'NotoSerifSC', + 400, + 'a17a0dbf1d43a65b75ebd0222a6aa4e6a6fb68f8ecc982c05c9584717ed3567f', + 14781184, + ), + 500: GoogleFontsFile( + 'NotoSerifSC', + 500, + '6a74a2bb8923bef7e34b0436f0edd9ab03e3369fdeabb41807b820e6127fa4e6', + 14781200, + ), + 600: GoogleFontsFile( + 'NotoSerifSC', + 600, + 'ebbd878444e9c226709d1259352d9d821849ee8105b5191d44101889603e154b', + 14780624, + ), + 700: GoogleFontsFile( + 'NotoSerifSC', + 700, + 'bf6e98a81314a396a59661bf892ac872a9338c1b252845bec5659af39ca2304f', + 14780140, + ), + 800: GoogleFontsFile( + 'NotoSerifSC', + 800, + '13be96afae56fd632bbf58ec62eb7b295af62fb6c7b3e16eff73748f0e04daf9', + 14780920, + ), + 900: GoogleFontsFile( + 'NotoSerifSC', + 900, + 'e50e6bffa405fcb45583a0f40f120e1c158b83b4a17fae29bbe2359d36a5b831', + 14780544, + ), +}; + +/// Noto Serif TC (Traditional Chinese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif+TC +final _notoSerifTc = { + 200: GoogleFontsFile('NotoSerifTC', 200, '7d21dcf9bae351366c21de7a554917af318fdf928b5f17a820b547584ebd3b03', 9926428), + 300: GoogleFontsFile('NotoSerifTC', 300, '2816a6528f03c7c7364da893e52ee3247622aa67efd5b96fac5c800af0cf7cfd', 9928912), + 400: GoogleFontsFile('NotoSerifTC', 400, '33247894b46a436114cb173a756d5f5a698f485c9cd88427a50c72301a81282f', 9930576), + 500: GoogleFontsFile('NotoSerifTC', 500, '3b3fa68244c613cee26f10dae75f702d5c61908973a763f2a87a4d3c9c14298a', 9932116), + 600: GoogleFontsFile('NotoSerifTC', 600, '1251e0304fa33bbf5c44cb361a0a969f998af22377a7b8e0bd9e862cf6c45d76', 9932824), + 700: GoogleFontsFile('NotoSerifTC', 700, 'db3ce7ba3443c00e9ff3ba87ebc51838598cb44bc25ea946480f2aebd290ad0e', 9933360), + 800: GoogleFontsFile('NotoSerifTC', 800, '96de55c76632a173cbb6ec9224dbd3040fa75234fadee1d7d03b081debbbdd37', 9933988), + 900: GoogleFontsFile('NotoSerifTC', 900, '2b58e95c7c7a35311152cb28da071dd10a156c30b1cfde117bac68cdca4984ea', 9934072), +}; + +/// Noto Serif JP (Japanese) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif+JP +final _notoSerifJp = { + 200: GoogleFontsFile('NotoSerifJP', 200, '320e653bbc19e207ade23a39d4896aee4424d85e213f6c3f05584d1dc358eaf3', 7999636), + 300: GoogleFontsFile('NotoSerifJP', 300, 'b01bd95435bede8e6e55adde97d61d85cf3cad907a8e5e21df3fdee97436c972', 8000752), + 400: GoogleFontsFile('NotoSerifJP', 400, '100644e0b414be1c2b1f524e63cb888a8ca2a29c59bc685b1d3a1dccdb8bef3d', 8000776), + 500: GoogleFontsFile('NotoSerifJP', 500, '7f2c9f09930f9571d72946c4836178d99966b6e3dae4d0fb6a39d9278a1979e7', 7999616), + 600: GoogleFontsFile('NotoSerifJP', 600, '53bcadccd57b01926f9da05cb4c3edf4a572fe9918d463b16ce2c8e76adcc059', 7997840), + 700: GoogleFontsFile('NotoSerifJP', 700, 'afcb90bae847b37af92ad759d2ed65ab5691eb6f76180a9f3f3eae9121afc30c', 7995008), + 800: GoogleFontsFile('NotoSerifJP', 800, '6341d1d0229059ed23e9f8293d29052cdc869a8a358118109165e8979c395342', 7994148), + 900: GoogleFontsFile('NotoSerifJP', 900, 'cb22da84d7cef667d91b79672b6a6457bcb22c9354ad8e96184a558a1eeb5786', 7992068), +}; + +/// Noto Serif KR (Korean) +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif+KR +final _notoSerifKr = { + 200: GoogleFontsFile( + 'NotoSerifKR', + 200, + '54ba0237db05724a034c17d539fb253d29059dcb908cfc953c93b3e0d9de8197', + 14020456, + ), + 300: GoogleFontsFile( + 'NotoSerifKR', + 300, + 'ae26b0d843cb7966777c3b764139d0de052c62e4bf52e47e24b20da304b17101', + 14029668, + ), + 400: GoogleFontsFile( + 'NotoSerifKR', + 400, + '558c8dac58a96ed9bd55c0e3b605699b9ca87545eaba6e887bbf5c07a4e77e61', + 14032260, + ), + 500: GoogleFontsFile( + 'NotoSerifKR', + 500, + 'f9534728d53d16ffa1e8a1382d95495e5ba8779be7cc7c70d2d40fff283bae93', + 14041584, + ), + 600: GoogleFontsFile( + 'NotoSerifKR', + 600, + 'c571b015c56cee39099f0aaeeece3b81c49a8b206dd2ab577c03ca6bd4e2a7bb', + 14040680, + ), + 700: GoogleFontsFile( + 'NotoSerifKR', + 700, + 'f5397eff043cbe24929663e25ddb03a3b383195c8b877b6a4fcc48ecc8247002', + 14038616, + ), + 800: GoogleFontsFile( + 'NotoSerifKR', + 800, + 'abb4439400202f9efd9863fad31138021b95a579acb4ae98516311da0bbae842', + 14036636, + ), + 900: GoogleFontsFile( + 'NotoSerifKR', + 900, + '17b5842749bdec2f53cb3c0ccbe8292ddf025864e0466fad64ca7b96e9f7be06', + 14031812, + ), +}; + +/// Noto Serif Thai +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Serif+Thai +final _notoSerifThai = { + 100: GoogleFontsFile('NotoSerifThai', 100, '5eb35c0094128d7d01680b8843b2da94cc9dc4da0367bd73d9067287b48cc074', 59812), + 200: GoogleFontsFile('NotoSerifThai', 200, '48d9621d9f86d32d042924a1dca011561a6e12bb6577ecf77737d966721c6f96', 59968), + 300: GoogleFontsFile('NotoSerifThai', 300, 'd7e9e8ab36992509761cfbb52a8ccc910571ef167bd2cf9a15b7e393185aeadf', 59908), + 400: GoogleFontsFile('NotoSerifThai', 400, '3b677be028abaef2960675aa839310cf8b76eb02dd776b005e535ce8fd7b0dba', 59668), + 500: GoogleFontsFile('NotoSerifThai', 500, '269e49f943f4d5e3caebf7d381eca11ec24a3179713e9fc9594664d29f00638b', 59904), + 600: GoogleFontsFile('NotoSerifThai', 600, 'c2f95d912f539a2afb1a4fcaff25b3cfec88ff80bab99abc18e7e2b8a2ed0371', 59844), + 700: GoogleFontsFile('NotoSerifThai', 700, '26cc8f7b7d541cc050522a077448d3069e480d35edbd314748ab819fbce36b12', 59760), + 800: GoogleFontsFile('NotoSerifThai', 800, 'c7bcf386351f299d1a0440e23d14334dd32fcc736451a25721557bb13bf7ee9d', 60072), + 900: GoogleFontsFile('NotoSerifThai', 900, '3700c400ed31b5a182e21b6269e583e7dff8b8e16400504a9979684488574efa', 60004), +}; + +/// Noto Serif Hebrew +/// +/// See: +/// - https://fonts.google.com/specimen/Noto+Serif+Hebrew +final _notoSerifHebrew = { + 100: GoogleFontsFile( + 'NotoSerifHebrew', + 100, + 'd53174aa0c8cd8df260a9004a3007e393160b062d50f775fecd519f057067cbd', + 54652, + ), + 200: GoogleFontsFile( + 'NotoSerifHebrew', + 200, + 'd31e71918ab5ff0f0e030903449509e146010510779991a47d4a063373f14a7c', + 54720, + ), + 300: GoogleFontsFile( + 'NotoSerifHebrew', + 300, + '7017169ff82520c5bf669e4ab770ca0804795609313ce54c8a29b66df36cd20a', + 54804, + ), + 400: GoogleFontsFile( + 'NotoSerifHebrew', + 400, + '001e675f8528148912f3c8b4ce0f2e3d05c7d6ff0cbaa4c415df9301cfeec28e', + 54612, + ), + 500: GoogleFontsFile( + 'NotoSerifHebrew', + 500, + '4927576763b95c2ed87e58dbef8ac565d8054f419a4641d2eb6bb59afd498e6c', + 54704, + ), + 600: GoogleFontsFile( + 'NotoSerifHebrew', + 600, + 'fd86539b46574a35e1898c62c3e30ff092e1b6588a36660bcf1e91845be1e36a', + 54712, + ), + 700: GoogleFontsFile( + 'NotoSerifHebrew', + 700, + 'eb9fd16284df252ac1e4c53c73617a8e027cf66425e197f39c4cc7e9773baf4a', + 54632, + ), + 800: GoogleFontsFile( + 'NotoSerifHebrew', + 800, + 'cdbfc88d81100057725ac72b7b26cc125b718916102f9771adeeb1b8ab890c36', + 54816, + ), + 900: GoogleFontsFile( + 'NotoSerifHebrew', + 900, + 'ec3cf5173830f6e5485ef7f012b9b8dd0603116b32021d000269bf3dd1f18324', + 54744, + ), +}; + +/// Noto Naskh Arabic +/// +/// See: +/// * https://fonts.google.com/specimen/Noto+Naskh+Arabic +final _notoNaskhArabic = { + 400: GoogleFontsFile( + 'NotoNaskhArabic', + 400, + 'a19b33c4365bbd6e3f3ac85864fb134e44358ad188c30a9d67d606685d5261da', + 215356, + ), + 500: GoogleFontsFile( + 'NotoNaskhArabic', + 500, + 'd8639b9c7c51cc662e5cf98ab913988835ca5cfde7fdd6db376c6f39f4ac8ea8', + 215768, + ), + 600: GoogleFontsFile( + 'NotoNaskhArabic', + 600, + '76501d5ae7dea1d55ded66269abc936ece44353e17a70473c64f7072c61d7e89', + 215720, + ), + 700: GoogleFontsFile( + 'NotoNaskhArabic', + 700, + 'bb9d4b9c041d13d8bc2c01fa6c5a4629bb4d19a158eec78a8249420a59418aa4', + 215344, + ), +}; diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 93f717cb..5c76b8fc 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: url_launcher: ^6.3.1 synchronized: ^3.3.1 file_selector: ^1.0.3 + http: ^1.5.0 dev_dependencies: flutter_test: diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index c6830e00..93d74b0d 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -1,5 +1,6 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; +import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; @@ -52,15 +53,16 @@ Future _init() async { _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); - return using((arena) { + await using((arena) { final config = arena.allocate(sizeOf()); config.ref.version = 2; - final fontPaths = [_appLocalFontPath!.path, ...Pdfrx.fontPaths]; + final fontPaths = [?_appLocalFontPath?.path, ...Pdfrx.fontPaths]; if (fontPaths.isNotEmpty) { - final fontPathArray = arena.allocate>(sizeOf>() * (fontPaths.length + 1)); + // NOTE: m_pUserFontPaths must not be freed until FPDF_DestroyLibrary is called; on pdfrx, it's never freed. + final fontPathArray = malloc>(sizeOf>() * (fontPaths.length + 1)); for (int i = 0; i < fontPaths.length; i++) { - fontPathArray[i] = fontPaths[i].toUtf8(arena); + fontPathArray[i] = fontPaths[i].toNativeUtf8().cast(); } fontPathArray[fontPaths.length] = nullptr; config.ref.m_pUserFontPaths = fontPathArray; @@ -74,10 +76,120 @@ Future _init() async { _initialized = true; }); }); + + await _initializeFontEnvironment(); } final backgroundWorker = BackgroundWorker.create(); +/// Stores the fonts that were not found during mapping. +/// NOTE: This is used by [backgroundWorker] and should not be used directly; use [_getAndClearMissingFonts] instead. +final _lastMissingFonts = {}; + +/// MapFont function used by PDFium to map font requests to system fonts. +/// NOTE: This is used by [backgroundWorker] and should not be used directly. +NativeCallable< + Pointer Function( + Pointer, + Int, + pdfium_bindings.FPDF_BOOL, + Int, + Int, + Pointer, + Pointer, + ) +>? +_mapFont; + +/// Setup the system font info in PDFium. +Future _initializeFontEnvironment() async { + await (await backgroundWorker).computeWithArena((arena, params) { + // kBase14FontNames + const fontNamesToIgnore = { + 'Courier': true, + 'Courier-Bold': true, + 'Courier-BoldOblique': true, + 'Courier-Oblique': true, + 'Helvetica': true, + 'Helvetica-Bold': true, + 'Helvetica-BoldOblique': true, + 'Helvetica-Oblique': true, + 'Times-Roman': true, + 'Times-Bold': true, + 'Times-BoldItalic': true, + 'Times-Italic': true, + 'Symbol': true, + 'ZapfDingbats': true, + }; + + final sysFontInfoBuffer = pdfium.FPDF_GetDefaultSystemFontInfo(); + final mapFontOriginal = sysFontInfoBuffer.ref.MapFont + .asFunction< + Pointer Function( + Pointer, + int, + int, + int, + int, + Pointer, + Pointer, + ) + >(); + + _mapFont?.close(); + _mapFont = + NativeCallable< + Pointer Function( + Pointer, + Int, + pdfium_bindings.FPDF_BOOL, + Int, + Int, + Pointer, + Pointer, + ) + >.isolateLocal(( + Pointer sysFontInfo, + int weight, + int italic, + int charset, + int pitchFamily, + Pointer face, + Pointer bExact, + ) { + final result = mapFontOriginal(sysFontInfo, weight, italic, charset, pitchFamily, face, bExact); + if (result.address == 0) { + final faceName = face.cast().toDartString(); + if (!fontNamesToIgnore.containsKey(faceName)) { + _lastMissingFonts[faceName] = PdfFontQuery( + face: faceName, + weight: weight, + isItalic: italic != 0, + charset: PdfFontCharset.fromPdfiumCharsetId(charset), + pitchFamily: pitchFamily, + ); + } + } + return result; + }); + + sysFontInfoBuffer.ref.MapFont = _mapFont!.nativeFunction; + + // when registering a new SetSystemFontInfo, the previous one is automatically released + // and the only last one remains on memory + pdfium.FPDF_SetSystemFontInfo(sysFontInfoBuffer); + }, {}); +} + +/// Retrieve and clear the last missing fonts from [_lastMissingFonts] in a thread-safe manner. +Future> _getAndClearMissingFonts() async { + return await (await backgroundWorker).compute((params) { + final fonts = _lastMissingFonts.values.toList(); + _lastMissingFonts.clear(); + return fonts; + }, null); +} + class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { PdfrxEntryFunctionsImpl(); @@ -313,15 +425,27 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } @override - Future reloadFonts() => throw UnimplementedError('FIXME: PdfrxEntryFunctions.reloadFonts is not implemented.'); + Future reloadFonts() async { + await _initializeFontEnvironment(); + } @override - Future addFontData({required String face, required Uint8List data}) => - throw UnimplementedError('FIXME: PdfrxEntryFunctions.addFontData is not implemented.'); + Future addFontData({required String face, required Uint8List data}) async { + await _appLocalFontPath!.create(recursive: true); + final name = base64Encode(utf8.encode(face)); + final file = File('${_appLocalFontPath!.path}/$name.ttf'); + await file.writeAsBytes(data); + stderr.writeln('Added font data: $face (${data.length} bytes) at ${file.path}'); + } @override - Future clearAllFontData() => - throw UnimplementedError('FIXME: PdfrxEntryFunctions.clearAllFontData is not implemented.'); + Future clearAllFontData() async { + try { + await _appLocalFontPath!.delete(recursive: true); + } catch (e) { + // ignored + } + } } extension _FpdfUtf8StringExt on String { @@ -403,10 +527,12 @@ class _PdfDocumentPdfium extends PdfDocument { formInfo: Pointer.fromAddress(result.formInfo), disposeCallback: disposeCallback, ); + final pages = await pdfDoc._loadPagesInLimitedTime( maxPageCountToLoadAdditionally: useProgressiveLoading ? 1 : null, ); pdfDoc._pages = List.unmodifiable(pages.pages); + pdfDoc._notifyMissingFonts(); return pdfDoc; } catch (e) { pdfDoc?.dispose(); @@ -414,6 +540,14 @@ class _PdfDocumentPdfium extends PdfDocument { } } + /// Notify missing fonts by sending [PdfDocumentMissingFontsEvent]. + Future _notifyMissingFonts() async { + final lastMissingFonts = await _getAndClearMissingFonts(); + if (lastMissingFonts.isNotEmpty) { + subject.add(PdfDocumentMissingFontsEvent(this, lastMissingFonts)); + } + } + @override Future loadPagesProgressively({ PdfPageLoadingCallback? onPageLoadProgress, @@ -745,6 +879,8 @@ class _PdfPagePdfium extends PdfPage { ); }); + document._notifyMissingFonts(); + if (!isSucceeded) { return null; } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index eecf3055..1841187a 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -28,6 +28,7 @@ class Pdfrx { /// Font paths scanned by pdfium if supported. /// /// It should be set before calling any Pdfrx's functions. + /// /// It is not supported on Flutter Web. static final fontPaths = []; @@ -116,10 +117,19 @@ abstract class PdfrxEntryFunctions { bool withCredentials = false, }); + /// Reload the fonts. Future reloadFonts(); + /// Add font data to font cache. + /// + /// For Web platform, this is the only way to add custom fonts (the fonts are cached on memory). + /// + /// For other platforms, the font data is cached on temporary files in the cache directory; if you want to keep + /// the font data permanently, you should save the font data to some other persistent storage and set its path + /// to [Pdfrx.fontPaths]. Future addFontData({required String face, required Uint8List data}); + /// Clear all font data added by [addFontData]. Future clearAllFontData(); } From 185a3796bdc985e1765871ded6427c39dd840b31 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 5 Sep 2025 15:29:11 +0900 Subject: [PATCH 291/663] #456: iOS support (updating PDFium related config) --- packages/pdfrx/.gitignore | 4 + packages/pdfrx/darwin/pdfium/build-config.sh | 2 +- .../viewer/ios/Flutter/AppFrameworkInfo.plist | 2 +- packages/pdfrx/example/viewer/ios/Podfile | 2 +- .../pdfrx/example/viewer/ios/Podfile.lock | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 12 +-- packages/pdfrx/example/viewer/macos/Podfile | 2 +- .../pdfrx/example/viewer/macos/Podfile.lock | 4 +- .../macos/Runner.xcodeproj/project.pbxproj | 6 +- packages/pdfrx/src/pdfium_interop.cpp | 79 +++++++++++++------ 10 files changed, 74 insertions(+), 43 deletions(-) diff --git a/packages/pdfrx/.gitignore b/packages/pdfrx/.gitignore index 86f70671..c8585d1e 100644 --- a/packages/pdfrx/.gitignore +++ b/packages/pdfrx/.gitignore @@ -40,3 +40,7 @@ build/ /test/.tmp .claude/ + +# Build outputs and intermediate files +**.xcframework/ +*.zip diff --git a/packages/pdfrx/darwin/pdfium/build-config.sh b/packages/pdfrx/darwin/pdfium/build-config.sh index 70898979..0f156160 100755 --- a/packages/pdfrx/darwin/pdfium/build-config.sh +++ b/packages/pdfrx/darwin/pdfium/build-config.sh @@ -9,7 +9,7 @@ echo "**************************************************************" echo " Building PDFium for $1/$2" echo "**************************************************************" -LAST_KNOWN_GOOD_COMMIT=chromium/7202 +LAST_KNOWN_GOOD_COMMIT=chromium/7390 SCRIPT_DIR=$(cd $(dirname $0) && pwd) diff --git a/packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist b/packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist index 7c569640..1dc6cf76 100644 --- a/packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/pdfrx/example/viewer/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/packages/pdfrx/example/viewer/ios/Podfile b/packages/pdfrx/example/viewer/ios/Podfile index d97f17e2..e51a31d9 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile +++ b/packages/pdfrx/example/viewer/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 820ec283..d2934529 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -32,11 +32,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe -PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 +PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 COCOAPODS: 1.16.2 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index f11e0be1..7be7e647 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -460,7 +460,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -481,7 +481,7 @@ DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -592,7 +592,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -641,7 +641,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -664,7 +664,7 @@ DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -691,7 +691,7 @@ DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/packages/pdfrx/example/viewer/macos/Podfile b/packages/pdfrx/example/viewer/macos/Podfile index c795730d..b52666a1 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile +++ b/packages/pdfrx/example/viewer/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index e79442df..ea61c8ad 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -32,11 +32,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 COCOAPODS: 1.16.2 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index dbb99d4a..c62bf808 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -561,7 +561,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -640,7 +640,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -687,7 +687,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/packages/pdfrx/src/pdfium_interop.cpp b/packages/pdfrx/src/pdfium_interop.cpp index 343edf11..a28b92ec 100644 --- a/packages/pdfrx/src/pdfium_interop.cpp +++ b/packages/pdfrx/src/pdfium_interop.cpp @@ -2,7 +2,9 @@ #include #include #include + #include +#include #if defined(_WIN32) #define PDFRX_EXPORT __declspec(dllexport) @@ -79,7 +81,6 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDF_InitLibrary), reinterpret_cast(FPDF_DestroyLibrary), reinterpret_cast(FPDF_SetSandBoxPolicy), - // reinterpret_cast(FPDF_SetPrintMode), reinterpret_cast(FPDF_LoadDocument), reinterpret_cast(FPDF_LoadMemDocument), reinterpret_cast(FPDF_LoadMemDocument64), @@ -89,7 +90,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDF_DocumentHasValidCrossReferenceTable), reinterpret_cast(FPDF_GetTrailerEnds), reinterpret_cast(FPDF_GetDocPermissions), - // reinterpret_cast(FPDF_GetDocUserPermissions), + reinterpret_cast(FPDF_GetDocUserPermissions), reinterpret_cast(FPDF_GetSecurityHandlerRevision), reinterpret_cast(FPDF_GetPageCount), reinterpret_cast(FPDF_LoadPage), @@ -100,7 +101,6 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDF_GetPageBoundingBox), reinterpret_cast(FPDF_GetPageSizeByIndexF), reinterpret_cast(FPDF_GetPageSizeByIndex), - // reinterpret_cast(FPDF_RenderPage), reinterpret_cast(FPDF_RenderPageBitmap), reinterpret_cast(FPDF_RenderPageBitmapWithMatrix), reinterpret_cast(FPDF_ClosePage), @@ -129,6 +129,16 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDF_GetXFAPacketCount), reinterpret_cast(FPDF_GetXFAPacketName), reinterpret_cast(FPDF_GetXFAPacketContent), + reinterpret_cast(FPDFDOC_InitFormFillEnvironment), + reinterpret_cast(FPDFDOC_ExitFormFillEnvironment), + reinterpret_cast(FPDFPage_HasFormFieldAtPoint), + reinterpret_cast(FPDFPage_FormFieldZOrderAtPoint), + reinterpret_cast(FPDF_SetFormFieldHighlightColor), + reinterpret_cast(FPDF_SetFormFieldHighlightAlpha), + reinterpret_cast(FPDF_RemoveFormFieldHighlight), + reinterpret_cast(FPDF_FFLDraw), + reinterpret_cast(FPDF_GetFormType), + reinterpret_cast(FPDF_LoadXFA), reinterpret_cast(FPDFAnnot_IsSupportedSubtype), reinterpret_cast(FPDFPage_CreateAnnot), reinterpret_cast(FPDFPage_GetAnnotCount), @@ -172,6 +182,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFAnnot_GetFlags), reinterpret_cast(FPDFAnnot_SetFlags), reinterpret_cast(FPDFAnnot_GetFormFieldFlags), + reinterpret_cast(FPDFAnnot_SetFormFieldFlags), reinterpret_cast(FPDFAnnot_GetFormFieldAtPoint), reinterpret_cast(FPDFAnnot_GetFormFieldName), reinterpret_cast(FPDFAnnot_GetFormFieldAlternateName), @@ -181,6 +192,8 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFAnnot_GetOptionLabel), reinterpret_cast(FPDFAnnot_IsOptionSelected), reinterpret_cast(FPDFAnnot_GetFontSize), + reinterpret_cast(FPDFAnnot_SetFontColor), + reinterpret_cast(FPDFAnnot_GetFontColor), reinterpret_cast(FPDFAnnot_IsChecked), reinterpret_cast(FPDFAnnot_SetFocusableSubtypes), reinterpret_cast(FPDFAnnot_GetFocusableSubtypesCount), @@ -190,17 +203,19 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFAnnot_GetFormControlIndex), reinterpret_cast(FPDFAnnot_GetFormFieldExportValue), reinterpret_cast(FPDFAnnot_SetURI), + reinterpret_cast(FPDFAnnot_GetFileAttachment), + reinterpret_cast(FPDFAnnot_AddFileAttachment), reinterpret_cast(FPDFText_LoadPage), reinterpret_cast(FPDFText_ClosePage), reinterpret_cast(FPDFText_CountChars), reinterpret_cast(FPDFText_GetUnicode), + reinterpret_cast(FPDFText_GetTextObject), reinterpret_cast(FPDFText_IsGenerated), reinterpret_cast(FPDFText_IsHyphen), reinterpret_cast(FPDFText_HasUnicodeMapError), reinterpret_cast(FPDFText_GetFontSize), reinterpret_cast(FPDFText_GetFontInfo), reinterpret_cast(FPDFText_GetFontWeight), - // reinterpret_cast(FPDFText_GetTextRenderMode), reinterpret_cast(FPDFText_GetFillColor), reinterpret_cast(FPDFText_GetStrokeColor), reinterpret_cast(FPDFText_GetCharAngle), @@ -221,13 +236,25 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFText_FindClose), reinterpret_cast(FPDFLink_LoadWebLinks), reinterpret_cast(FPDFLink_CountWebLinks), - reinterpret_cast(FPDFLink_LoadWebLinks), - reinterpret_cast(FPDFLink_CountWebLinks), reinterpret_cast(FPDFLink_GetURL), reinterpret_cast(FPDFLink_CountRects), reinterpret_cast(FPDFLink_GetRect), reinterpret_cast(FPDFLink_GetTextRange), reinterpret_cast(FPDFLink_CloseWebLinks), + reinterpret_cast(FPDFBookmark_GetFirstChild), + reinterpret_cast(FPDFBookmark_GetNextSibling), + reinterpret_cast(FPDFBookmark_GetTitle), + reinterpret_cast(FPDFBookmark_GetCount), + reinterpret_cast(FPDFBookmark_Find), + reinterpret_cast(FPDFBookmark_GetDest), + reinterpret_cast(FPDFBookmark_GetAction), + reinterpret_cast(FPDFAction_GetType), + reinterpret_cast(FPDFAction_GetDest), + reinterpret_cast(FPDFAction_GetFilePath), + reinterpret_cast(FPDFAction_GetURIPath), + reinterpret_cast(FPDFDest_GetDestPageIndex), + reinterpret_cast(FPDFDest_GetView), + reinterpret_cast(FPDFDest_GetLocationInPage), reinterpret_cast(FPDFLink_GetLinkAtPoint), reinterpret_cast(FPDFLink_GetLinkZOrderAtPoint), reinterpret_cast(FPDFLink_GetDest), @@ -237,25 +264,10 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFLink_GetAnnotRect), reinterpret_cast(FPDFLink_CountQuadPoints), reinterpret_cast(FPDFLink_GetQuadPoints), - reinterpret_cast(FPDFLink_CloseWebLinks), - reinterpret_cast(FPDFAction_GetType), - reinterpret_cast(FPDFAction_GetDest), - reinterpret_cast(FPDFAction_GetFilePath), - reinterpret_cast(FPDFAction_GetURIPath), - reinterpret_cast(FPDFDest_GetDestPageIndex), - reinterpret_cast(FPDFDest_GetView), - reinterpret_cast(FPDFDest_GetLocationInPage), - reinterpret_cast(FPDFBookmark_GetFirstChild), - reinterpret_cast(FPDFBookmark_GetNextSibling), - reinterpret_cast(FPDFBookmark_GetTitle), - reinterpret_cast(FPDFBookmark_GetCount), - reinterpret_cast(FPDFBookmark_Find), - reinterpret_cast(FPDFBookmark_GetDest), - reinterpret_cast(FPDFBookmark_GetAction), - reinterpret_cast(FPDFDOC_InitFormFillEnvironment), - reinterpret_cast(FPDFDOC_ExitFormFillEnvironment), - reinterpret_cast(FPDF_FFLDraw), - reinterpret_cast(FPDF_GetFormType), + reinterpret_cast(FPDF_GetPageAAction), + reinterpret_cast(FPDF_GetFileIdentifier), + reinterpret_cast(FPDF_GetMetaText), + reinterpret_cast(FPDF_GetPageLabel), reinterpret_cast(FPDF_CreateNewDocument), reinterpret_cast(FPDFPage_New), reinterpret_cast(FPDFPage_Delete), @@ -271,11 +283,15 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFPageObj_Destroy), reinterpret_cast(FPDFPageObj_HasTransparency), reinterpret_cast(FPDFPageObj_GetType), + reinterpret_cast(FPDFPageObj_GetIsActive), + reinterpret_cast(FPDFPageObj_SetIsActive), reinterpret_cast(FPDFPageObj_Transform), + reinterpret_cast(FPDFPageObj_TransformF), reinterpret_cast(FPDFPageObj_GetMatrix), reinterpret_cast(FPDFPageObj_SetMatrix), reinterpret_cast(FPDFPage_TransformAnnots), reinterpret_cast(FPDFPageObj_NewImageObj), + reinterpret_cast(FPDFPageObj_GetMarkedContentID), reinterpret_cast(FPDFPageObj_CountMarks), reinterpret_cast(FPDFPageObj_GetMark), reinterpret_cast(FPDFPageObj_AddMark), @@ -303,6 +319,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFImageObj_GetImageFilter), reinterpret_cast(FPDFImageObj_GetImageMetadata), reinterpret_cast(FPDFImageObj_GetImagePixelSize), + reinterpret_cast(FPDFImageObj_GetIccProfileDataDecoded), reinterpret_cast(FPDFPageObj_CreateNewPath), reinterpret_cast(FPDFPageObj_CreateNewRect), reinterpret_cast(FPDFPageObj_GetBounds), @@ -339,6 +356,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFText_SetCharcodes), reinterpret_cast(FPDFText_LoadFont), reinterpret_cast(FPDFText_LoadStandardFont), + reinterpret_cast(FPDFText_LoadCidType2Font), reinterpret_cast(FPDFTextObj_GetFontSize), reinterpret_cast(FPDFFont_Close), reinterpret_cast(FPDFPageObj_CreateTextObj), @@ -347,7 +365,8 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFTextObj_GetText), reinterpret_cast(FPDFTextObj_GetRenderedBitmap), reinterpret_cast(FPDFTextObj_GetFont), - // reinterpret_cast(FPDFFont_GetFontName), + reinterpret_cast(FPDFFont_GetBaseFontName), + reinterpret_cast(FPDFFont_GetFamilyName), reinterpret_cast(FPDFFont_GetFontData), reinterpret_cast(FPDFFont_GetIsEmbedded), reinterpret_cast(FPDFFont_GetFlags), @@ -361,6 +380,14 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() reinterpret_cast(FPDFGlyphPath_GetGlyphPathSegment), reinterpret_cast(FPDFFormObj_CountObjects), reinterpret_cast(FPDFFormObj_GetObject), + reinterpret_cast(FPDFFormObj_RemoveObject), + reinterpret_cast(FPDF_GetDefaultTTFMap), + reinterpret_cast(FPDF_GetDefaultTTFMapCount), + reinterpret_cast(FPDF_GetDefaultTTFMapEntry), + reinterpret_cast(FPDF_AddInstalledFont), + reinterpret_cast(FPDF_SetSystemFontInfo), + reinterpret_cast(FPDF_GetDefaultSystemFontInfo), + reinterpret_cast(FPDF_FreeDefaultSystemFontInfo), }; return bindings; } From 39eed1b48faa0d9099491fe22e3fd164775afada Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 5 Sep 2025 15:52:42 +0900 Subject: [PATCH 292/663] iOS/macOS: podspec references pdfium-apple-v11 and updates for #396 --- packages/pdfrx/darwin/pdfrx.podspec | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index dcf206af..ce5e3cd7 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -2,11 +2,11 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint pdfrx.podspec` to validate before publishing. # -lib_tag = 'pdfium-apple-v10' +lib_tag = 'pdfium-apple-v11' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.6' + s.version = '0.0.7' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. @@ -36,18 +36,11 @@ Pod::Spec.new do |s| s.prepare_command = <<-CMD mkdir -p pdfium/.lib/#{lib_tag} cd pdfium/.lib/#{lib_tag} - if [ ! -f ios.zip ]; then - curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip - fi - if [ ! -d ios ]; then - unzip -o ios.zip - fi - if [ ! -f macos.zip ]; then - curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip - fi - if [ ! -d macos ]; then - unzip -o macos.zip - fi + rm -rf ios.zip macos.zip ios/ macos/ + curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip + unzip -o ios.zip + curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip + unzip -o macos.zip CMD s.swift_version = '5.0' From ce8a68944f789d14823ad60d0b6ca245c64102d7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 5 Sep 2025 15:56:20 +0900 Subject: [PATCH 293/663] WIP --- packages/pdfrx/example/viewer/ios/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index d2934529..459df8ab 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.6): + - pdfrx (0.0.7): - Flutter - FlutterMacOS - url_launcher_ios (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 + pdfrx: 5e9c3a2528cbc4a6ea19abfa72d6c132b5bf811f url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 From 86f2777c4a5df467fc623c19b25c376de8b98220 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Fri, 5 Sep 2025 16:10:23 +0900 Subject: [PATCH 294/663] Release pdfrx v2.1.11 and pdfrx_engine v0.1.14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Experimental support for dynamic font installation on native platforms (#456) - Improved font handling for missing fonts on iOS/macOS platforms 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index ec4b79be..665cb818 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.1.11 + +- NEW: Experimental support for dynamic font installation on native platforms to handle missing fonts in PDFs ([#456](https://github.com/espresso3389/pdfrx/issues/456)) + - Example viewer has missing font support using Noto Sans/Serif on Google Fonts (**For production use, please take care of your license integrity**) +- iOS/macOS uses pdfium-apple-v11 (chromium/7390) +- Updated to pdfrx_engine 0.1.14 + # 2.1.10 - NEW: Add `dismissPdfiumWasmWarnings` to `pdfrxFlutterInitialize` to optionally hide WASM warnings in debug ([#452](https://github.com/espresso3389/pdfrx/issues/452)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index ce34969f..b6de5006 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.10 + pdfrx: ^2.1.11 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 6585e3d4..f879bf41 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.10 +version: 2.1.11 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.13 + pdfrx_engine: ^0.1.14 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 6709c0e9..34f691dc 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.14 + +- Experimental support for dynamic font installation on native platforms ([#456](https://github.com/espresso3389/pdfrx/issues/456)) + ## 0.1.13 - Add font loading APIs for WASM: `reloadFonts()` and `addFontData()` methods diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 78409978..f713c469 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.13 +version: 0.1.14 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From dbc766ce778e70da0b70b47b91860954d6d5b41a Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Fri, 5 Sep 2025 22:43:52 +0900 Subject: [PATCH 295/663] FIXED: #458 coordinate calculation error on loading annotation links --- .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 4 ++-- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 93d74b0d..8a176901 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -1052,7 +1052,7 @@ class _PdfPagePdfium extends PdfPage { r.top > r.bottom ? r.top : r.bottom, r.right, r.top > r.bottom ? r.bottom : r.top, - ); + ).translate(-params.bbLeft, -params.bbBottom); final dest = _processAnnotDest(annot, document, arena); if (dest != nullptr) { links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena))); @@ -1069,7 +1069,7 @@ class _PdfPagePdfium extends PdfPage { pdfium.FPDF_ClosePage(page); } }), - (document: document.document.address, pageNumber: pageNumber), + (document: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom), ); static pdfium_bindings.FPDF_DEST _processAnnotDest( diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 1841187a..6157293b 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1294,8 +1294,16 @@ class PdfRect { } } + /// Inflate (or deflate) the rectangle. + /// + /// [dx] is added to left and right, and [dy] is added to top and bottom. PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); + /// Translate the rectangle. + /// + /// [dx] is added to left and right, and [dy] is added to top and bottom. + PdfRect translate(double dx, double dy) => PdfRect(left + dx, top + dy, right + dx, bottom + dy); + @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1603,6 +1611,11 @@ class PdfPoint { throw ArgumentError.value(rotate, 'rotate'); } } + + /// Translate the point. + /// + /// [dx] is added to x, and [dy] is added to y. + PdfPoint translate(double dx, double dy) => PdfPoint(x + dx, y + dy); } /// Compares two lists for element-by-element equality. From 9469d6e96c26efa4a38e8bcfc2af66636ecca6e9 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Sat, 6 Sep 2025 02:52:53 +0900 Subject: [PATCH 296/663] #458: More fixes for timing issues. --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 6 +++--- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 17 +++++++++++++---- .../lib/src/native/pdfrx_pdfium.dart | 5 +++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index d5a35b95..59d2723d 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -486,7 +486,7 @@ class _PdfPageWasm extends PdfPage { @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { - if (document.isDisposed) return []; + if (document.isDisposed || !isLoaded) return []; final result = await _sendCommand( 'loadLinks', parameters: { @@ -522,7 +522,7 @@ class _PdfPageWasm extends PdfPage { @override Future loadText() async { - if (document.isDisposed) return ''; + if (document.isDisposed || !isLoaded) return ''; final result = await _sendCommand( 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, @@ -533,7 +533,7 @@ class _PdfPageWasm extends PdfPage { @override Future> loadTextCharRects() async { - if (document.isDisposed) return []; + if (document.isDisposed || !isLoaded) return []; final result = await _sendCommand( 'loadTextCharRects', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 4e5133d2..a7995ebd 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1061,6 +1061,8 @@ class _PdfViewerState extends State /// and [onTextLoaded] callback will be called when the text is loaded. /// If [onTextLoaded] is not provided and [invalidate] is true, the widget will be rebuilt when the text is loaded. PdfPageText? _getCachedTextOrDelayLoadText(int pageNumber, {void Function()? onTextLoaded, bool invalidate = true}) { + final page = _document!.pages[pageNumber - 1]; + if (!page.isLoaded) return null; if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]; if (onTextLoaded == null && invalidate) { onTextLoaded = _invalidate; @@ -1069,11 +1071,14 @@ class _PdfViewerState extends State return null; } - Future _loadTextAsync(int pageNumber, {void Function()? onTextLoaded}) async { + Future _loadTextAsync(int pageNumber, {void Function()? onTextLoaded}) async { + final page = _document!.pages[pageNumber - 1]; + if (!page.isLoaded) return null; if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]!; return await synchronized(() async { if (_textCache.containsKey(pageNumber)) return _textCache[pageNumber]!; final page = _document!.pages[pageNumber - 1]; + if (!page.isLoaded) return null; final text = await page.loadStructuredText(); _textCache[pageNumber] = text; if (onTextLoaded != null) { @@ -2580,7 +2585,7 @@ class _PdfViewerState extends State for (int i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { final text = await _loadTextAsync(i); - if (text.fullText.isEmpty) continue; + if (text == null || text.fullText.isEmpty) continue; selections.add(text.getRangeFromAB(0, text.charRects.length - 1)); } @@ -2610,14 +2615,14 @@ class _PdfViewerState extends State PdfPageText? first; for (int i = 1; i <= _document!.pages.length; i++) { final text = await _loadTextAsync(i); - if (text.fullText.isEmpty) continue; + if (text == null || text.fullText.isEmpty) continue; first = text; break; } PdfPageText? last; for (int i = _document!.pages.length; i >= 1; i--) { final text = await _loadTextAsync(i); - if (text.fullText.isEmpty) continue; + if (text == null || text.fullText.isEmpty) continue; last = text; break; } @@ -2654,6 +2659,9 @@ class _PdfViewerState extends State } final text = await _loadTextAsync(i + 1); + if (text == null || text.fullText.isEmpty) { + continue; + } final page = _document!.pages[i]; final point = offset .translate(-pageRect.left, -pageRect.top) @@ -3581,6 +3589,7 @@ class _CanvasLinkPainter { } List? _ensureLinksLoaded(PdfPage page, {void Function()? onLoaded}) { + if (!page.isLoaded) return null; final links = _links[page.pageNumber]; if (links != null) return links; synchronized(() async { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 8a176901..260211c6 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -915,7 +915,7 @@ class _PdfPagePdfium extends PdfPage { @override Future loadText() async { - if (document.isDisposed) return ''; + if (document.isDisposed || !isLoaded) return ''; return await (await backgroundWorker).compute( (params) => using((arena) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); @@ -939,7 +939,7 @@ class _PdfPagePdfium extends PdfPage { @override Future> loadTextCharRects() async { - if (document.isDisposed) return []; + if (document.isDisposed || !isLoaded) return []; return await (await backgroundWorker).compute( (params) => using((arena) { final doubleSize = sizeOf(); @@ -974,6 +974,7 @@ class _PdfPagePdfium extends PdfPage { @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) async { + if (document.isDisposed || !isLoaded) return []; final links = await _loadAnnotLinks(); if (enableAutoLinkDetection) { links.addAll(await _loadWebLinks()); From 1c56e343e928235c6964512450a911a95dcc0ebb Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Sat, 6 Sep 2025 03:14:12 +0900 Subject: [PATCH 297/663] To fix #434, the change integrate loadText/loadTextCharRects into one function that breaks API compatibility with older versions --- packages/pdfrx/assets/pdfium_worker.js | 31 +++---------- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 18 ++------ .../lib/src/native/pdfrx_pdfium.dart | 33 +++----------- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 43 +++++++------------ 4 files changed, 30 insertions(+), 95 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 133a9320..0f64d0ef 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1167,7 +1167,7 @@ function _memset(ptr, value, num) { /** * * @param {{pageIndex: number, docHandle: number}} params - * @returns {{fullText: string, missingFonts: FontQueries}} + * @returns {{fullText: string, charRects: number[][], missingFonts: FontQueries}} */ function loadText(params) { _resetMissingFonts(); @@ -1179,33 +1179,11 @@ function loadText(params) { const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); let fullText = ''; - for (let i = 0; i < count; i++) { - fullText += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); - } - - Pdfium.wasmExports.FPDFText_ClosePage(textPage); - Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - - _updateMissingFonts(docHandle); - return { fullText, missingFonts: missingFonts[docHandle] }; -} - -/** - * - * @param {{pageIndex: number, docHandle: number}} params - * @returns {{charRects: number[][]}} - */ -function loadTextCharRects(params) { - const { pageIndex, docHandle } = params; - const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); - const textPage = Pdfium.wasmExports.FPDFText_LoadPage(pageHandle); - if (textPage == null) return { charRects: [] }; - const rectBuffer = Pdfium.wasmExports.malloc(8 * 4); // double[4] const rect = new Float64Array(Pdfium.memory.buffer, rectBuffer, 4); - const count = Pdfium.wasmExports.FPDFText_CountChars(textPage); let charRects = []; for (let i = 0; i < count; i++) { + fullText += String.fromCodePoint(Pdfium.wasmExports.FPDFText_GetUnicode(textPage, i)); Pdfium.wasmExports.FPDFText_GetCharBox( textPage, i, @@ -1220,7 +1198,9 @@ function loadTextCharRects(params) { Pdfium.wasmExports.FPDFText_ClosePage(textPage); Pdfium.wasmExports.FPDF_ClosePage(pageHandle); - return { charRects }; + + _updateMissingFonts(docHandle); + return { fullText, charRects, missingFonts: missingFonts[docHandle] }; } /** @@ -1504,7 +1484,6 @@ const functions = { closePage, renderPage, loadText, - loadTextCharRects, loadLinks, reloadFonts, addFontData, diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 59d2723d..81b68de6 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -521,23 +521,12 @@ class _PdfPageWasm extends PdfPage { } @override - Future loadText() async { - if (document.isDisposed || !isLoaded) return ''; + Future loadText() async { + if (document.isDisposed || !isLoaded) return null; final result = await _sendCommand( 'loadText', parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, ); - document.updateMissingFonts(result['missingFonts']); - return result['fullText'] as String; - } - - @override - Future> loadTextCharRects() async { - if (document.isDisposed || !isLoaded) return []; - final result = await _sendCommand( - 'loadTextCharRects', - parameters: {'docHandle': document.document['docHandle'], 'pageIndex': pageNumber - 1}, - ); final charRectsAll = (result['charRects'] as List).map((rect) { final r = rect as List; return PdfRect( @@ -547,7 +536,8 @@ class _PdfPageWasm extends PdfPage { (r[3] as double) - bbBottom, ); }).toList(); - return charRectsAll; + document.updateMissingFonts(result['missingFonts']); + return PdfPageRawText(result['fullText'] as String, charRectsAll); } @override diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 260211c6..801deda1 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -914,44 +914,21 @@ class _PdfPagePdfium extends PdfPage { PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this); @override - Future loadText() async { - if (document.isDisposed || !isLoaded) return ''; - return await (await backgroundWorker).compute( - (params) => using((arena) { - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); - final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); - final textPage = pdfium.FPDFText_LoadPage(page); - try { - final charCount = pdfium.FPDFText_CountChars(textPage); - final sb = StringBuffer(); - for (int i = 0; i < charCount; i++) { - sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); - } - return sb.toString(); - } finally { - pdfium.FPDFText_ClosePage(textPage); - pdfium.FPDF_ClosePage(page); - } - }), - (docHandle: document.document.address, pageNumber: pageNumber), - ); - } - - @override - Future> loadTextCharRects() async { - if (document.isDisposed || !isLoaded) return []; + Future loadText() async { + if (document.isDisposed || !isLoaded) return null; return await (await backgroundWorker).compute( (params) => using((arena) { final doubleSize = sizeOf(); final rectBuffer = arena.allocate(4 * sizeOf()); - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); final textPage = pdfium.FPDFText_LoadPage(page); try { final charCount = pdfium.FPDFText_CountChars(textPage); + final sb = StringBuffer(); final charRects = []; for (int i = 0; i < charCount; i++) { + sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); pdfium.FPDFText_GetCharBox( textPage, i, @@ -962,7 +939,7 @@ class _PdfPagePdfium extends PdfPage { ); charRects.add(_rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom)); } - return charRects; + return PdfPageRawText(sb.toString(), charRects); } finally { pdfium.FPDFText_ClosePage(textPage); pdfium.FPDF_ClosePage(page); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 6157293b..89401c40 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -693,18 +693,14 @@ abstract class PdfPage { } Future _loadFormattedText() async { - final inputFullText = await loadText(); - final inputCharRects = await loadTextCharRects(); - - if (inputFullText.length != inputCharRects.length) { - throw Exception( - 'Page $pageNumber: Internal Error: text and character rects length mismatch (${inputFullText.length} <=> ${inputCharRects.length})', - ); + final input = await loadText(); + if (input == null) { + return null; } if (rotation.index != 0) { - for (int i = 0; i < inputCharRects.length; i++) { - inputCharRects[i] = inputCharRects[i].rotate(rotation.index, this); + for (int i = 0; i < input.charRects.length; i++) { + input.charRects[i] = input.charRects[i].rotate(rotation.index, this); } } @@ -712,14 +708,14 @@ abstract class PdfPage { final charRects = []; // Process the whole text - final lnMatches = _reNewLine.allMatches(inputFullText).toList(); + final lnMatches = _reNewLine.allMatches(input.fullText).toList(); int lineStart = 0; int prevEnd = 0; for (int i = 0; i < lnMatches.length; i++) { lineStart = prevEnd; final match = lnMatches[i]; - fullText.write(inputFullText.substring(lineStart, match.start)); - charRects.addAll(inputCharRects.sublist(lineStart, match.start)); + fullText.write(input.fullText.substring(lineStart, match.start)); + charRects.addAll(input.charRects.sublist(lineStart, match.start)); prevEnd = match.end; // Microsoft Word sometimes outputs vertical text like this: "縦\n書\nき\nの\nテ\nキ\nス\nト\nで\nす\n。\n" @@ -729,8 +725,8 @@ abstract class PdfPage { final len = match.start - lineStart; final nextLen = next.start - match.end; if (len == 1 && nextLen == 1) { - final rect = inputCharRects[lineStart]; - final nextRect = inputCharRects[match.end]; + final rect = input.charRects[lineStart]; + final nextRect = input.charRects[match.end]; final nextCenterX = nextRect.center.x; if (rect.left < nextCenterX && nextCenterX < rect.right && rect.top > nextRect.top) { // The line is vertical, and the line-feed is virtual @@ -738,12 +734,12 @@ abstract class PdfPage { } } } - fullText.write(inputFullText.substring(match.start, match.end)); - charRects.addAll(inputCharRects.sublist(match.start, match.end)); + fullText.write(input.fullText.substring(match.start, match.end)); + charRects.addAll(input.charRects.sublist(match.start, match.end)); } - if (prevEnd < inputFullText.length) { - fullText.write(inputFullText.substring(prevEnd)); - charRects.addAll(inputCharRects.sublist(prevEnd)); + if (prevEnd < input.fullText.length) { + fullText.write(input.fullText.substring(prevEnd)); + charRects.addAll(input.charRects.sublist(prevEnd)); } return PdfPageRawText(fullText.toString(), charRects); @@ -752,14 +748,7 @@ abstract class PdfPage { /// Load plain text for the page. /// /// For text with character bounding boxes, use [loadStructuredText]. - Future loadText(); - - /// Load character bounding boxes for the page text. - /// - /// Each [PdfRect] corresponds to a character in the text loaded by [loadText]. - /// - /// For text with character bounding boxes, use [loadStructuredText]. - Future> loadTextCharRects(); + Future loadText(); /// Load links. /// From 4941e3109213497f3f9a0b1e7ef7d51d2eaf4ea7 Mon Sep 17 00:00:00 2001 From: pdfrx-release-bot Date: Sat, 6 Sep 2025 04:13:02 +0900 Subject: [PATCH 298/663] Release pdfrx v2.1.12 and pdfrx_engine v0.1.15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 665cb818..b268b09e 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.1.12 + +- **BREAKING**: API changes in text loading methods - `loadText()` and `loadTextCharRects()` are now integrated into `loadText()` ([#434](https://github.com/espresso3389/pdfrx/issues/434)) +- FIXED: PDF link positioning errors that caused misplaced clickable areas ([#458](https://github.com/espresso3389/pdfrx/issues/458)) +- Updated to pdfrx_engine 0.1.15 + # 2.1.11 - NEW: Experimental support for dynamic font installation on native platforms to handle missing fonts in PDFs ([#456](https://github.com/espresso3389/pdfrx/issues/456)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b6de5006..004491f2 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.11 + pdfrx: ^2.1.12 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index f879bf41..fa11469c 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.11 +version: 2.1.12 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.14 + pdfrx_engine: ^0.1.15 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 34f691dc..9e5d1528 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.15 + +- **BREAKING**: Integrated `loadText()` and `loadTextCharRects()` into a single function `loadText()` to fix crash issue ([#434](https://github.com/espresso3389/pdfrx/issues/434)) +- FIXED: Coordinate calculation errors when loading annotation links ([#458](https://github.com/espresso3389/pdfrx/issues/458)) + ## 0.1.14 - Experimental support for dynamic font installation on native platforms ([#456](https://github.com/espresso3389/pdfrx/issues/456)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index f713c469..3873fb77 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.14 +version: 0.1.15 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From aea0d5c5ecdb63b6cdec38159bfba719b856cc2a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 8 Sep 2025 22:54:41 +0900 Subject: [PATCH 299/663] chromium/7390 --- packages/pdfrx/android/CMakeLists.txt | 2 +- packages/pdfrx/assets/pdfium.wasm | Bin 3960317 -> 3981008 bytes packages/pdfrx/linux/CMakeLists.txt | 2 +- packages/pdfrx/windows/CMakeLists.txt | 2 +- .../lib/src/pdfrx_initialize_dart.dart | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/android/CMakeLists.txt b/packages/pdfrx/android/CMakeLists.txt index 23349cd4..60902654 100644 --- a/packages/pdfrx/android/CMakeLists.txt +++ b/packages/pdfrx/android/CMakeLists.txt @@ -12,7 +12,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7202) +set(PDFIUM_RELEASE chromium%2F7390) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfrx/assets/pdfium.wasm b/packages/pdfrx/assets/pdfium.wasm index 72c415b66e5243d7c575f9e33854a359c2c6858b..7a6ed81a0eae77f0da1e5857208285fcf0f78942 100644 GIT binary patch delta 754091 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

` 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

vBve{s!+ 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 delta 722004 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%0A@8&uY5lWj=mr?m5w5mkf;B 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

L)b5{@^Yufdxxl81<-%Np3@B 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 diff --git a/packages/pdfrx/linux/CMakeLists.txt b/packages/pdfrx/linux/CMakeLists.txt index 5ec52403..ac7005ed 100644 --- a/packages/pdfrx/linux/CMakeLists.txt +++ b/packages/pdfrx/linux/CMakeLists.txt @@ -29,7 +29,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7202) +set(PDFIUM_RELEASE chromium%2F7390) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfrx/windows/CMakeLists.txt b/packages/pdfrx/windows/CMakeLists.txt index 00d70a99..03b7083e 100644 --- a/packages/pdfrx/windows/CMakeLists.txt +++ b/packages/pdfrx/windows/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7202) +set(PDFIUM_RELEASE chromium%2F7390) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 9e733f76..7b1735ef 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -46,7 +46,7 @@ class _PdfiumDownloader { _PdfiumDownloader._(); /// The release of pdfium to download. - static const pdfrxCurrentPdfiumRelease = 'chromium%2F7202'; + static const pdfrxCurrentPdfiumRelease = 'chromium%2F7390'; /// Downloads the pdfium module for the current platform and architecture. /// From 16cbf61a79412b4a75153c998262a94579a16d4f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 8 Sep 2025 22:55:30 +0900 Subject: [PATCH 300/663] Viewer example: prefer Noto Serif/Sans CJK --- packages/pdfrx/example/viewer/lib/main.dart | 2 +- .../example/viewer/lib/noto_google_fonts.dart | 829 ++++++++++++++---- packages/pdfrx/lib/src/pdfrx_flutter.dart | 14 +- 3 files changed, 659 insertions(+), 186 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 137af90d..bfca81bf 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -428,7 +428,7 @@ class _MainPageState extends State with WidgetsBindingObserver { for (final font in event.missingFonts) { final gf = getGoogleFontsUriFromFontQuery(font); if (gf != null) { - debugPrint('Downloading font: ${gf.faceName}, weight: ${gf.weight}'); + debugPrint('Downloading font "${gf.faceName}" from ${gf.uri}...'); final downloaded = (await http.get(gf.uri)).bodyBytes; debugPrint(' Downloaded ${downloaded.length} bytes'); await PdfrxEntryFunctions.instance.addFontData(face: font.face, data: downloaded); diff --git a/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart index 76dc2e03..7d1b9194 100644 --- a/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart +++ b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart @@ -1,28 +1,65 @@ // The code is based on the following code from Google Fonts: -// https://github.com/material-foundation/flutter-packages/blob/main/packages/google_fonts/lib/src/google_fonts_parts/part_n.dart +// https://github.com/flutter/packages/blob/main/packages/google_fonts/lib/src/google_fonts_parts/part_n.g.dart // // Noto Sans/Serif is licensed under the SIL Open Font License, Version 1.1 . // https://fonts.google.com/noto/specimen/Noto+Sans/license +import 'package:flutter/foundation.dart'; import 'package:pdfrx/pdfrx.dart'; -class GoogleFontsFile { - GoogleFontsFile(this.faceName, this.weight, this.expectedFileHash, this.expectedLength); +abstract class GoogleFontsFile { + String get faceName; + Uri get uri; +} + +class GoogleFontsFileCJK implements GoogleFontsFile { + const GoogleFontsFileCJK(this.faceName, this.uri); + @override + final String faceName; + + @override + final Uri uri; +} + +class GoogleFontsFileSingle implements GoogleFontsFile { + const GoogleFontsFileSingle(this.faceName, this.weight, this.expectedFileHash, this.expectedLength); + @override final String faceName; final int weight; final String expectedFileHash; final int expectedLength; + @override Uri get uri => Uri.parse('https://fonts.gstatic.com/s/a/$expectedFileHash.ttf'); } -GoogleFontsFile? _getNearestWeight(Map fonts, int weight) { +GoogleFontsFileSingle? _getNearestWeight(Map fonts, int weight) { final weights = fonts.keys.toList(); weights.sort((a, b) => (a - weight).abs().compareTo((b - weight).abs())); return fonts[weights.first]; } -GoogleFontsFile? getGoogleFontsUriFromFontQuery(PdfFontQuery query) { +final notoSerifCJK = GoogleFontsFileCJK( + 'Noto Serif CJK', + Uri.parse('https://github.com/googlefonts/noto-cjk/raw/main/Serif/Variable/OTC/NotoSerifCJK-VF.otf.ttc'), +); +final notoSansCJK = GoogleFontsFileCJK( + 'Noto Sans CJK', + Uri.parse('https://github.com/googlefonts/noto-cjk/raw/main/Sans/Variable/OTC/NotoSansCJK-VF.otf.ttc'), +); + +GoogleFontsFile? getGoogleFontsUriFromFontQuery(PdfFontQuery query, {bool preferCJK = true}) { + // For CJK, prefer full CJK fonts (but not for Web because of CORS issues on GitHub) + if (!kIsWeb && + preferCJK && + (query.charset == PdfFontCharset.gb2312 || + query.charset == PdfFontCharset.chineseBig5 || + query.charset == PdfFontCharset.shiftJis || + query.charset == PdfFontCharset.hangul)) { + if (query.isRoman) return notoSerifCJK; + return notoSansCJK; + } + final fontTable = switch (query.isRoman) { true => switch (query.charset) { PdfFontCharset.gb2312 => _notoSerifSc, @@ -62,72 +99,117 @@ GoogleFontsFile? getGoogleFontsUriFromFontQuery(PdfFontQuery query) { /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans -final _notoSans = { - 100: GoogleFontsFile('NotoSans', 100, 'bc6ceb177561b27cfb9123c0dd372a54774cb6bcebe4ce18c12706bbb7ee902c', 523812), - 200: GoogleFontsFile('NotoSans', 200, '807ad06b65dbbaf657e4a7dcb6d2b0734c8831cd21a1f9172387ad0411cc396f', 524708), - 300: GoogleFontsFile('NotoSans', 300, '4e3e9bb50c6e6ade7e4a491bf0033d6b6ec3326a2621834201e735691cec4968', 524492), - 400: GoogleFontsFile('NotoSans', 400, '725edd9b341324f91a3859e24824c455d43c31be72ca6e710acd0f95920d61ee', 523940), - 500: GoogleFontsFile('NotoSans', 500, 'a77c7c7a4d75c23c5e68bcff3d44f71eb1ec0f80fe245457053ea43a4ce61bd4', 524252), - 600: GoogleFontsFile('NotoSans', 600, 'fc5b5ba2d400f44b0686c46db557e6b8067a97ade7337f14f823f524675c038c', 524444), - 700: GoogleFontsFile('NotoSans', 700, '222685dcf83610e3e88a0ecd4c602efde7a7b832832502649bfe2dcf1aa0bf15', 523772), - 800: GoogleFontsFile('NotoSans', 800, 'c6e87f6834db59a2a64ce43dce2fdc1aa3441f2a23afb0bfd667621403ed688c', 524672), - 900: GoogleFontsFile('NotoSans', 900, '7ead4fec44c3271cf7dc5d9f74795eb05fa9fb3cedc7bde3232eb10573d5f6cd', 524708), +final _notoSans = { + 100: GoogleFontsFileSingle( + 'NotoSans', + 100, + 'bc6ceb177561b27cfb9123c0dd372a54774cb6bcebe4ce18c12706bbb7ee902c', + 523812, + ), + 200: GoogleFontsFileSingle( + 'NotoSans', + 200, + '807ad06b65dbbaf657e4a7dcb6d2b0734c8831cd21a1f9172387ad0411cc396f', + 524708, + ), + 300: GoogleFontsFileSingle( + 'NotoSans', + 300, + '4e3e9bb50c6e6ade7e4a491bf0033d6b6ec3326a2621834201e735691cec4968', + 524492, + ), + 400: GoogleFontsFileSingle( + 'NotoSans', + 400, + '725edd9b341324f91a3859e24824c455d43c31be72ca6e710acd0f95920d61ee', + 523940, + ), + 500: GoogleFontsFileSingle( + 'NotoSans', + 500, + 'a77c7c7a4d75c23c5e68bcff3d44f71eb1ec0f80fe245457053ea43a4ce61bd4', + 524252, + ), + 600: GoogleFontsFileSingle( + 'NotoSans', + 600, + 'fc5b5ba2d400f44b0686c46db557e6b8067a97ade7337f14f823f524675c038c', + 524444, + ), + 700: GoogleFontsFileSingle( + 'NotoSans', + 700, + '222685dcf83610e3e88a0ecd4c602efde7a7b832832502649bfe2dcf1aa0bf15', + 523772, + ), + 800: GoogleFontsFileSingle( + 'NotoSans', + 800, + 'c6e87f6834db59a2a64ce43dce2fdc1aa3441f2a23afb0bfd667621403ed688c', + 524672, + ), + 900: GoogleFontsFileSingle( + 'NotoSans', + 900, + '7ead4fec44c3271cf7dc5d9f74795eb05fa9fb3cedc7bde3232eb10573d5f6cd', + 524708, + ), }; /// Noto Sans Italic (Latin, Greek, Cyrillic, Vietnamese, and more) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans -final _notoSansItalic = { - 100: GoogleFontsFile( +final _notoSansItalic = { + 100: GoogleFontsFileSingle( 'NotoSans-Italic', 100, '8b32677abe42a47cdade4998d4124a3e1b44efa656c5badf27de546768c82f0d', 541316, ), - 200: GoogleFontsFile( + 200: GoogleFontsFileSingle( 'NotoSans-Italic', 200, 'd64c291d542bb1211538aa1448a7f6bbaca4dbd170e78b8b8242be5c9ff28959', 541752, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSans-Italic', 300, '3a902e6bbe1ffba43428cb2981f1185ef529505836c311af5f6e5690bf9b44c8', 541688, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSans-Italic', 400, '3d23478749575c0febb6169fc3dba6cb8cdb4202e8fb47ae1867c71a21792295', 539972, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSans-Italic', 500, '085819a42ab67069f29329ae066ff8206a4b518bf6496dbf1193284f891fdbd1', 540456, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSans-Italic', 600, 'ecb66a73df07fac622c73fdc0e4972bd51f50165367807433d7fc620378f9577', 540608, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSans-Italic', 700, 'f72d0f7c9c7279b2762017fbafa2bcd9aaccdf7a79b8cf686f874e2eeb0e51ce', 540016, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSans-Italic', 800, '0ef3e94eb6875007204e41604898141fa5104f7e20b87cb5640509a8f10430b5', 540812, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSans-Italic', 900, 'b0e0148ef878a4ca6a295b6b56b1bfb4773400ff8ee0a31a1338285725dd514f', @@ -139,136 +221,361 @@ final _notoSansItalic = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+SC -final _notoSansSc = { - 100: GoogleFontsFile('NotoSansSC', 100, 'f1b8c2a287d23095abd470376c60519c9ff650ae8744b82bf76434ac5438982a', 10538940), - 200: GoogleFontsFile('NotoSansSC', 200, 'cba9bb657b61103aeb3cd0f360e8d3958c66febf59fbf58a4762f61e52015d36', 10544320), - 300: GoogleFontsFile('NotoSansSC', 300, '4cdbb86a1d6eca92c7bcaa0c759593bc2600a153600532584a8016c24eaca56c', 10545812), - 400: GoogleFontsFile('NotoSansSC', 400, 'eacedb2999b6cd30457f3820f277842f0dfbb28152a246fca8161779a8945425', 10540772), - 500: GoogleFontsFile('NotoSansSC', 500, '5383032c8e54fc5fa09773ce16483f64d9cdb7d1f8e87073a556051eb60f8529', 10533968), - 600: GoogleFontsFile('NotoSansSC', 600, '85c00dac0627c2c0184c24669735fad5adbb4f150bcb320c05620d46ed086381', 10530476), - 700: GoogleFontsFile('NotoSansSC', 700, 'a7a29b6d611205bb39b9a1a5c2be5a48416fbcbcfd7e6de98976e73ecb48720b', 10530536), - 800: GoogleFontsFile('NotoSansSC', 800, '038de57b1dc5f6428317a8b0fc11984789c25f49a9c24d47d33d2c03e3491d28', 10525556), - 900: GoogleFontsFile('NotoSansSC', 900, '501582a5e956ab1f4d9f9b2d683cf1646463eea291b21f928419da5e0c5a26eb', 10521812), +final _notoSansSc = { + 100: GoogleFontsFileSingle( + 'NotoSansSC', + 100, + 'f1b8c2a287d23095abd470376c60519c9ff650ae8744b82bf76434ac5438982a', + 10538940, + ), + 200: GoogleFontsFileSingle( + 'NotoSansSC', + 200, + 'cba9bb657b61103aeb3cd0f360e8d3958c66febf59fbf58a4762f61e52015d36', + 10544320, + ), + 300: GoogleFontsFileSingle( + 'NotoSansSC', + 300, + '4cdbb86a1d6eca92c7bcaa0c759593bc2600a153600532584a8016c24eaca56c', + 10545812, + ), + 400: GoogleFontsFileSingle( + 'NotoSansSC', + 400, + 'eacedb2999b6cd30457f3820f277842f0dfbb28152a246fca8161779a8945425', + 10540772, + ), + 500: GoogleFontsFileSingle( + 'NotoSansSC', + 500, + '5383032c8e54fc5fa09773ce16483f64d9cdb7d1f8e87073a556051eb60f8529', + 10533968, + ), + 600: GoogleFontsFileSingle( + 'NotoSansSC', + 600, + '85c00dac0627c2c0184c24669735fad5adbb4f150bcb320c05620d46ed086381', + 10530476, + ), + 700: GoogleFontsFileSingle( + 'NotoSansSC', + 700, + 'a7a29b6d611205bb39b9a1a5c2be5a48416fbcbcfd7e6de98976e73ecb48720b', + 10530536, + ), + 800: GoogleFontsFileSingle( + 'NotoSansSC', + 800, + '038de57b1dc5f6428317a8b0fc11984789c25f49a9c24d47d33d2c03e3491d28', + 10525556, + ), + 900: GoogleFontsFileSingle( + 'NotoSansSC', + 900, + '501582a5e956ab1f4d9f9b2d683cf1646463eea291b21f928419da5e0c5a26eb', + 10521812, + ), }; /// Noto Sans TC (Traditional Chinese) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+TC -final _notoSansTc = { - 100: GoogleFontsFile('NotoSansTC', 100, '53debc0456f3a7d4bdb00e14704fc29ea129d38bd8a9f6565cf656ddc27abb91', 7089040), - 200: GoogleFontsFile('NotoSansTC', 200, '5ef06c341be841ab9e166a9cc7ebc0e39cfe695da81d819672f3d14b3fca56a8', 7092508), - 300: GoogleFontsFile('NotoSansTC', 300, '9e50ec0d5779016c848855daa73f8d866ef323f0431d5770f53b60a1506f1c4a', 7092872), - 400: GoogleFontsFile('NotoSansTC', 400, 'b4f9cfdee95b77d72fe945347c0b7457f1ffc0d5d05eaf6ff688e60a86067c95', 7090948), - 500: GoogleFontsFile('NotoSansTC', 500, '2011294f66de6692639ee00a9e74d67bc9134f251100feb5448ab6322a4a2a75', 7087068), - 600: GoogleFontsFile('NotoSansTC', 600, '440471acbbc2a3b33bf11befde184b2cafe5b0fcde243e2b832357044baa4aa1', 7084432), - 700: GoogleFontsFile('NotoSansTC', 700, '22779de66d31884014b0530df89e69d596018a486a84a57994209dff1dcb97cf', 7085728), - 800: GoogleFontsFile('NotoSansTC', 800, 'f5e8e3e746319570b0979bfa3a90b6ec6a84ec38fe9e41c45a395724c31db7b4', 7082400), - 900: GoogleFontsFile('NotoSansTC', 900, '2b1ab3d7db76aa94006fa19dc38b61e93578833d2e3f268a0a3b0b1321852af6', 7079980), +final _notoSansTc = { + 100: GoogleFontsFileSingle( + 'NotoSansTC', + 100, + '53debc0456f3a7d4bdb00e14704fc29ea129d38bd8a9f6565cf656ddc27abb91', + 7089040, + ), + 200: GoogleFontsFileSingle( + 'NotoSansTC', + 200, + '5ef06c341be841ab9e166a9cc7ebc0e39cfe695da81d819672f3d14b3fca56a8', + 7092508, + ), + 300: GoogleFontsFileSingle( + 'NotoSansTC', + 300, + '9e50ec0d5779016c848855daa73f8d866ef323f0431d5770f53b60a1506f1c4a', + 7092872, + ), + 400: GoogleFontsFileSingle( + 'NotoSansTC', + 400, + 'b4f9cfdee95b77d72fe945347c0b7457f1ffc0d5d05eaf6ff688e60a86067c95', + 7090948, + ), + 500: GoogleFontsFileSingle( + 'NotoSansTC', + 500, + '2011294f66de6692639ee00a9e74d67bc9134f251100feb5448ab6322a4a2a75', + 7087068, + ), + 600: GoogleFontsFileSingle( + 'NotoSansTC', + 600, + '440471acbbc2a3b33bf11befde184b2cafe5b0fcde243e2b832357044baa4aa1', + 7084432, + ), + 700: GoogleFontsFileSingle( + 'NotoSansTC', + 700, + '22779de66d31884014b0530df89e69d596018a486a84a57994209dff1dcb97cf', + 7085728, + ), + 800: GoogleFontsFileSingle( + 'NotoSansTC', + 800, + 'f5e8e3e746319570b0979bfa3a90b6ec6a84ec38fe9e41c45a395724c31db7b4', + 7082400, + ), + 900: GoogleFontsFileSingle( + 'NotoSansTC', + 900, + '2b1ab3d7db76aa94006fa19dc38b61e93578833d2e3f268a0a3b0b1321852af6', + 7079980, + ), }; /// Noto Sans JP (Japanese) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+JP -final _notoSansJp = { - 100: GoogleFontsFile('NotoSansJP', 100, '78a1fa1d16c437fe5d97df787782b6098a750350b5913b9f80089dc81f512417', 5706804), - 200: GoogleFontsFile('NotoSansJP', 200, 'c0532e4abf0ca438ea0e56749a3106a5badb2f10a89c8ba217b43dae4ec6e590', 5708144), - 300: GoogleFontsFile('NotoSansJP', 300, '64f10b3b9e06c99b76b16e1441174fba6adf994fcd6b8036cef2fbfa38535a84', 5707688), - 400: GoogleFontsFile('NotoSansJP', 400, '209c70f533554d512ef0a417b70dfe2997aeec080d2fe41695c55b361643f9ba', 5703748), - 500: GoogleFontsFile('NotoSansJP', 500, 'c5233cdc5a2901be5503f0d95ff48b4b5170afff6a39f95a076520cb73f17860', 5700280), - 600: GoogleFontsFile('NotoSansJP', 600, '852ad9268beb7d467374ec5ff0d416a22102c52d984ec21913f6d886409b85c4', 5697576), - 700: GoogleFontsFile('NotoSansJP', 700, 'eee16e4913b766be0eb7b9a02cd6ec3daf27292ca0ddf194cae01279aac1c9d0', 5698756), - 800: GoogleFontsFile('NotoSansJP', 800, '68d3c7136501158a6cf7d15c1c13e4af995aa164e34d1c250c3eef259cda74dd', 5696016), - 900: GoogleFontsFile('NotoSansJP', 900, '6ff9b55a270592e78670f98a2f866f621d05b6e1c3a18a14301da455a36f6561', 5693644), +final _notoSansJp = { + 100: GoogleFontsFileSingle( + 'NotoSansJP', + 100, + '78a1fa1d16c437fe5d97df787782b6098a750350b5913b9f80089dc81f512417', + 5706804, + ), + 200: GoogleFontsFileSingle( + 'NotoSansJP', + 200, + 'c0532e4abf0ca438ea0e56749a3106a5badb2f10a89c8ba217b43dae4ec6e590', + 5708144, + ), + 300: GoogleFontsFileSingle( + 'NotoSansJP', + 300, + '64f10b3b9e06c99b76b16e1441174fba6adf994fcd6b8036cef2fbfa38535a84', + 5707688, + ), + 400: GoogleFontsFileSingle( + 'NotoSansJP', + 400, + '209c70f533554d512ef0a417b70dfe2997aeec080d2fe41695c55b361643f9ba', + 5703748, + ), + 500: GoogleFontsFileSingle( + 'NotoSansJP', + 500, + 'c5233cdc5a2901be5503f0d95ff48b4b5170afff6a39f95a076520cb73f17860', + 5700280, + ), + 600: GoogleFontsFileSingle( + 'NotoSansJP', + 600, + '852ad9268beb7d467374ec5ff0d416a22102c52d984ec21913f6d886409b85c4', + 5697576, + ), + 700: GoogleFontsFileSingle( + 'NotoSansJP', + 700, + 'eee16e4913b766be0eb7b9a02cd6ec3daf27292ca0ddf194cae01279aac1c9d0', + 5698756, + ), + 800: GoogleFontsFileSingle( + 'NotoSansJP', + 800, + '68d3c7136501158a6cf7d15c1c13e4af995aa164e34d1c250c3eef259cda74dd', + 5696016, + ), + 900: GoogleFontsFileSingle( + 'NotoSansJP', + 900, + '6ff9b55a270592e78670f98a2f866f621d05b6e1c3a18a14301da455a36f6561', + 5693644, + ), }; /// Noto Sans KR (Korean) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+KR -final _notoSansKr = { - 100: GoogleFontsFile('NotoSansKR', 100, '302d55d333b15473a5b4909964ad17885a53cb41c34e3b434471f22ea55faea1', 6177560), - 200: GoogleFontsFile('NotoSansKR', 200, '1b03f89eccef4f2931d49db437091de1b15ced57186990749350a2cec1f4feb8', 6177360), - 300: GoogleFontsFile('NotoSansKR', 300, 'f8ed45f767a44de83d969ea276c3b4419c41a291d8460c32379e95930eae878e', 6175264), - 400: GoogleFontsFile('NotoSansKR', 400, '82547e25c2011910dae0116ba57d3ab9abd63f4865405677bd6f79c64487ae31', 6169044), - 500: GoogleFontsFile('NotoSansKR', 500, 'f67bdb1581dbb91b1ce92bdf89a0f3a4ca2545d821d204b17c5443bcda6b3677', 6166588), - 600: GoogleFontsFile('NotoSansKR', 600, '922e269443119b1ffa72c9631d4c7dcb365ab29ba1587b96e715d29c9a66d1b4', 6165240), - 700: GoogleFontsFile('NotoSansKR', 700, 'ed93ef6659b28599d47e40d020b9f55d18a01d94fdd43c9c171e44a66ddc1d66', 6165036), - 800: GoogleFontsFile('NotoSansKR', 800, 'e7088e3dfcc13f400aa9433a4042fce57b3dbe41038040073e9b5909a9390048', 6164096), - 900: GoogleFontsFile('NotoSansKR', 900, '14c5cfe30331277d21fa0086e66e11a7c414d4a5ce403229bdb0f384d3376888', 6163040), +final _notoSansKr = { + 100: GoogleFontsFileSingle( + 'NotoSansKR', + 100, + '302d55d333b15473a5b4909964ad17885a53cb41c34e3b434471f22ea55faea1', + 6177560, + ), + 200: GoogleFontsFileSingle( + 'NotoSansKR', + 200, + '1b03f89eccef4f2931d49db437091de1b15ced57186990749350a2cec1f4feb8', + 6177360, + ), + 300: GoogleFontsFileSingle( + 'NotoSansKR', + 300, + 'f8ed45f767a44de83d969ea276c3b4419c41a291d8460c32379e95930eae878e', + 6175264, + ), + 400: GoogleFontsFileSingle( + 'NotoSansKR', + 400, + '82547e25c2011910dae0116ba57d3ab9abd63f4865405677bd6f79c64487ae31', + 6169044, + ), + 500: GoogleFontsFileSingle( + 'NotoSansKR', + 500, + 'f67bdb1581dbb91b1ce92bdf89a0f3a4ca2545d821d204b17c5443bcda6b3677', + 6166588, + ), + 600: GoogleFontsFileSingle( + 'NotoSansKR', + 600, + '922e269443119b1ffa72c9631d4c7dcb365ab29ba1587b96e715d29c9a66d1b4', + 6165240, + ), + 700: GoogleFontsFileSingle( + 'NotoSansKR', + 700, + 'ed93ef6659b28599d47e40d020b9f55d18a01d94fdd43c9c171e44a66ddc1d66', + 6165036, + ), + 800: GoogleFontsFileSingle( + 'NotoSansKR', + 800, + 'e7088e3dfcc13f400aa9433a4042fce57b3dbe41038040073e9b5909a9390048', + 6164096, + ), + 900: GoogleFontsFileSingle( + 'NotoSansKR', + 900, + '14c5cfe30331277d21fa0086e66e11a7c414d4a5ce403229bdb0f384d3376888', + 6163040, + ), }; /// Noto Sans Thai (Thai) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+Thai -final _notoSansThai = { - 100: GoogleFontsFile('NotoSansThai', 100, '77e781c33ba38f872109864fcf2f7bab58c7f85d73baf213fbcf7df2a7ea6b3f', 45684), - 200: GoogleFontsFile('NotoSansThai', 200, 'c8dc3faea7ead6f573771d50e3d2cc84b49431295bde43af0bd5f6356a628f72', 45792), - 300: GoogleFontsFile('NotoSansThai', 300, '9a1ba366a64ee23d486f48f0a276d75baef6432da4db5efb92f7c9b35dd5198d', 45728), - 400: GoogleFontsFile('NotoSansThai', 400, '5f71b18a03432951e2bce4e74497752958bd8c9976be06201af5390d47922be3', 45636), - 500: GoogleFontsFile('NotoSansThai', 500, '4c82507facc222df924a0272cda2bfdddc629de12b5684816aea0eb5851a61a7', 45720), - 600: GoogleFontsFile('NotoSansThai', 600, 'e81c6d83f8a625690b1ecc5de4f6b7b66a4d2ee9cbaf5b4f9ede73359c1db064', 45732), - 700: GoogleFontsFile('NotoSansThai', 700, '81bba197f8c779233db14166526e226f68e60cd9e33f2046b80f8075158cb433', 45640), - 800: GoogleFontsFile('NotoSansThai', 800, '7ae7ca1dae7a3df8e839ae08364e14e8e015337bab7dc2842abfc3315e477404', 45704), - 900: GoogleFontsFile('NotoSansThai', 900, '689d439d52c795a225c7fe4657a1072151407a86cc2910a51280337b8b1f57a3', 45584), +final _notoSansThai = { + 100: GoogleFontsFileSingle( + 'NotoSansThai', + 100, + '77e781c33ba38f872109864fcf2f7bab58c7f85d73baf213fbcf7df2a7ea6b3f', + 45684, + ), + 200: GoogleFontsFileSingle( + 'NotoSansThai', + 200, + 'c8dc3faea7ead6f573771d50e3d2cc84b49431295bde43af0bd5f6356a628f72', + 45792, + ), + 300: GoogleFontsFileSingle( + 'NotoSansThai', + 300, + '9a1ba366a64ee23d486f48f0a276d75baef6432da4db5efb92f7c9b35dd5198d', + 45728, + ), + 400: GoogleFontsFileSingle( + 'NotoSansThai', + 400, + '5f71b18a03432951e2bce4e74497752958bd8c9976be06201af5390d47922be3', + 45636, + ), + 500: GoogleFontsFileSingle( + 'NotoSansThai', + 500, + '4c82507facc222df924a0272cda2bfdddc629de12b5684816aea0eb5851a61a7', + 45720, + ), + 600: GoogleFontsFileSingle( + 'NotoSansThai', + 600, + 'e81c6d83f8a625690b1ecc5de4f6b7b66a4d2ee9cbaf5b4f9ede73359c1db064', + 45732, + ), + 700: GoogleFontsFileSingle( + 'NotoSansThai', + 700, + '81bba197f8c779233db14166526e226f68e60cd9e33f2046b80f8075158cb433', + 45640, + ), + 800: GoogleFontsFileSingle( + 'NotoSansThai', + 800, + '7ae7ca1dae7a3df8e839ae08364e14e8e015337bab7dc2842abfc3315e477404', + 45704, + ), + 900: GoogleFontsFileSingle( + 'NotoSansThai', + 900, + '689d439d52c795a225c7fe4657a1072151407a86cc2910a51280337b8b1f57a3', + 45584, + ), }; /// Noto Sans Hebrew (Hebrew) /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+Hebrew -final _notoSansHebrew = { - 100: GoogleFontsFile( +final _notoSansHebrew = { + 100: GoogleFontsFileSingle( 'NotoSansHebrew', 100, '724a57dd8003a31bad4428c37d10b2777cec5b5bfd20c6ed1be44d265989b599', 46472, ), - 200: GoogleFontsFile( + 200: GoogleFontsFileSingle( 'NotoSansHebrew', 200, 'ee40f0088e4408bd36620fd1fa7290fa145bf8964d2368aa181794e5b17ad819', 46532, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSansHebrew', 300, '5686c511d470cd4e52afd09f7e1f004efe33549ff0d38cb23fe3621de1969cc9', 46488, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSansHebrew', 400, '95e23e29b8422a9a461300a8b8e97630d8a2b8de319a9decbf53dc51e880ac41', 46476, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSansHebrew', 500, '7fa6696c1d7d0d7f4ac63f1c5dafdc52bf0035a3d5b63a181b58e5515af338f6', 46652, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSansHebrew', 600, 'cc6deb0701c8034e8ca4eb52ad13770cbe6e494a2bedb91238ad5cb7c591f0ae', 46648, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSansHebrew', 700, 'fbb2c56fd00f54b81ecb4da7033e1729f1c3fd2b14f19a15db35d3f3dd5aadf9', 46440, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSansHebrew', 800, '0fb06ecce97f71320c91adf9be6369c8c12979ac65d229fa7fb123f2476726a1', 46472, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSansHebrew', 900, '8638b2f26a6e16bacf0b34c34d5b8a62efa912a3a90bfb93f0eb25a7b3f8705e', @@ -280,56 +587,56 @@ final _notoSansHebrew = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Sans+Arabic -final _notoSansArabic = { - 100: GoogleFontsFile( +final _notoSansArabic = { + 100: GoogleFontsFileSingle( 'NotoSansArabic', 100, '6cf2614bfc2885011fd9d47b2bcc7e5a576b3e35d379d4301d8247683a680245', 162152, ), - 200: GoogleFontsFile( + 200: GoogleFontsFileSingle( 'NotoSansArabic', 200, 'cecf509869241973813ea04cf6c437ff1e571722fcd54e329880185baf750b19', 162412, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSansArabic', 300, 'c5219bd6425340861eb21a05d40d54da31875cb534dd128d5799b6b83674b9d1', 162324, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSansArabic', 400, '25c2bf5bc8222800e2d8887c3af985f61d5803177bd92b355cb8bffa09c48862', 161592, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSansArabic', 500, '47f226b1505792703ac273600be1dbce8c3cc83cd1981b3db5ef15e0f09bdd8a', 162156, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSansArabic', 600, '332c2d597ed4d1f4d1ed84ed493a341cf81515f5e4d392789a4764e084ff4f1f', 162512, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSansArabic', 700, '9235e0a73b449ef9a790df7bf5933644ede59c06099f7e96d8cda26c999641cd', 162268, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSansArabic', 800, '3614725eeafdb55d8eeabb81fb6fb294a807327fa01c2230b4e074f56922d0b5', 162896, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSansArabic', 900, 'cdbb85b809be063fb065f55b7226dc5161f4804795be56e007d7d3ce70208446', @@ -341,72 +648,117 @@ final _notoSansArabic = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif -final _notoSerif = { - 100: GoogleFontsFile('NotoSerif', 100, '7fd15a02691cfb99c193341bbb082778b1f3ca27e15fdcb7076816591994b7c7', 452700), - 200: GoogleFontsFile('NotoSerif', 200, '9446cf19cd57af964054d0afd385b76f9dec5e3b927c74a2d955041f97fad39b', 453240), - 300: GoogleFontsFile('NotoSerif', 300, '384650b173fced05061be4249607b7caedbc6ba463724075c3ede879ee78d456', 453240), - 400: GoogleFontsFile('NotoSerif', 400, 'b7373b9f9dab0875961c5d214edef00a9384ab593cde30c6462d7b29935ef8b2', 452276), - 500: GoogleFontsFile('NotoSerif', 500, '105a9e9c9bb80bcf8f8c408ed3473f1d9baad881686ea4602ecebebf22bbed50', 453160), - 600: GoogleFontsFile('NotoSerif', 600, '30257a49c70dd2e8abe6cc6a904df863dbc6f9ccf85f4b28a5c858aaa258eab6', 453104), - 700: GoogleFontsFile('NotoSerif', 700, 'dad0f53be4da04bfb608c81cfb72441fba851b336b2bd867592698cfaa2a0c3c', 452576), - 800: GoogleFontsFile('NotoSerif', 800, '12c5c47e6810fc5ea4291b6948adfba87c366eb3c081d66c99f989efd2b55975', 454040), - 900: GoogleFontsFile('NotoSerif', 900, '16f59df53d64f8a896e3dcacadc5b78e8b5fb503318bf01d9ddbe00e90dcceea', 453924), +final _notoSerif = { + 100: GoogleFontsFileSingle( + 'NotoSerif', + 100, + '7fd15a02691cfb99c193341bbb082778b1f3ca27e15fdcb7076816591994b7c7', + 452700, + ), + 200: GoogleFontsFileSingle( + 'NotoSerif', + 200, + '9446cf19cd57af964054d0afd385b76f9dec5e3b927c74a2d955041f97fad39b', + 453240, + ), + 300: GoogleFontsFileSingle( + 'NotoSerif', + 300, + '384650b173fced05061be4249607b7caedbc6ba463724075c3ede879ee78d456', + 453240, + ), + 400: GoogleFontsFileSingle( + 'NotoSerif', + 400, + 'b7373b9f9dab0875961c5d214edef00a9384ab593cde30c6462d7b29935ef8b2', + 452276, + ), + 500: GoogleFontsFileSingle( + 'NotoSerif', + 500, + '105a9e9c9bb80bcf8f8c408ed3473f1d9baad881686ea4602ecebebf22bbed50', + 453160, + ), + 600: GoogleFontsFileSingle( + 'NotoSerif', + 600, + '30257a49c70dd2e8abe6cc6a904df863dbc6f9ccf85f4b28a5c858aaa258eab6', + 453104, + ), + 700: GoogleFontsFileSingle( + 'NotoSerif', + 700, + 'dad0f53be4da04bfb608c81cfb72441fba851b336b2bd867592698cfaa2a0c3c', + 452576, + ), + 800: GoogleFontsFileSingle( + 'NotoSerif', + 800, + '12c5c47e6810fc5ea4291b6948adfba87c366eb3c081d66c99f989efd2b55975', + 454040, + ), + 900: GoogleFontsFileSingle( + 'NotoSerif', + 900, + '16f59df53d64f8a896e3dcacadc5b78e8b5fb503318bf01d9ddbe00e90dcceea', + 453924, + ), }; /// Noto Serif (Italic) /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif -final _notoSerifItalic = { - 100: GoogleFontsFile( +final _notoSerifItalic = { + 100: GoogleFontsFileSingle( 'NotoSerif-Italic', 100, '98c7bc89a0eca32e9045076dd4557dadf866820b3faf5dffe946614cd59bdbb8', 479008, ), - 200: GoogleFontsFile( + 200: GoogleFontsFileSingle( 'NotoSerif-Italic', 200, '24a3e4603729024047e3af2a77e85fd3064c604b193add5b5ecb48fdeb630f4e', 479532, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSerif-Italic', 300, '940fb65bf51f2a2306bc12343c9661aa4309634ea15bf2b1a0c8da2d23e9e9f3', 479180, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSerif-Italic', 400, '65aae32ed0a63e3f6ce0fcde1cd5d04cd179699f7e1fef0d36a24948a3b17ce3', 477448, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSerif-Italic', 500, '322ec18ea04041aabc9f9b3529ff23e7d4e4e18d4330d39d4d422058c66ddded', 478256, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSerif-Italic', 600, '77e9996939afbc0723270879a0754de4374091b9b856f19790c098292992859c', 478316, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSerif-Italic', 700, 'b4cf981f0033c2e3d72585d84de3980bdfb87eaa4fe1d95392025ecd0fe0b83c', 477644, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSerif-Italic', 800, 'a9d0052ceaeea5a1962b7b1a23d995e39dd299ae59cfc288d3e9a68f1bf002e7', 478924, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSerif-Italic', 900, '99f429bfa3aea82cc9620a6242992534d8c7b10f75d0ec7ca15e1790ca315de7', @@ -418,50 +770,50 @@ final _notoSerifItalic = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif+SC -final _notoSerifSc = { - 200: GoogleFontsFile( +final _notoSerifSc = { + 200: GoogleFontsFileSingle( 'NotoSerifSC', 200, '288d1ce3098084328c59b62c0ee3ae79a41f2c29eef8c0b2ba9384c2c18f41ed', 14778664, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSerifSC', 300, '7725ad7c403a2d10fd0fe29ae5d50445057a3559c348d67f129d0c9b8521bce8', 14780440, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSerifSC', 400, 'a17a0dbf1d43a65b75ebd0222a6aa4e6a6fb68f8ecc982c05c9584717ed3567f', 14781184, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSerifSC', 500, '6a74a2bb8923bef7e34b0436f0edd9ab03e3369fdeabb41807b820e6127fa4e6', 14781200, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSerifSC', 600, 'ebbd878444e9c226709d1259352d9d821849ee8105b5191d44101889603e154b', 14780624, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSerifSC', 700, 'bf6e98a81314a396a59661bf892ac872a9338c1b252845bec5659af39ca2304f', 14780140, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSerifSC', 800, '13be96afae56fd632bbf58ec62eb7b295af62fb6c7b3e16eff73748f0e04daf9', 14780920, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSerifSC', 900, 'e50e6bffa405fcb45583a0f40f120e1c158b83b4a17fae29bbe2359d36a5b831', @@ -473,80 +825,160 @@ final _notoSerifSc = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif+TC -final _notoSerifTc = { - 200: GoogleFontsFile('NotoSerifTC', 200, '7d21dcf9bae351366c21de7a554917af318fdf928b5f17a820b547584ebd3b03', 9926428), - 300: GoogleFontsFile('NotoSerifTC', 300, '2816a6528f03c7c7364da893e52ee3247622aa67efd5b96fac5c800af0cf7cfd', 9928912), - 400: GoogleFontsFile('NotoSerifTC', 400, '33247894b46a436114cb173a756d5f5a698f485c9cd88427a50c72301a81282f', 9930576), - 500: GoogleFontsFile('NotoSerifTC', 500, '3b3fa68244c613cee26f10dae75f702d5c61908973a763f2a87a4d3c9c14298a', 9932116), - 600: GoogleFontsFile('NotoSerifTC', 600, '1251e0304fa33bbf5c44cb361a0a969f998af22377a7b8e0bd9e862cf6c45d76', 9932824), - 700: GoogleFontsFile('NotoSerifTC', 700, 'db3ce7ba3443c00e9ff3ba87ebc51838598cb44bc25ea946480f2aebd290ad0e', 9933360), - 800: GoogleFontsFile('NotoSerifTC', 800, '96de55c76632a173cbb6ec9224dbd3040fa75234fadee1d7d03b081debbbdd37', 9933988), - 900: GoogleFontsFile('NotoSerifTC', 900, '2b58e95c7c7a35311152cb28da071dd10a156c30b1cfde117bac68cdca4984ea', 9934072), +final _notoSerifTc = { + 200: GoogleFontsFileSingle( + 'NotoSerifTC', + 200, + '7d21dcf9bae351366c21de7a554917af318fdf928b5f17a820b547584ebd3b03', + 9926428, + ), + 300: GoogleFontsFileSingle( + 'NotoSerifTC', + 300, + '2816a6528f03c7c7364da893e52ee3247622aa67efd5b96fac5c800af0cf7cfd', + 9928912, + ), + 400: GoogleFontsFileSingle( + 'NotoSerifTC', + 400, + '33247894b46a436114cb173a756d5f5a698f485c9cd88427a50c72301a81282f', + 9930576, + ), + 500: GoogleFontsFileSingle( + 'NotoSerifTC', + 500, + '3b3fa68244c613cee26f10dae75f702d5c61908973a763f2a87a4d3c9c14298a', + 9932116, + ), + 600: GoogleFontsFileSingle( + 'NotoSerifTC', + 600, + '1251e0304fa33bbf5c44cb361a0a969f998af22377a7b8e0bd9e862cf6c45d76', + 9932824, + ), + 700: GoogleFontsFileSingle( + 'NotoSerifTC', + 700, + 'db3ce7ba3443c00e9ff3ba87ebc51838598cb44bc25ea946480f2aebd290ad0e', + 9933360, + ), + 800: GoogleFontsFileSingle( + 'NotoSerifTC', + 800, + '96de55c76632a173cbb6ec9224dbd3040fa75234fadee1d7d03b081debbbdd37', + 9933988, + ), + 900: GoogleFontsFileSingle( + 'NotoSerifTC', + 900, + '2b58e95c7c7a35311152cb28da071dd10a156c30b1cfde117bac68cdca4984ea', + 9934072, + ), }; /// Noto Serif JP (Japanese) /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif+JP -final _notoSerifJp = { - 200: GoogleFontsFile('NotoSerifJP', 200, '320e653bbc19e207ade23a39d4896aee4424d85e213f6c3f05584d1dc358eaf3', 7999636), - 300: GoogleFontsFile('NotoSerifJP', 300, 'b01bd95435bede8e6e55adde97d61d85cf3cad907a8e5e21df3fdee97436c972', 8000752), - 400: GoogleFontsFile('NotoSerifJP', 400, '100644e0b414be1c2b1f524e63cb888a8ca2a29c59bc685b1d3a1dccdb8bef3d', 8000776), - 500: GoogleFontsFile('NotoSerifJP', 500, '7f2c9f09930f9571d72946c4836178d99966b6e3dae4d0fb6a39d9278a1979e7', 7999616), - 600: GoogleFontsFile('NotoSerifJP', 600, '53bcadccd57b01926f9da05cb4c3edf4a572fe9918d463b16ce2c8e76adcc059', 7997840), - 700: GoogleFontsFile('NotoSerifJP', 700, 'afcb90bae847b37af92ad759d2ed65ab5691eb6f76180a9f3f3eae9121afc30c', 7995008), - 800: GoogleFontsFile('NotoSerifJP', 800, '6341d1d0229059ed23e9f8293d29052cdc869a8a358118109165e8979c395342', 7994148), - 900: GoogleFontsFile('NotoSerifJP', 900, 'cb22da84d7cef667d91b79672b6a6457bcb22c9354ad8e96184a558a1eeb5786', 7992068), +final _notoSerifJp = { + 200: GoogleFontsFileSingle( + 'NotoSerifJP', + 200, + '320e653bbc19e207ade23a39d4896aee4424d85e213f6c3f05584d1dc358eaf3', + 7999636, + ), + 300: GoogleFontsFileSingle( + 'NotoSerifJP', + 300, + 'b01bd95435bede8e6e55adde97d61d85cf3cad907a8e5e21df3fdee97436c972', + 8000752, + ), + 400: GoogleFontsFileSingle( + 'NotoSerifJP', + 400, + '100644e0b414be1c2b1f524e63cb888a8ca2a29c59bc685b1d3a1dccdb8bef3d', + 8000776, + ), + 500: GoogleFontsFileSingle( + 'NotoSerifJP', + 500, + '7f2c9f09930f9571d72946c4836178d99966b6e3dae4d0fb6a39d9278a1979e7', + 7999616, + ), + 600: GoogleFontsFileSingle( + 'NotoSerifJP', + 600, + '53bcadccd57b01926f9da05cb4c3edf4a572fe9918d463b16ce2c8e76adcc059', + 7997840, + ), + 700: GoogleFontsFileSingle( + 'NotoSerifJP', + 700, + 'afcb90bae847b37af92ad759d2ed65ab5691eb6f76180a9f3f3eae9121afc30c', + 7995008, + ), + 800: GoogleFontsFileSingle( + 'NotoSerifJP', + 800, + '6341d1d0229059ed23e9f8293d29052cdc869a8a358118109165e8979c395342', + 7994148, + ), + 900: GoogleFontsFileSingle( + 'NotoSerifJP', + 900, + 'cb22da84d7cef667d91b79672b6a6457bcb22c9354ad8e96184a558a1eeb5786', + 7992068, + ), }; /// Noto Serif KR (Korean) /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif+KR -final _notoSerifKr = { - 200: GoogleFontsFile( +final _notoSerifKr = { + 200: GoogleFontsFileSingle( 'NotoSerifKR', 200, '54ba0237db05724a034c17d539fb253d29059dcb908cfc953c93b3e0d9de8197', 14020456, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSerifKR', 300, 'ae26b0d843cb7966777c3b764139d0de052c62e4bf52e47e24b20da304b17101', 14029668, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSerifKR', 400, '558c8dac58a96ed9bd55c0e3b605699b9ca87545eaba6e887bbf5c07a4e77e61', 14032260, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSerifKR', 500, 'f9534728d53d16ffa1e8a1382d95495e5ba8779be7cc7c70d2d40fff283bae93', 14041584, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSerifKR', 600, 'c571b015c56cee39099f0aaeeece3b81c49a8b206dd2ab577c03ca6bd4e2a7bb', 14040680, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSerifKR', 700, 'f5397eff043cbe24929663e25ddb03a3b383195c8b877b6a4fcc48ecc8247002', 14038616, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSerifKR', 800, 'abb4439400202f9efd9863fad31138021b95a579acb4ae98516311da0bbae842', 14036636, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSerifKR', 900, '17b5842749bdec2f53cb3c0ccbe8292ddf025864e0466fad64ca7b96e9f7be06', @@ -558,72 +990,117 @@ final _notoSerifKr = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Serif+Thai -final _notoSerifThai = { - 100: GoogleFontsFile('NotoSerifThai', 100, '5eb35c0094128d7d01680b8843b2da94cc9dc4da0367bd73d9067287b48cc074', 59812), - 200: GoogleFontsFile('NotoSerifThai', 200, '48d9621d9f86d32d042924a1dca011561a6e12bb6577ecf77737d966721c6f96', 59968), - 300: GoogleFontsFile('NotoSerifThai', 300, 'd7e9e8ab36992509761cfbb52a8ccc910571ef167bd2cf9a15b7e393185aeadf', 59908), - 400: GoogleFontsFile('NotoSerifThai', 400, '3b677be028abaef2960675aa839310cf8b76eb02dd776b005e535ce8fd7b0dba', 59668), - 500: GoogleFontsFile('NotoSerifThai', 500, '269e49f943f4d5e3caebf7d381eca11ec24a3179713e9fc9594664d29f00638b', 59904), - 600: GoogleFontsFile('NotoSerifThai', 600, 'c2f95d912f539a2afb1a4fcaff25b3cfec88ff80bab99abc18e7e2b8a2ed0371', 59844), - 700: GoogleFontsFile('NotoSerifThai', 700, '26cc8f7b7d541cc050522a077448d3069e480d35edbd314748ab819fbce36b12', 59760), - 800: GoogleFontsFile('NotoSerifThai', 800, 'c7bcf386351f299d1a0440e23d14334dd32fcc736451a25721557bb13bf7ee9d', 60072), - 900: GoogleFontsFile('NotoSerifThai', 900, '3700c400ed31b5a182e21b6269e583e7dff8b8e16400504a9979684488574efa', 60004), +final _notoSerifThai = { + 100: GoogleFontsFileSingle( + 'NotoSerifThai', + 100, + '5eb35c0094128d7d01680b8843b2da94cc9dc4da0367bd73d9067287b48cc074', + 59812, + ), + 200: GoogleFontsFileSingle( + 'NotoSerifThai', + 200, + '48d9621d9f86d32d042924a1dca011561a6e12bb6577ecf77737d966721c6f96', + 59968, + ), + 300: GoogleFontsFileSingle( + 'NotoSerifThai', + 300, + 'd7e9e8ab36992509761cfbb52a8ccc910571ef167bd2cf9a15b7e393185aeadf', + 59908, + ), + 400: GoogleFontsFileSingle( + 'NotoSerifThai', + 400, + '3b677be028abaef2960675aa839310cf8b76eb02dd776b005e535ce8fd7b0dba', + 59668, + ), + 500: GoogleFontsFileSingle( + 'NotoSerifThai', + 500, + '269e49f943f4d5e3caebf7d381eca11ec24a3179713e9fc9594664d29f00638b', + 59904, + ), + 600: GoogleFontsFileSingle( + 'NotoSerifThai', + 600, + 'c2f95d912f539a2afb1a4fcaff25b3cfec88ff80bab99abc18e7e2b8a2ed0371', + 59844, + ), + 700: GoogleFontsFileSingle( + 'NotoSerifThai', + 700, + '26cc8f7b7d541cc050522a077448d3069e480d35edbd314748ab819fbce36b12', + 59760, + ), + 800: GoogleFontsFileSingle( + 'NotoSerifThai', + 800, + 'c7bcf386351f299d1a0440e23d14334dd32fcc736451a25721557bb13bf7ee9d', + 60072, + ), + 900: GoogleFontsFileSingle( + 'NotoSerifThai', + 900, + '3700c400ed31b5a182e21b6269e583e7dff8b8e16400504a9979684488574efa', + 60004, + ), }; /// Noto Serif Hebrew /// /// See: /// - https://fonts.google.com/specimen/Noto+Serif+Hebrew -final _notoSerifHebrew = { - 100: GoogleFontsFile( +final _notoSerifHebrew = { + 100: GoogleFontsFileSingle( 'NotoSerifHebrew', 100, 'd53174aa0c8cd8df260a9004a3007e393160b062d50f775fecd519f057067cbd', 54652, ), - 200: GoogleFontsFile( + 200: GoogleFontsFileSingle( 'NotoSerifHebrew', 200, 'd31e71918ab5ff0f0e030903449509e146010510779991a47d4a063373f14a7c', 54720, ), - 300: GoogleFontsFile( + 300: GoogleFontsFileSingle( 'NotoSerifHebrew', 300, '7017169ff82520c5bf669e4ab770ca0804795609313ce54c8a29b66df36cd20a', 54804, ), - 400: GoogleFontsFile( + 400: GoogleFontsFileSingle( 'NotoSerifHebrew', 400, '001e675f8528148912f3c8b4ce0f2e3d05c7d6ff0cbaa4c415df9301cfeec28e', 54612, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoSerifHebrew', 500, '4927576763b95c2ed87e58dbef8ac565d8054f419a4641d2eb6bb59afd498e6c', 54704, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoSerifHebrew', 600, 'fd86539b46574a35e1898c62c3e30ff092e1b6588a36660bcf1e91845be1e36a', 54712, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoSerifHebrew', 700, 'eb9fd16284df252ac1e4c53c73617a8e027cf66425e197f39c4cc7e9773baf4a', 54632, ), - 800: GoogleFontsFile( + 800: GoogleFontsFileSingle( 'NotoSerifHebrew', 800, 'cdbfc88d81100057725ac72b7b26cc125b718916102f9771adeeb1b8ab890c36', 54816, ), - 900: GoogleFontsFile( + 900: GoogleFontsFileSingle( 'NotoSerifHebrew', 900, 'ec3cf5173830f6e5485ef7f012b9b8dd0603116b32021d000269bf3dd1f18324', @@ -635,26 +1112,26 @@ final _notoSerifHebrew = { /// /// See: /// * https://fonts.google.com/specimen/Noto+Naskh+Arabic -final _notoNaskhArabic = { - 400: GoogleFontsFile( +final _notoNaskhArabic = { + 400: GoogleFontsFileSingle( 'NotoNaskhArabic', 400, 'a19b33c4365bbd6e3f3ac85864fb134e44358ad188c30a9d67d606685d5261da', 215356, ), - 500: GoogleFontsFile( + 500: GoogleFontsFileSingle( 'NotoNaskhArabic', 500, 'd8639b9c7c51cc662e5cf98ab913988835ca5cfde7fdd6db376c6f39f4ac8ea8', 215768, ), - 600: GoogleFontsFile( + 600: GoogleFontsFileSingle( 'NotoNaskhArabic', 600, '76501d5ae7dea1d55ded66269abc936ece44353e17a70473c64f7072c61d7e89', 215720, ), - 700: GoogleFontsFile( + 700: GoogleFontsFileSingle( 'NotoNaskhArabic', 700, 'bb9d4b9c041d13d8bc2c01fa6c5a4629bb4d19a158eec78a8249420a59418aa4', diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 8fcc2018..54c28fbc 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -41,22 +41,18 @@ void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { await Pdfrx.loadAsset!('packages/pdfrx/assets/pdfium.wasm'); if (!kIsWeb) { debugPrint( - '*********************\n' '⚠️\u001b[37;41;1mDEBUG TIME WARNING: The app is bundling PDFium WASM module (about 4MB) as a part of the app.\u001b[0m\n' - 'For production use (not for Web/Debug), you\'d better remove the PDFium WASM module.\n' - 'See https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\n' - '*********************\n', + '\u001b[91mFor production use (not for Web/Debug), you\'d better remove the PDFium WASM module.\u001b[0m\n' + '\u001b[91mSee https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\u001b[0m\n', ); } } catch (e) { if (kIsWeb) { debugPrint( - '*********************\n' '⚠️\u001b[37;41;1mDEBUG TIME WARNING: The app is running on Web, but the PDFium WASM module is not bundled with the app.\u001b[0m\n' - 'Make sure to include the PDFium WASM module in your web project.\n' - 'If you explicitly set Pdfrx.pdfiumWasmModulesUrl, you can ignore this warning.\n' - 'See https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\n' - '*********************\n', + '\u001b[91mMake sure to include the PDFium WASM module in your web project.\u001b[0m\n' + '\u001b[91mIf you explicitly set Pdfrx.pdfiumWasmModulesUrl, you can ignore this warning.\u001b[0m\n' + '\u001b[91mSee https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx#note-for-building-release-builds for more details.\u001b[0m\n', ); } } From 4875081c0ab61b2fd3d11b3ffe591725f041717f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 8 Sep 2025 22:55:37 +0900 Subject: [PATCH 301/663] WIP --- README.md | 4 ++-- packages/pdfrx_engine/example/main.dart | 2 +- packages/pdfrx_engine/pubspec.yaml | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3620172d..923e340e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.10 + pdfrx: ^2.1.12 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.13 + pdfrx_engine: ^0.1.15 ``` ## Development diff --git a/packages/pdfrx_engine/example/main.dart b/packages/pdfrx_engine/example/main.dart index 598fe8d6..5017cfb8 100644 --- a/packages/pdfrx_engine/example/main.dart +++ b/packages/pdfrx_engine/example/main.dart @@ -55,7 +55,7 @@ Future main(List args) async { await outputImageFile.writeAsBytes(img.encodePng(image)); final outputTextFile = File('$outputDir/page_$pageNumber.txt'); - await outputTextFile.writeAsString(await page.loadText()); + await outputTextFile.writeAsString((await page.loadText())?.fullText ?? ''); } // Clean up diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 3873fb77..cb32584d 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -9,7 +9,8 @@ environment: sdk: ^3.9.0 resolution: workspace -# Add regular dependencies here. +# Some of the dependencies are intentionally not pinned to a specific version +# to allow for more flexibility in resolving versions when used in larger projects. dependencies: collection: crypto: ^3.0.6 From b3c497a89581ccebaccf08df1ec3de839e5906df Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Sep 2025 06:37:38 +0900 Subject: [PATCH 302/663] 2.1.13 FIXED: #443 --- CLAUDE.md | 18 ++---------------- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/bin/remove_wasm_modules.dart | 10 +++++----- packages/pdfrx/pubspec.yaml | 4 ++-- pubspec.yaml | 3 --- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index df0dd228..54ce3261 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,18 +21,7 @@ pdfrx is a monorepo containing two packages: ### Monorepo Management -This project uses Melos for managing the multi-package repository: - -```bash -# Install melos globally (if not already installed) -dart pub global activate melos - -# Bootstrap the project (install dependencies for all packages) -melos bootstrap - -# Run analysis on all packages -melos analyze -``` +This project uses pub workspace for managing the multi-package repository. All you have to do is to run `dart pub get` on somewhere in the repo directory. ### Basic Flutter Commands @@ -104,7 +93,7 @@ Both packages may need to be released when changes are made: - Consider to add notes for new features or breaking changes - Notify the owner if you find any issues with the example app or documentation 4. Update `README.md` on the repo root if needed -5. Run `melos bootstrap` to update all dependencies +5. Run `dart pub get` to update all dependencies 6. Run tests to ensure everything works - Run `dart test` in `packages/pdfrx_engine/` - Run `flutter test` in `packages/pdfrx/` @@ -192,9 +181,6 @@ dart test # Test pdfrx Flutter plugin cd packages/pdfrx flutter test - -# Run all tests using melos -melos run test ``` ## Platform-Specific Notes diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index b268b09e..e376f2d4 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.13 + +- FIXED: [#443](https://github.com/espresso3389/pdfrx/issues/443) `dart run pdfrx:remove_wasm_modules` failure + # 2.1.12 - **BREAKING**: API changes in text loading methods - `loadText()` and `loadTextCharRects()` are now integrated into `loadText()` ([#434](https://github.com/espresso3389/pdfrx/issues/434)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 004491f2..b56ba239 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.12 + pdfrx: ^2.1.13 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart index e7382010..c93fb36e 100644 --- a/packages/pdfrx/bin/remove_wasm_modules.dart +++ b/packages/pdfrx/bin/remove_wasm_modules.dart @@ -22,17 +22,17 @@ Future main(List args) async { } } - final pubspecLock = File(path.join(projectRoot, 'pubspec.lock')); - if (!pubspecLock.existsSync()) { - print('No pubspec.lock found in $projectRoot'); + final projectPubspecYaml = File(path.join(projectRoot, 'pubspec.yaml')); + if (!projectPubspecYaml.existsSync()) { + print('No pubspec.yaml found in $projectRoot'); return 2; } - final deps = await oss.listDependencies(pubspecLockPath: pubspecLock.path); + final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); final pdfrxWasmPackage = deps.allDependencies.firstWhere((p) => p.name == 'pdfrx'); print('Found: ${pdfrxWasmPackage.name} ${pdfrxWasmPackage.version}: ${pdfrxWasmPackage.pubspecYamlPath}'); - final pubspecPath = pdfrxWasmPackage.pubspecYamlPath!; + final pubspecPath = pdfrxWasmPackage.pubspecYamlPath; final pubspecFile = File(pubspecPath); if (!pubspecFile.existsSync()) { diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index fa11469c..bcd40c78 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.12 +version: 2.1.13 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -28,7 +28,7 @@ dependencies: url_launcher: ^6.3.1 vector_math: ^2.1.4 web: ^1.1.1 - dart_pubspec_licenses: ^3.0.4 + dart_pubspec_licenses: ^3.0.7 dev_dependencies: ffigen: ^19.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 9f4d3288..be7db1ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,7 @@ name: pdfrx_workspace environment: sdk: ">=3.9.0 <4.0.0" -dev_dependencies: - melos: ^7.1.0 workspace: - packages/pdfrx - packages/pdfrx_engine - packages/pdfrx/example/viewer -melos: From 7ffd876f06205085e63e45769c4331d3296b3771 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Sep 2025 06:49:56 +0900 Subject: [PATCH 303/663] Update CI scripts not to use melos --- .github/workflows/build-test.yml | 96 +++++++++--------------------- .github/workflows/github-pages.yml | 5 -- README.md | 13 +--- 3 files changed, 30 insertions(+), 84 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index deee5d6b..229b0fee 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -55,13 +55,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '18' - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -70,17 +70,11 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v yes | ~/flutter/bin/flutter doctor --android-licenses - - - name: Install melos - run: ~/flutter/bin/dart pub global activate melos - - - name: Bootstrap monorepo - run: ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build App Bundle working-directory: packages/pdfrx/example/viewer run: flutter build apk --debug --verbose @@ -91,7 +85,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -99,16 +93,11 @@ jobs: ~/flutter/bin/flutter config --no-enable-android --no-enable-macos-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v - - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build iOS (no signing) working-directory: packages/pdfrx/example/viewer run: flutter build ios --debug --no-codesign --verbose @@ -119,7 +108,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -127,16 +116,11 @@ jobs: ~/flutter/bin/flutter config --no-enable-android --no-enable-ios ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v - - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build macOS working-directory: packages/pdfrx/example/viewer run: flutter build macos --debug --verbose @@ -147,12 +131,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Install Linux dependencies run: | sudo apt-get update -y sudo apt-get install -y ninja-build libgtk-3-dev - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -160,16 +144,11 @@ jobs: ~/flutter/bin/flutter config --no-enable-android ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v - - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build Linux working-directory: packages/pdfrx/example/viewer run: flutter build linux --debug --verbose @@ -180,12 +159,12 @@ jobs: runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - + - name: Install Linux dependencies run: | sudo apt-get update -y sudo apt-get install -y ninja-build libgtk-3-dev - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -193,16 +172,11 @@ jobs: ~/flutter/bin/flutter config --no-enable-android ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v - - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build Linux working-directory: packages/pdfrx/example/viewer run: flutter build linux --debug --verbose @@ -213,7 +187,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v4 - + - name: Setup Flutter shell: pwsh run: | @@ -222,16 +196,11 @@ jobs: C:\flutter\bin\flutter.bat config --no-enable-android C:\flutter\bin\flutter.bat channel stable C:\flutter\bin\flutter.bat doctor -v - - - name: Melos setup - run: | - C:\flutter\bin\dart pub global activate melos - C:\flutter\bin\dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat pub get - + - name: Build Windows working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat build windows --debug --verbose @@ -243,7 +212,7 @@ jobs: # continue-on-error: true # ARM64 build might fail on x64 runners # steps: # - uses: actions/checkout@v4 - + # - name: Setup Flutter # shell: pwsh # run: | @@ -252,14 +221,12 @@ jobs: # C:\flutter\bin\flutter.bat config --no-enable-android # C:\flutter\bin\flutter.bat channel stable # C:\flutter\bin\flutter.bat doctor -v - # - name: Melos setup - # run: | - # C:\flutter\bin\dart pub global activate melos - # C:\flutter\bin\dart pub global run melos:melos bootstrap + # - name: Monorepo setup + # run: ~/flutter/bin/dart pub get # - name: Install dependencies # working-directory: packages/pdfrx/example/viewer # run: C:\flutter\bin\flutter.bat pub get - + # - name: Build Windows ARM64 # working-directory: packages/pdfrx/example/viewer # run: C:\flutter\bin\flutter.bat build windows --debug --verbose @@ -270,7 +237,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -278,20 +245,15 @@ jobs: ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v - - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - + - name: Build Web working-directory: packages/pdfrx/example/viewer run: flutter build web --verbose - + - name: Build Web (WASM) working-directory: packages/pdfrx/example/viewer run: flutter build web --wasm --verbose diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 834acdc1..6207ad5a 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -27,11 +27,6 @@ jobs: FLUTTER_VERSION=$(flutter --version | head -n 1 | awk '{print $2}') echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV - - name: Melos setup - run: | - ~/flutter/bin/dart pub global activate melos - ~/flutter/bin/dart pub global run melos:melos bootstrap - - name: Check pdfrx version run: | PDFRX_VERSION=$(awk -F": " '/^version:/ {print $2}' pubspec.yaml) diff --git a/README.md b/README.md index 923e340e..cd34a5b5 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,7 @@ dependencies: ## Development -This is a monorepo managed with [Melos](https://melos.invertase.dev/). To work with the packages: - -```bash -# Install melos globally -dart pub global activate melos - -# Bootstrap the project -melos bootstrap - -# Run analysis on all packages -melos analyze -``` +This is a monorepo managed with pub workspaces. Just do `dart pub get` on some directory inside the repo to obtain all the dependencies. ## Example Application From 3a00462ba6878691cdc1c39705611a26b77906ca Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 09:38:40 +0900 Subject: [PATCH 304/663] #396, #460 Enhance podspec script to check for existing PDFium frameworks before downloading --- packages/pdfrx/darwin/pdfrx.podspec | 27 ++++++++++++++----- .../pdfrx/example/viewer/ios/Podfile.lock | 8 +++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index ce5e3cd7..a8f613c7 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -6,7 +6,7 @@ lib_tag = 'pdfium-apple-v11' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.7' + s.version = '0.0.8' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. @@ -36,11 +36,26 @@ Pod::Spec.new do |s| s.prepare_command = <<-CMD mkdir -p pdfium/.lib/#{lib_tag} cd pdfium/.lib/#{lib_tag} - rm -rf ios.zip macos.zip ios/ macos/ - curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip - unzip -o ios.zip - curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip - unzip -o macos.zip + # Check if iOS framework headers exist + if [ ! -f "ios/pdfium.xcframework/ios-arm64/Headers/fpdfview.h" ]; then + echo "Downloading iOS PDFium framework..." + rm -rf ios.zip ios/ + curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip + unzip -o ios.zip + rm -f ios.zip + else + echo "iOS PDFium framework already exists, skipping download." + fi + # Check if macOS framework headers exist + if [ ! -f "macos/pdfium.xcframework/macos-arm64_x86_64/Headers/fpdfview.h" ]; then + echo "Downloading macOS PDFium framework..." + rm -rf macos.zip macos/ + curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip + unzip -o macos.zip + rm -f macos.zip + else + echo "macOS PDFium framework already exists, skipping download." + fi CMD s.swift_version = '5.0' diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 459df8ab..67e482f1 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c + file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 5e9c3a2528cbc4a6ea19abfa72d6c132b5bf811f - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + pdfrx: aab50d03b29bd4fbfec302e13e72178d0a0ed3d5 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 From fe69be9aa082237798526a0c9712a26dfe0c8af3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 09:47:59 +0900 Subject: [PATCH 305/663] Release pdfrx v2.1.14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhance podspec script to check for existing PDFium frameworks before downloading - Improves build performance on iOS/macOS 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e376f2d4..e815b5a7 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.14 + +- Enhance podspec script to check for existing PDFium frameworks before downloading ([#396](https://github.com/espresso3389/pdfrx/issues/396), [#460](https://github.com/espresso3389/pdfrx/issues/460)) + - Improves build performance by avoiding redundant downloads of PDFium frameworks on iOS/macOS + # 2.1.13 - FIXED: [#443](https://github.com/espresso3389/pdfrx/issues/443) `dart run pdfrx:remove_wasm_modules` failure diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b56ba239..8049b408 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.13 + pdfrx: ^2.1.14 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index bcd40c78..bd5eb525 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.13 +version: 2.1.14 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 050c4268ce1efec144b591e6dc2ccb2491b1ab92 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 18:05:09 +0900 Subject: [PATCH 306/663] Migrate wiki documentation to doc/ directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Imported all wiki pages to doc/ directory - Created comprehensive index in doc/README.md - Updated internal wiki links to reference local files - Added documentation section to main README.md This makes the documentation part of the main repository, improving versioning and accessibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 8 ++ doc/Adding-Page-Number-on-Page-Bottom.md | 15 ++++ doc/Customizing-Key-Handling-on-PdfViewer.md | 43 ++++++++++ doc/Dark-Night-Mode-Support.md | 10 +++ ...tected-PDF-Files-using-PasswordProvider.md | 49 +++++++++++ ...rOverlayBuilder-and-pageOverlaysBuilder.md | 54 ++++++++++++ doc/Document-Loading-Indicator.md | 13 +++ doc/Document-Outline-(a.k.a-Bookmarks).md | 11 +++ "doc/Double\342\200\220tap-to-Zoom.md" | 28 ++++++ doc/PDF-Link-Handling.md | 86 +++++++++++++++++++ doc/Page-Layout-Customization.md | 84 ++++++++++++++++++ doc/README.md | 40 +++++++++ doc/Showing-Scroll-Thumbs.md | 40 +++++++++ doc/Text-Search.md | 82 ++++++++++++++++++ doc/Text-Selection.md | 52 +++++++++++ doc/[2.1.X]-Text-Search.md | 80 +++++++++++++++++ doc/[2.1.X]-Text-Selection.md | 40 +++++++++ doc/[macOS]-Deal-with-App-Sandbox.md | 22 +++++ doc/pdfrx-Initialization.md | 37 ++++++++ 19 files changed, 794 insertions(+) create mode 100644 doc/Adding-Page-Number-on-Page-Bottom.md create mode 100644 doc/Customizing-Key-Handling-on-PdfViewer.md create mode 100644 doc/Dark-Night-Mode-Support.md create mode 100644 doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md create mode 100644 doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md create mode 100644 doc/Document-Loading-Indicator.md create mode 100644 doc/Document-Outline-(a.k.a-Bookmarks).md create mode 100644 "doc/Double\342\200\220tap-to-Zoom.md" create mode 100644 doc/PDF-Link-Handling.md create mode 100644 doc/Page-Layout-Customization.md create mode 100644 doc/README.md create mode 100644 doc/Showing-Scroll-Thumbs.md create mode 100644 doc/Text-Search.md create mode 100644 doc/Text-Selection.md create mode 100644 doc/[2.1.X]-Text-Search.md create mode 100644 doc/[2.1.X]-Text-Selection.md create mode 100644 doc/[macOS]-Deal-with-App-Sandbox.md create mode 100644 doc/pdfrx-Initialization.md diff --git a/README.md b/README.md index cd34a5b5..4de387f1 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,14 @@ dependencies: pdfrx_engine: ^0.1.15 ``` +## Documentation + +Comprehensive documentation is available in the [doc/](doc/) directory, including: +- Getting started guides +- Feature tutorials +- Platform-specific configurations +- Code examples + ## Development This is a monorepo managed with pub workspaces. Just do `dart pub get` on some directory inside the repo to obtain all the dependencies. diff --git a/doc/Adding-Page-Number-on-Page-Bottom.md b/doc/Adding-Page-Number-on-Page-Bottom.md new file mode 100644 index 00000000..4b7a5772 --- /dev/null +++ b/doc/Adding-Page-Number-on-Page-Bottom.md @@ -0,0 +1,15 @@ +If you want to add page number on each page, you can do that by [PdfViewerParams.pageOverlaysBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html): + +```dart +pageOverlaysBuilder: (context, pageRect, page) { + return [ + Align( + alignment: Alignment.bottomCenter, + child: Text( + page.pageNumber.toString(), + style: const TextStyle(color: Colors.red), + ), + ), + ]; +}, +``` diff --git a/doc/Customizing-Key-Handling-on-PdfViewer.md b/doc/Customizing-Key-Handling-on-PdfViewer.md new file mode 100644 index 00000000..a3b77d68 --- /dev/null +++ b/doc/Customizing-Key-Handling-on-PdfViewer.md @@ -0,0 +1,43 @@ +Because [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) handles certain keys to allow users to scroll/zoom PDF view by keyboards, it may sometimes interfere with other widget's key handling, such as [TextField](https://api.flutter.dev/flutter/material/TextField-class.html)'s text input. + +To customize the key handling behavior, you can use [PdfViewerParams.onKey](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onKey.html) and [PdfViewerParams.keyHandlerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/keyHandlerParams.html). + +## Default implementation + +By default, [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) handles the following keys: + +Key | Description +------------|---------------------- +[pageUp](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/pageUp-constant.html) | Scroll up +[pageDown](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/pageDown-constant.html) | Scroll up +[home](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/home-constant.html) | Go to first page +[end](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/end-constant.html) | go to last page +[equal](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/equal-constant.html) | Combination with `⌘`/`Ctrl` to zoom up (**currently not I18N-ed**) +[minus](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/equal-constant.html) | Combination with `⌘`/`Ctrl` to zoom down +[arrowUp](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowUp-constant.html) | Scroll upward +[arrowDown](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowDown-constant.html) | Scroll downward +[arrowLeft](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowLeft-constant.html) | Scroll to left +[arrowRight](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowRight-constant.html) | Scroll to right + +And, the other keys are **not handled** and handled by other widgets. + +## Overriding the default implementation + +The following fragment illustrates how to use [PdfViewerParams.onKey](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onKey.html): + +```dart +onKey: (params, key, isRealKeyPress) { + if (key == LogicalKeyboardKey.space) { + // handling the key inside the function + handleSpace(); + return true; + } + if (key == LogicalKeyboardKey.arrowLeft || key == LogicalKeyboardKey.arrowRight) { + // returning false to disable the default logic + return false; + } + // returning null to let the default logic to handle the keys + return null; +}, +``` + diff --git a/doc/Dark-Night-Mode-Support.md b/doc/Dark-Night-Mode-Support.md new file mode 100644 index 00000000..d6a8a737 --- /dev/null +++ b/doc/Dark-Night-Mode-Support.md @@ -0,0 +1,10 @@ +[PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) does not have any native dark (or night) mode support but it can be easily implemented using [ColorFiltered](https://api.flutter.dev/flutter/widgets/ColorFiltered-class.html) widget: + +```dart +ColorFiltered( + colorFilter: ColorFilter.mode(Colors.white, darkMode ? BlendMode.difference : BlendMode.dst), + child: PdfViewer.file(filePath, ...), +), +``` + +The trick is originally introduced by [pckimlong](https://github.com/pckimlong). \ No newline at end of file diff --git a/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md new file mode 100644 index 00000000..96911f82 --- /dev/null +++ b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md @@ -0,0 +1,49 @@ +To support password protected PDF files, use [passwordProvider](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPasswordProvider.html) to supply passwords interactively: + +```dart +PdfViewer.asset( + 'assets/test.pdf', + // Most easiest way to return some password + passwordProvider: _showPasswordDialog, + + ... +), + +... + +Future _showPasswordDialog() async { + final textController = TextEditingController(); + return await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: const Text('Enter password'), + content: TextField( + controller: textController, + autofocus: true, + keyboardType: TextInputType.visiblePassword, + obscureText: true, + onSubmitted: (value) => Navigator.of(context).pop(value), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(textController.text), + child: const Text('OK'), + ), + ], + ); + }, + ); +} +``` + +When [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) tries to open a password protected document, it calls the function passed to `passwordProvider` (except the first attempt; see [below](#firstattemptbyemptypassword)) repeatedly to get a new password until the document is successfully opened. And if the function returns null, the viewer will give up the password trials and the function is no longer called. + +### `firstAttemptByEmptyPassword` + +By default, the first password attempt uses empty password. This is because encrypted PDF files frequently use empty password for viewing purpose. It's _normally_ useful but if you want to use authoring password, it can be disabled by setting `firstAttemptByEmptyPassword` to false. \ No newline at end of file diff --git a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md new file mode 100644 index 00000000..38be7037 --- /dev/null +++ b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md @@ -0,0 +1,54 @@ +The following fragment shows red-dot on user tapped location: + +```dart +int? _pageNumber; +Offset? _offsetInPage; + +... + +viewerOverlayBuilder: (context, size, handleLinkTap) => [ + Positioned.fill(child: GestureDetector( + onTapDown: (details) { + // global position -> in-document position + final posInDoc = controller.globalToDocument(details.globalPosition); + if (posInDoc == null) return; + // determine which page contains the point (position) + final pageIndex = controller.layout.pageLayouts.indexWhere((pageRect) => pageRect.contains(posInDoc)); + if (pageIndex < 0) return; + // in-document position -> in-page offset + _offsetInPage = posInDoc - controller.layout.pageLayouts[pageIndex].topLeft; + _pageNumber = pageIndex + 1; + + // NOTE: you're hosting PdfViewer inside some StatefulWidget + // or inside StatefulBuilder + setState(() {}); + }, + )), +], +pageOverlaysBuilder: (context, pageRect, page) { + return [ + if (_pageNumber == page.pageNumber && _offsetInPage != null) + Positioned( + left: _offsetInPage!.dx * controller.currentZoom, // position should be zoomed + top: _offsetInPage!.dy * controller.currentZoom, + child: Container( + width: 10, + height: 10, + color: Colors.red, + ), + ), + ]; +}, +``` + +On [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html); + +- Convert the global tap position to in-document position using [PdfViewerController.globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) + - The in-document position is position in document structure (i.e., page layout in 72-dpi). +- Determine which page contains the position using [PdfViewerController.layout.pageLayouts](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageLayout/pageLayouts.html) +- Convert the in-document position to the in-page position (just subtract the page's top-left position) + +On [PdfViewerParams.pageOverlaysBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html), + +- `pageRect` is the zoomed page rectangle inside the view +- To correctly locate the position, it must be zoomed (by [PdfViewerController.currentZoom](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/currentZoom.html)) diff --git a/doc/Document-Loading-Indicator.md b/doc/Document-Loading-Indicator.md new file mode 100644 index 00000000..125217ea --- /dev/null +++ b/doc/Document-Loading-Indicator.md @@ -0,0 +1,13 @@ +[PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) may take long time to download PDF file and you want to show some loading indicator. You can do that by [PdfViewerParams.loadingBannerBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/loadingBannerBuilder.html): + +```dart +loadingBannerBuilder: (context, bytesDownloaded, totalBytes) { + return Center( + child: CircularProgressIndicator( + // totalBytes may not be available on certain case + value: totalBytes != null ? bytesDownloaded / totalBytes : null, + backgroundColor: Colors.grey, + ), + ); +} +``` \ No newline at end of file diff --git a/doc/Document-Outline-(a.k.a-Bookmarks).md b/doc/Document-Outline-(a.k.a-Bookmarks).md new file mode 100644 index 00000000..6db216a5 --- /dev/null +++ b/doc/Document-Outline-(a.k.a-Bookmarks).md @@ -0,0 +1,11 @@ +PDF defines document outline ([PdfOutlineNode](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfOutlineNode-class.html)), which is sometimes called as bookmarks or index. And you can access it by [PdfDocument.loadOutline](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/loadOutline.html). + +The following fragment obtains it on [PdfViewerParams.onViewerReady](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onViewerReady.html): + +```dart +onViewerReady: (document, controller) async { + outline.value = await document.loadOutline(); +}, +``` + +[PdfOutlineNode](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfOutlineNode-class.html) is tree structured data and for more information, see the usage on the [example code](https://github.com/espresso3389/pdfrx/blob/master/example/viewer/lib/outline_view.dart). diff --git "a/doc/Double\342\200\220tap-to-Zoom.md" "b/doc/Double\342\200\220tap-to-Zoom.md" new file mode 100644 index 00000000..2e73c250 --- /dev/null +++ "b/doc/Double\342\200\220tap-to-Zoom.md" @@ -0,0 +1,28 @@ + +You can implement double-tap-to-zoom feature using [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html): + +```dart +viewerOverlayBuilder: (context, size, handleLinkTap) => [ + GestureDetector( + behavior: HitTestBehavior.translucent, + // Your code here: + onDoubleTap: () { + controller.zoomUp(loop: true); + }, + // If you use GestureDetector on viewerOverlayBuilder, it breaks link-tap handling + // and you should manually handle it using onTapUp callback + onTapUp: (details) { + handleLinkTap(details.localPosition); + }, + // Make the GestureDetector covers all the viewer widget's area + // but also make the event go through to the viewer. + child: IgnorePointer( + child: + SizedBox(width: size.width, height: size.height), + ), + ), + ... +], +``` + +If you want to use [PdfViewerScrollThumb](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerScrollThumb-class.html) with double-tap-to-zoom enabled, place the double-tap-to-zoom code before [PdfViewerScrollThumb](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerScrollThumb-class.html). \ No newline at end of file diff --git a/doc/PDF-Link-Handling.md b/doc/PDF-Link-Handling.md new file mode 100644 index 00000000..b9a44e08 --- /dev/null +++ b/doc/PDF-Link-Handling.md @@ -0,0 +1,86 @@ +To enable links in PDF file, you should set [PdfViewerParams.linkHandlerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/linkHandlerParams.html). + +The following fragment handles user's tap on link: + +```dart +linkHandlerParams: PdfLinkHandlerParams( + onLinkTap: (link) { + // handle URL or Dest + if (link.url != null) { + // FIXME: Don't open the link without prompting user to do so or validating the link destination + launchUrl(link.url!); + } else if (link.dest != null) { + controller.goToDest(link.dest); + } + }, +), +``` + +## Security Considerations on Link Navigation + +It's too dangerous to open link URL without prompting user to do so/validating it. + +The following fragment is an example code to prompt user to open the URL: + +```dart +Future shouldOpenUrl(BuildContext context, Uri url) async { + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: const Text('Navigate to URL?'), + content: SelectionArea( + child: Text.rich( + TextSpan( + children: [ + const TextSpan( + text: + 'Do you want to navigate to the following location?\n'), + TextSpan( + text: url.toString(), + style: const TextStyle(color: Colors.blue), + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Go'), + ), + ], + ); + }, + ); + return result ?? false; +} +``` + +## PDF Destinations + +For PDF destinations, you can use [PdfViewerController.goToDest](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/goToDest.html) to go to the destination. Or you can use [PdfViewerController.calcMatrixForDest](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/calcMatrixForDest.html) to get the matrix for it. + +## Link Appearance + +For link appearance, you can change its color using [PdfLinkHandlerParams.linkColor](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfLinkHandlerParams/linkColor.html). + +For more further customization, you can use [PdfLinkHandlerParams.customPainter](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfLinkHandlerParams/customPainter.html): + +```dart +customPainter: (canvas, pageRect, page, links) { + final paint = Paint() + ..color = Colors.red.withOpacity(0.2) + ..style = PaintingStyle.fill; + for (final link in links) { + // you can customize here to make your own link appearance + final rect = link.rect.toRectInPageRect(page: page, pageRect: pageRect); + canvas.drawRect(rect, paint); + } +} +``` diff --git a/doc/Page-Layout-Customization.md b/doc/Page-Layout-Customization.md new file mode 100644 index 00000000..7031f021 --- /dev/null +++ b/doc/Page-Layout-Customization.md @@ -0,0 +1,84 @@ +## Horizontal Scroll View + +By default, the pages are laid out vertically. +You can customize the layout logic by [PdfViewerParams.layoutPages](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/layoutPages.html): + +```dart +layoutPages: (pages, params) { + final height = + pages.fold(0.0, (prev, page) => max(prev, page.height)) + + params.margin * 2; + final pageLayouts = []; + double x = params.margin; + for (var page in pages) { + pageLayouts.add( + Rect.fromLTWH( + x, + (height - page.height) / 2, // center vertically + page.width, + page.height, + ), + ); + x += page.width + params.margin; + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size(x, height), + ); +}, +``` + +## Facing Pages + +The following code will show pages in "facing-sequential-layout" that is often used in PDF viewer apps: + +```dart +/// Page reading order; true to L-to-R that is commonly used by books like manga or such +var isRightToLeftReadingOrder = false; +/// Use the first page as cover page +var needCoverPage = true; + +... + +layoutPages: (pages, params) { + final width = pages.fold( + 0.0, (prev, page) => max(prev, page.width)); + + final pageLayouts = []; + final offset = needCoverPage ? 1 : 0; + double y = params.margin; + for (int i = 0; i < pages.length; i++) { + final page = pages[i]; + final pos = i + offset; + final isLeft = isRightToLeftReadingOrder + ? (pos & 1) == 1 + : (pos & 1) == 0; + + final otherSide = (pos ^ 1) - offset; + final h = 0 <= otherSide && otherSide < pages.length + ? max(page.height, pages[otherSide].height) + : page.height; + + pageLayouts.add( + Rect.fromLTWH( + isLeft + ? width + params.margin - page.width + : params.margin * 2 + width, + y + (h - page.height) / 2, + page.width, + page.height, + ), + ); + if (pos & 1 == 1 || i + 1 == pages.length) { + y += h + params.margin; + } + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size( + (params.margin + width) * 2 + params.margin, + y, + ), + ); +}, +``` \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..7b259c91 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,40 @@ +# pdfrx Documentation + +Welcome to the pdfrx documentation! This guide provides comprehensive information about using the pdfrx PDF viewer plugin for Flutter. + +## Getting Started + +- [pdfrx Initialization](pdfrx-Initialization.md) - How to properly initialize pdfrx in your app + +## Core Features + +### Text Features +- [Text Search](Text-Search.md) - Implementing text search functionality +- [Text Search (2.1.X)]([2.1.X]-Text-Search.md) - Updated text search API for v2.1+ +- [Text Selection](Text-Selection.md) - Enabling text selection +- [Text Selection (2.1.X)]([2.1.X]-Text-Selection.md) - Updated text selection API for v2.1+ + +### Navigation & Display +- [Document Outline (Bookmarks)](Document-Outline-(a.k.a-Bookmarks).md) - Working with PDF bookmarks +- [PDF Link Handling](PDF-Link-Handling.md) - Handling links within PDFs +- [Page Layout Customization](Page-Layout-Customization.md) - Customizing page layouts +- [Double-tap to Zoom](Double‐tap-to-Zoom.md) - Implementing zoom gestures + +### UI Customization +- [Adding Page Number on Page Bottom](Adding-Page-Number-on-Page-Bottom.md) - Display page numbers +- [Document Loading Indicator](Document-Loading-Indicator.md) - Show loading progress +- [Showing Scroll Thumbs](Showing-Scroll-Thumbs.md) - Display scroll indicators +- [Dark/Night Mode Support](Dark-Night-Mode-Support.md) - Implement dark mode + +### Advanced Topics +- [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts +- [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs +- [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays + +## Platform-Specific + +- [macOS: App Sandbox]([macOS]-Deal-with-App-Sandbox.md) - macOS sandbox configuration + +## API Reference + +For detailed API documentation, visit the [pub.dev documentation](https://pub.dev/documentation/pdfrx/latest/). \ No newline at end of file diff --git a/doc/Showing-Scroll-Thumbs.md b/doc/Showing-Scroll-Thumbs.md new file mode 100644 index 00000000..f9d62f34 --- /dev/null +++ b/doc/Showing-Scroll-Thumbs.md @@ -0,0 +1,40 @@ +By default, the viewer does never show any scroll bars nor scroll thumbs. +You can add scroll thumbs by using [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html): + +```dart +viewerOverlayBuilder: (context, size, handleLinkTap) => [ + // Add vertical scroll thumb on viewer's right side + PdfViewerScrollThumb( + controller: controller, + orientation: ScrollbarOrientation.right, + thumbSize: const Size(40, 25), + thumbBuilder: + (context, thumbSize, pageNumber, controller) => + Container( + color: Colors.black, + // Show page number on the thumb + child: Center( + child: Text( + pageNumber.toString(), + style: const TextStyle(color: Colors.white), + ), + ), + ), + ), + // Add horizontal scroll thumb on viewer's bottom + PdfViewerScrollThumb( + controller: controller, + orientation: ScrollbarOrientation.bottom, + thumbSize: const Size(80, 30), + thumbBuilder: + (context, thumbSize, pageNumber, controller) => + Container( + color: Colors.red, + ), + ), +], +``` + +Basically, [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html) can be used to insert any widgets under viewer's internal [Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html). + +But if you want to place many visual objects that does not interact with user, you'd better use [PdfViewerParams.pagePaintCallback](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html). \ No newline at end of file diff --git a/doc/Text-Search.md b/doc/Text-Search.md new file mode 100644 index 00000000..31f7eab1 --- /dev/null +++ b/doc/Text-Search.md @@ -0,0 +1,82 @@ +**For text search API on pdfrx v2.X, see [Text Search (2.1.X)]([2.1.X]-Text-Search.md).** + +[TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. + +The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html): + +```dart +class _MainPageState extends State { + final controller = PdfViewerController(); + // create a PdfTextSearcher and add a listener to update the GUI on search result changes + late final textSearcher = PdfTextSearcher(controller)..addListener(_update); + + void _update() { + if (mounted) { + setState(() {}); + } + } + + @override + void dispose() { + // dispose the PdfTextSearcher + textSearcher.removeListener(_update); + textSearcher.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Pdfrx example'), + ), + body: PdfViewer.asset( + 'assets/hello.pdf', + controller: controller, + params: PdfViewerParams( + // add pageTextMatchPaintCallback that paints search hit highlights + pagePaintCallbacks: [ + textSearcher.pageTextMatchPaintCallback + ], + ), + ) + ); + } + ... +} +``` + +On the fragment above, it does: + +- Create [TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html) instance +- Add a listener (Using [PdfTextSearcher.addListener](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/addListener.html)) to update UI on search result change +- Add [TextSearcher.pageTextMatchPaintCallback](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/pageTextMatchPaintCallback.html) to [PdfViewerParams.pagePaintCallbacks](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/pagePaintCallbacks.html) to show search matches + +Then, you can use [TextSearcher.startTextSearch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/startTextSearch.html) to search text in the PDF document: + +```dart +textSearcher.startTextSearch('hello', caseInsensitive: true); +``` + +The search starts running in background and the search progress is notified by the listener. + +There are several functions that helps you to navigate user to the search matches: + +- [TextSearcher.goToMatchOfIndex](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToMatchOfIndex.html) to go to the match of the specified index +- [TextSearcher.goToNextMatch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToNextMatch.html) to go to the next match +- [TextSearcher.goToPrevMatch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToPrevMatch.html) to go to the previous match + +You can get the search result (even during the search running) in the list of [PdfTextRangeWithFragments](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextRangeWithFragments-class.html) by [PdfTextSearcher.matches](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/matches.html): + +```dart +for (final match in textSearcher.matches) { + print(match.pageNumber); + ... +} +``` + +You can also cancel the background search: + +```dart +textSearcher.resetTextSearch(); +``` \ No newline at end of file diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md new file mode 100644 index 00000000..4be8901a --- /dev/null +++ b/doc/Text-Selection.md @@ -0,0 +1,52 @@ +**For text selection API on pdfrx v2.1.X, see [Text Selection (2.1.X)]([2.1.X]-Text-Selection.md).** + +The following fragment uses [PdfViewerParams.enableTextSelection](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/enableTextSelection.html) to enable text selection feature: + +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: PdfViewerParams( + enableTextSelection: true, + ... + ), + ... +), +``` + +If you want to handle text selection changes, you can use [PdfViewerParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/onTextSelectionChange.html) like the following fragment: + +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: PdfViewerParams( + enableTextSelection: true, + onTextSelectionChange: (selections) { + ... + }, + ... + ), + ... +), +``` + +`selections` is a list of [PdfPageText](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfPageText-class.html) objects. + +## Further Text Selection Customization + +Instead of using [PdfViewerParams.enableTextSelection](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/enableTextSelection.html), you can also use [PdfViewerParams.selectableRegionInjector](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/selectableRegionInjector.html) to inject your custom [SelectionArea](https://api.flutter.dev/flutter/material/SelectionArea-class.html) or [SelectableRegion](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html): + +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: PdfViewerParams( + selectableRegionInjector: (context, child) { + // Your customized SelectionArea + return SelectionArea( + child: child, + ... + ); + } + ), + ... +), +``` diff --git a/doc/[2.1.X]-Text-Search.md b/doc/[2.1.X]-Text-Search.md new file mode 100644 index 00000000..c136bebe --- /dev/null +++ b/doc/[2.1.X]-Text-Search.md @@ -0,0 +1,80 @@ +[TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. + +The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html): + +```dart +class _MainPageState extends State { + final controller = PdfViewerController(); + // create a PdfTextSearcher and add a listener to update the GUI on search result changes + late final textSearcher = PdfTextSearcher(controller)..addListener(_update); + + void _update() { + if (mounted) { + setState(() {}); + } + } + + @override + void dispose() { + // dispose the PdfTextSearcher + textSearcher.removeListener(_update); + textSearcher.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Pdfrx example'), + ), + body: PdfViewer.asset( + 'assets/hello.pdf', + controller: controller, + params: PdfViewerParams( + // add pageTextMatchPaintCallback that paints search hit highlights + pagePaintCallbacks: [ + textSearcher.pageTextMatchPaintCallback + ], + ), + ) + ); + } + ... +} +``` + +On the fragment above, it does: + +- Create [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) instance +- Add a listener (Using [PdfTextSearcher.addListener](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/addListener.html)) to update UI on search result change +- Add [TextSearcher.pageTextMatchPaintCallback](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/pageTextMatchPaintCallback.html) to [PdfViewerParams.pagePaintCallbacks](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html) to show search matches + +Then, you can use [TextSearcher.startTextSearch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/startTextSearch.html) to search text in the PDF document: + +```dart +textSearcher.startTextSearch('hello', caseInsensitive: true); +``` + +The search starts running in background and the search progress is notified by the listener. + +There are several functions that helps you to navigate user to the search matches: + +- [TextSearcher.goToMatchOfIndex](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToMatchOfIndex.html) to go to the match of the specified index +- [TextSearcher.goToNextMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToNextMatch.html) to go to the next match +- [TextSearcher.goToPrevMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToPrevMatch.html) to go to the previous match + +You can get the search result (even during the search running) in the list of [PdfPageTextRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageTextRange-class.html) by [PdfTextSearcher.matches](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/matches.html): + +```dart +for (final match in textSearcher.matches) { + print(match.pageNumber); + ... +} +``` + +You can also cancel the background search: + +```dart +textSearcher.resetTextSearch(); +``` \ No newline at end of file diff --git a/doc/[2.1.X]-Text-Selection.md b/doc/[2.1.X]-Text-Selection.md new file mode 100644 index 00000000..3066c332 --- /dev/null +++ b/doc/[2.1.X]-Text-Selection.md @@ -0,0 +1,40 @@ +TODO: Update the contents + +On pdfrx 2.0.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). + +Text selection feature is enabled by default and if you want to disable it, do like the following fragment: + +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: PdfViewerParams( + textSelectionParams: PdfTextSelectionParams( + enabled: false, + ), + ... + ), + ... +), +``` + +If you want to handle text selection changes, you can use [PdfTextSelectionParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/onTextSelectionChange.html). + +The handler function receives a parameter of [PdfTextSelection](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection-class.html) and you can obtain the current text selection and its associated text ranges: + +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: PdfViewerParams( + enableTextSelection: true, + textSelectionParams: PdfTextSelectionParams( + onTextSelectionChange: (selections) async { + // Get the selected string + final String text = selections.getSelectedText(); + }, + ), + ... + ), + ... +), +``` + diff --git a/doc/[macOS]-Deal-with-App-Sandbox.md b/doc/[macOS]-Deal-with-App-Sandbox.md new file mode 100644 index 00000000..cf4af1ee --- /dev/null +++ b/doc/[macOS]-Deal-with-App-Sandbox.md @@ -0,0 +1,22 @@ +For macOS, Flutter app restricts its capability by enabling [App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) by default. You can change the behavior by editing your app's entitlements files depending on your configuration. + +- [`macos/Runner/Release.entitlements`](https://github.com/espresso3389/flutter_pdf_render/blob/master/example/macos/Runner/Release.entitlements) +- [`macos/Runner/DebugProfile.entitlements`](https://github.com/espresso3389/flutter_pdf_render/blob/master/example/macos/Runner/DebugProfile.entitlements) + +#### Deal with App Sandbox + +The easiest option to access files on your local storage (i.e. SSD or HDD), set [`com.apple.security.app-sandbox`](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_app-sandbox) to false on your entitlements file though it is not recommended for releasing apps because it completely disables [App Sandbox](https://developer.apple.com/documentation/security/app_sandbox). + +Another option is to use [`com.apple.security.files.user-selected.read-only`](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_user-selected_read-only) along with [file_selector_macos](https://pub.dev/packages/file_selector_macos). The option is better in security than the previous option. + +Anyway, the example code for the plugin illustrates how to download and preview internet hosted PDF file. It uses +[`com.apple.security.network.client`](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client) along with [flutter_cache_manager](https://pub.dev/packages/flutter_cache_manager): + +```xml + + com.apple.security.app-sandbox + + com.apple.security.network.client + + +``` \ No newline at end of file diff --git a/doc/pdfrx-Initialization.md b/doc/pdfrx-Initialization.md new file mode 100644 index 00000000..69b609ae --- /dev/null +++ b/doc/pdfrx-Initialization.md @@ -0,0 +1,37 @@ +If you use Flutter widgets like [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) or [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html), they implicitly initialize the library by calling [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html). + +But if you use [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) directly, you should explicitly do either one of the following ways: + +- Call [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) +- Call [pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) +- [Initialize things by yourself](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization#initialize-things-by-yourself) + +The first one is the recommended and the easiest way to initialize Flutter app. + +For pure Dart apps (or even some of Flutter apps), you can use [pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html). + +## Initialize Things By Yourself + +Basically, these initialization functions do the following things: + +- Set [Pdfrx.getCacheDirectory](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/getCacheDirectory.html) +- Map PdfDocument [factory/interop functions](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions-class.html) to actual platform ones +- Set [Pdfrx.loadAsset](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/loadAsset.html) (Flutter only) +- Download PDFium binary on-demand ([pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) only) + +## Cache Directory + +The mechanism to locate cache directory is different between pure Dart apps and Flutter apps: + +Init. Func. | Underlying API | Notes +------------|----------------|------------------- +[pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) | [Directory.systemTemp](https://api.flutter.dev/flutter/dart-io/Directory/systemTemp.html) | May not be suitable for mobile apps. +[pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) | [path_provider.getTemporaryDirectory](https://pub.dev/documentation/path_provider/latest/path_provider/getTemporaryDirectory.html) | Always app local directory. + +## Download PDFium Binary On-Demand + +For pure Dart apps, because it is typically used on desktop environments, pdfrx downloads PDFium binary if your environment does not have it. + +- PDFium binaries are downloaded from https://github.com/bblanchon/pdfium-binaries/releases +- By default, the binary is downloaded to `[TMP_DIR]/pdfrx.cache` +- You can explicitly specify `libpdfium` shared library file path/name by `PDFIUM_PATH` environment variable From ba980048fa5cb79cdf04b6f767f1cba4e46826a3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 18:08:26 +0900 Subject: [PATCH 307/663] Clean up documentation for v2.1.X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove outdated documentation for older versions - Rename [2.1.X] prefixed files to standard names - Update documentation index - Simplify structure since repo is already on v2.1.X 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- doc/README.md | 2 - doc/Text-Search.md | 22 +++++----- doc/Text-Selection.md | 44 +++++++------------ doc/[2.1.X]-Text-Search.md | 80 ----------------------------------- doc/[2.1.X]-Text-Selection.md | 40 ------------------ 5 files changed, 26 insertions(+), 162 deletions(-) delete mode 100644 doc/[2.1.X]-Text-Search.md delete mode 100644 doc/[2.1.X]-Text-Selection.md diff --git a/doc/README.md b/doc/README.md index 7b259c91..143b136c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -10,9 +10,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ### Text Features - [Text Search](Text-Search.md) - Implementing text search functionality -- [Text Search (2.1.X)]([2.1.X]-Text-Search.md) - Updated text search API for v2.1+ - [Text Selection](Text-Selection.md) - Enabling text selection -- [Text Selection (2.1.X)]([2.1.X]-Text-Selection.md) - Updated text selection API for v2.1+ ### Navigation & Display - [Document Outline (Bookmarks)](Document-Outline-(a.k.a-Bookmarks).md) - Working with PDF bookmarks diff --git a/doc/Text-Search.md b/doc/Text-Search.md index 31f7eab1..c136bebe 100644 --- a/doc/Text-Search.md +++ b/doc/Text-Search.md @@ -1,8 +1,6 @@ -**For text search API on pdfrx v2.X, see [Text Search (2.1.X)]([2.1.X]-Text-Search.md).** +[TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. -[TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. - -The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html): +The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html): ```dart class _MainPageState extends State { @@ -48,11 +46,11 @@ class _MainPageState extends State { On the fragment above, it does: -- Create [TextSearcher](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher-class.html) instance -- Add a listener (Using [PdfTextSearcher.addListener](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/addListener.html)) to update UI on search result change -- Add [TextSearcher.pageTextMatchPaintCallback](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/pageTextMatchPaintCallback.html) to [PdfViewerParams.pagePaintCallbacks](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/pagePaintCallbacks.html) to show search matches +- Create [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) instance +- Add a listener (Using [PdfTextSearcher.addListener](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/addListener.html)) to update UI on search result change +- Add [TextSearcher.pageTextMatchPaintCallback](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/pageTextMatchPaintCallback.html) to [PdfViewerParams.pagePaintCallbacks](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html) to show search matches -Then, you can use [TextSearcher.startTextSearch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/startTextSearch.html) to search text in the PDF document: +Then, you can use [TextSearcher.startTextSearch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/startTextSearch.html) to search text in the PDF document: ```dart textSearcher.startTextSearch('hello', caseInsensitive: true); @@ -62,11 +60,11 @@ The search starts running in background and the search progress is notified by t There are several functions that helps you to navigate user to the search matches: -- [TextSearcher.goToMatchOfIndex](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToMatchOfIndex.html) to go to the match of the specified index -- [TextSearcher.goToNextMatch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToNextMatch.html) to go to the next match -- [TextSearcher.goToPrevMatch](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/goToPrevMatch.html) to go to the previous match +- [TextSearcher.goToMatchOfIndex](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToMatchOfIndex.html) to go to the match of the specified index +- [TextSearcher.goToNextMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToNextMatch.html) to go to the next match +- [TextSearcher.goToPrevMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToPrevMatch.html) to go to the previous match -You can get the search result (even during the search running) in the list of [PdfTextRangeWithFragments](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextRangeWithFragments-class.html) by [PdfTextSearcher.matches](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfTextSearcher/matches.html): +You can get the search result (even during the search running) in the list of [PdfPageTextRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageTextRange-class.html) by [PdfTextSearcher.matches](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/matches.html): ```dart for (final match in textSearcher.matches) { diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index 4be8901a..3066c332 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -1,52 +1,40 @@ -**For text selection API on pdfrx v2.1.X, see [Text Selection (2.1.X)]([2.1.X]-Text-Selection.md).** +TODO: Update the contents -The following fragment uses [PdfViewerParams.enableTextSelection](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/enableTextSelection.html) to enable text selection feature: +On pdfrx 2.0.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). + +Text selection feature is enabled by default and if you want to disable it, do like the following fragment: ```dart PdfViewer.asset( 'assets/test.pdf', params: PdfViewerParams( - enableTextSelection: true, + textSelectionParams: PdfTextSelectionParams( + enabled: false, + ), ... ), ... ), ``` -If you want to handle text selection changes, you can use [PdfViewerParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/onTextSelectionChange.html) like the following fragment: +If you want to handle text selection changes, you can use [PdfTextSelectionParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/onTextSelectionChange.html). + +The handler function receives a parameter of [PdfTextSelection](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection-class.html) and you can obtain the current text selection and its associated text ranges: ```dart PdfViewer.asset( 'assets/test.pdf', params: PdfViewerParams( enableTextSelection: true, - onTextSelectionChange: (selections) { - ... - }, + textSelectionParams: PdfTextSelectionParams( + onTextSelectionChange: (selections) async { + // Get the selected string + final String text = selections.getSelectedText(); + }, + ), ... ), ... ), ``` -`selections` is a list of [PdfPageText](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfPageText-class.html) objects. - -## Further Text Selection Customization - -Instead of using [PdfViewerParams.enableTextSelection](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/enableTextSelection.html), you can also use [PdfViewerParams.selectableRegionInjector](https://pub.dev/documentation/pdfrx/1.3.5/pdfrx/PdfViewerParams/selectableRegionInjector.html) to inject your custom [SelectionArea](https://api.flutter.dev/flutter/material/SelectionArea-class.html) or [SelectableRegion](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html): - -```dart -PdfViewer.asset( - 'assets/test.pdf', - params: PdfViewerParams( - selectableRegionInjector: (context, child) { - // Your customized SelectionArea - return SelectionArea( - child: child, - ... - ); - } - ), - ... -), -``` diff --git a/doc/[2.1.X]-Text-Search.md b/doc/[2.1.X]-Text-Search.md deleted file mode 100644 index c136bebe..00000000 --- a/doc/[2.1.X]-Text-Search.md +++ /dev/null @@ -1,80 +0,0 @@ -[TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. - -The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html): - -```dart -class _MainPageState extends State { - final controller = PdfViewerController(); - // create a PdfTextSearcher and add a listener to update the GUI on search result changes - late final textSearcher = PdfTextSearcher(controller)..addListener(_update); - - void _update() { - if (mounted) { - setState(() {}); - } - } - - @override - void dispose() { - // dispose the PdfTextSearcher - textSearcher.removeListener(_update); - textSearcher.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Pdfrx example'), - ), - body: PdfViewer.asset( - 'assets/hello.pdf', - controller: controller, - params: PdfViewerParams( - // add pageTextMatchPaintCallback that paints search hit highlights - pagePaintCallbacks: [ - textSearcher.pageTextMatchPaintCallback - ], - ), - ) - ); - } - ... -} -``` - -On the fragment above, it does: - -- Create [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) instance -- Add a listener (Using [PdfTextSearcher.addListener](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/addListener.html)) to update UI on search result change -- Add [TextSearcher.pageTextMatchPaintCallback](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/pageTextMatchPaintCallback.html) to [PdfViewerParams.pagePaintCallbacks](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html) to show search matches - -Then, you can use [TextSearcher.startTextSearch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/startTextSearch.html) to search text in the PDF document: - -```dart -textSearcher.startTextSearch('hello', caseInsensitive: true); -``` - -The search starts running in background and the search progress is notified by the listener. - -There are several functions that helps you to navigate user to the search matches: - -- [TextSearcher.goToMatchOfIndex](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToMatchOfIndex.html) to go to the match of the specified index -- [TextSearcher.goToNextMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToNextMatch.html) to go to the next match -- [TextSearcher.goToPrevMatch](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/goToPrevMatch.html) to go to the previous match - -You can get the search result (even during the search running) in the list of [PdfPageTextRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageTextRange-class.html) by [PdfTextSearcher.matches](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher/matches.html): - -```dart -for (final match in textSearcher.matches) { - print(match.pageNumber); - ... -} -``` - -You can also cancel the background search: - -```dart -textSearcher.resetTextSearch(); -``` \ No newline at end of file diff --git a/doc/[2.1.X]-Text-Selection.md b/doc/[2.1.X]-Text-Selection.md deleted file mode 100644 index 3066c332..00000000 --- a/doc/[2.1.X]-Text-Selection.md +++ /dev/null @@ -1,40 +0,0 @@ -TODO: Update the contents - -On pdfrx 2.0.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). - -Text selection feature is enabled by default and if you want to disable it, do like the following fragment: - -```dart -PdfViewer.asset( - 'assets/test.pdf', - params: PdfViewerParams( - textSelectionParams: PdfTextSelectionParams( - enabled: false, - ), - ... - ), - ... -), -``` - -If you want to handle text selection changes, you can use [PdfTextSelectionParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/onTextSelectionChange.html). - -The handler function receives a parameter of [PdfTextSelection](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection-class.html) and you can obtain the current text selection and its associated text ranges: - -```dart -PdfViewer.asset( - 'assets/test.pdf', - params: PdfViewerParams( - enableTextSelection: true, - textSelectionParams: PdfTextSelectionParams( - onTextSelectionChange: (selections) async { - // Get the selected string - final String text = selections.getSelectedText(); - }, - ), - ... - ), - ... -), -``` - From 2f9cff228a66e8de0d53cd7e553bfbb99cb3eb60 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 18:11:21 +0900 Subject: [PATCH 308/663] WIP --- .../Double-tap-to-Zoom.md | 0 doc/README.md | 8 ++++++-- 2 files changed, 6 insertions(+), 2 deletions(-) rename "doc/Double\342\200\220tap-to-Zoom.md" => doc/Double-tap-to-Zoom.md (100%) diff --git "a/doc/Double\342\200\220tap-to-Zoom.md" b/doc/Double-tap-to-Zoom.md similarity index 100% rename from "doc/Double\342\200\220tap-to-Zoom.md" rename to doc/Double-tap-to-Zoom.md diff --git a/doc/README.md b/doc/README.md index 143b136c..b6a03349 100644 --- a/doc/README.md +++ b/doc/README.md @@ -9,22 +9,26 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ## Core Features ### Text Features + - [Text Search](Text-Search.md) - Implementing text search functionality - [Text Selection](Text-Selection.md) - Enabling text selection ### Navigation & Display + - [Document Outline (Bookmarks)](Document-Outline-(a.k.a-Bookmarks).md) - Working with PDF bookmarks - [PDF Link Handling](PDF-Link-Handling.md) - Handling links within PDFs - [Page Layout Customization](Page-Layout-Customization.md) - Customizing page layouts -- [Double-tap to Zoom](Double‐tap-to-Zoom.md) - Implementing zoom gestures +- [Double-tap to Zoom](Double-tap-to-Zoom.md) - Implementing zoom gestures ### UI Customization + - [Adding Page Number on Page Bottom](Adding-Page-Number-on-Page-Bottom.md) - Display page numbers - [Document Loading Indicator](Document-Loading-Indicator.md) - Show loading progress - [Showing Scroll Thumbs](Showing-Scroll-Thumbs.md) - Display scroll indicators - [Dark/Night Mode Support](Dark-Night-Mode-Support.md) - Implement dark mode ### Advanced Topics + - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays @@ -35,4 +39,4 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ## API Reference -For detailed API documentation, visit the [pub.dev documentation](https://pub.dev/documentation/pdfrx/latest/). \ No newline at end of file +For detailed API documentation, visit the [pub.dev documentation](https://pub.dev/documentation/pdfrx/latest/). From e87d1a6c5f2933af2590b1da256a7360bcf7120a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 18:15:11 +0900 Subject: [PATCH 309/663] Update wiki links to point to new doc/ directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated all wiki links in pdfrx/README.md - Updated wiki link in pdfrx_engine/README.md - Links now use blob/master/doc/ path format for GitHub - Removed [2.1.X] prefix references since current version is 2.1.X 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/README.md | 24 ++++++++++++------------ packages/pdfrx_engine/README.md | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 8049b408..5006c4bb 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -80,7 +80,7 @@ Future main() { } ``` -For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization) +For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/blob/master/doc/pdfrx-Initialization.md) Tip: To silence debug-time WASM warnings, call `pdfrxFlutterInitialize(dismissPdfiumWasmWarnings: true)` during startup. @@ -142,7 +142,7 @@ PdfViewer.asset( ), ``` -See [Deal with Password Protected PDF Files using PasswordProvider](https://github.com/espresso3389/pdfrx/wiki/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider) for more information. +See [Deal with Password Protected PDF Files using PasswordProvider](https://github.com/espresso3389/pdfrx/blob/master/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) for more information. ### Text Selection @@ -168,26 +168,26 @@ The text selection feature supports various customizations, such as: - Context Menu Customization using [PdfViewerParams.buildContextMenu](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/buildContextMenu.html) - Text Selection Magnifier Customization using [PdfTextSelectionParams.magnifier](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/magnifier.html) -For more text selection customization, see [Text Selection](https://github.com/espresso3389/pdfrx/wiki/%5B2.1.X%5D-Text-Selection). +For more text selection customization, see [Text Selection](https://github.com/espresso3389/pdfrx/blob/master/doc/Text-Selection.md). ### PDF Feature Support -- [PDF Link Handling](https://github.com/espresso3389/pdfrx/wiki/PDF-Link-Handling) -- [Document Outline (a.k.a Bookmarks)](https://github.com/espresso3389/pdfrx/wiki/Document-Outline-(a.k.a-Bookmarks)) -- [Text Search](https://github.com/espresso3389/pdfrx/wiki/%5B2.1.X%5D-Text-Search) +- [PDF Link Handling](https://github.com/espresso3389/pdfrx/blob/master/doc/PDF-Link-Handling.md) +- [Document Outline (a.k.a Bookmarks)](https://github.com/espresso3389/pdfrx/blob/master/doc/Document-Outline-(a.k.a-Bookmarks).md) +- [Text Search](https://github.com/espresso3389/pdfrx/blob/master/doc/Text-Search.md) ### Viewer Customization -- [Page Layout (Horizontal Scroll View/Facing Pages)](https://github.com/espresso3389/pdfrx/wiki/Page-Layout-Customization) -- [Showing Scroll Thumbs](https://github.com/espresso3389/pdfrx/wiki/Showing-Scroll-Thumbs) -- [Dark/Night Mode Support](https://github.com/espresso3389/pdfrx/wiki/Dark-Night-Mode-Support) -- [Document Loading Indicator](https://github.com/espresso3389/pdfrx/wiki/Document-Loading-Indicator) +- [Page Layout (Horizontal Scroll View/Facing Pages)](https://github.com/espresso3389/pdfrx/blob/master/doc/Page-Layout-Customization.md) +- [Showing Scroll Thumbs](https://github.com/espresso3389/pdfrx/blob/master/doc/Showing-Scroll-Thumbs.md) +- [Dark/Night Mode Support](https://github.com/espresso3389/pdfrx/blob/master/doc/Dark-Night-Mode-Support.md) +- [Document Loading Indicator](https://github.com/espresso3389/pdfrx/blob/master/doc/Document-Loading-Indicator.md) - [Viewer Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html) ### Additional Customizations -- [Double‐tap to Zoom](https://github.com/espresso3389/pdfrx/wiki/Double%E2%80%90tap-to-Zoom) -- [Adding Page Number on Page Bottom](https://github.com/espresso3389/pdfrx/wiki/Adding-Page-Number-on-Page-Bottom) +- [Double‐tap to Zoom](https://github.com/espresso3389/pdfrx/blob/master/doc/Double‐tap-to-Zoom.md) +- [Adding Page Number on Page Bottom](https://github.com/espresso3389/pdfrx/blob/master/doc/Adding-Page-Number-on-Page-Bottom.md) - [Per-page Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html) - [Per-page Customization using Canvas](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html) diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 43b87648..0f7ce17d 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -40,7 +40,7 @@ void main() async { } ``` -You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure the native PDFium library is properly loaded. For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/wiki/pdfrx-Initialization) +You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure the native PDFium library is properly loaded. For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/blob/master/doc/pdfrx-Initialization.md) ## PDF API From 3ea6e13bb1ccf10d2d3d7e90de39b9ab3c73bb67 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 18:19:29 +0900 Subject: [PATCH 310/663] WIP --- packages/pdfrx/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 5006c4bb..a5af6c7b 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -186,7 +186,7 @@ For more text selection customization, see [Text Selection](https://github.com/e ### Additional Customizations -- [Double‐tap to Zoom](https://github.com/espresso3389/pdfrx/blob/master/doc/Double‐tap-to-Zoom.md) +- [Double-tap to Zoom](https://github.com/espresso3389/pdfrx/blob/master/doc/Double-tap-to-Zoom.md) - [Adding Page Number on Page Bottom](https://github.com/espresso3389/pdfrx/blob/master/doc/Adding-Page-Number-on-Page-Bottom.md) - [Per-page Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html) - [Per-page Customization using Canvas](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html) From 307b98c0dc6401aab8f50d2f5be08ac26eca6f1a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 19:06:15 +0900 Subject: [PATCH 311/663] Fix documentation integrity issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove TODO marker from Text-Selection.md and add proper title - Fix broken links pointing to flutter_pdf_render repository - Add missing title to Double-tap-to-Zoom.md - Update version reference from 2.0.X to 2.1.X in Text-Selection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- doc/Double-tap-to-Zoom.md | 2 ++ doc/Text-Selection.md | 4 ++-- doc/[macOS]-Deal-with-App-Sandbox.md | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/Double-tap-to-Zoom.md b/doc/Double-tap-to-Zoom.md index 2e73c250..c22b3322 100644 --- a/doc/Double-tap-to-Zoom.md +++ b/doc/Double-tap-to-Zoom.md @@ -1,4 +1,6 @@ +# Double-tap to Zoom + You can implement double-tap-to-zoom feature using [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html): ```dart diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index 3066c332..01741665 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -1,6 +1,6 @@ -TODO: Update the contents +# Text Selection -On pdfrx 2.0.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). +On pdfrx 2.1.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). Text selection feature is enabled by default and if you want to disable it, do like the following fragment: diff --git a/doc/[macOS]-Deal-with-App-Sandbox.md b/doc/[macOS]-Deal-with-App-Sandbox.md index cf4af1ee..a6017058 100644 --- a/doc/[macOS]-Deal-with-App-Sandbox.md +++ b/doc/[macOS]-Deal-with-App-Sandbox.md @@ -1,7 +1,7 @@ For macOS, Flutter app restricts its capability by enabling [App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) by default. You can change the behavior by editing your app's entitlements files depending on your configuration. -- [`macos/Runner/Release.entitlements`](https://github.com/espresso3389/flutter_pdf_render/blob/master/example/macos/Runner/Release.entitlements) -- [`macos/Runner/DebugProfile.entitlements`](https://github.com/espresso3389/flutter_pdf_render/blob/master/example/macos/Runner/DebugProfile.entitlements) +- [`macos/Runner/Release.entitlements`](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx/example/viewer/macos/Runner/Release.entitlements) +- [`macos/Runner/DebugProfile.entitlements`](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx/example/viewer/macos/Runner/DebugProfile.entitlements) #### Deal with App Sandbox From a0e037c27b82349d3e12c0af5f0b65998f4d68ce Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 10 Sep 2025 19:14:28 +0900 Subject: [PATCH 312/663] Add proper H1 titles to all documentation files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added descriptive H1 titles to 12 documentation files that were missing them: - macOS App Sandbox Configuration - Adding Page Numbers to Pages - Customizing Key Handling on PdfViewer - Dark/Night Mode Support - Handling Password-Protected PDF Files - Working with Viewer Overlays and Page Overlays - Document Loading Indicator - Document Outline (Bookmarks) - Page Layout Customization - PDF Link Handling - pdfrx Initialization - Showing Scroll Thumbs - Text Search All documentation files now have consistent structure with proper titles. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- doc/Adding-Page-Number-on-Page-Bottom.md | 2 ++ doc/Customizing-Key-Handling-on-PdfViewer.md | 2 ++ doc/Dark-Night-Mode-Support.md | 2 ++ ...-with-Password-Protected-PDF-Files-using-PasswordProvider.md | 2 ++ doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md | 2 ++ doc/Document-Loading-Indicator.md | 2 ++ doc/Document-Outline-(a.k.a-Bookmarks).md | 2 ++ doc/PDF-Link-Handling.md | 2 ++ doc/Page-Layout-Customization.md | 2 ++ doc/Showing-Scroll-Thumbs.md | 2 ++ doc/Text-Search.md | 2 ++ doc/[macOS]-Deal-with-App-Sandbox.md | 2 ++ doc/pdfrx-Initialization.md | 2 ++ 13 files changed, 26 insertions(+) diff --git a/doc/Adding-Page-Number-on-Page-Bottom.md b/doc/Adding-Page-Number-on-Page-Bottom.md index 4b7a5772..12e1ecb7 100644 --- a/doc/Adding-Page-Number-on-Page-Bottom.md +++ b/doc/Adding-Page-Number-on-Page-Bottom.md @@ -1,3 +1,5 @@ +# Adding Page Numbers to Pages + If you want to add page number on each page, you can do that by [PdfViewerParams.pageOverlaysBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html): ```dart diff --git a/doc/Customizing-Key-Handling-on-PdfViewer.md b/doc/Customizing-Key-Handling-on-PdfViewer.md index a3b77d68..0413c193 100644 --- a/doc/Customizing-Key-Handling-on-PdfViewer.md +++ b/doc/Customizing-Key-Handling-on-PdfViewer.md @@ -1,3 +1,5 @@ +# Customizing Key Handling on PdfViewer + Because [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) handles certain keys to allow users to scroll/zoom PDF view by keyboards, it may sometimes interfere with other widget's key handling, such as [TextField](https://api.flutter.dev/flutter/material/TextField-class.html)'s text input. To customize the key handling behavior, you can use [PdfViewerParams.onKey](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onKey.html) and [PdfViewerParams.keyHandlerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/keyHandlerParams.html). diff --git a/doc/Dark-Night-Mode-Support.md b/doc/Dark-Night-Mode-Support.md index d6a8a737..889561f6 100644 --- a/doc/Dark-Night-Mode-Support.md +++ b/doc/Dark-Night-Mode-Support.md @@ -1,3 +1,5 @@ +# Dark/Night Mode Support + [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) does not have any native dark (or night) mode support but it can be easily implemented using [ColorFiltered](https://api.flutter.dev/flutter/widgets/ColorFiltered-class.html) widget: ```dart diff --git a/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md index 96911f82..6e855fd4 100644 --- a/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md +++ b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md @@ -1,3 +1,5 @@ +# Handling Password-Protected PDF Files + To support password protected PDF files, use [passwordProvider](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPasswordProvider.html) to supply passwords interactively: ```dart diff --git a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md index 38be7037..3c7941b0 100644 --- a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md +++ b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md @@ -1,3 +1,5 @@ +# Working with Viewer Overlays and Page Overlays + The following fragment shows red-dot on user tapped location: ```dart diff --git a/doc/Document-Loading-Indicator.md b/doc/Document-Loading-Indicator.md index 125217ea..2065a014 100644 --- a/doc/Document-Loading-Indicator.md +++ b/doc/Document-Loading-Indicator.md @@ -1,3 +1,5 @@ +# Document Loading Indicator + [PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) may take long time to download PDF file and you want to show some loading indicator. You can do that by [PdfViewerParams.loadingBannerBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/loadingBannerBuilder.html): ```dart diff --git a/doc/Document-Outline-(a.k.a-Bookmarks).md b/doc/Document-Outline-(a.k.a-Bookmarks).md index 6db216a5..3b915208 100644 --- a/doc/Document-Outline-(a.k.a-Bookmarks).md +++ b/doc/Document-Outline-(a.k.a-Bookmarks).md @@ -1,3 +1,5 @@ +# Document Outline (Bookmarks) + PDF defines document outline ([PdfOutlineNode](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfOutlineNode-class.html)), which is sometimes called as bookmarks or index. And you can access it by [PdfDocument.loadOutline](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/loadOutline.html). The following fragment obtains it on [PdfViewerParams.onViewerReady](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onViewerReady.html): diff --git a/doc/PDF-Link-Handling.md b/doc/PDF-Link-Handling.md index b9a44e08..f47e1dc8 100644 --- a/doc/PDF-Link-Handling.md +++ b/doc/PDF-Link-Handling.md @@ -1,3 +1,5 @@ +# PDF Link Handling + To enable links in PDF file, you should set [PdfViewerParams.linkHandlerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/linkHandlerParams.html). The following fragment handles user's tap on link: diff --git a/doc/Page-Layout-Customization.md b/doc/Page-Layout-Customization.md index 7031f021..03462ab8 100644 --- a/doc/Page-Layout-Customization.md +++ b/doc/Page-Layout-Customization.md @@ -1,3 +1,5 @@ +# Page Layout Customization + ## Horizontal Scroll View By default, the pages are laid out vertically. diff --git a/doc/Showing-Scroll-Thumbs.md b/doc/Showing-Scroll-Thumbs.md index f9d62f34..072277b4 100644 --- a/doc/Showing-Scroll-Thumbs.md +++ b/doc/Showing-Scroll-Thumbs.md @@ -1,3 +1,5 @@ +# Showing Scroll Thumbs + By default, the viewer does never show any scroll bars nor scroll thumbs. You can add scroll thumbs by using [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html): diff --git a/doc/Text-Search.md b/doc/Text-Search.md index c136bebe..b974e3db 100644 --- a/doc/Text-Search.md +++ b/doc/Text-Search.md @@ -1,3 +1,5 @@ +# Text Search + [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) is just a helper class that helps you to implement text searching feature on your app. The following fragment illustrates the overall usage of the [TextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html): diff --git a/doc/[macOS]-Deal-with-App-Sandbox.md b/doc/[macOS]-Deal-with-App-Sandbox.md index a6017058..636a767e 100644 --- a/doc/[macOS]-Deal-with-App-Sandbox.md +++ b/doc/[macOS]-Deal-with-App-Sandbox.md @@ -1,3 +1,5 @@ +# macOS App Sandbox Configuration + For macOS, Flutter app restricts its capability by enabling [App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) by default. You can change the behavior by editing your app's entitlements files depending on your configuration. - [`macos/Runner/Release.entitlements`](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx/example/viewer/macos/Runner/Release.entitlements) diff --git a/doc/pdfrx-Initialization.md b/doc/pdfrx-Initialization.md index 69b609ae..4f7b7328 100644 --- a/doc/pdfrx-Initialization.md +++ b/doc/pdfrx-Initialization.md @@ -1,3 +1,5 @@ +# pdfrx Initialization + If you use Flutter widgets like [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) or [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html), they implicitly initialize the library by calling [pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html). But if you use [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) directly, you should explicitly do either one of the following ways: From fda1be727741f38d9ac244f09b053ec743324690 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 11 Sep 2025 16:40:52 +0900 Subject: [PATCH 313/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 13 ++++++++++++- .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 4 ++-- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 13 +++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a7995ebd..02d4000e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2981,7 +2981,18 @@ class PdfTextSelectionAnchor { /// The index of the character in [page]. /// - /// Please note that the index is always inclusive, even for the end anchor (B). + /// Please note that the index is always inclusive, even for the end anchor (B); + /// + /// When selecting `"Selection"` in `"This is a Selection."`, the index of the start anchor (A) is 10, + /// and the index of the end anchor (B) is 19 (not 18). + /// + /// ``` + /// A B + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + /// | T | h | i | s | | i | s | | a | | S | e | l | e | c | t | i | o | n | . | + /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + /// ``` final int index; /// The point of the anchor in the document coordinates, which is an apex of [rect] depending on diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 801deda1..50284564 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -659,8 +659,8 @@ class _PdfDocumentPdfium extends PdfDocument { width: last.width, height: last.height, rotation: last.rotation, - bbLeft: last.bbLeft, - bbBottom: last.bbBottom, + bbLeft: 0, + bbBottom: 0, isLoaded: false, ), ); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 89401c40..05416ff0 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -447,10 +447,6 @@ abstract class PdfPage { /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderFlags.none]. /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. /// - /// The following figure illustrates what each parameter means: - /// - /// ![image](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM4MCIgdmlld0JveD0iMCAwIDMxMjMgMTg1MyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBvdmVyZmxvdz0iaGlkZGVuIj48ZGVmcz48Y2xpcFBhdGggaWQ9InByZWZpeF9fYSI+PHBhdGggZD0iTTQ4MiAxNDhoMzEyM3YxODUzSDQ4MnoiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjcHJlZml4X19hKSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4MiAtMTQ4KSI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTQ4MiAxNDhoMzEyM3YxODUzSDQ4MnoiLz48cGF0aCBkPSJNMTE5Ny41IDQ1MS41aDgwOC44TTY2NC41IDExODkuNWwxMzQxLjQgNTAzLjQ2IiBzdHJva2U9IiNCRkJGQkYiIHN0cm9rZS13aWR0aD0iMS4xNDYiIHN0cm9rZS1taXRlcmxpbWl0PSI4IiBzdHJva2UtZGFzaGFycmF5PSI0LjU4MyAzLjQzOCIgZmlsbD0ibm9uZSIvPjxwYXRoIHN0cm9rZT0iI0JGQkZCRiIgc3Ryb2tlLXdpZHRoPSI2Ljg3NSIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIGZpbGw9IiNEOUQ5RDkiIGQ9Ik0yMDA1LjUgNDUxLjVoMTMwNnYxMjQxaC0xMzA2eiIvPjxwYXRoIHN0cm9rZT0iI0JGQkZCRiIgc3Ryb2tlLXdpZHRoPSI2Ljg3NSIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIGZpbGw9IiNEOUQ5RDkiIGQ9Ik0yMzI2LjUgMTEzNi41aDYyMnY0MjNoLTYyMnoiLz48cGF0aCBkPSJNMjE0Ni41IDk3N2MwLTE5My4wMjQgMjIyLjUxLTM0OS41IDQ5Ny0zNDkuNXM0OTcgMTU2LjQ3NiA0OTcgMzQ5LjVjMCAxOTMuMDItMjIyLjUxIDM0OS41LTQ5NyAzNDkuNXMtNDk3LTE1Ni40OC00OTctMzQ5LjV6IiBmaWxsPSIjRjJGMkYyIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJNMjQzMi41MSA4NzIuNDczYzAtMjAuMTA2IDIzLjE3LTM2LjQwNiA1MS43Ny0zNi40MDYgMjguNTkgMCA1MS43NyAxNi4zIDUxLjc3IDM2LjQwNiAwIDIwLjEwNy0yMy4xOCAzNi40MDctNTEuNzcgMzYuNDA3LTI4LjYgMC01MS43Ny0xNi4zLTUxLjc3LTM2LjQwN20zMTguNDQgMGMwLTIwLjEwNiAyMy4xOC0zNi40MDYgNTEuNzctMzYuNDA2IDI4LjYgMCA1MS43NyAxNi4zIDUxLjc3IDM2LjQwNiAwIDIwLjEwNy0yMy4xNyAzNi40MDctNTEuNzcgMzYuNDA3LTI4LjU5IDAtNTEuNzctMTYuMy01MS43Ny0zNi40MDciIHN0cm9rZT0iI0JGQkZCRiIgc3Ryb2tlLXdpZHRoPSI2Ljg3NSIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIGZpbGw9IiNDM0MzQzMiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGQ9Ik0yMzc0LjEyIDExMjkuNDJjMTc5LjU5IDg2LjczIDM1OC45NiA4Ni43MyA1MzguMTMgME0yMTQ2LjUgOTc3YzAtMTkzLjAyNCAyMjIuNTEtMzQ5LjUgNDk3LTM0OS41czQ5NyAxNTYuNDc2IDQ5NyAzNDkuNWMwIDE5My4wMi0yMjIuNTEgMzQ5LjUtNDk3IDM0OS41cy00OTctMTU2LjQ4LTQ5Ny0zNDkuNXoiIHN0cm9rZT0iI0JGQkZCRiIgc3Ryb2tlLXdpZHRoPSI2Ljg3NSIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMjAyOC40MiAxNzI5LjA2aDEyNTkuMzZ2Ni44OEgyMDI4LjQyem00LjU4IDE3LjE5bC0yNy41LTEzLjc1IDI3LjUtMTMuNzV6bTEyNTAuMTktMjcuNWwyNy41IDEzLjc1LTI3LjUgMTMuNzV6Ii8+PHRleHQgZm9udC1mYW1pbHk9IkNvbnNvbGFzLHNhbnMtc2VyaWYiIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtc2l6ZT0iODMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0NTIgMTgwNSkiPmZ1bGxXaWR0aDwvdGV4dD48cGF0aCBkPSJNMzM2Mi45NCA0NzQuNDE2VjE2NzAuMzJoLTYuODhWNDc0LjQxNnpNMzM0NS43NSA0NzlsMTMuNzUtMjcuNSAxMy43NSAyNy41em0yNy41IDExODYuNzNsLTEzLjc1IDI3LjUtMTMuNzUtMjcuNXoiLz48dGV4dCBmb250LWZhbWlseT0iQ29uc29sYXMsc2Fucy1zZXJpZiIgZm9udC13ZWlnaHQ9IjQwMCIgZm9udC1zaXplPSI4MyIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgMTI2Ni4xMDUgMjEwOS4xMDUpIj5mdWxsSGVpZ2h0PC90ZXh0PjxwYXRoIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSI2Ljg3NSIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIGZpbGw9IiNGRkYiIGZpbGwtb3BhY2l0eT0iLjI1OSIgZD0iTTIwNzguNSA1MzMuNWg3NTV2NjY2aC03NTV6Ii8+PHBhdGggZD0iTTIxMDEuNDIgMTIzMC4wNmg3MDkuMzZ2Ni44OGgtNzA5LjM2em00LjU4IDE3LjE5bC0yNy41LTEzLjc1IDI3LjUtMTMuNzV6bTcwMC4xOS0yNy41bDI3LjUgMTMuNzUtMjcuNSAxMy43NXpNMjg3My45NCA1NTYuNDE3djYyMC41MTNoLTYuODhWNTU2LjQxN3pNMjg1Ni43NSA1NjFsMTMuNzUtMjcuNSAxMy43NSAyNy41em0yNy41IDYxMS4zNWwtMTMuNzUgMjcuNS0xMy43NS0yNy41eiIvPjx0ZXh0IGZvbnQtZmFtaWx5PSJDb25zb2xhcyxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iNDAwIiBmb250LXNpemU9IjgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMzQwLjk1IDEzMTApIj53aWR0aDwvdGV4dD48dGV4dCBmb250LWZhbWlseT0iQ29uc29sYXMsc2Fucy1zZXJpZiIgZm9udC13ZWlnaHQ9IjQwMCIgZm9udC1zaXplPSI4MyIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgMTA2NS4zNTUgMTgyOS4zNTUpIj5oZWlnaHQ8L3RleHQ+PHBhdGggc3Ryb2tlPSIjQkZCRkJGIiBzdHJva2Utd2lkdGg9IjYuODc1IiBzdHJva2UtbWl0ZXJsaW1pdD0iOCIgZmlsbD0iI0Q5RDlEOSIgZD0iTTY2NC41IDQ1MS41aDUzM3Y3MzhoLTUzM3oiLz48cGF0aCBzdHJva2U9IiNCRkJGQkYiIHN0cm9rZS13aWR0aD0iNi44NzUiIHN0cm9rZS1taXRlcmxpbWl0PSI4IiBmaWxsPSIjRDlEOUQ5IiBkPSJNNzk1LjUgODU4LjVoMjU0djI1MmgtMjU0eiIvPjxwYXRoIGQ9Ik03MjEuNSA3NjRjMC0xMTQuNTk5IDkwLjg4Ni0yMDcuNSAyMDMtMjA3LjUgMTEyLjExIDAgMjAzIDkyLjkwMSAyMDMgMjA3LjVzLTkwLjg5IDIwNy41LTIwMyAyMDcuNWMtMTEyLjExNCAwLTIwMy05Mi45MDEtMjAzLTIwNy41eiIgZmlsbD0iI0YyRjJGMiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0iTTgzOC4zMTkgNzAxLjk0MmMwLTExLjkzNyA5LjQ2Ny0yMS42MTUgMjEuMTQ2LTIxLjYxNXMyMS4xNDYgOS42NzggMjEuMTQ2IDIxLjYxNWMwIDExLjkzNy05LjQ2NyAyMS42MTUtMjEuMTQ2IDIxLjYxNXMtMjEuMTQ2LTkuNjc4LTIxLjE0Ni0yMS42MTVtMTMwLjA3IDBjMC0xMS45MzcgOS40NjgtMjEuNjE1IDIxLjE0Ni0yMS42MTUgMTEuNjc1IDAgMjEuMTQ1IDkuNjc4IDIxLjE0NSAyMS42MTUgMCAxMS45MzctOS40NyAyMS42MTUtMjEuMTQ1IDIxLjYxNS0xMS42NzggMC0yMS4xNDYtOS42NzgtMjEuMTQ2LTIxLjYxNSIgc3Ryb2tlPSIjQkZCRkJGIiBzdHJva2Utd2lkdGg9IjYuODc1IiBzdHJva2UtbWl0ZXJsaW1pdD0iOCIgZmlsbD0iI0MzQzNDMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0iTTgxNC40NzMgODU0LjQ5MmM3My4zNTEgNTEuNDkzIDE0Ni42MTcgNTEuNDkzIDIxOS43OTcgME03MjEuNSA3NjRjMC0xMTQuNTk5IDkwLjg4Ni0yMDcuNSAyMDMtMjA3LjUgMTEyLjExIDAgMjAzIDkyLjkwMSAyMDMgMjA3LjVzLTkwLjg5IDIwNy41LTIwMyAyMDcuNWMtMTEyLjExNCAwLTIwMy05Mi45MDEtMjAzLTIwNy41eiIgc3Ryb2tlPSIjQkZCRkJGIiBzdHJva2Utd2lkdGg9IjYuODc1IiBzdHJva2UtbWl0ZXJsaW1pdD0iOCIgZmlsbD0ibm9uZSIvPjx0ZXh0IGZvbnQtZmFtaWx5PSJDb25zb2xhcyxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iNDAwIiBmb250LXNpemU9IjgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMDg4LjEzIDYwOCkiPig8L3RleHQ+PHRleHQgZm9udC1mYW1pbHk9IkNvbnNvbGFzLHNhbnMtc2VyaWYiIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtc2l6ZT0iODMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIxMzMuMzkgNjA4KSI+eCx5PC90ZXh0Pjx0ZXh0IGZvbnQtZmFtaWx5PSJDb25zb2xhcyxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iNDAwIiBmb250LXNpemU9IjgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMjY5LjE3IDYwOCkiPik8L3RleHQ+PHBhdGggZD0iTTIwNjEgNTM0YzAtOS45NDEgOC41MS0xOCAxOS0xOHMxOSA4LjA1OSAxOSAxOC04LjUxIDE4LTE5IDE4LTE5LTguMDU5LTE5LTE4eiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0iTTExOTcuNSAxMTg5LjVsMjExNCA1MDMuNDYiIHN0cm9rZT0iI0JGQkZCRiIgc3Ryb2tlLXdpZHRoPSIxLjE0NiIgc3Ryb2tlLW1pdGVybGltaXQ9IjgiIHN0cm9rZS1kYXNoYXJyYXk9IjQuNTgzIDMuNDM4IiBmaWxsPSJub25lIi8+PHRleHQgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtc2l6ZT0iODMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc3NS4xMSA0MDkpIj5PcmlnaW5hbDwvdGV4dD48dGV4dCBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC13ZWlnaHQ9IjQwMCIgZm9udC1zaXplPSI4MyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjU0Ny42OSA0MDYpIj5TY2FsZWQ8L3RleHQ+PHBhdGggZD0iTTEyNDMuOTQgNDc0LjQxN3Y2OTIuNDMzaC02Ljg4VjQ3NC40MTd6TTEyMjYuNzUgNDc5bDEzLjc1LTI3LjUgMTMuNzUgMjcuNXptMjcuNSA2ODMuMjdsLTEzLjc1IDI3LjUtMTMuNzUtMjcuNXoiLz48dGV4dCBmb250LWZhbWlseT0iQ29uc29sYXMsc2Fucy1zZXJpZiIgZm9udC13ZWlnaHQ9IjQwMCIgZm9udC1zaXplPSI4MyIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgMzQwLjQwNSA5MzQuNDA1KSI+cGFnZS5oZWlnaHQ8L3RleHQ+PHBhdGggZD0iTTY4Ny40MTcgMTIyNC4wNmg0ODYuNzYzdjYuODhINjg3LjQxN3ptNC41ODMgMTcuMTlsLTI3LjUtMTMuNzUgMjcuNS0xMy43NXptNDc3LjU5LTI3LjVsMjcuNSAxMy43NS0yNy41IDEzLjc1eiIvPjx0ZXh0IGZvbnQtZmFtaWx5PSJDb25zb2xhcyxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iNDAwIiBmb250LXNpemU9IjgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3MDEuNDUxIDEyOTYpIj5wYWdlLndpZHRoPC90ZXh0PjwvZz48L3N2Zz4=) - /// /// The following code extract the area of (20,30)-(120,130) from the page image rendered at 1000x1500 pixels: /// ```dart /// final image = await page.render( @@ -481,7 +477,12 @@ abstract class PdfPage { static final _reSpaces = RegExp(r'(\s+)', unicode: true); static final _reNewLine = RegExp(r'\r?\n', unicode: true); - /// Load text. + /// Load structured text with character bounding boxes. + /// + /// The function internally does test flow analysis (reading order) and line segmentation to detect + /// text direction and line breaks. + /// + /// To access the raw text, use [loadText]. Future loadStructuredText() async { final raw = await _loadFormattedText(); if (raw == null) { @@ -1034,7 +1035,7 @@ enum PdfTextDirection { /// Text fragment in PDF page. class PdfPageTextFragment { - PdfPageTextFragment({ + const PdfPageTextFragment({ required this.pageText, required this.index, required this.length, From d05dcba7070dc424c375914dbf584fb766c74e3f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 17 Sep 2025 14:48:13 +0900 Subject: [PATCH 314/663] #467 --- packages/pdfrx/android/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/android/CMakeLists.txt b/packages/pdfrx/android/CMakeLists.txt index 60902654..c91ddfba 100644 --- a/packages/pdfrx/android/CMakeLists.txt +++ b/packages/pdfrx/android/CMakeLists.txt @@ -76,6 +76,9 @@ set(pdfrx_bundled_libraries ) target_include_directories(pdfrx PRIVATE ${PDFIUM_LATEST_DIR}/include) + # Support 16KB page size (#365) -target_link_options(pdfrx PRIVATE "-Wl,-z,max-page-size=16384") +# Consistent build IDs for identical binaries (#467) +target_link_options(pdfrx PRIVATE "-Wl,-z,max-page-size=16384,--build-id=none") + target_link_libraries(pdfrx PRIVATE ${PDFIUM_LATEST_LIB_FILENAME}) From e18b2f6010f8055b06a5b4d8fa9facdde577d2f7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 18 Sep 2025 02:33:44 +0900 Subject: [PATCH 315/663] WIP: pinact/dependabot changes to reduce attack surface for supply-chain attacks --- .github/dependabot.yml | 11 +++++++++++ .github/workflows/build-test.yml | 16 ++++++++-------- .github/workflows/claude.yml | 2 +- .github/workflows/github-pages.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 10 +++++----- 5 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..02c80476 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# Set update schedule for GitHub Actions +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 229b0fee..49c5f73a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -54,10 +54,10 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: '18' @@ -84,7 +84,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_ios }} runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Flutter run: | @@ -107,7 +107,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_macos }} runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Flutter run: | @@ -130,7 +130,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Install Linux dependencies run: | @@ -158,7 +158,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }} runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Install Linux dependencies run: | @@ -186,7 +186,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }} runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Flutter shell: pwsh @@ -236,7 +236,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Flutter run: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index de55216f..6e1b52f9 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -25,7 +25,7 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: fetch-depth: 1 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 6207ad5a..97325001 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 44cd91d2..1bdcb9fc 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -11,11 +11,11 @@ jobs: target: [ios, macos] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: pdfium-${{ matrix.target }} path: ./packages/pdfrx/darwin/pdfium/pdfium-${{ matrix.target }}.zip @@ -25,17 +25,17 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: pdfium-ios path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From 0a7481548e05311edb28a041ed5602da0318e16d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:34:30 +0000 Subject: [PATCH 316/663] Bump actions/download-artifact from 4.3.0 to 5.0.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...634f93cb2916e3fdff6788551b99b062d0335ce0) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 1bdcb9fc..8116fa26 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -25,12 +25,12 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: pdfium-ios path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ From d66816d78d5aa87a95fac792871c89034b65c400 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:34:34 +0000 Subject: [PATCH 317/663] Bump softprops/action-gh-release from 1 to 2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/de2c0eb89ae2a093876385947365aca7b0e5f844...6cbd405e2c4e67a21c47fa9e383d020e4e28b836) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 1bdcb9fc..714e763e 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -35,7 +35,7 @@ jobs: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From b96af349e3653a838f3d687111ec58e042378de2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:34:41 +0000 Subject: [PATCH 318/663] Bump actions/checkout from 2.7.0 to 5.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 2.7.0 to 5.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.7.0...08c6903cd8c0fde910a37f88322edcfb5dd907a8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 14 +++++++------- .github/workflows/claude.yml | 2 +- .github/workflows/github-pages.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 49c5f73a..604ed482 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -54,7 +54,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 @@ -84,7 +84,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_ios }} runs-on: macos-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Flutter run: | @@ -107,7 +107,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_macos }} runs-on: macos-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Flutter run: | @@ -130,7 +130,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install Linux dependencies run: | @@ -158,7 +158,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }} runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install Linux dependencies run: | @@ -186,7 +186,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }} runs-on: windows-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Flutter shell: pwsh @@ -236,7 +236,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Flutter run: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6e1b52f9..85cd1aeb 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -25,7 +25,7 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 97325001..35f30a74 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 1bdcb9fc..63ae8614 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -11,7 +11,7 @@ jobs: target: [ios, macos] steps: - name: Checkout - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact From 56c7b2616809feef612a4d418752030d78b53d26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:34:46 +0000 Subject: [PATCH 319/663] Bump actions/setup-java from 4.7.1 to 5.0.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.7.1 to 5.0.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/c5195efecf7bdfc987ee8bae7a71cb8b11521c00...dded0888837ed1f317902acf8a20df0ad188d165) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 49c5f73a..1ea899b9 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Setup Java - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: 'temurin' java-version: '18' From 4c6427102eaebf4103914f080e95de3fd2fa5520 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 18 Sep 2025 18:44:41 +0900 Subject: [PATCH 320/663] More error handling logics (related to #468) --- .../lib/src/native/pdf_file_cache.dart | 21 ++++++++++- .../lib/src/native/pdfrx_pdfium.dart | 37 +++++++++++++------ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 3 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index d20004dd..6b034636 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -13,6 +13,7 @@ import '../pdfrx_api.dart'; import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; import 'native_utils.dart'; +import 'pdfium_bindings.dart' as pdfium_bindings; final _rafFinalizer = Finalizer((raf) { // Attempt to close the file if it hasn't been closed explicitly. @@ -84,6 +85,13 @@ class PdfFileCache { } } + Future deleteCacheFile() async { + await close(); + try { + await file.delete(); + } catch (_) {} + } + Future _ensureFileOpen() async { if (_raf == null) { _raf = await file.open(mode: FileMode.append); @@ -372,6 +380,11 @@ Future pdfDocumentFromUri( }, ); } catch (e) { + if (e is PdfException && e.errorCode == pdfium_bindings.FPDF_ERR_FORMAT) { + // the file seems broken; delete the cache file. + // NOTE: the trick does not work on Windows :( + await cache.deleteCacheFile(); + } cache.close(); httpClientWrapper.reset(); rethrow; @@ -463,7 +476,13 @@ Future<_DownloadResult> _downloadBlock( } if (isFullDownload) { - fileSize ??= cachedBytesSoFar; + if (fileSize != null) { + if (fileSize != cache.fileSize) { + throw PdfException('File size mismatch after full download: expected $fileSize, got ${cache.fileSize}'); + } + } else { + fileSize = cachedBytesSoFar; + } await cache.initializeWithFileSize(fileSize, truncateExistingContent: false); await cache.setCached(0, lastBlock: cache.totalBlocks - 1); } else { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 50284564..e3db78b9 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -380,16 +380,6 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { headers: headers, ); - static bool _isPasswordError({int? error}) { - if (Platform.isWindows) { - // FIXME: Windows does not return error code correctly - // And we have to mimic every error is password error - return true; - } - error ??= pdfium.FPDF_GetLastError(); - return error == pdfium_bindings.FPDF_ERR_PASSWORD; - } - static Future _openByFunc( FutureOr Function(String? password) openPdfDocument, { required String sourceName, @@ -417,13 +407,36 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { disposeCallback: disposeCallback, ); } - if (_isPasswordError()) { + final error = pdfium.FPDF_GetLastError(); + if (Platform.isWindows || error == pdfium_bindings.FPDF_ERR_PASSWORD) { + // FIXME: Windows does not return error code correctly; we have to mimic every error is password error continue; } - throw PdfException('Failed to load PDF document (FPDF_GetLastError=${pdfium.FPDF_GetLastError()}).'); + throw PdfException('Failed to load PDF document ${_getPdfiumErrorString()}.', error); } } + static String _getPdfiumErrorString([int? error]) { + error ??= pdfium.FPDF_GetLastError(); + final errStr = _errorMappings[error]; + if (errStr != null) { + return '($errStr: $error)'; + } + return '(FPDF_GetLastError=$error)'; + } + + static final _errorMappings = { + 0: 'FPDF_ERR_SUCCESS', + 1: 'FPDF_ERR_UNKNOWN', + 2: 'FPDF_ERR_FILE', + 3: 'FPDF_ERR_FORMAT', + 4: 'FPDF_ERR_PASSWORD', + 5: 'FPDF_ERR_SECURITY', + 6: 'FPDF_ERR_PAGE', + 7: 'FPDF_ERR_XFALOAD', + 8: 'FPDF_ERR_XFALAYOUT', + }; + @override Future reloadFonts() async { await _initializeFontEnvironment(); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 05416ff0..59f307be 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1519,8 +1519,9 @@ class PdfOutlineNode { } class PdfException implements Exception { - const PdfException(this.message); + const PdfException(this.message, [this.errorCode]); final String message; + final int? errorCode; @override String toString() => 'PdfException: $message'; } From 68a820dc6982ea1f0da78e887596257963121ef9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 18 Sep 2025 23:05:56 +0900 Subject: [PATCH 321/663] FIXED: Package.swift does not points pdfium-apple-v11 dependencies --- packages/pdfrx/darwin/pdfrx/Package.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift index 3259deef..42dc67c4 100644 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ b/packages/pdfrx/darwin/pdfrx/Package.swift @@ -23,13 +23,13 @@ let package = Package( ), .binaryTarget( name: "pdfium", - url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v10/pdfium-ios.zip", - checksum: "d716939a98f8a27a84eb463e62aee91c42a8f11ab50d49f4698c56195b728727" + url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-ios.zip", + checksum: "968e270318f9a52697f42b677ff5b46bde4da0702fb3930384d0a7f7e62c3073" ), .binaryTarget( name: "pdfium-macos", - url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v10/pdfium-macos.zip", - checksum: "dd7d79041554c1dafe24008cb7d5c4f3a18977953ef38fa8756338fa2b7bd9ab" + url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-macos.zip", + checksum: "682ebbbb750fc185295e5b803f497e6ce25ab967476478253a1911977fe22c93" ) ] ) From a4df67e6dd14dc0a55a66bd5589a764091d682de Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 19 Sep 2025 00:34:37 +0900 Subject: [PATCH 322/663] Release pdfrx_engine v0.1.16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - More error handling logic for improved stability (#468) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 9e5d1528..6b324483 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.16 + +- More error handling logic for improved stability ([#468](https://github.com/espresso3389/pdfrx/issues/468)) + ## 0.1.15 - **BREAKING**: Integrated `loadText()` and `loadTextCharRects()` into a single function `loadText()` to fix crash issue ([#434](https://github.com/espresso3389/pdfrx/issues/434)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index cb32584d..7c793617 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.15 +version: 0.1.16 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 6a0d9b6a2eb532e596831868259872f8c01c6fc9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 19 Sep 2025 00:34:54 +0900 Subject: [PATCH 323/663] Release pdfrx v2.1.15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FIXED: Package.swift does not point to pdfium-apple-v11 dependencies correctly - More error handling logic for improved stability (#468) - Updated to pdfrx_engine 0.1.16 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e815b5a7..5aa599fb 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.1.15 + +- FIXED: Package.swift does not point to pdfium-apple-v11 dependencies correctly +- More error handling logic for improved stability ([#468](https://github.com/espresso3389/pdfrx/issues/468)) +- Updated to pdfrx_engine 0.1.16 + # 2.1.14 - Enhance podspec script to check for existing PDFium frameworks before downloading ([#396](https://github.com/espresso3389/pdfrx/issues/396), [#460](https://github.com/espresso3389/pdfrx/issues/460)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index a5af6c7b..abd1ac28 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.14 + pdfrx: ^2.1.15 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index bd5eb525..701cbefa 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.14 +version: 2.1.15 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.15 + pdfrx_engine: ^0.1.16 collection: crypto: ^3.0.6 ffi: From 0a7fe819188201db0101a4bccdf7979c9a8b92b0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 21 Sep 2025 21:48:51 +0900 Subject: [PATCH 324/663] #474 Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it --- packages/pdfrx/lib/src/pdfrx_flutter.dart | 3 +++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 13 +++++++------ packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 +++ .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 3 +++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 3 +++ .../pdfrx_engine/lib/src/pdfrx_initialize_dart.dart | 2 ++ 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 54c28fbc..2b53238e 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -59,6 +59,9 @@ void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { }(); } + /// NOTE: it's actually async, but hopefully, it finishes quickly... + PdfrxEntryFunctions.instance.initPdfium(); + _isInitialized = true; } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 81b68de6..3eb5e00c 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -64,7 +64,8 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { bool _initialized = false; - Future _init() async { + @override + Future initPdfium() async { if (_initialized) return; await synchronized(() async { if (_initialized) return; @@ -219,7 +220,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { try { if (progressCallback != null) { - await _init(); + await initPdfium(); progressCallbackReg = _PdfiumWasmCallback.register( ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, ); @@ -256,7 +257,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { required bool firstAttemptByEmptyPassword, required void Function()? onDispose, }) async { - await _init(); + await initPdfium(); for (int i = 0; ; i++) { final String? password; @@ -286,20 +287,20 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { @override Future reloadFonts() async { - await _init(); + await initPdfium(); await _sendCommand('reloadFonts', parameters: {'dummy': true}); } @override Future addFontData({required String face, required Uint8List data}) async { - await _init(); + await initPdfium(); final jsData = data.buffer.toJS; await _sendCommand('addFontData', parameters: {'face': face, 'data': jsData}, transfer: [jsData].toJS); } @override Future clearAllFontData() async { - await _init(); + await initPdfium(); await _sendCommand('clearAllFontData', parameters: {'dummy': true}); } } diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 96f6e9c3..0b6e88e8 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -16,6 +16,9 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { ); } + @override + Future initPdfium() => unimplemented(); + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index e3db78b9..e41f21b2 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -193,6 +193,9 @@ Future> _getAndClearMissingFonts() async { class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { PdfrxEntryFunctionsImpl(); + @override + Future initPdfium() => _init(); + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 59f307be..c7d2131c 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -71,6 +71,9 @@ class Pdfrx { abstract class PdfrxEntryFunctions { static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); + /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. + Future initPdfium(); + Future openAsset( String name, { PdfPasswordProvider? passwordProvider, diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 7b1735ef..a37834b1 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -38,6 +38,8 @@ Future pdfrxInitialize({ Pdfrx.pdfiumModulePath = await _PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); } + await PdfrxEntryFunctions.instance.initPdfium(); + _isInitialized = true; } From 7f1f1a183514ca86ff538cd687638ae4a4b7df13 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 21 Sep 2025 22:20:21 +0900 Subject: [PATCH 325/663] Update doc to note about PdfrxEntryFunctions.initPdfium --- packages/pdfrx/.gitignore | 2 ++ packages/pdfrx/lib/src/pdfrx_flutter.dart | 1 + packages/pdfrx_engine/.gitignore | 3 +++ packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart | 1 + packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart | 1 + 5 files changed, 8 insertions(+) diff --git a/packages/pdfrx/.gitignore b/packages/pdfrx/.gitignore index c8585d1e..19a190f3 100644 --- a/packages/pdfrx/.gitignore +++ b/packages/pdfrx/.gitignore @@ -44,3 +44,5 @@ build/ # Build outputs and intermediate files **.xcframework/ *.zip + +doc/api/ diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 2b53238e..d11c740a 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -14,6 +14,7 @@ bool _isInitialized = false; /// This function actually sets up the following functions: /// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. /// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. +/// - Call [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. /// /// For Dart (non-Flutter) programs, you should call [pdfrxInitialize] instead. /// diff --git a/packages/pdfrx_engine/.gitignore b/packages/pdfrx_engine/.gitignore index b5050d45..79e6782a 100644 --- a/packages/pdfrx_engine/.gitignore +++ b/packages/pdfrx_engine/.gitignore @@ -12,3 +12,6 @@ pubspec_overrides.yaml /tmp/ **/*.exe + +doc/api/ + diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart index f8172a76..db3f8053 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart @@ -10,6 +10,7 @@ library; /// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. /// - If Pdfium module is not found, it will be downloaded from the internet. /// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). +/// - Calls [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease}) async { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index a37834b1..4d1f5029 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -16,6 +16,7 @@ bool _isInitialized = false; /// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. /// - If Pdfium module is not found, it will be downloaded from the internet. /// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). +/// - Calls [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({ From 529bbabdc09fe00f2409e17cf4afb0bd682327b0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 02:56:26 +0900 Subject: [PATCH 326/663] #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 6 ++ .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 6 ++ .../lib/src/native/pdfrx_pdfium.dart | 5 ++ .../pdfrx_engine/lib/src/native/worker.dart | 62 ++++++++++++++++--- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 11 ++++ 5 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 3eb5e00c..a4096265 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -103,6 +103,12 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { }); } + @override + Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { + // We don't share PDFium wasm instance with other libraries, so no need to block calls anyway + return await action(); + } + static String? _pdfiumWasmModulesUrlFromMetaTag() { final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; return meta?.content; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 0b6e88e8..6fa3e3d2 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -19,6 +19,12 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future initPdfium() => unimplemented(); + @override + Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { + unimplemented(); // actually never returns + return await action(); + } + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index e41f21b2..fcc68438 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -196,6 +196,11 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future initPdfium() => _init(); + @override + Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { + return await (await backgroundWorker).suspendDuringAction(action); + } + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 25439c5e..514bc158 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:developer' as developer; import 'dart:isolate'; @@ -32,24 +33,56 @@ class BackgroundWorker { final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); late final StreamSubscription sub; + final suspendingQueue = Queue<_ComputeParams>(); + int suspendingLevel = 0; sub = receivePort.listen((message) { - if (message is _ComputeParams) { + if (message is _SuspendRequest) { + suspendingLevel++; message.execute(); + } else if (message is _ResumeRequest) { + if (suspendingLevel > 0) { + suspendingLevel--; + while (suspendingQueue.isNotEmpty) { + suspendingQueue.removeFirst().execute(); + } + } + message.execute(); + } else if (message is _ComputeParams) { + if (suspendingLevel > 0) { + suspendingQueue.add(message); + } else { + message.execute(); + } } else { sub.cancel(); receivePort.close(); - return; } }); } + Future _sendComputeParams(T Function(SendPort) createParams) async { + if (_isDisposed) { + throw StateError('Worker is already disposed'); + } + final receivePort = ReceivePort(); + _sendPort.send(createParams(receivePort.sendPort)); + return await receivePort.first; + } + Future compute(PdfrxComputeCallback callback, M message) async { + return await _sendComputeParams((sendPort) => _ExecuteParams(sendPort, callback, message)) as R; + } + + Future suspendDuringAction(FutureOr Function() action) async { if (_isDisposed) { throw StateError('Worker is already disposed'); } - final sendPort = ReceivePort(); - _sendPort.send(_ComputeParams(sendPort.sendPort, callback, message)); - return await sendPort.first as R; + await _sendComputeParams((sendPort) => _SuspendRequest._(sendPort)); + try { + return await action(); + } finally { + await _sendComputeParams((sendPort) => _ResumeRequest._(sendPort)); + } } /// [compute] wrapper that also provides [Arena] for temporary memory allocation. @@ -67,11 +100,26 @@ class BackgroundWorker { } } -class _ComputeParams { - _ComputeParams(this.sendPort, this.callback, this.message); +class _ComputeParams { + _ComputeParams(this.sendPort); final SendPort sendPort; + + void execute() => sendPort.send(null); +} + +class _ExecuteParams extends _ComputeParams { + _ExecuteParams(super.sendPort, this.callback, this.message); final PdfrxComputeCallback callback; final M message; + @override void execute() => sendPort.send(callback(message)); } + +class _SuspendRequest extends _ComputeParams { + _SuspendRequest._(super.sendPort); +} + +class _ResumeRequest extends _ComputeParams { + _ResumeRequest._(super.sendPort); +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index c7d2131c..7097988f 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1,6 +1,8 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first /// Pdfrx API +/// @docImport './pdfrx_initialize_dart.dart'; +/// @docImport 'package:pdfrx/pdfrx_flutter.dart'; library; import 'dart:async'; @@ -72,8 +74,17 @@ abstract class PdfrxEntryFunctions { static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. + /// + /// For actual apps, call `pdfrxFlutterInitialize` (for Flutter) or [pdfrxInitialize] (for Dart only) instead of this function. Future initPdfium(); + /// This function blocks pdfrx internally calls PDFium functions during the execution of [action]. + /// + /// Because PDFium is not thread-safe, if your app is calling some other libraries that potentially calls PDFium + /// functions, pdfrx may interfere with those calls and cause crashes or data corruption. + /// To avoid such problems, you can wrap the code that calls those libraries with this function. + Future suspendPdfiumWorkerDuringAction(FutureOr Function() action); + Future openAsset( String name, { PdfPasswordProvider? passwordProvider, From 1da082fdd875c94b09f01d249d6c03e4cf0c4cc8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 03:52:33 +0900 Subject: [PATCH 327/663] Independent pdfium.dart for pdfium ffi access --- ...operability-with-other-PDFium-Libraries.md | 404 ++++++++++++++++++ doc/README.md | 1 + .../pdfrx_engine/lib/src/native/pdfium.dart | 30 ++ .../lib/src/native/pdfrx_pdfium.dart | 25 +- 4 files changed, 436 insertions(+), 24 deletions(-) create mode 100644 doc/Interoperability-with-other-PDFium-Libraries.md create mode 100644 packages/pdfrx_engine/lib/src/native/pdfium.dart diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md new file mode 100644 index 00000000..05345eec --- /dev/null +++ b/doc/Interoperability-with-other-PDFium-Libraries.md @@ -0,0 +1,404 @@ +# Interoperability with other PDFium Libraries + +This document explains how pdfrx can coexist with other PDFium-based libraries in the same application, and the mechanisms provided to ensure safe concurrent usage. + +## The Challenge + +PDFium is not thread-safe. When multiple libraries in the same application use PDFium, they can interfere with each other causing: + +- Crashes due to concurrent access +- Data corruption +- Unexpected behavior + +This is particularly important when your application uses: + +- Multiple PDF libraries (e.g., pdfrx alongside another PDFium wrapper) +- Direct PDFium calls through FFI +- Native plugins that internally use PDFium + +## Solution: Coordinated PDFium Access + +pdfrx provides mechanisms to coordinate PDFium access across different libraries through the `PdfrxEntryFunctions` class. + +## Low-Level PDFium Bindings + +For advanced use cases, pdfrx_engine exposes direct access to PDFium's C API through FFI bindings. This allows you to: + +- Call any PDFium function directly +- Implement custom PDF processing not covered by the high-level API +- Integrate with existing PDFium-based code + +To access the low-level bindings: + +```dart +// Import the binding definitions and loader +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfrx_engine/src/native/pdfium.dart'; + +// Load the PDFium bindings +final pdfium = await loadPdfiumBindings(); + +// Use any PDFium C API function +final version = pdfium.FPDF_GetVersionString().cast().toDartString(); +``` + +**Important**: When using low-level bindings, you must: + +1. Initialize PDFium first using `pdfrxFlutterInitialize()` or `pdfrxInitialize()` +2. Wrap all PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts +3. Properly manage memory allocation and deallocation + +See [Example 2](#example-2-low-level-pdfium-bindings-access) below for detailed usage. + +### PDFium Initialization + +PDFium requires initialization through its C API functions `FPDF_InitLibrary()` or `FPDF_InitLibraryWithConfig()` before any PDF operations can be performed. Starting from pdfrx v2.1.15, this initialization is handled automatically when you call: + +- `pdfrxFlutterInitialize()` - For Flutter applications +- `pdfrxInitialize()` - For pure Dart applications + +These functions internally call the PDFium's `FPDF_InitLibraryWithConfig()` to properly initialize the PDFium library. This ensures: + +- PDFium is initialized exactly once for all libraries +- The initialization happens at the right time +- The PDFium instance can be shared across multiple libraries + +**Important**: pdfrx does relatively important PDFium initialization process with `FPDF_InitLibraryWithConfig()`, so it's recommended to call `pdfrxFlutterInitialize()` or `pdfrxInitialize()` for initialization rather than calling `FPDF_InitLibrary()` or `FPDF_InitLibraryWithConfig()` on your code or by other libraries without any other important reason. + +### Suspending PDFium Worker + +The most important feature for interoperability is `PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction()`. This function temporarily suspends pdfrx's internal PDFium operations while you call other PDFium-based libraries: + +```dart +import 'package:pdfrx/pdfrx.dart'; + +// Suspend pdfrx's PDFium operations while calling another library +final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { + // Safe to call other PDFium-based libraries here + // pdfrx won't interfere with these calls + return await otherPdfLibrary.processPdf(); +}); +``` + +## Implementation Details + +### Native Platforms (iOS, Android, Windows, macOS, Linux) + +On native platforms, pdfrx uses a background isolate worker to handle PDFium operations. The `suspendPdfiumWorkerDuringAction` method: + +1. Pauses the background worker's PDFium operations +2. Executes your action (which can safely call PDFium) +3. Resumes the background worker + +This ensures that pdfrx and other libraries never call PDFium simultaneously. + +### Web Platform + +On web, pdfrx uses a Web Worker with PDFium WASM. Since the WASM instance is isolated within the worker, there's no risk of interference with other libraries. The `suspendPdfiumWorkerDuringAction` method simply executes the action without additional synchronization. + +## Usage Examples + +### Example 1: Using pdfrx with Another PDFium Library + +```dart +import 'package:pdfrx/pdfrx.dart'; +import 'package:another_pdf_lib/another_pdf_lib.dart' as other; + +class PdfProcessor { + // Initialize both libraries + static Future initialize() async { + // Initialize pdfrx (which calls FPDF_InitLibraryWithConfig internally) + pdfrxFlutterInitialize(); + + // The other library can now use the same PDFium instance + // (assuming it doesn't call FPDF_InitLibrary/FPDF_InitLibraryWithConfig again) + } + + // Process PDF with the other library + Future extractTextWithOtherLibrary(String path) async { + // Suspend pdfrx operations during the other library's work + return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { + final doc = await other.PdfDocument.open(path); + final text = await doc.extractText(); + await doc.close(); + return text; + }); + } + + // Continue using pdfrx normally + Future renderWithPdfrx(String path) async { + final doc = await PdfDocument.openFile(path); + // ... render pages ... + doc.dispose(); + } +} +``` + +### Example 2: Low-Level PDFium Bindings Access + +pdfrx_engine provides direct access to PDFium bindings for advanced use cases. You can import the low-level bindings to make direct PDFium API calls: + +```dart +import 'dart:ffi'; +import 'dart:typed_data'; +import 'package:pdfrx/pdfrx.dart'; +// Access low-level PDFium bindings +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfrx_engine/src/native/pdfium.dart'; + +class LowLevelPdfiumAccess { + Future useDirectBindings() async { + // Initialize PDFium through pdfrx + await pdfrxFlutterInitialize(); + + // Access the PDFium bindings directly + final pdfium = await loadPdfiumBindings(); + + // Now you can use all PDFium C API functions through the bindings + // Remember to wrap calls with suspendPdfiumWorkerDuringAction + await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { + // Example: Get PDFium version string + final versionPtr = pdfium.FPDF_GetVersionString(); + final version = versionPtr.cast().toDartString(); + print('PDFium version: $version'); + + // You can call any PDFium function from the bindings + // pdfium.FPDF_LoadDocument(...) + // pdfium.FPDF_GetPageCount(...) + // etc. + }); + } + + Future> extractCustomMetadata(Uint8List pdfData) async { + final pdfium = await loadPdfiumBindings(); + + return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { + final dataPtr = calloc(pdfData.length); + dataPtr.asTypedList(pdfData.length).setAll(0, pdfData); + + try { + // Load document using low-level API + final doc = pdfium.FPDF_LoadMemDocument( + dataPtr.cast(), + pdfData.length, + nullptr, + ); + + if (doc == nullptr) { + throw Exception('Failed to load PDF'); + } + + try { + // Access document metadata + final metadata = {}; + + // Get page count + final pageCount = pdfium.FPDF_GetPageCount(doc); + metadata['pageCount'] = pageCount.toString(); + + // Get document metadata tags + for (final tag in ['Title', 'Author', 'Subject', 'Keywords', 'Creator']) { + final buffer = calloc(256); + try { + final tagPtr = tag.toNativeUtf8(); + final len = pdfium.FPDF_GetMetaText( + doc, + tagPtr.cast(), + buffer.cast(), + 256, + ); + if (len > 0) { + // PDFium returns UTF-16 encoded text + final text = buffer.cast().toDartString(length: (len ~/ 2) - 1); + metadata[tag] = text; + } + calloc.free(tagPtr); + } finally { + calloc.free(buffer); + } + } + + return metadata; + } finally { + pdfium.FPDF_CloseDocument(doc); + } + } finally { + calloc.free(dataPtr); + } + }); + } +} +``` + +**Important Notes about Low-Level Bindings:** + +- Import `package:pdfrx_engine/src/native/pdfium_bindings.dart` for the FFI binding definitions +- Import `package:pdfrx_engine/src/native/pdfium.dart` for the `loadPdfiumBindings()` function +- Always wrap PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts +- Remember to properly manage memory (use `calloc` and `calloc.free`) +- PDFium text APIs often return UTF-16 encoded strings + +### Example 3: Batch Processing with Multiple Libraries + +```dart +import 'package:pdfrx/pdfrx.dart'; + +class BatchProcessor { + Future processBatch(List files) async { + pdfrxFlutterInitialize(); + + for (final file in files) { + // Use pdfrx for rendering + final doc = await PdfDocument.openFile(file); + final pageImage = await doc.pages[0].render(); + doc.dispose(); + + // Use another library for text extraction + // (wrapped to prevent interference) + final text = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { + return await otherLibrary.extractText(file); + }); + + // Process results... + } + } +} +``` + +## Best Practices + +### 1. Initialize Once + +PDFium must be initialized only once at application startup. The initialization calls `FPDF_InitLibraryWithConfig()` internally: + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize PDFium for all libraries (calls FPDF_InitLibraryWithConfig) + pdfrxFlutterInitialize(); + + runApp(MyApp()); +} +``` + +**Note**: If you have other PDFium-based libraries, ensure they don't call `FPDF_InitLibrary()` or `FPDF_InitLibraryWithConfig()` again. + +### 2. Always Wrap External PDFium Calls + +When calling other PDFium-based libraries, always use `suspendPdfiumWorkerDuringAction`: + +```dart +// ❌ Bad - May cause crashes +final result = await otherPdfLibrary.process(data); + +// ✅ Good - Safe concurrent access +final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction( + () => otherPdfLibrary.process(data) +); +``` + +### 3. Short Suspension Periods + +Keep the suspension period as short as possible to maintain pdfrx responsiveness: + +```dart +// ❌ Bad - Long suspension +await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { + final result = await otherLibrary.process(data); + await longRunningNonPdfiumOperation(); // Don't include this + return result; +}); + +// ✅ Good - Minimal suspension +final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction( + () => otherLibrary.process(data) +); +await longRunningNonPdfiumOperation(); // Run outside suspension +``` + +### 4. Error Handling + +Always handle errors appropriately: + +```dart +try { + final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { + return await riskyPdfiumOperation(); + }); +} catch (e) { + // Handle PDFium-related errors + print('PDFium operation failed: $e'); +} +``` + +## Platform-Specific Considerations + +### Native Platforms + +- PDFium binary is shared across all libraries in the process +- Only one `FPDF_InitLibrary()`/`FPDF_InitLibraryWithConfig()` call is needed (and recommended) +- Thread safety must be managed through suspension mechanism + +### Web Platform + +- Each library typically has its own WASM instance +- Suspension is less critical but still provided for API consistency +- Memory usage may be higher due to multiple WASM instances + +## Migration Guide + +If you're adding pdfrx to an existing application that already uses PDFium: + +1. **Check Initialization**: Ensure `FPDF_InitLibrary()`/`FPDF_InitLibraryWithConfig()` is called only once +2. **Identify Conflict Points**: Find where both libraries might access PDFium simultaneously +3. **Add Suspension**: Wrap external PDFium calls with `suspendPdfiumWorkerDuringAction` +4. **Test Thoroughly**: Test concurrent operations to ensure stability + +## Troubleshooting + +### Common Issues + +**Issue**: Random crashes when using multiple PDF libraries +**Solution**: Ensure all external PDFium calls are wrapped with `suspendPdfiumWorkerDuringAction` + +**Issue**: "PDFium already initialized" errors +**Solution**: Remove duplicate `FPDF_InitLibrary()`/`FPDF_InitLibraryWithConfig()` calls; let one library handle initialization + +**Issue**: Deadlocks or hangs +**Solution**: Check for nested suspension calls or circular dependencies + +### Debug Mode + +Enable verbose logging to debug interoperability issues: + +```dart +// Enable detailed logging (development only) +if (kDebugMode) { + // Monitor suspension events + PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { + print('PDFium operations suspended'); + // Your code here + print('PDFium operations resumed'); + }); +} +``` + +## API Reference + +### PdfrxEntryFunctions + +The main class for PDFium interoperability: + +- [`suspendPdfiumWorkerDuringAction(action)`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions/suspendPdfiumWorkerDuringAction.html) - Suspend pdfrx operations during action execution + +### Initialization Functions + +- [`pdfrxFlutterInitialize()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) - Initialize for Flutter apps (calls `FPDF_InitLibraryWithConfig` internally) +- [`pdfrxInitialize()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) - Initialize for Dart-only apps (calls `FPDF_InitLibraryWithConfig` internally) + +## See Also + +- [pdfrx Initialization](pdfrx-Initialization.md) - General initialization guide +- [API Documentation](https://pub.dev/documentation/pdfrx/latest/pdfrx/) - Complete API reference +- [GitHub Issues](https://github.com/espresso3389/pdfrx/issues) - Report problems or request features diff --git a/doc/README.md b/doc/README.md index b6a03349..e760ec5d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -32,6 +32,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays +- [Interoperability with other PDFium Libraries](Interoperability-with-other-PDFium-Libraries.md) - Using pdfrx alongside other PDFium-based libraries ## Platform-Specific diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart new file mode 100644 index 00000000..25d3e1c0 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -0,0 +1,30 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:ffi'; +import 'dart:io'; + +import '../pdfrx_api.dart'; +import 'pdfium_bindings.dart' as pdfium_bindings; + +/// Get the module file name for pdfium. +String _getModuleFileName() { + if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; + if (Platform.isAndroid) return 'libpdfium.so'; + if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; + if (Platform.isWindows) return 'pdfium.dll'; + if (Platform.isLinux) { + return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfium.so'; + } + throw UnsupportedError('Unsupported platform'); +} + +DynamicLibrary _getModule() { + try { + return DynamicLibrary.open(_getModuleFileName()); + } catch (e) { + // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) + return DynamicLibrary.process(); + } +} + +/// Loaded PDFium module. +final pdfium = pdfium_bindings.pdfium(_getModule()); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index fcc68438..3b40efa3 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -13,34 +13,11 @@ import 'package:synchronized/extension.dart'; import '../pdfrx_api.dart'; import 'native_utils.dart'; import 'pdf_file_cache.dart'; +import 'pdfium.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart'; import 'worker.dart'; -/// Get the module file name for pdfium. -String _getModuleFileName() { - if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; - if (Platform.isAndroid) return 'libpdfium.so'; - if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; - if (Platform.isWindows) return 'pdfium.dll'; - if (Platform.isLinux) { - return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfium.so'; - } - throw UnsupportedError('Unsupported platform'); -} - -DynamicLibrary _getModule() { - try { - return DynamicLibrary.open(_getModuleFileName()); - } catch (e) { - // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) - return DynamicLibrary.process(); - } -} - -/// Loaded PDFium module. -final pdfium = pdfium_bindings.pdfium(_getModule()); - Directory? _appLocalFontPath; bool _initialized = false; From 05810e968f36f03871be4b9bcf2c9600c063f30e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 04:20:20 +0900 Subject: [PATCH 328/663] Add document for FFI --- doc/Low-Level-PDFium-Bindings-Access.md | 213 ++++++++++++++++++ doc/README.md | 1 + .../lib/src/native/pdfrx_pdfium.dart | 4 +- 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 doc/Low-Level-PDFium-Bindings-Access.md diff --git a/doc/Low-Level-PDFium-Bindings-Access.md b/doc/Low-Level-PDFium-Bindings-Access.md new file mode 100644 index 00000000..084803fc --- /dev/null +++ b/doc/Low-Level-PDFium-Bindings-Access.md @@ -0,0 +1,213 @@ +# Low-Level PDFium Bindings Access + +This document explains how to access low-level PDFium bindings directly when working with the pdfrx_engine package. + +## Overview + +While pdfrx_engine provides high-level Dart APIs for PDF operations, you may occasionally need direct access to PDFium's native functions. The package exposes these through two key imports: + +- `package:pdfrx_engine/src/native/pdfium_bindings.dart` - Raw FFI bindings generated from PDFium headers +- `package:pdfrx_engine/src/native/pdfium.dart` - Helper utilities and wrapper functions + +## Importing Low-Level Bindings + +### Raw FFI Bindings + +```dart +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +``` + +This import provides access to the auto-generated FFI bindings that directly map to PDFium's C API. These bindings are generated using `ffigen` from PDFium headers and include all PDFium functions with their original names (e.g., `FPDF_InitLibrary`, `FPDF_LoadDocument`, etc.). + +### Helper Utilities + +```dart +import 'package:pdfrx_engine/src/native/pdfium.dart'; +``` + +This import provides: + +- The `pdfium` global instance for accessing PDFium functions +- Helper utilities for memory management +- Platform-specific initialization functions +- Convenience wrappers around common PDFium operations + +### Initialization + +PDFium must be initialized before use. The high-level API handles this automatically, but when using raw bindings directly, you may need to ensure initialization. + +```dart +// +// Manual initialization +// +import 'package:pdfrx_engine/pdfrx_engine.dart'; // or import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_engine/src/native/pdfium.dart'; +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; + +// Low level initialization with PDFium's FPDF_InitLibrary +Pdfrx.pdfiumModulePath = 'somewhere/in/your/filesystem/libpdfium.so'; +pdfium.FPDF_InitLibrary(); // or pdfium.FPDF_InitLibraryWithConfig(...) + +// +// Flutter app +// +import 'package:pdfrx/pdfrx.dart'; + +pdfrxFlutterInitialize(); + +// +// Pure Dart +// +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +await pdfrxInitialize(); +``` + +For more information about initialization, see [pdfrx Initialization](pdfrx-Initialization.md). + +## Usage Examples + +### Basic PDFium Function Access + +```dart +import 'package:pdfrx_engine/src/native/pdfium.dart'; +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void example() { + // Use arena to automatically manage memory + using((arena) { + // Access PDFium functions through the global pdfium instance + final doc = pdfium.FPDF_LoadDocument( + 'path/to/file.pdf'.toNativeUtf8(allocator: arena).cast(), + nullptr, + ); + + if (doc != nullptr) { + final pageCount = pdfium.FPDF_GetPageCount(doc); + print('Page count: $pageCount'); + + // Don't forget to clean up + pdfium.FPDF_CloseDocument(doc); + } + }); +} +``` + +### Working with Pages + +```dart +import 'package:pdfrx_engine/src/native/pdfium.dart'; +import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; + +void workWithPage(FPDF_DOCUMENT doc) { + final page = pdfium.FPDF_LoadPage(doc, 0); // Load first page + + if (page != nullptr) { + final width = pdfium.FPDF_GetPageWidth(page); + final height = pdfium.FPDF_GetPageHeight(page); + print('Page dimensions: ${width}x${height}'); + + pdfium.FPDF_ClosePage(page); + } +} +``` + +### Memory Management + +When working with raw bindings, you're responsible for proper memory management. Always use `Arena` to ensure allocated memory is properly released: + +```dart +import 'package:pdfrx_engine/src/native/pdfium.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void memoryExample() { + // Use arena for automatic memory management + using((arena) { + final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena); + + final doc = pdfium.FPDF_LoadDocument( + pathPtr.cast(), + nullptr, + ); + + if (doc != nullptr) { + // Use document... + pdfium.FPDF_CloseDocument(doc); + } + // Memory allocated by arena is automatically freed when the using block ends + }); +} + +// Alternative: Manual memory management (not recommended) +void manualMemoryExample() { + final pathPtr = 'path/to/file.pdf'.toNativeUtf8(); + + try { + final doc = pdfium.FPDF_LoadDocument( + pathPtr.cast(), + nullptr, + ); + + if (doc != nullptr) { + // Use document... + pdfium.FPDF_CloseDocument(doc); + } + } finally { + // Must manually free allocated memory + calloc.free(pathPtr); + } +} +``` + +## Important Considerations + +### Thread Safety + +PDFium is not thread-safe. Ensure all PDFium calls are made from the same thread, typically the main isolate. + +### Error Handling + +Always check return values from PDFium functions: + +```dart +using((arena) { + final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena); + final doc = pdfium.FPDF_LoadDocument(pathPtr.cast(), nullptr); + + if (doc == nullptr) { + final error = pdfium.FPDF_GetLastError(); + print('Failed to load document. Error code: $error'); + // Handle error... + } else { + // Use document... + pdfium.FPDF_CloseDocument(doc); + } +}); +``` + +## When to Use Low-Level Bindings + +Consider using low-level bindings when: + +1. You need functionality not exposed by the high-level API +2. You're implementing performance-critical operations +3. You need fine-grained control over memory management +4. You're extending pdfrx_engine with new features + +## Warning + +Using low-level bindings bypasses many safety checks and conveniences provided by the high-level API. Ensure you: + +- Properly manage memory allocation and deallocation +- Handle errors appropriately +- Follow PDFium's threading requirements +- Test thoroughly on all target platforms + +## See Also + +- [PDFium API Documentation](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/public/) +- [FFI Package Documentation](https://pub.dev/packages/ffi) +- [pdfrx_engine API Reference](https://pub.dev/documentation/pdfrx_engine/latest/) diff --git a/doc/README.md b/doc/README.md index e760ec5d..f54e6126 100644 --- a/doc/README.md +++ b/doc/README.md @@ -32,6 +32,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays +- [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - Using PDFium function directly - [Interoperability with other PDFium Libraries](Interoperability-with-other-PDFium-Libraries.md) - Using pdfrx alongside other PDFium-based libraries ## Platform-Specific diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 3b40efa3..53667fb4 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -39,7 +39,9 @@ Future _init() async { // NOTE: m_pUserFontPaths must not be freed until FPDF_DestroyLibrary is called; on pdfrx, it's never freed. final fontPathArray = malloc>(sizeOf>() * (fontPaths.length + 1)); for (int i = 0; i < fontPaths.length; i++) { - fontPathArray[i] = fontPaths[i].toNativeUtf8().cast(); + fontPathArray[i] = fontPaths[i] + .toNativeUtf8() + .cast(); // NOTE: the block allocated by toNativeUtf8 never released } fontPathArray[fontPaths.length] = nullptr; config.ref.m_pUserFontPaths = fontPathArray; From d16f32affef0401bd4723763db5755a9c1ba52f3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 04:35:05 +0900 Subject: [PATCH 329/663] WIP --- ...operability-with-other-PDFium-Libraries.md | 19 +------------------ doc/pdfrx-Initialization.md | 3 ++- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md index 05345eec..ec05e012 100644 --- a/doc/Interoperability-with-other-PDFium-Libraries.md +++ b/doc/Interoperability-with-other-PDFium-Libraries.md @@ -28,23 +28,9 @@ For advanced use cases, pdfrx_engine exposes direct access to PDFium's C API thr - Implement custom PDF processing not covered by the high-level API - Integrate with existing PDFium-based code -To access the low-level bindings: - -```dart -// Import the binding definitions and loader -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; -import 'package:pdfrx_engine/src/native/pdfium.dart'; - -// Load the PDFium bindings -final pdfium = await loadPdfiumBindings(); - -// Use any PDFium C API function -final version = pdfium.FPDF_GetVersionString().cast().toDartString(); -``` - **Important**: When using low-level bindings, you must: -1. Initialize PDFium first using `pdfrxFlutterInitialize()` or `pdfrxInitialize()` +1. Initialize PDFium first using `pdfrxFlutterInitialize()` or `pdfrxInitialize()` (See [pdfrx Initialization](pdfrx-Initialization.md)) 2. Wrap all PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts 3. Properly manage memory allocation and deallocation @@ -151,9 +137,6 @@ class LowLevelPdfiumAccess { // Initialize PDFium through pdfrx await pdfrxFlutterInitialize(); - // Access the PDFium bindings directly - final pdfium = await loadPdfiumBindings(); - // Now you can use all PDFium C API functions through the bindings // Remember to wrap calls with suspendPdfiumWorkerDuringAction await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { diff --git a/doc/pdfrx-Initialization.md b/doc/pdfrx-Initialization.md index 4f7b7328..a09d0bbc 100644 --- a/doc/pdfrx-Initialization.md +++ b/doc/pdfrx-Initialization.md @@ -20,6 +20,7 @@ Basically, these initialization functions do the following things: - Map PdfDocument [factory/interop functions](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions-class.html) to actual platform ones - Set [Pdfrx.loadAsset](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/loadAsset.html) (Flutter only) - Download PDFium binary on-demand ([pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) only) +- Call [PdfrxEntryFunctions.initPdfium](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions/initPdfium.html) to initialize the PDFium library (internally calls `FPDF_InitLibraryWithConfig`) ## Cache Directory @@ -34,6 +35,6 @@ Init. Func. | Underlying API | Notes For pure Dart apps, because it is typically used on desktop environments, pdfrx downloads PDFium binary if your environment does not have it. -- PDFium binaries are downloaded from https://github.com/bblanchon/pdfium-binaries/releases +- PDFium binaries are downloaded from - By default, the binary is downloaded to `[TMP_DIR]/pdfrx.cache` - You can explicitly specify `libpdfium` shared library file path/name by `PDFIUM_PATH` environment variable From b40ffd60d515538a59750bded5fc6d16a93130e8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 04:50:33 +0900 Subject: [PATCH 330/663] WIP --- ...operability-with-other-PDFium-Libraries.md | 73 +++++-------------- doc/Low-Level-PDFium-Bindings-Access.md | 2 + 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md index ec05e012..0f7c0181 100644 --- a/doc/Interoperability-with-other-PDFium-Libraries.md +++ b/doc/Interoperability-with-other-PDFium-Libraries.md @@ -95,9 +95,6 @@ class PdfProcessor { static Future initialize() async { // Initialize pdfrx (which calls FPDF_InitLibraryWithConfig internally) pdfrxFlutterInitialize(); - - // The other library can now use the same PDFium instance - // (assuming it doesn't call FPDF_InitLibrary/FPDF_InitLibraryWithConfig again) } // Process PDF with the other library @@ -127,6 +124,7 @@ pdfrx_engine provides direct access to PDFium bindings for advanced use cases. Y ```dart import 'dart:ffi'; import 'dart:typed_data'; +import 'package:ffi/ffi.dart'; import 'package:pdfrx/pdfrx.dart'; // Access low-level PDFium bindings import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; @@ -140,11 +138,6 @@ class LowLevelPdfiumAccess { // Now you can use all PDFium C API functions through the bindings // Remember to wrap calls with suspendPdfiumWorkerDuringAction await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { - // Example: Get PDFium version string - final versionPtr = pdfium.FPDF_GetVersionString(); - final version = versionPtr.cast().toDartString(); - print('PDFium version: $version'); - // You can call any PDFium function from the bindings // pdfium.FPDF_LoadDocument(...) // pdfium.FPDF_GetPageCount(...) @@ -153,13 +146,11 @@ class LowLevelPdfiumAccess { } Future> extractCustomMetadata(Uint8List pdfData) async { - final pdfium = await loadPdfiumBindings(); - return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { - final dataPtr = calloc(pdfData.length); - dataPtr.asTypedList(pdfData.length).setAll(0, pdfData); + return using((arena) { + final dataPtr = arena.allocate(pdfData.length); + dataPtr.asTypedList(pdfData.length).setAll(0, pdfData); - try { // Load document using low-level API final doc = pdfium.FPDF_LoadMemDocument( dataPtr.cast(), @@ -181,23 +172,18 @@ class LowLevelPdfiumAccess { // Get document metadata tags for (final tag in ['Title', 'Author', 'Subject', 'Keywords', 'Creator']) { - final buffer = calloc(256); - try { - final tagPtr = tag.toNativeUtf8(); - final len = pdfium.FPDF_GetMetaText( - doc, - tagPtr.cast(), - buffer.cast(), - 256, - ); - if (len > 0) { - // PDFium returns UTF-16 encoded text - final text = buffer.cast().toDartString(length: (len ~/ 2) - 1); - metadata[tag] = text; - } - calloc.free(tagPtr); - } finally { - calloc.free(buffer); + final buffer = arena.allocate(256); + final tagPtr = tag.toNativeUtf8(allocator: arena); + final len = pdfium.FPDF_GetMetaText( + doc, + tagPtr.cast(), + buffer.cast(), + 256, + ); + if (len > 0) { + // PDFium returns UTF-16 encoded text + final text = buffer.cast().toDartString(length: (len ~/ 2) - 1); + metadata[tag] = text; } } @@ -205,9 +191,7 @@ class LowLevelPdfiumAccess { } finally { pdfium.FPDF_CloseDocument(doc); } - } finally { - calloc.free(dataPtr); - } + }); }); } } @@ -218,7 +202,7 @@ class LowLevelPdfiumAccess { - Import `package:pdfrx_engine/src/native/pdfium_bindings.dart` for the FFI binding definitions - Import `package:pdfrx_engine/src/native/pdfium.dart` for the `loadPdfiumBindings()` function - Always wrap PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts -- Remember to properly manage memory (use `calloc` and `calloc.free`) +- Remember to properly manage memory (use `Arena` for automatic memory management) - PDFium text APIs often return UTF-16 encoded strings ### Example 3: Batch Processing with Multiple Libraries @@ -281,26 +265,7 @@ final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringActio ); ``` -### 3. Short Suspension Periods - -Keep the suspension period as short as possible to maintain pdfrx responsiveness: - -```dart -// ❌ Bad - Long suspension -await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async { - final result = await otherLibrary.process(data); - await longRunningNonPdfiumOperation(); // Don't include this - return result; -}); - -// ✅ Good - Minimal suspension -final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction( - () => otherLibrary.process(data) -); -await longRunningNonPdfiumOperation(); // Run outside suspension -``` - -### 4. Error Handling +### 3. Error Handling Always handle errors appropriately: diff --git a/doc/Low-Level-PDFium-Bindings-Access.md b/doc/Low-Level-PDFium-Bindings-Access.md index 084803fc..02212a76 100644 --- a/doc/Low-Level-PDFium-Bindings-Access.md +++ b/doc/Low-Level-PDFium-Bindings-Access.md @@ -65,6 +65,8 @@ await pdfrxInitialize(); For more information about initialization, see [pdfrx Initialization](pdfrx-Initialization.md). +**Important:** pdfrx does not support unloading PDFium. Never call `FPDF_DestroyLibrary` as it will cause undefined behavior. PDFium remains loaded for the lifetime of the application. + ## Usage Examples ### Basic PDFium Function Access From 3829f1f79409ab132de6f1a3203f1eb8a575e64c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 12:13:42 +0900 Subject: [PATCH 331/663] Release pdfrx v2.1.16 and pdfrx_engine v0.1.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pdfrx_engine v0.1.17: - #474: Add PdfrxEntryFunctions.initPdfium and suspendPdfiumWorkerDuringAction - Documentation improvements for low-level PDFium bindings access pdfrx v2.1.16: - #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction for safe interoperability - Documentation improvements for PDFium interoperability and initialization - Updated to pdfrx_engine 0.1.17 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 6 ++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 5aa599fb..52b8c84c 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.1.16 + +- #474 Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it +- #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction +- Documentation improvements for low-level PDFium bindings access/PDFium interoperability and initialization +- Updated to pdfrx_engine 0.1.17 + # 2.1.15 - FIXED: Package.swift does not point to pdfium-apple-v11 dependencies correctly diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index abd1ac28..209b5063 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.15 + pdfrx: ^2.1.16 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 701cbefa..8dc7713c 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.15 +version: 2.1.16 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.16 + pdfrx_engine: ^0.1.17 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 6b324483..33094e79 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.17 + +- #474 Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it +- #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction +- Documentation improvements for low-level PDFium bindings access/PDFium interoperability and initialization + ## 0.1.16 - More error handling logic for improved stability ([#468](https://github.com/espresso3389/pdfrx/issues/468)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 7c793617..82f3da51 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.16 +version: 0.1.17 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From e0a7150fd8717edb7f63319e4b29a84f6cb355fe Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 22 Sep 2025 12:55:03 +0900 Subject: [PATCH 332/663] WIP --- packages/pdfrx/CHANGELOG.md | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 52b8c84c..cf2bd48b 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,7 +1,7 @@ # 2.1.16 -- #474 Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it -- #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction - Documentation improvements for low-level PDFium bindings access/PDFium interoperability and initialization - Updated to pdfrx_engine 0.1.17 diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 33094e79..d20e7c81 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.1.17 -- #474 Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it -- #474: Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction - Documentation improvements for low-level PDFium bindings access/PDFium interoperability and initialization ## 0.1.16 From 9e40aa11e36612752cb465a4f85f48ec3478537c Mon Sep 17 00:00:00 2001 From: Babek Teymurov Date: Mon, 22 Sep 2025 10:24:56 +0400 Subject: [PATCH 333/663] Add annotationContent support to PdfLink --- .../lib/src/native/pdfrx_pdfium.dart | 31 +++++++++++++++++-- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 7 +++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 53667fb4..270381c9 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -1011,6 +1011,28 @@ class _PdfPagePdfium extends PdfPage { return urlBuffer.cast().toDartString(); } + static String? _getAnnotationContent(pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { + final contentLength = pdfium.FPDFAnnot_GetStringValue( + annot, + 'Contents'.toNativeUtf8(allocator: arena).cast(), + nullptr, + 0, + ); + + if (contentLength > 0) { + final contentBuffer = arena.allocate(contentLength); + pdfium.FPDFAnnot_GetStringValue( + annot, + 'Contents'.toNativeUtf8(allocator: arena).cast(), + contentBuffer, + contentLength, + ); + return contentBuffer.cast().toDartString(); + } + + return null; + } + Future> _loadAnnotLinks() async => document.isDisposed ? [] : await (await backgroundWorker).compute( @@ -1031,13 +1053,16 @@ class _PdfPagePdfium extends PdfPage { r.right, r.top > r.bottom ? r.bottom : r.top, ).translate(-params.bbLeft, -params.bbBottom); + + final content = _getAnnotationContent(annot, arena); + final dest = _processAnnotDest(annot, document, arena); if (dest != nullptr) { - links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena))); + links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena), annotationContent: content)); } else { final uri = _processAnnotLink(annot, document, arena); - if (uri != null) { - links.add(PdfLink([rect], url: uri)); + if (uri != null || content != null) { + links.add(PdfLink([rect], url: uri, annotationContent: content)); } } pdfium.FPDFPage_CloseAnnot(annot); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 7097988f..ba11a150 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1465,7 +1465,7 @@ enum PdfDestCommand { /// Either one of [url] or [dest] is valid (not null). /// See [PdfPage.loadLinks]. class PdfLink { - const PdfLink(this.rects, {this.url, this.dest}); + const PdfLink(this.rects, {this.url, this.dest, this.annotationContent}); /// Link URL. final Uri? url; @@ -1477,6 +1477,7 @@ class PdfLink { /// Link location. final List rects; + final String? annotationContent; /// Compact the link. /// @@ -1484,12 +1485,12 @@ class PdfLink { /// [rects] is typically growable and also modifiable. The method ensures that [rects] is unmodifiable. /// [dest] is also compacted by calling [PdfDest.compact]. PdfLink compact() { - return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact()); + return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact(), annotationContent: annotationContent); } @override String toString() { - return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects}'; + return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects, annotationContent: $annotationContent }'; } @override From d3525cabc67309d111b8b080c32b87324fa9935f Mon Sep 17 00:00:00 2001 From: Babek Teymurov Date: Mon, 22 Sep 2025 12:09:00 +0400 Subject: [PATCH 334/663] Add annotationContent Wasm --- packages/pdfrx/assets/pdfium_worker.js | 62 ++++++++++++++++----- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 19 +++++-- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 0f64d0ef..e37a13b1 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -621,7 +621,7 @@ const emEnv = { _localtime_js: function (time, tmPtr) { _notImplemented('_localtime_js'); }, - _tzset_js: function () {}, + _tzset_js: function () { }, emscripten_date_now: function () { return Date.now(); }, @@ -903,7 +903,7 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { } catch (e) { try { if (formHandle !== 0) Pdfium.wasmExports.FPDFDOC_ExitFormFillEnvironment(formHandle); - } catch (e) {} + } catch (e) { } Pdfium.wasmExports.free(formInfo); delete disposers[docHandle]; onDispose(); @@ -935,12 +935,12 @@ function _loadPagesInLimitedTime(docHandle, pagesLoadedCountSoFar, maxPageCountT throw new Error(`FPDF_LoadPage failed (${_getErrorMessage(error)})`); } - const rectBuffer = Pdfium.wasmExports.malloc(4 * 4); // FS_RECTF: float[4] - Pdfium.wasmExports.FPDF_GetPageBoundingBox(pageHandle, rectBuffer); - const rect = new Float32Array(Pdfium.memory.buffer, rectBuffer, 4); - const bbLeft = rect[0]; - const bbBottom = rect[3]; - Pdfium.wasmExports.free(rectBuffer); + const rectBuffer = Pdfium.wasmExports.malloc(4 * 4); // FS_RECTF: float[4] + Pdfium.wasmExports.FPDF_GetPageBoundingBox(pageHandle, rectBuffer); + const rect = new Float32Array(Pdfium.memory.buffer, rectBuffer, 4); + const bbLeft = rect[0]; + const bbBottom = rect[3]; + Pdfium.wasmExports.free(rectBuffer); pages.push({ pageIndex: i, @@ -977,7 +977,7 @@ function closeDocument(params) { if (params.formHandle) { try { Pdfium.wasmExports.FPDFDOC_ExitFormFillEnvironment(params.formHandle); - } catch (e) {} + } catch (e) { } } Pdfium.wasmExports.free(params.formInfo); Pdfium.wasmExports.FPDF_CloseDocument(params.docHandle); @@ -1267,6 +1267,7 @@ function _getLinkUrl(linkPage, linkIndex) { return url; } + /** * @param {{docHandle: number, pageIndex: number}} params * @returns {Array} @@ -1275,20 +1276,31 @@ function _loadAnnotLinks(params) { const { pageIndex, docHandle } = params; const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); const count = Pdfium.wasmExports.FPDFPage_GetAnnotCount(pageHandle); - const rectF = Pdfium.wasmExports.malloc(4 * 4); // float[4] + const rectF = Pdfium.wasmExports.malloc(4 * 4); const links = []; for (let i = 0; i < count; i++) { const annot = Pdfium.wasmExports.FPDFPage_GetAnnot(pageHandle, i); Pdfium.wasmExports.FPDFAnnot_GetRect(annot, rectF); const [l, t, r, b] = new Float32Array(Pdfium.memory.buffer, rectF, 4); const rect = [l, t > b ? t : b, r, t > b ? b : t]; + + const content = _getAnnotationContent(annot); + const dest = _processAnnotDest(annot, docHandle); if (dest) { - links.push({ rects: [rect], dest: _pdfDestFromDest(dest, docHandle) }); + links.push({ + rects: [rect], + dest: _pdfDestFromDest(dest, docHandle), + annotationContent: content + }); } else { const url = _processAnnotLink(annot, docHandle); - if (url) { - links.push({ rects: [rect], url: url }); + if (url || content) { + links.push({ + rects: [rect], + url: url, + annotationContent: content + }); } } Pdfium.wasmExports.FPDFPage_CloseAnnot(annot); @@ -1298,6 +1310,30 @@ function _loadAnnotLinks(params) { return links; } +function _getAnnotationContent(annot) { + const contentsKey = StringUtils.allocateUTF8("Contents"); + + const contentLength = Pdfium.wasmExports.FPDFAnnot_GetStringValue( + annot, contentsKey, null, 0 + ); + + let content = null; + if (contentLength > 0) { + const contentBuffer = Pdfium.wasmExports.malloc(contentLength * 2); + Pdfium.wasmExports.FPDFAnnot_GetStringValue( + annot, contentsKey, contentBuffer, contentLength + ); + content = StringUtils.utf16BytesToString( + new Uint8Array(Pdfium.memory.buffer, contentBuffer, contentLength * 2) + ); + Pdfium.wasmExports.free(contentBuffer); + } + + StringUtils.freeUTF8(contentsKey); + return content; +} + + /** * * @param {number} annot diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index a4096265..a3827fdb 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -515,15 +515,24 @@ class _PdfPageWasm extends PdfPage { (rect[3] as double) - bbBottom, ); }).toList(); + final url = link['url']; + final dest = link['dest']; + final annotationContent = link['annotationContent'] as String?; + if (url is String) { - return PdfLink(rects, url: Uri.tryParse(url)); + return PdfLink(rects, url: Uri.tryParse(url), annotationContent: annotationContent); } - final dest = link['dest']; - if (dest is! Map) { - throw FormatException('Unexpected link destination structure: $dest'); + + if (dest != null && dest is Map) { + return PdfLink(rects, dest: _pdfDestFromMap(dest), annotationContent: annotationContent); + } + + if (annotationContent != null) { + return PdfLink(rects, annotationContent: annotationContent); } - return PdfLink(rects, dest: _pdfDestFromMap(dest)); + + return PdfLink(rects, annotationContent: annotationContent); }).toList(); } From cf0aca4fe0f9599b509e7084f2ed2ee28a3db0bc Mon Sep 17 00:00:00 2001 From: james Date: Tue, 23 Sep 2025 16:06:25 +0930 Subject: [PATCH 335/663] Added ScrollPhysics to InteractiveViewer + required changes to PdfViewer to support this --- packages/pdfrx/example/viewer/lib/main.dart | 5 +- .../lib/src/utils/double_extensions.dart | 5 + .../lib/src/widgets/interactive_viewer.dart | 670 +++++++++++++++--- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 106 ++- 4 files changed, 685 insertions(+), 101 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index bfca81bf..16aa83fd 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -311,7 +311,8 @@ class _MainPageState extends State with WidgetsBindingObserver { keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, maxScale: 8, - onViewSizeChanged: (viewSize, oldViewSize, controller) { + scrollPhysics: const BouncingScrollPhysics(), + /*onViewSizeChanged: (viewSize, oldViewSize, controller) { if (oldViewSize != null) { // // Calculate the matrix to keep the center position during device @@ -325,7 +326,7 @@ class _MainPageState extends State with WidgetsBindingObserver { // during widget-tree's build process. Future.delayed(const Duration(milliseconds: 200), () => controller.goTo(newMatrix)); } - }, + }, */ viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/utils/double_extensions.dart b/packages/pdfrx/lib/src/utils/double_extensions.dart index 48d51d86..5a86e0b4 100644 --- a/packages/pdfrx/lib/src/utils/double_extensions.dart +++ b/packages/pdfrx/lib/src/utils/double_extensions.dart @@ -10,4 +10,9 @@ extension DoubleExtensions on double { final d = this > another ? this / another : another / this; return d - 1 < error; } + + /// Round the double to keep 10-bits of precision under the binary point. + /// + /// It's almost 3 decimal places (i.e. 1.23456789 => 1.234) but cleaner in binary representation. + double round10BitFrac() => (this * 1024).round() / 1024; } diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index e7ffa6ec..b7de973f 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -11,7 +11,9 @@ import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; +import 'package:flutter/services.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4, Quad, Vector3; +import '../utils/double_extensions.dart'; // Examples can assume: // late BuildContext context; @@ -80,6 +82,9 @@ class InteractiveViewer extends StatefulWidget { this.alignment, this.trackpadScrollCausesScale = false, this.onWheelDelta, + this.scrollPhysics, + this.scrollPhysicsScale, + this.scrollPhysicsAutoAdjustBoundaries = true, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), @@ -125,6 +130,9 @@ class InteractiveViewer extends StatefulWidget { this.alignment, this.trackpadScrollCausesScale = false, this.onWheelDelta, + this.scrollPhysics, + this.scrollPhysicsScale, + this.scrollPhysicsAutoAdjustBoundaries = true, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), @@ -383,12 +391,22 @@ class InteractiveViewer extends StatefulWidget { /// To override the default mouse wheel behavior. /// - final void Function(PointerScrollEvent)? onWheelDelta; + final void Function(PointerScrollEvent event)? onWheelDelta; // Used as the coefficient of friction in the inertial translation animation. // This value was eyeballed to give a feel similar to Google Photos. static const double _kDrag = 0.0000135; + /// ScrollPhysics to use for panning + final ScrollPhysics? scrollPhysics; + + /// ScrollPhysic to use for scaling + final ScrollPhysics? scrollPhysicsScale; + + /// Whether to automatically increase the ScrollPhysics boundaries when the + /// child size is smaller than the viewport size. + final bool scrollPhysicsAutoAdjustBoundaries; + /// Returns the closest point to the given point on the given line segment. @visibleForTesting static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) { @@ -468,6 +486,8 @@ class InteractiveViewer extends StatefulWidget { } class _InteractiveViewerState extends State with TickerProviderStateMixin { + // Preserve the originally provided boundaryMargin for recalculation overrides. + late final EdgeInsets _originalBoundaryMargin = widget.boundaryMargin; late TransformationController _transformer = widget.transformationController ?? TransformationController(); final GlobalKey _childKey = GlobalKey(); @@ -484,6 +504,18 @@ class _InteractiveViewerState extends State with TickerProvid double _currentRotation = 0.0; // Rotation of _transformationController.value. _GestureType? _gestureType; + // For ScrollPhysics + late AnimationController _snapController; // Snap-back animation controller and matrices/scales + late Matrix4 _snapStartMatrix; // Snap-back for matrix interpolation + Matrix4? _snapTargetMatrix; // Holds the transform at the exact moment the pinch ends + late Offset _snapFocalPoint; // Focal point for matrix snap-back interpolation + double _lastScale = 1.0; // to enable us to work in incremental scale changes for pinch zoom + Simulation? simulationX; // Simulations to use if scrollPhysics is specified + Simulation? simulationY; + Simulation? combinedSimulation; + Simulation? simulationScale; // Simulation for scale fling + // end ScrollPhysics + // TODO(justinmc): Add rotateEnabled parameter to the widget and remove this // hardcoded value when the rotation feature is implemented. // https://github.com/flutter/flutter/issues/57698 @@ -531,7 +563,7 @@ class _InteractiveViewerState extends State with TickerProvid final Offset alignedTranslation; - if (_currentAxis != null) { + if (_currentAxis != null && _gestureType == _GestureType.pan) { alignedTranslation = switch (widget.panAxis) { PanAxis.horizontal => _alignAxis(translation, Axis.horizontal), PanAxis.vertical => _alignAxis(translation, Axis.vertical), @@ -554,6 +586,53 @@ class _InteractiveViewerState extends State with TickerProvid return nextMatrix; } + /// ScrollPhysics + /// If the ScrollPhysics is defined we apply physics (bouncing or clamping) during pan. + if (widget.scrollPhysics != null) { + final physics = + (_gestureType == _GestureType.scale) + ? (widget.scrollPhysicsScale ?? widget.scrollPhysics!) + : widget.scrollPhysics!; + // current translation in scene coordinates (negative because controller stores inverse) + final Offset currentOffset = _getMatrixTranslation(_transformer.value) * -1; + // build scroll metrics + final ScrollMetrics metricsX = _calculateScrollMetrics(currentOffset.dx, AxisDirection.right); + final ScrollMetrics metricsY = _calculateScrollMetrics(currentOffset.dy, AxisDirection.down); + + final double proposedX = currentOffset.dx - alignedTranslation.dx; + final double proposedY = currentOffset.dy - alignedTranslation.dy; + + final double overscrollX = + proposedX == currentOffset.dx ? 0 : physics.applyBoundaryConditions(metricsX, proposedX); // : 0. + final double overscrollY = + proposedY == currentOffset.dy ? 0 : physics.applyBoundaryConditions(metricsY, proposedY); // : 0. + + // If the overscroll is zero, the ScrollPhysics (such as BouncingScrollPhysics) is + // enabling us to go out of boundaries, so we apply physics to the translation. + if (overscrollX == 0 && overscrollY == 0) { + if (_gestureType == _GestureType.scale) { + // TODO: better handle pan offsets when pinch zooming - for now, don't apply + // physics as it introduces issues around the snapback animation position + // due to an incorrect focal point, as well as causing undesired zoom behavior + // such as when zooming out at the bottom of a document + return nextMatrix; + } + // Check if the offset is accepted by the ScrollPhysics, and so apply it. + double dx = 0.0; + if (alignedTranslation.dx != 0 && physics.shouldAcceptUserOffset(_normalizeScrollMetrics(metricsX))) { + dx = physics.applyPhysicsToUserOffset(metricsX, alignedTranslation.dx); + } + double dy = 0.0; + if (alignedTranslation.dy != 0 && physics.shouldAcceptUserOffset(_normalizeScrollMetrics(metricsY))) { + dy = physics.applyPhysicsToUserOffset(metricsY, alignedTranslation.dy); + } + return matrix.clone()..translateByDouble(dx, dy, 0, 1); + } else { + // correct any overscroll + return matrix.clone()..translateByDouble(alignedTranslation.dx + overscrollX, alignedTranslation.dy + overscrollY, 0, 1); + } + } + // Expand the boundaries with rotation. This prevents the problem where a // mismatch in orientation between the viewport and boundaries effectively // limits translation. With this approach, all points that are visible with @@ -579,8 +658,8 @@ class _InteractiveViewerState extends State with TickerProvid // calculating the translation to put the viewport inside that Quad is more // complicated than this when rotated. // https://github.com/flutter/flutter/issues/57698 - final Matrix4 correctedMatrix = matrix.clone() - ..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); + final Matrix4 correctedMatrix = + matrix.clone()..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); // Double check that the corrected translation fits. final Quad correctedViewport = _transformViewport(correctedMatrix, _viewport); @@ -610,23 +689,104 @@ class _InteractiveViewerState extends State with TickerProvid // Return a new matrix representing the given matrix after applying the given // scale. Matrix4 _matrixScale(Matrix4 matrix, double scale) { + // No-op for unity scale if (scale == 1.0) { return matrix.clone(); } assert(scale != 0.0); - // Don't allow a scale that results in an overall scale beyond min/max - // scale. - final double currentScale = _transformer.value.getMaxScaleOnAxis(); - final double totalScale = math.max( - currentScale * scale, - // Ensure that the scale cannot make the child so big that it can't fit - // inside the boundaries (in either direction). - math.max(_viewport.width / _boundaryRect.width, _viewport.height / _boundaryRect.height), - ); - final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); - final double clampedScale = clampedTotalScale / currentScale; - return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); + // fallback to widget.scrollPhysics if widgry.scrollPhysicsScale not specified + final scrollPhysics = widget.scrollPhysicsScale ?? widget.scrollPhysics; + + if (scrollPhysics != null) { + // Compute current and desired scales + final double currentScale = _transformer.value.getMaxScaleOnAxis(); + // scale provided is a desired change in scale between the current scale + // and the start of the gesture + final double scaleChange = scale; + + // desired but not necessarily achieved if physics is applied + final double desiredScale = currentScale * scale; + + final allowedScale = _getAllowedScale(desiredScale); + // Early return if not allowed to zoom outside bounds + if (allowedScale != desiredScale) { + return matrix.clone()..scaleByDouble(allowedScale, allowedScale, allowedScale, 1); + } + + // Compute ratio of this update's scale to the previous update + final double scaleRatio = scaleChange / _lastScale; + // Store for next frame + _lastScale = scaleChange; + // Physics requires the incremental scale change since last update + final double incrementalScale = currentScale * scaleRatio; + + // Content-space-based scrollPhysics for scale overscroll and undershoot + if (_gestureType == _GestureType.scale && + !_snapController.isAnimating && + ((desiredScale < widget.minScale) || (desiredScale > widget.maxScale))) { + final Size contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; + + // Compute current and desired absolute scale + final double contentWidth = contentSize.width * currentScale; + final double desiredContentWidth = contentSize.width * incrementalScale; + final double contentHeight = contentSize.height * currentScale; + final double desiredContentHeight = contentSize.height * incrementalScale; + + // Build horizontal and vertical metrics + final ScrollMetrics metricsX = FixedScrollMetrics( + pixels: contentWidth, + minScrollExtent: contentSize.width * widget.minScale, + maxScrollExtent: contentSize.width * widget.maxScale, + viewportDimension: contentSize.width * widget.maxScale, + axisDirection: AxisDirection.right, + devicePixelRatio: 1.0, + ); + final ScrollMetrics metricsY = FixedScrollMetrics( + pixels: contentHeight, + minScrollExtent: contentSize.height * widget.minScale, + maxScrollExtent: contentSize.height * widget.maxScale, + viewportDimension: contentSize.height * widget.maxScale, + axisDirection: AxisDirection.down, + devicePixelRatio: 1.0, + ); + + // Compute content deltas + final double deltaX = desiredContentWidth - contentWidth; + final double deltaY = desiredContentHeight - contentHeight; + + // Apply scroll physics half the delta to simulate exceeding a boundary + // on one side + final double adjustedX = scrollPhysics.applyPhysicsToUserOffset(metricsX, deltaX / 2) * 2; + final double adjustedY = scrollPhysics.applyPhysicsToUserOffset(metricsY, deltaY / 2) * 2; + + // Convert back to scale factors + final double newScaleX = (contentWidth + adjustedX) / contentWidth; + final double newScaleY = (contentHeight + adjustedY) / contentHeight; + final double factor = (newScaleX + newScaleY) / 2; + + return matrix.clone()..scale(factor); + } else { + final double clampedTotalScale = clampDouble(desiredScale, widget.minScale, widget.maxScale); + final double clampedScale = clampedTotalScale / currentScale; + + // Apply the scale factor to the matrix + return matrix.clone()..scale(clampedScale); + } + } else { + // Don't allow a scale that results in an overall scale beyond min/max + // scale. + final double currentScale = _transformer.value.getMaxScaleOnAxis(); + final double totalScale = math.max( + currentScale * scale, + // Ensure that the scale cannot make the child so big that it can't fit + // inside the boundaries (in either direction). + math.max(_viewport.width / _boundaryRect.width, _viewport.height / _boundaryRect.height), + ); + final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); + final double clampedScale = clampedTotalScale / currentScale; + return matrix.clone()..scale(clampedScale); + } } // Return a new matrix representing the given matrix after applying the given @@ -678,6 +838,7 @@ class _InteractiveViewerState extends State with TickerProvid _animation?.removeListener(_handleInertiaAnimation); _animation = null; } + if (_scaleController.isAnimating) { _scaleController.stop(); _scaleController.reset(); @@ -688,7 +849,9 @@ class _InteractiveViewerState extends State with TickerProvid _gestureType = null; _currentAxis = null; _scaleStart = _transformer.value.getMaxScaleOnAxis(); + _lastScale = 1.0; // ScrollPhysics _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); + _snapFocalPoint = details.localFocalPoint; _rotationStart = _currentRotation; } @@ -721,6 +884,7 @@ class _InteractiveViewerState extends State with TickerProvid // previous call to _onScaleUpdate. final double desiredScale = _scaleStart! * details.scale; final double scaleChange = desiredScale / scale; + _snapFocalPoint = details.localFocalPoint; _transformer.value = _matrixScale(_transformer.value, scaleChange); // While scaling, translate such that the user's two fingers stay on @@ -736,7 +900,7 @@ class _InteractiveViewerState extends State with TickerProvid // _referenceFocalPoint so subsequent updates happen in relation to // the new effective focal point. final Offset focalPointSceneCheck = _transformer.toScene(details.localFocalPoint); - if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) { + if (_referenceFocalPoint!.round10BitFrac() != focalPointSceneCheck.round10BitFrac()) { _referenceFocalPoint = focalPointSceneCheck; } @@ -776,7 +940,6 @@ class _InteractiveViewerState extends State with TickerProvid // are handled with GestureDetector's scale gesture. void _onScaleEnd(ScaleEndDetails details) { widget.onInteractionEnd?.call(details); - _scaleStart = null; _rotationStart = null; _referenceFocalPoint = null; @@ -792,61 +955,128 @@ class _InteractiveViewerState extends State with TickerProvid switch (_gestureType) { case _GestureType.pan: - if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) { - _currentAxis = null; - return; + if (widget.scrollPhysics != null) { + if (_snapController.isAnimating) return; + + final Vector3 currentTranslation = _transformer.value.getTranslation(); + final Offset currentOffset = Offset(currentTranslation.x, currentTranslation.y); + final adjustedOffset = currentOffset * -1; + + final flingVelocityX = + math.min(details.velocity.pixelsPerSecond.dx.abs(), widget.scrollPhysics!.maxFlingVelocity) * + details.velocity.pixelsPerSecond.dx.sign; + final flingVelocityY = + math.min(details.velocity.pixelsPerSecond.dy.abs(), widget.scrollPhysics!.maxFlingVelocity) * + details.velocity.pixelsPerSecond.dy.sign; + + final metricsX = _calculateScrollMetrics(adjustedOffset.dx, AxisDirection.right); + final metricsY = _calculateScrollMetrics(adjustedOffset.dy, AxisDirection.down); + + if (details.velocity.pixelsPerSecond.distance <= widget.scrollPhysics!.minFlingVelocity && + !metricsX.outOfRange && + !metricsY.outOfRange) { + return; + } + + simulationX = widget.scrollPhysics!.createBallisticSimulation(metricsX, -flingVelocityX); + simulationY = widget.scrollPhysics!.createBallisticSimulation(metricsY, -flingVelocityY); + combinedSimulation = _getCombinedSimulation(simulationX, simulationY); + + if (combinedSimulation == null) { + return; + } + + _controller.addListener(_handleInertiaAnimation); + _controller.animateWith(combinedSimulation!); + } else { + if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) { + _currentAxis = null; + return; + } + final Vector3 translationVector = _transformer.value.getTranslation(); + final Offset translation = Offset(translationVector.x, translationVector.y); + // (Removed FrictionSimulation logic for scale; only pan uses it.) + final FrictionSimulation frictionSimulationX = FrictionSimulation( + widget.interactionEndFrictionCoefficient, + translation.dx, + details.velocity.pixelsPerSecond.dx, + ); + final FrictionSimulation frictionSimulationY = FrictionSimulation( + widget.interactionEndFrictionCoefficient, + translation.dy, + details.velocity.pixelsPerSecond.dy, + ); + final double tFinal = _getFinalTime( + details.velocity.pixelsPerSecond.distance, + widget.interactionEndFrictionCoefficient, + ); + _animation = Tween( + begin: translation, + end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX), + ).animate(CurvedAnimation(parent: _controller, curve: Curves.decelerate)); + _controller.duration = Duration(milliseconds: (tFinal * 1000).round()); + _animation!.addListener(_handleInertiaAnimation); + _controller.forward(); } - final Vector3 translationVector = _transformer.value.getTranslation(); - final Offset translation = Offset(translationVector.x, translationVector.y); - final FrictionSimulation frictionSimulationX = FrictionSimulation( - widget.interactionEndFrictionCoefficient, - translation.dx, - details.velocity.pixelsPerSecond.dx, - ); - final FrictionSimulation frictionSimulationY = FrictionSimulation( - widget.interactionEndFrictionCoefficient, - translation.dy, - details.velocity.pixelsPerSecond.dy, - ); - final double tFinal = _getFinalTime( - details.velocity.pixelsPerSecond.distance, - widget.interactionEndFrictionCoefficient, - ); - _animation = Tween( - begin: translation, - end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX), - ).animate(CurvedAnimation(parent: _controller, curve: Curves.decelerate)); - _controller.duration = Duration(milliseconds: (tFinal * 1000).round()); - _animation!.addListener(_handleInertiaAnimation); - _controller.forward(); + break; case _GestureType.scale: - if (details.scaleVelocity.abs() < 0.1) { - _currentAxis = null; - return; + if (widget.scrollPhysics != null) { + final double endScale = _transformer.value.getMaxScaleOnAxis(); + final double clampedScale = endScale.clamp(widget.minScale, widget.maxScale); + + if (clampedScale != endScale) { + HapticFeedback.lightImpact(); + } + // even if the the scale doesn't change, we may be out of bounds, and + // want to animate the snap back to bounds + _snapStartMatrix = _transformer.value.clone(); + final Offset pivotScene = _transformer.toScene(_snapFocalPoint); + final Matrix4 endMatrix = + _snapStartMatrix.clone() + ..translateByDouble(pivotScene.dx, pivotScene.dy, 0, 1) + ..scaleByDouble(clampedScale / endScale, clampedScale / endScale, clampedScale / endScale, 1) + ..translateByDouble(-pivotScene.dx, -pivotScene.dy, 0, 1); + _snapTargetMatrix = _matrixClamp(endMatrix); + + _snapController + ..removeListener(_animateSnap) + ..addListener(_animateSnap) + ..forward(from: 0.0).then((_) { + _snapTargetMatrix = null; + }); + break; + } else { + if (details.scaleVelocity.abs() < 0.1) { + _currentAxis = null; + return; + } + final double scale = _transformer.value.getMaxScaleOnAxis(); + final FrictionSimulation frictionSimulation = FrictionSimulation( + widget.interactionEndFrictionCoefficient * widget.scaleFactor, + scale, + details.scaleVelocity / 10, + ); + final double tFinal = _getFinalTime( + details.scaleVelocity.abs(), + widget.interactionEndFrictionCoefficient, + effectivelyMotionless: 0.1, + ); + _scaleAnimation = Tween( + begin: scale, + end: frictionSimulation.x(tFinal), + ).animate(CurvedAnimation(parent: _scaleController, curve: Curves.decelerate)); + _scaleController.duration = Duration(milliseconds: (tFinal * 1000).round()); + _scaleAnimation!.addListener(_handleScaleAnimation); + _scaleController.forward(); } - final double scale = _transformer.value.getMaxScaleOnAxis(); - final FrictionSimulation frictionSimulation = FrictionSimulation( - widget.interactionEndFrictionCoefficient * widget.scaleFactor, - scale, - details.scaleVelocity / 10, - ); - final double tFinal = _getFinalTime( - details.scaleVelocity.abs(), - widget.interactionEndFrictionCoefficient, - effectivelyMotionless: 0.1, - ); - _scaleAnimation = Tween( - begin: scale, - end: frictionSimulation.x(tFinal), - ).animate(CurvedAnimation(parent: _scaleController, curve: Curves.decelerate)); - _scaleController.duration = Duration(milliseconds: (tFinal * 1000).round()); - _scaleAnimation!.addListener(_handleScaleAnimation); - _scaleController.forward(); - case _GestureType.rotate || null: + case _GestureType.rotate: + case null: break; } } + // (Removed: _handleScaleEndAnimation) + // Handle mousewheel and web trackpad scroll events. void _receivedPointerSignal(PointerSignalEvent event) { final Offset local = event.localPosition; @@ -933,21 +1163,253 @@ class _InteractiveViewerState extends State with TickerProvid void _handleInertiaAnimation() { if (!_controller.isAnimating) { + if (widget.scrollPhysics != null) { + _controller.removeListener(_handleInertiaAnimation); + } else { + _animation?.removeListener(_handleInertiaAnimation); + _animation = null; + } _currentAxis = null; - _animation?.removeListener(_handleInertiaAnimation); - _animation = null; _controller.reset(); return; } // Translate such that the resulting translation is _animation.value. final Vector3 translationVector = _transformer.value.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); - _transformer.value = _matrixTranslate( - _transformer.value, - _transformer.toScene(_animation!.value) - _transformer.toScene(translation), + final Offset translationScene = _transformer.toScene(translation); + + if (widget.scrollPhysics != null) { + /// When using scrollPhysics, we apply a simulation rather than an animation to the offsets + final double t = _controller.lastElapsedDuration!.inMilliseconds / 1000.0; + final double simulationOffsetX = simulationX != null ? -simulationX!.x(t) : translationVector.x; + final double simulationOffsetY = simulationY != null ? -simulationY!.x(t) : translationVector.y; + final Offset simulationOffset = Offset(simulationOffsetX, simulationOffsetY); + final Offset simulationScene = _transformer.toScene(simulationOffset); + final Offset translationChangeScene = simulationScene - translationScene; + _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); + } else { + // Translate such that the resulting translation is _animation.value. + final Offset animationScene = _transformer.toScene(_animation!.value); + final Offset translationChangeScene = animationScene - translationScene; + _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); + } + } + + /// ScrollPhysics helpers + /// ChildSize is the size of the child (without the boundary margin). + Size _childSize() { + final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; + final Size childSize = childRenderBox.size; + return childSize; + } + + /// These are the boundaries for constructing a ScrollMetrics object. + Rect _computePanBoundaries({ + required Size viewportSize, + required double scale, + EdgeInsets? boundaryMargin, + bool overrideAutoAdjustBoundaries = false, + }) { + // Use original boundaryMargin unless a specific one is passed for override. + final EdgeInsets baseMargin = + (overrideAutoAdjustBoundaries && !widget.scrollPhysicsAutoAdjustBoundaries) || boundaryMargin == null + ? _originalBoundaryMargin + : boundaryMargin; + + // If boundaries are infinite, provide very large finite extents to disable clamping + if (_boundaryRect.isInfinite) { + return const Rect.fromLTRB(-double.maxFinite, -double.maxFinite, double.maxFinite, double.maxFinite); + } + // Compute the raw boundary rect using the baseMargin, then scale it + final Rect baseBoundaryRect = baseMargin.inflateRect(Offset.zero & _childSize()); + final double effectiveWidth = baseBoundaryRect.width * scale; + final double effectiveHeight = baseBoundaryRect.height * scale; + + final extraWidth = effectiveWidth - viewportSize.width; + final extraHeight = effectiveHeight - viewportSize.height; + + // Always center when content is smaller than viewport, using a small tolerance for floating imprecision. + const double kOverflowTolerance = 0.1; // logical pixels + final extraBoundaryHorizontal = extraWidth < -kOverflowTolerance ? (extraWidth.abs() / 2) : 0.0; + final extraBoundaryVertical = extraHeight < -kOverflowTolerance ? (extraHeight.abs() / 2) : 0.0; + + final minX = -((baseMargin.left * scale + extraBoundaryHorizontal)); + final maxX = -((baseMargin.left * scale + extraBoundaryHorizontal)) + math.max(extraWidth, 0); + final minY = -((baseMargin.top * scale + extraBoundaryVertical)); + final maxY = -((baseMargin.bottom * scale + extraBoundaryVertical)) + math.max(extraHeight, 0); + return Rect.fromLTRB(minX, minY, maxX, maxY).round10BitFrac(); + } + + // Normalize ScrollMetrics such that minScrollExtent = 0 and pixels shift accordingly. + // ScrollPhysics.shouldAcceptUserOffset() does not work where minScrollExtent and pixels + // are both the same value but not 0.0. + ScrollMetrics _normalizeScrollMetrics(ScrollMetrics scrollMetrics) { + double range = scrollMetrics.maxScrollExtent - scrollMetrics.minScrollExtent; + double pixels = scrollMetrics.pixels - scrollMetrics.minScrollExtent; + + // Define a small tolerance around zero to ignore tiny drifts + const double kTolerance = 0.01; + range = range.abs() < kTolerance ? 0.0 : range; + pixels = pixels.abs() < kTolerance ? 0.0 : pixels; + + return FixedScrollMetrics( + pixels: pixels, + minScrollExtent: 0.0, + maxScrollExtent: range < 0.0 ? 0.0 : range, + viewportDimension: scrollMetrics.viewportDimension, + axisDirection: scrollMetrics.axisDirection, + devicePixelRatio: scrollMetrics.devicePixelRatio, + ); + } + + /// Creates a synthetic ScrollMetrics objects for the + /// InteractiveViewer so that we can use ScrollPhysics + ScrollMetrics _calculateScrollMetrics(double pixels, AxisDirection axisDirection) { + final panBoundaries = _computePanBoundaries( + viewportSize: _viewport.size, + scale: _transformer.value.getMaxScaleOnAxis(), + boundaryMargin: widget.boundaryMargin, + ); + + final Axis axis = switch (axisDirection) { + AxisDirection.left => Axis.horizontal, + AxisDirection.right => Axis.horizontal, + AxisDirection.up => Axis.vertical, + AxisDirection.down => Axis.vertical, + }; + + final minX = panBoundaries.left; + final maxX = panBoundaries.right; + final minY = panBoundaries.top; + final maxY = panBoundaries.bottom; + + final scrollMetrics = FixedScrollMetrics( + pixels: pixels, + minScrollExtent: axis == Axis.horizontal ? minX : minY, + maxScrollExtent: axis == Axis.horizontal ? maxX : maxY, + viewportDimension: axis == Axis.horizontal ? _viewport.width : _viewport.height, + axisDirection: axisDirection, + devicePixelRatio: MediaQuery.of(context).devicePixelRatio, ); + return scrollMetrics; + } + + /// ScrollPhysics + /// Clamp the given full transform matrix to the content boundaries by + /// directly clamping its translation component. + Matrix4 _matrixClamp(Matrix4 matrix) { + final totalTranslation = _getMatrixTranslation(matrix); + final scale = matrix.getMaxScaleOnAxis(); + final viewSize = _viewport.size; + final panBoundaries = _computePanBoundaries( + viewportSize: viewSize, + scale: scale, + boundaryMargin: widget.boundaryMargin, + overrideAutoAdjustBoundaries: true, + ); + + // Ensure bounds are ordered correctly for clamp. + final minX = math.min(-panBoundaries.left, -panBoundaries.right); + final maxX = math.max(-panBoundaries.left, -panBoundaries.right); + final minY = math.min(-panBoundaries.top, -panBoundaries.bottom); + final maxY = math.max(-panBoundaries.top, -panBoundaries.bottom); + final clampedX = totalTranslation.dx.clamp(minX, maxX); + final clampedY = totalTranslation.dy.clamp(minY, maxY); + + return matrix.clone()..setTranslation(Vector3(clampedX, clampedY, 0.0)); + } + + /// Animate snap-back by interpolating scale and translation in scene-space. + void _animateSnap() { + if (_snapTargetMatrix == null) { + return; + } + final double t = Curves.ease.transform(_snapController.value); + final Matrix4 lerped = Matrix4Tween(begin: _snapStartMatrix, end: _snapTargetMatrix!).transform(t); + _transformer.value = lerped; + } + + /// Determines whether [proposedScale] can be applied without clamping, + /// by probing the widget.scrollPhysics. + double _getAllowedScale(double proposedScale) { + final scrollPhysics = widget.scrollPhysicsScale ?? widget.scrollPhysics; + if (scrollPhysics == null) { + return proposedScale.clamp(widget.minScale, widget.maxScale); + } + + final Size contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; + + final double currentScale = _transformer.value.getMaxScaleOnAxis(); + final double contentWidth = contentSize.width * currentScale; + final double desiredContentWidth = contentSize.width * proposedScale; + final double contentHeight = contentSize.height * currentScale; + final double desiredContentHeight = contentSize.height * proposedScale; + + final ScrollMetrics metricsX = FixedScrollMetrics( + pixels: contentWidth, + minScrollExtent: contentSize.width * widget.minScale, + maxScrollExtent: contentSize.width * widget.maxScale, + viewportDimension: _viewport.width, + axisDirection: AxisDirection.right, + devicePixelRatio: 1.0, + ); + final ScrollMetrics metricsY = FixedScrollMetrics( + pixels: contentHeight, + minScrollExtent: contentSize.height * widget.minScale, + maxScrollExtent: contentSize.height * widget.maxScale, + viewportDimension: _viewport.height, + axisDirection: AxisDirection.down, + devicePixelRatio: 1.0, + ); + + final double adjustmentX = scrollPhysics.applyBoundaryConditions(metricsX, desiredContentWidth); + final double adjustmentY = scrollPhysics.applyBoundaryConditions(metricsY, desiredContentHeight); + + if (adjustmentX == 0.0 && adjustmentY == 0.0) { + // No adjustment needed, so the proposed scale is allowed. + return proposedScale; + } else { + final allowedContentWidth = desiredContentWidth - adjustmentX; + final allowedContentHeight = desiredContentHeight - adjustmentY; + + if (proposedScale > widget.maxScale) { + return math.max(allowedContentWidth / contentWidth, allowedContentHeight / contentHeight); + } else { + return math.max(allowedContentWidth / contentWidth, allowedContentHeight / contentHeight); + } + } + } + + void _stopAnimation() { + _controller.stop(); + _animation?.removeListener(_handleInertiaAnimation); + _animation = null; } + Simulation? _getCombinedSimulation(Simulation? simulationX, Simulation? simulationY) { + if (simulationX == null && simulationY == null) { + return null; + } + if (simulationX == null) { + return simulationY; + } + if (simulationY == null) { + return simulationX; + } + return CombinedSimulation(simulationX: simulationX, simulationY: simulationY); + } + + void _onPointerDown(PointerDownEvent event) { + if (widget.scrollPhysics != null && _controller.isAnimating) { + // ability to stop a in-progress pan fling is particularly important + // when scroll physics is enabled as the duration and distance of the + // pan can be considerable. + _stopAnimation(); + } + } + // end ScrollPhysics + + // Handle inertia scale animation. void _handleScaleAnimation() { if (!_scaleController.isAnimating) { _currentAxis = null; @@ -980,6 +1442,7 @@ class _InteractiveViewerState extends State with TickerProvid super.initState(); _controller = AnimationController(vsync: this); _scaleController = AnimationController(vsync: this); + _snapController = AnimationController(vsync: this, duration: const Duration(milliseconds: 250)); _transformer.addListener(_handleTransformation); } @@ -1004,6 +1467,7 @@ class _InteractiveViewerState extends State with TickerProvid void dispose() { _controller.dispose(); _scaleController.dispose(); + _snapController.dispose(); _transformer.removeListener(_handleTransformation); if (widget.transformationController == null) { _transformer.dispose(); @@ -1046,8 +1510,10 @@ class _InteractiveViewerState extends State with TickerProvid return Listener( key: _parentKey, onPointerSignal: _receivedPointerSignal, + onPointerDown: _onPointerDown, // ScrollPhysics child: GestureDetector( - behavior: HitTestBehavior.opaque, // Necessary when panning off screen. + behavior: HitTestBehavior.opaque, + // Necessary when panning off screen. onScaleEnd: _onScaleEnd, onScaleStart: _onScaleStart, onScaleUpdate: _onScaleUpdate, @@ -1107,7 +1573,7 @@ enum _GestureType { pan, scale, rotate } // Given a velocity and drag, calculate the time at which motion will come to // a stop, within the margin of effectivelyMotionless. -double _getFinalTime(double velocity, double drag, {double effectivelyMotionless = 10}) { +double _getFinalTime(double velocity, double drag, {double effectivelyMotionless = 0.5}) { return math.log(effectivelyMotionless / velocity) / math.log(drag / 100); } @@ -1134,10 +1600,11 @@ Quad _transformViewport(Matrix4 matrix, Rect viewport) { // Find the axis aligned bounding box for the rect rotated about its center by // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { - final Matrix4 rotationMatrix = Matrix4.identity() - ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) - ..rotateZ(rotation) - ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); + final Matrix4 rotationMatrix = + Matrix4.identity() + ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) + ..rotateZ(rotation) + ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); final Quad boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)), @@ -1164,13 +1631,7 @@ Offset _exceedsBy(Quad boundary, Quad viewport) { } } - return _round(largestExcess); -} - -// Round the output values. This works around a precision problem where -// values that should have been zero were given as within 10^-10 of zero. -Offset _round(Offset offset) { - return Offset(double.parse(offset.dx.toStringAsFixed(9)), double.parse(offset.dy.toStringAsFixed(9))); + return largestExcess.round10BitFrac(); } // Align the given offset to the given axis by allowing movement only in the @@ -1188,7 +1649,44 @@ Axis? _getPanAxis(Offset point1, Offset point2) { if (point1 == point2) { return null; } - final double x = point2.dx - point1.dx; - final double y = point2.dy - point1.dy; + final x = point2.dx - point1.dx; + final y = point2.dy - point1.dy; return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical; } + +/// A simulation that combines two one-dimensional simulations into one, +/// one for the x axis and one for the y axis. +class CombinedSimulation extends Simulation { + CombinedSimulation({required this.simulationX, required this.simulationY}); + final Simulation simulationX; + final Simulation simulationY; + + // For a combined simulation you don’t necessarily need to use x(t) directly. + // It is provided here so that animateWith() can drive a time value. + @override + double x(double time) => simulationX.x(time); + + // Returns the combined velocity magnitude of the two simulations. + @override + double dx(double time) { + final double dxX = simulationX.dx(time); + final double dxY = simulationY.dx(time); + return math.sqrt(dxX * dxX + dxY * dxY); + } + + @override + bool isDone(double time) { + return simulationX.isDone(time) && simulationY.isDone(time); + } +} + +extension _OffsetRounder on Offset { + /// Round the double to keep 10-bits of precision under the binary point. + Offset round10BitFrac() => Offset(dx.round10BitFrac(), dy.round10BitFrac()); +} + +extension _RectRounder on Rect { + /// Round the double to keep 10-bits of precision under the binary point. + Rect round10BitFrac() => + Rect.fromLTRB(left.round10BitFrac(), top.round10BitFrac(), right.round10BitFrac(), bottom.round10BitFrac()); +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 02d4000e..4fe19bfc 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -266,6 +266,10 @@ class _PdfViewerState extends State Offset _pointerOffset = Offset.zero; PointerDeviceKind? _pointerDeviceKind; + // boundary margins adjusted to center content that's smaller than + // the viewport - used by InteractiveViewer's scrollPhysics + EdgeInsets? _adjustedBoundaryMargins; + @override void initState() { super.initState(); @@ -447,7 +451,10 @@ class _PdfViewerState extends State iv.InteractiveViewer( transformationController: _txController, constrained: false, - boundaryMargin: widget.params.boundaryMargin ?? const EdgeInsets.all(double.infinity), + boundaryMargin: _adjustedBoundaryMargins ?? + (widget.params.scrollPhysics == null + ? const EdgeInsets.all(double.infinity) + : EdgeInsets.zero), maxScale: widget.params.maxScale, minScale: minScale, panAxis: widget.params.panAxis, @@ -458,6 +465,8 @@ class _PdfViewerState extends State onInteractionUpdate: widget.params.onInteractionUpdate, interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + scrollPhysics: widget.params.scrollPhysics, + scrollPhysicsAutoAdjustBoundaries: false, // PDF pages child: GestureDetector( behavior: HitTestBehavior.opaque, @@ -518,6 +527,32 @@ class _PdfViewerState extends State ); } + Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { + final boundaryMargin = _adjustedBoundaryMargins ?? widget.params.boundaryMargin; + + if (boundaryMargin == null || boundaryMargin.horizontal.isInfinite) { + return candidate; + } + + final PdfPageLayout layout = _layout!; + final Rect visible = candidate.calcVisibleRect(viewSize); + double dxDoc = 0.0; + double dyDoc = 0.0; + + if (visible.left < 0) { + dxDoc = -visible.left - boundaryMargin.left; + } else if (visible.right > layout.documentSize.width) { + dxDoc = layout.documentSize.width - visible.right + boundaryMargin.right; + } + + if (visible.top < 0) { + dyDoc = -visible.top - boundaryMargin.top; + } else if (visible.bottom > layout.documentSize.height) { + dyDoc = layout.documentSize.height - visible.bottom + boundaryMargin.bottom; + } + return candidate.clone()..translateByDouble(-dxDoc, -dyDoc, 0, 1); + } + void _updateLayout(Size viewSize) { if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); @@ -528,6 +563,7 @@ class _PdfViewerState extends State _calcCoverFitScale(); _calcZoomStopTable(); + _adjustBoundaryMargins(viewSize, max(_minScale, _currentZoom)); void callOnViewerSizeChanged() { if (isViewSizeChanged) { @@ -736,17 +772,19 @@ class _PdfViewerState extends State } void _calcCoverFitScale() { + final params = widget.params; + final bmh = params.boundaryMargin?.horizontal == double.infinity ? 0 : params.boundaryMargin?.horizontal ?? 0; + final bmv = params.boundaryMargin?.vertical == double.infinity ? 0 : params.boundaryMargin?.vertical ?? 0; + if (_viewSize != null) { - final s1 = _viewSize!.width / _layout!.documentSize.width; - final s2 = _viewSize!.height / _layout!.documentSize.height; + final s1 = _viewSize!.width / (_layout!.documentSize.width + bmh); + final s2 = _viewSize!.height / (_layout!.documentSize.height + bmv); _coverScale = max(s1, s2); } final pageNumber = _pageNumber ?? _gotoTargetPageNumber; if (pageNumber != null) { - final params = widget.params; final rect = _layout!.pageLayouts[pageNumber - 1]; - final m2 = params.margin * 2; - _alternativeFitScale = min((_viewSize!.width - m2) / rect.width, (_viewSize!.height - m2) / rect.height); + _alternativeFitScale = min((_viewSize!.width) / (rect.width + bmh), (_viewSize!.height) / (rect.height + bmv)); if (_alternativeFitScale! <= 0) { _alternativeFitScale = null; } @@ -828,6 +866,35 @@ class _PdfViewerState extends State static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; + // Auto-adjust boundaries when content is smaller than the view, centering + // the content and ensuring InteractiveViewer's scrollPhysics works when specified + _adjustBoundaryMargins(Size viewSize, double zoom) { + if (widget.params.scrollPhysics == null) return; + + final boundaryMargin = + widget.params.boundaryMargin == null || widget.params.boundaryMargin!.horizontal.isInfinite + ? EdgeInsets.zero + : widget.params.boundaryMargin!; + + final currentDocumentSize = boundaryMargin.inflateSize(_layout!.documentSize); + + final double effectiveWidth = currentDocumentSize.width * zoom; + final double effectiveHeight = currentDocumentSize.height * zoom; + final double extraWidth = effectiveWidth - viewSize.width; + final double extraBoundaryHorizontal = extraWidth < 0 ? (-extraWidth / 2) / zoom : 0.0; + final double extraHeight = effectiveHeight - viewSize.height; + final double extraBoundaryVertical = extraHeight < 0 ? (-extraHeight / 2) / zoom : 0.0; + + _adjustedBoundaryMargins = + boundaryMargin + + EdgeInsets.fromLTRB( + extraBoundaryHorizontal, + extraBoundaryVertical, + extraBoundaryHorizontal, + extraBoundaryVertical, + ); + } + List _buildPageOverlayWidgets(BuildContext context) { final renderBox = context.findRenderObject(); if (renderBox is! RenderBox) return []; @@ -1281,7 +1348,7 @@ class _PdfViewerState extends State Matrix4 _normalizeMatrix(Matrix4 newValue) { final layout = _layout; final viewSize = _viewSize; - if (layout == null || viewSize == null) return newValue; + if (layout == null || viewSize == null || widget.params.scrollPhysics != null) return newValue; final position = newValue.calcPosition(viewSize); final newZoom = max(newValue.zoom, minScale); final hw = viewSize.width / 2 / newZoom; @@ -1289,7 +1356,7 @@ class _PdfViewerState extends State final x = position.dx.range(hw, layout.documentSize.width - hw); final y = position.dy.range(hh, layout.documentSize.height - hh); - return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize).scaledByDouble(1.0, 1.0, newZoom, 1.0); + return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize); // see note in _calcMatrixFor } /// Calculate matrix to center the specified position. @@ -1299,7 +1366,7 @@ class _PdfViewerState extends State return Matrix4.compose( vec.Vector3(-position.dx * zoom + hw, -position.dy * zoom + hh, 0), vec.Quaternion.identity(), - vec.Vector3(zoom, zoom, 1), + vec.Vector3(zoom, zoom, zoom), // setting zoom of 1 on z caused a call to matrix.maxScaleOnAxis() to return 1 even when x and y are < 1 ); } @@ -1359,8 +1426,13 @@ class _PdfViewerState extends State } } - Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) => - _calcMatrixForArea(rect: _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin), anchor: anchor); + Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) =>_calcMatrixForArea( + rect: (widget.params.boundaryMargin ?? EdgeInsets.zero).inflateRect( + _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin), + ), + anchor: anchor, + zoomMax: _currentZoom, + ); Rect _calcRectForRectInsidePage({required int pageNumber, required PdfRect rect}) { final page = _document!.pages[pageNumber - 1]; @@ -1524,7 +1596,10 @@ class _PdfViewerState extends State _gotoTargetPageNumber = pageNumber; await _goTo( - _calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), + _calcMatrixForClampedToNearestBoundary( + _calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), + viewSize: _viewSize!, + ), duration: duration, ); _setCurrentPageNumber(targetPageNumber); @@ -3313,7 +3388,12 @@ class PdfViewerController extends ValueListenable { for (int i = 0; i < layout.pageLayouts.length; i++) { final page = layout.pageLayouts[i]; if (page.intersect(viewRect).isEmpty) continue; - final zoom = (viewSize.width - params.margin * 2) / page.width; + final EdgeInsets boundaryMargin = + params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity + ? EdgeInsets.zero + : params.boundaryMargin!; + final zoom = viewSize.width / (page.width + (params.margin * 2) + boundaryMargin.horizontal); + // NOTE: keep the y-position but center the x-position final newMatrix = calcMatrixFor(Offset(page.left + page.width / 2, pos.dy), zoom: zoom); From 1dc442b53f194e4498c9c30ac7c1036926ac0578 Mon Sep 17 00:00:00 2001 From: james Date: Tue, 23 Sep 2025 16:27:58 +0930 Subject: [PATCH 336/663] added scrollPhysics parameter --- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 61f90e26..0d558622 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -70,6 +70,7 @@ class PdfViewerParams { this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.behaviorControlParams = const PdfViewerBehaviorControlParams(), this.forceReload = false, + this.scrollPhysics, }); /// Margin around the page. @@ -534,6 +535,9 @@ class PdfViewerParams { /// sometimes it is useful to force reload the viewer by setting this to true. final bool forceReload; + /// Scroll physics for the viewer. + final ScrollPhysics? scrollPhysics; + /// Determine whether the viewer needs to be reloaded or not. /// bool doChangesRequireReload(PdfViewerParams? other) { @@ -565,7 +569,8 @@ class PdfViewerParams { other.scrollByArrowKey != scrollByArrowKey || other.horizontalCacheExtent != horizontalCacheExtent || other.verticalCacheExtent != verticalCacheExtent || - other.linkHandlerParams != linkHandlerParams; + other.linkHandlerParams != linkHandlerParams || + other.scrollPhysics != scrollPhysics; } @override @@ -625,7 +630,8 @@ class PdfViewerParams { other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && - other.forceReload == forceReload; + other.forceReload == forceReload && + other.scrollPhysics == scrollPhysics; } @override @@ -683,7 +689,8 @@ class PdfViewerParams { onKey.hashCode ^ keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ - forceReload.hashCode; + forceReload.hashCode ^ + scrollPhysics.hashCode; } } From 524b7e126abde4c1b46beb937645f5f9acec24fd Mon Sep 17 00:00:00 2001 From: james Date: Tue, 23 Sep 2025 16:41:24 +0930 Subject: [PATCH 337/663] example viewer - don't enable scrollPhysics by default --- packages/pdfrx/example/viewer/lib/main.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 16aa83fd..5ba4f0b6 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -311,8 +311,8 @@ class _MainPageState extends State with WidgetsBindingObserver { keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, maxScale: 8, - scrollPhysics: const BouncingScrollPhysics(), - /*onViewSizeChanged: (viewSize, oldViewSize, controller) { + //scrollPhysics: const BouncingScrollPhysics(), + onViewSizeChanged: (viewSize, oldViewSize, controller) { if (oldViewSize != null) { // // Calculate the matrix to keep the center position during device @@ -326,7 +326,7 @@ class _MainPageState extends State with WidgetsBindingObserver { // during widget-tree's build process. Future.delayed(const Duration(milliseconds: 200), () => controller.goTo(newMatrix)); } - }, */ + }, viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures From e3e5482e86e0796dae6662f6c8be51cf7934e85d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 23 Sep 2025 21:01:10 +0900 Subject: [PATCH 338/663] Once remove AGENTS.md and CLAUDE.md --- AGENTS.md | 1 - CLAUDE.md | 266 ------------------------------------------------------ 2 files changed, 267 deletions(-) delete mode 120000 AGENTS.md delete mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index 681311eb..00000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 54ce3261..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,266 +0,0 @@ -# AGENTS.md - -This file provides guidance to AI agents and developers when working with code in this repository. - -## Project Overview - -pdfrx is a monorepo containing two packages: - -1. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium - - Pure Dart package with no Flutter dependencies - - Provides core PDF document API and PDFium bindings - - Can be used independently for non-Flutter Dart applications - -2. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter - - Depends on pdfrx_engine for PDF rendering functionality - - Provides Flutter widgets and UI components - - Supports iOS, Android, Windows, macOS, Linux, and Web - - Uses PDFium for native platforms and PDFium WASM for web platforms - -## Development Commands - -### Monorepo Management - -This project uses pub workspace for managing the multi-package repository. All you have to do is to run `dart pub get` on somewhere in the repo directory. - -### Basic Flutter Commands - -```bash -# For the main pdfrx package -cd packages/pdfrx -flutter pub get # Install dependencies -flutter analyze # Run static analysis -flutter test # Run all tests -flutter format . # Format code (120 char line width) - -# For the pdfrx_engine package -cd packages/pdfrx_engine -dart pub get # Install dependencies -dart analyze # Run static analysis -dart test # Run all tests -dart format . # Format code (120 char line width) -``` - -### Platform-Specific Builds - -```bash -# Example app -cd packages/pdfrx/example/viewer -flutter run # Run on connected device/emulator -flutter build appbundle # Build Android App Bundle -flutter build ios # Build iOS (requires macOS) -flutter build web --wasm # Build for web -flutter build linux # Build for Linux -flutter build windows # Build for Windows -flutter build macos # Build for macOS -``` - -### FFI Bindings Generation (pdfrx_engine) - -- FFI bindings for PDFium are generated using `ffigen` in the pdfrx_engine package. -- FFI bindings depends on the Pdfium headers which are downloaded during `dart test` on pdfrx_engine (Linux only). - -```bash -# Run on Linux -cd packages/pdfrx_engine -dart test -dart run ffigen # Regenerate PDFium FFI bindings -``` - -## Release Process - -Both packages may need to be released when changes are made: - -### For pdfrx_engine package updates - -1. Update version in `packages/pdfrx_engine/pubspec.yaml` - - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) -2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes - - Don't mention CI/CD changes and `CLAUDE.md`/`AGENTS.md` related changes (unless they are significant) -3. Update `packages/pdfrx_engine/README.md` if needed -4. Update `README.md` on the repo root if needed -5. Run `dart pub publish` in `packages/pdfrx_engine/` - -### For pdfrx package updates - -1. Update version in `packages/pdfrx/pubspec.yaml` - - If pdfrx_engine was updated, update the dependency version -2. Update `packages/pdfrx/CHANGELOG.md` with changes -3. Update `packages/pdfrx/README.md` with new version information - - Changes version in example fragments - - Consider to add notes for new features or breaking changes - - Notify the owner if you find any issues with the example app or documentation -4. Update `README.md` on the repo root if needed -5. Run `dart pub get` to update all dependencies -6. Run tests to ensure everything works - - Run `dart test` in `packages/pdfrx_engine/` - - Run `flutter test` in `packages/pdfrx/` -7. Ensure the example app builds correctly - - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app -8. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" -9. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` -10. Push changes and tags to remote -11. Run `flutter pub publish` in `packages/pdfrx/` -12. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - - If the PR references issues, please also comment on the issues - - Follow the template below for comments (but modify it as needed): - - ```md - The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). - - ...Fix/update summary... - - Written by [AGENT SIGNATURE] - ``` - - - Focus on the release notes and what was fixed/changed rather than upgrade instructions - - Include a link to the changelog for the specific version - -## Architecture Overview - -### Package Architecture - -The project is split into two packages with clear separation of concerns: - -#### pdfrx_engine (`packages/pdfrx_engine/`) - -- Platform-agnostic PDF rendering engine -- Conditional imports to support different platforms: - - `lib/src/native/` - Native platform implementation using PDFium via FFI - - `lib/src/web/` - Web implementation using PDFium WASM - - Platform-specific code determined at import time based on `dart:library.io` availability -- Main exports: - - `pdf_api.dart` - Core PDF document interfaces - -#### pdfrx (`packages/pdfrx/`) - -- Flutter plugin built on top of pdfrx_engine -- Contains all Flutter-specific code: - - Widget layer - - Platform channel implementations - - UI components and overlays - -### Core Components - -1. **Document API** (in `packages/pdfrx_engine/lib/src/pdf_api.dart`) - - `PdfDocument` - Main document interface - - `PdfPage` - Page representation - - `PdfDocumentRef` - Reference counting for document lifecycle - - Platform-agnostic interfaces implemented differently per platform - -2. **Widget Layer** (in `packages/pdfrx/lib/src/widgets/`) - - `PdfViewer` - Main viewer widget with multiple constructors - - `PdfPageView` - Single page display - - `PdfDocumentViewBuilder` - Safe document loading pattern - - Overlay widgets for text selection, links, search - -3. **Native Integration** - - pdfrx_engine uses Dart FFI for PDFium integration - - Native code in `packages/pdfrx_engine/src/pdfium_interop.cpp` - - Platform folders in `packages/pdfrx/` contain Flutter plugin build configurations - -### Key Patterns - -- **Factory Pattern**: `PdfDocumentFactory` creates platform-specific implementations -- **Builder Pattern**: `PdfDocumentViewBuilder` for safe async document loading -- **Overlay System**: Composable overlays for text, links, annotations -- **Conditional Imports**: Web vs native determined at compile time - -## Testing - -Tests download PDFium binaries automatically for supported platforms. Run tests with: - -```bash -# Test pdfrx_engine -cd packages/pdfrx_engine -dart test - -# Test pdfrx Flutter plugin -cd packages/pdfrx -flutter test -``` - -## Platform-Specific Notes - -### iOS/macOS - -- Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) -- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` -- Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) - -### Android - -- Uses CMake for native build -- Requires Android NDK -- Downloads PDFium binaries during build - -### Web - -- `packages/pdfrx/assets/pdfium.wasm` is prebuilt PDFium WASM binary -- `packages/pdfrx/assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim -- `packages/pdfrx/assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by pdfrx_engine's web implementation - -### Windows/Linux - -- CMake-based build -- Downloads PDFium binaries during build - -## Code Style - -- Single quotes for strings -- 120 character line width -- Relative imports within lib/ -- Follow flutter_lints with custom rules in analysis_options.yaml - -## Dependency Version Policy - -### pdfrx_engine - -This package follows standard Dart package versioning practices. - -### pdfrx - -This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: - -- Flutter SDK to manage these dependencies based on the user's Flutter version -- Broader compatibility across different Flutter stable versions -- Avoiding version conflicts for users on older Flutter stable releases - -When running `flutter pub publish`, warnings about missing version constraints for these packages can be safely ignored. Only packages that are not managed by Flutter SDK should have explicit version constraints. - -## Documentation Guidelines - -The following guidelines should be followed when writing documentation including comments, `README.md`, and other markdown files: - -- Use proper grammar and spelling -- Use clear and concise language -- Use consistent terminology -- Use proper headings for sections -- Use code blocks for code snippets -- Use bullet points for lists -- Use link to relevant issues/PRs when applicable -- Use backticks (`` ` ``) for code references and file/directory/path names in documentation - -### Commenting Guidelines - -- Use reference links for classes, enums, and functions in documentation -- Use `///` (dartdoc comments) for public API comments (and even for important private APIs) - -### Markdown Documentation Guidelines - -- Include links to issues/PRs when relevant; `#NNN` -> `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` -- Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible -- `README.md` should provide an overview of the project, how to use it, and any important notes -- `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles - - Be careful not to include implementation details in the changelog - - Focus on user-facing changes, new features, bug fixes, and breaking changes - - Use sections for different versions - - Use bullet points for changes - -## Special Notes - -- `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version -- For web search, if `gemini` command is available, use `gemini -p ""`. From d7389f49754a2aad7129eece79407a6d2fbec1ee Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 23 Sep 2025 21:09:20 +0900 Subject: [PATCH 339/663] Re-add AGENTS.md --- AGENTS.md | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..1c83ca23 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,271 @@ +# AGENTS.md + +This file provides guidance to AI agents and developers when working with code in this repository. + +## Project Overview + +pdfrx is a monorepo containing two packages: + +1. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium + - Pure Dart package with no Flutter dependencies + - Provides core PDF document API and PDFium bindings + - Can be used independently for non-Flutter Dart applications + +2. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter + - Depends on pdfrx_engine for PDF rendering functionality + - Provides Flutter widgets and UI components + - Supports iOS, Android, Windows, macOS, Linux, and Web + - Uses PDFium for native platforms and PDFium WASM for web platforms + +## Development Commands + +### Monorepo Management + +This project uses pub workspace for managing the multi-package repository. All you have to do is to run `dart pub get` on somewhere in the repo directory. + +### Basic Flutter Commands + +```bash +# For the main pdfrx package +cd packages/pdfrx +flutter pub get # Install dependencies +flutter analyze # Run static analysis +flutter test # Run all tests +flutter format . # Format code (120 char line width) + +# For the pdfrx_engine package +cd packages/pdfrx_engine +dart pub get # Install dependencies +dart analyze # Run static analysis +dart test # Run all tests +dart format . # Format code (120 char line width) +``` + +### Platform-Specific Builds + +```bash +# Example app +cd packages/pdfrx/example/viewer +flutter run # Run on connected device/emulator +flutter build appbundle # Build Android App Bundle +flutter build ios # Build iOS (requires macOS) +flutter build web --wasm # Build for web +flutter build linux # Build for Linux +flutter build windows # Build for Windows +flutter build macos # Build for macOS +``` + +### FFI Bindings Generation (pdfrx_engine) + +- FFI bindings for PDFium are generated using `ffigen` in the pdfrx_engine package. +- FFI bindings depends on the Pdfium headers which are downloaded during `dart test` on pdfrx_engine (Linux only). + +```bash +# Run on Linux +cd packages/pdfrx_engine +dart test +dart run ffigen # Regenerate PDFium FFI bindings +``` + +## Release Process + +Both packages may need to be released when changes are made: + +### For pdfrx_engine package updates + +1. Update version in `packages/pdfrx_engine/pubspec.yaml` + - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) + - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) + - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) +2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes + - Don't mention CI/CD changes and `CLAUDE.md`/`AGENTS.md` related changes (unless they are significant) +3. Update `packages/pdfrx_engine/README.md` if needed +4. Update `README.md` on the repo root if needed +5. Run `dart pub publish` in `packages/pdfrx_engine/` + +### For pdfrx package updates + +1. Update version in `packages/pdfrx/pubspec.yaml` + - If pdfrx_engine was updated, update the dependency version +2. Update `packages/pdfrx/CHANGELOG.md` with changes +3. Update `packages/pdfrx/README.md` with new version information + - Changes version in example fragments + - Consider to add notes for new features or breaking changes + - Notify the owner if you find any issues with the example app or documentation +4. Update `README.md` on the repo root if needed +5. Run `dart pub get` to update all dependencies +6. Run tests to ensure everything works + - Run `dart test` in `packages/pdfrx_engine/` + - Run `flutter test` in `packages/pdfrx/` +7. Ensure the example app builds correctly + - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app +8. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" +9. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` +10. Push changes and tags to remote +11. Run `flutter pub publish` in `packages/pdfrx/` +12. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release + - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release + - If the PR references issues, please also comment on the issues + - Follow the template below for comments (but modify it as needed): + + ```md + The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). + + ...Fix/update summary... + + Written by [AGENT SIGNATURE] + ``` + + - Focus on the release notes and what was fixed/changed rather than upgrade instructions + - Include a link to the changelog for the specific version + +## Architecture Overview + +### Package Architecture + +The project is split into two packages with clear separation of concerns: + +#### pdfrx_engine (`packages/pdfrx_engine/`) + +- Platform-agnostic PDF rendering engine +- Conditional imports to support different platforms: + - `lib/src/native/` - Native platform implementation using PDFium via FFI + - `lib/src/web/` - Web implementation using PDFium WASM + - Platform-specific code determined at import time based on `dart:library.io` availability +- Main exports: + - `pdf_api.dart` - Core PDF document interfaces + +#### pdfrx (`packages/pdfrx/`) + +- Flutter plugin built on top of pdfrx_engine +- Contains all Flutter-specific code: + - Widget layer + - Platform channel implementations + - UI components and overlays + +### Core Components + +1. **Document API** (in `packages/pdfrx_engine/lib/src/pdf_api.dart`) + - `PdfDocument` - Main document interface + - `PdfPage` - Page representation + - `PdfDocumentRef` - Reference counting for document lifecycle + - Platform-agnostic interfaces implemented differently per platform + +2. **Widget Layer** (in `packages/pdfrx/lib/src/widgets/`) + - `PdfViewer` - Main viewer widget with multiple constructors + - `PdfPageView` - Single page display + - `PdfDocumentViewBuilder` - Safe document loading pattern + - Overlay widgets for text selection, links, search + +3. **Native Integration** + - pdfrx_engine uses Dart FFI for PDFium integration + - Native code in `packages/pdfrx_engine/src/pdfium_interop.cpp` + - Platform folders in `packages/pdfrx/` contain Flutter plugin build configurations + +### Key Patterns + +- **Factory Pattern**: `PdfDocumentFactory` creates platform-specific implementations +- **Builder Pattern**: `PdfDocumentViewBuilder` for safe async document loading +- **Overlay System**: Composable overlays for text, links, annotations +- **Conditional Imports**: Web vs native determined at compile time + +## Testing + +Tests download PDFium binaries automatically for supported platforms. Run tests with: + +```bash +# Test pdfrx_engine +cd packages/pdfrx_engine +dart test + +# Test pdfrx Flutter plugin +cd packages/pdfrx +flutter test +``` + +## Platform-Specific Notes + +### iOS/macOS + +- Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) +- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` +- Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) + +### Android + +- Uses CMake for native build +- Requires Android NDK +- Downloads PDFium binaries during build + +### Web + +- `packages/pdfrx/assets/pdfium.wasm` is prebuilt PDFium WASM binary +- `packages/pdfrx/assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim +- `packages/pdfrx/assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by pdfrx_engine's web implementation + +### Windows/Linux + +- CMake-based build +- Downloads PDFium binaries during build + +## Code Style + +- Single quotes for strings +- 120 character line width +- Relative imports within lib/ +- Follow flutter_lints with custom rules in analysis_options.yaml + +## Dependency Version Policy + +### pdfrx_engine + +This package follows standard Dart package versioning practices. + +### pdfrx + +This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: + +- Flutter SDK to manage these dependencies based on the user's Flutter version +- Broader compatibility across different Flutter stable versions +- Avoiding version conflicts for users on older Flutter stable releases + +When running `flutter pub publish`, warnings about missing version constraints for these packages can be safely ignored. Only packages that are not managed by Flutter SDK should have explicit version constraints. + +## Documentation Guidelines + +The following guidelines should be followed when writing documentation including comments, `README.md`, and other markdown files: + +- Use proper grammar and spelling +- Use clear and concise language +- Use consistent terminology +- Use proper headings for sections +- Use code blocks for code snippets +- Use bullet points for lists +- Use link to relevant issues/PRs when applicable +- Use backticks (`` ` ``) for code references and file/directory/path names in documentation + +### Commenting Guidelines + +- Use reference links for classes, enums, and functions in documentation +- Use `///` (dartdoc comments) for public API comments (and even for important private APIs) + +### Markdown Documentation Guidelines + +- Include links to issues/PRs when relevant; `#NNN` -> `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` +- Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible +- `README.md` should provide an overview of the project, how to use it, and any important notes +- `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles + - Be careful not to include implementation details in the changelog + - Focus on user-facing changes, new features, bug fixes, and breaking changes + - Use sections for different versions + - Use bullet points for changes + +## Command Execution Guidelines + +- Run commands directly in the repository environment; do not rely on any agent sandbox when executing them. +- If a command cannot be executed without sandboxing, pause and coordinate with the user so it runs on their machine as needed. +- On Windows, use `pwsh.exe -Command ...` to run any commands to reduce issues caused by missing .bat/.cmd and shebang on shell-scripts + +## Special Notes + +- `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version From 72d9857237ae65110ef0e20eeb52bbcde2aa571e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 23 Sep 2025 23:04:21 +0900 Subject: [PATCH 340/663] Fixes #476: remove_wasm_modules error: Too many open files --- packages/pdfrx/example/viewer/pubspec.yaml | 10 +++++----- packages/pdfrx/pubspec.yaml | 12 ++++++------ packages/pdfrx_engine/pubspec.yaml | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 5c76b8fc..902ed5ab 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' environment: sdk: '>=3.9.0 <4.0.0' - flutter: '>=3.19.0' + flutter: ">=3.29.0" resolution: workspace dependencies: @@ -18,15 +18,15 @@ dependencies: cupertino_icons: ^1.0.8 rxdart: ^0.28.0 - url_launcher: ^6.3.1 - synchronized: ^3.3.1 - file_selector: ^1.0.3 + url_launcher: ^6.3.2 + synchronized: ^3.4.0 + file_selector: ^1.0.4 http: ^1.5.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8dc7713c..61022bca 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -24,17 +24,17 @@ dependencies: path: path_provider: ^2.1.5 rxdart: - synchronized: ^3.3.1 - url_launcher: ^6.3.1 - vector_math: ^2.1.4 + synchronized: ^3.4.0 + url_launcher: ^6.3.2 + vector_math: ^2.2.0 web: ^1.1.1 - dart_pubspec_licenses: ^3.0.7 + dart_pubspec_licenses: ^3.0.12 dev_dependencies: - ffigen: ^19.0.0 + ffigen: ^19.1.0 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 archive: ^4.0.7 flutter: diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 82f3da51..068b5e38 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -18,15 +18,15 @@ dependencies: http: path: rxdart: - synchronized: ^3.3.1 - vector_math: ^2.1.4 + synchronized: ^3.4.0 + vector_math: ^2.2.0 archive: ^4.0.7 image: ^4.5.4 dev_dependencies: - ffigen: ^19.0.0 - lints: ^5.0.0 - test: ^1.24.0 + ffigen: ^19.1.0 + lints: ^6.0.0 + test: ^1.26.2 # To generate the bindings, firstly you must run the test once to download PDFium headers: # dart run test From 0b2dd04f0fa48a5a5a2fdff49c3ce22140c6a523 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 23 Sep 2025 23:34:26 +0900 Subject: [PATCH 341/663] Release pdfrx_engine v0.1.18 --- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/pubspec.yaml | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index d20e7c81..c3941fa8 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.18 + +- FIXED: `dart run pdfrx:remove_wasm_modules` could hit "Too many open files" on some platforms ([#476](https://github.com/espresso3389/pdfrx/issues/476)) +- Dependency updates + ## 0.1.17 - [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 068b5e38..7b33f14e 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.17 +version: 0.1.18 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -54,3 +54,4 @@ ffigen: style: any length: full + From c574c981dd8ede9a34c7e95d3c388d2700396b9e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 23 Sep 2025 23:34:35 +0900 Subject: [PATCH 342/663] Release pdfrx v2.1.17 --- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 3 ++- packages/pdfrx/pubspec.yaml | 6 ++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index cf2bd48b..bc7ade85 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.17 + +- FIXED: `dart run pdfrx:remove_wasm_modules` could fail with "Too many open files" during WASM cleanup ([#476](https://github.com/espresso3389/pdfrx/issues/476)) +- Updated dependencies, including pdfrx_engine 0.1.18 + # 2.1.16 - [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 209b5063..a7b950df 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.16 + pdfrx: ^2.1.17 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. @@ -249,3 +249,4 @@ For advanced use cases requiring direct PDF manipulation without Flutter widgets - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Core document interface - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page rendering and manipulation + diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 61022bca..008f0113 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.16 +version: 2.1.17 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.17 + pdfrx_engine: ^0.1.18 collection: crypto: ^3.0.6 ffi: @@ -56,3 +56,5 @@ flutter: assets: - assets/ + + From 1a9443927e784a6c7e8ada861dc4a0ee5666a9eb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 00:04:51 +0900 Subject: [PATCH 343/663] WIP --- .vscode/settings.json | 1 + packages/pdfrx/example/viewer/lib/noto_google_fonts.dart | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a297c1e6..7ee8a285 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -125,6 +125,7 @@ "maxage", "microtask", "MOVETO", + "Naskh", "NATIVETEXT", "NESW", "newfstatat", diff --git a/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart index 7d1b9194..8e415ac7 100644 --- a/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart +++ b/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart @@ -69,7 +69,6 @@ GoogleFontsFile? getGoogleFontsUriFromFontQuery(PdfFontQuery query, {bool prefer PdfFontCharset.thai => _notoSerifThai, PdfFontCharset.hebrew => _notoSerifHebrew, PdfFontCharset.arabic => _notoNaskhArabic, - PdfFontCharset.arabic => _notoSansArabic, PdfFontCharset.greek || PdfFontCharset.vietnamese || PdfFontCharset.cyrillic || From 8488a6e8b9a234c0ac879e93c9fe2a718fbdd74b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 00:05:56 +0900 Subject: [PATCH 344/663] Matrix4: 'scale' is deprecated --- .../lib/src/widgets/interactive_viewer.dart | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index b7de973f..c9870d6b 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -13,6 +13,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; import 'package:flutter/services.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4, Quad, Vector3; + import '../utils/double_extensions.dart'; // Examples can assume: @@ -589,10 +590,9 @@ class _InteractiveViewerState extends State with TickerProvid /// ScrollPhysics /// If the ScrollPhysics is defined we apply physics (bouncing or clamping) during pan. if (widget.scrollPhysics != null) { - final physics = - (_gestureType == _GestureType.scale) - ? (widget.scrollPhysicsScale ?? widget.scrollPhysics!) - : widget.scrollPhysics!; + final physics = (_gestureType == _GestureType.scale) + ? (widget.scrollPhysicsScale ?? widget.scrollPhysics!) + : widget.scrollPhysics!; // current translation in scene coordinates (negative because controller stores inverse) final Offset currentOffset = _getMatrixTranslation(_transformer.value) * -1; // build scroll metrics @@ -602,10 +602,12 @@ class _InteractiveViewerState extends State with TickerProvid final double proposedX = currentOffset.dx - alignedTranslation.dx; final double proposedY = currentOffset.dy - alignedTranslation.dy; - final double overscrollX = - proposedX == currentOffset.dx ? 0 : physics.applyBoundaryConditions(metricsX, proposedX); // : 0. - final double overscrollY = - proposedY == currentOffset.dy ? 0 : physics.applyBoundaryConditions(metricsY, proposedY); // : 0. + final double overscrollX = proposedX == currentOffset.dx + ? 0 + : physics.applyBoundaryConditions(metricsX, proposedX); // : 0. + final double overscrollY = proposedY == currentOffset.dy + ? 0 + : physics.applyBoundaryConditions(metricsY, proposedY); // : 0. // If the overscroll is zero, the ScrollPhysics (such as BouncingScrollPhysics) is // enabling us to go out of boundaries, so we apply physics to the translation. @@ -629,7 +631,8 @@ class _InteractiveViewerState extends State with TickerProvid return matrix.clone()..translateByDouble(dx, dy, 0, 1); } else { // correct any overscroll - return matrix.clone()..translateByDouble(alignedTranslation.dx + overscrollX, alignedTranslation.dy + overscrollY, 0, 1); + return matrix.clone() + ..translateByDouble(alignedTranslation.dx + overscrollX, alignedTranslation.dy + overscrollY, 0, 1); } } @@ -658,8 +661,8 @@ class _InteractiveViewerState extends State with TickerProvid // calculating the translation to put the viewport inside that Quad is more // complicated than this when rotated. // https://github.com/flutter/flutter/issues/57698 - final Matrix4 correctedMatrix = - matrix.clone()..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); + final Matrix4 correctedMatrix = matrix.clone() + ..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); // Double check that the corrected translation fits. final Quad correctedViewport = _transformViewport(correctedMatrix, _viewport); @@ -711,7 +714,7 @@ class _InteractiveViewerState extends State with TickerProvid final allowedScale = _getAllowedScale(desiredScale); // Early return if not allowed to zoom outside bounds if (allowedScale != desiredScale) { - return matrix.clone()..scaleByDouble(allowedScale, allowedScale, allowedScale, 1); + return matrix.clone()..scaleByDouble(allowedScale, allowedScale, allowedScale, 1); } // Compute ratio of this update's scale to the previous update @@ -765,13 +768,13 @@ class _InteractiveViewerState extends State with TickerProvid final double newScaleY = (contentHeight + adjustedY) / contentHeight; final double factor = (newScaleX + newScaleY) / 2; - return matrix.clone()..scale(factor); + return matrix.clone()..scaleByDouble(factor, factor, factor, 1); } else { final double clampedTotalScale = clampDouble(desiredScale, widget.minScale, widget.maxScale); final double clampedScale = clampedTotalScale / currentScale; // Apply the scale factor to the matrix - return matrix.clone()..scale(clampedScale); + return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); } } else { // Don't allow a scale that results in an overall scale beyond min/max @@ -785,7 +788,7 @@ class _InteractiveViewerState extends State with TickerProvid ); final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); final double clampedScale = clampedTotalScale / currentScale; - return matrix.clone()..scale(clampedScale); + return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); } } @@ -1031,11 +1034,10 @@ class _InteractiveViewerState extends State with TickerProvid // want to animate the snap back to bounds _snapStartMatrix = _transformer.value.clone(); final Offset pivotScene = _transformer.toScene(_snapFocalPoint); - final Matrix4 endMatrix = - _snapStartMatrix.clone() - ..translateByDouble(pivotScene.dx, pivotScene.dy, 0, 1) - ..scaleByDouble(clampedScale / endScale, clampedScale / endScale, clampedScale / endScale, 1) - ..translateByDouble(-pivotScene.dx, -pivotScene.dy, 0, 1); + final Matrix4 endMatrix = _snapStartMatrix.clone() + ..translateByDouble(pivotScene.dx, pivotScene.dy, 0, 1) + ..scaleByDouble(clampedScale / endScale, clampedScale / endScale, clampedScale / endScale, 1) + ..translateByDouble(-pivotScene.dx, -pivotScene.dy, 0, 1); _snapTargetMatrix = _matrixClamp(endMatrix); _snapController @@ -1213,8 +1215,8 @@ class _InteractiveViewerState extends State with TickerProvid // Use original boundaryMargin unless a specific one is passed for override. final EdgeInsets baseMargin = (overrideAutoAdjustBoundaries && !widget.scrollPhysicsAutoAdjustBoundaries) || boundaryMargin == null - ? _originalBoundaryMargin - : boundaryMargin; + ? _originalBoundaryMargin + : boundaryMargin; // If boundaries are infinite, provide very large finite extents to disable clamping if (_boundaryRect.isInfinite) { @@ -1600,11 +1602,10 @@ Quad _transformViewport(Matrix4 matrix, Rect viewport) { // Find the axis aligned bounding box for the rect rotated about its center by // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { - final Matrix4 rotationMatrix = - Matrix4.identity() - ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) - ..rotateZ(rotation) - ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); + final Matrix4 rotationMatrix = Matrix4.identity() + ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) + ..rotateZ(rotation) + ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); final Quad boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)), From cc74b595578fc02cba2c31dc67b19383fb2f8efd Mon Sep 17 00:00:00 2001 From: james Date: Wed, 24 Sep 2025 11:44:26 +0930 Subject: [PATCH 345/663] Fix boundary respect for mouse wheel and scroll thumb with ScrollPhysics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add _setMatrixWithBoundaryCheck method for direct matrix manipulations - Preserve InteractiveViewer's ScrollPhysics behavior (bouncing/elastic) - Apply boundary checking only for bypass operations (mouse wheel, scroll thumb) - Use existing _calcMatrixForClampedToNearestBoundary when scrollPhysics enabled - Fix missing return type annotation for _adjustBoundaryMargins 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 22 +++++++++++++++++-- .../src/widgets/pdf_viewer_scroll_thumb.dart | 4 ++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 4fe19bfc..c21b7e22 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -868,7 +868,7 @@ class _PdfViewerState extends State // Auto-adjust boundaries when content is smaller than the view, centering // the content and ensuring InteractiveViewer's scrollPhysics works when specified - _adjustBoundaryMargins(Size viewSize, double zoom) { + void _adjustBoundaryMargins(Size viewSize, double zoom) { if (widget.params.scrollPhysics == null) return; final boundaryMargin = @@ -1333,7 +1333,7 @@ class _PdfViewerState extends State } else { m.translateByDouble(dx, dy, 0, 1); } - _txController.value = m; + _setMatrixWithBoundaryCheck(m); _stopInteraction(); } @@ -1345,6 +1345,19 @@ class _PdfViewerState extends State return _normalizeMatrix(newValue); } + /// Set matrix with boundary checking for direct manipulations (mouse wheel, scroll thumb, etc.) + /// This preserves InteractiveViewer's ScrollPhysics behavior while enforcing boundaries + /// for operations that bypass the gesture system. + void _setMatrixWithBoundaryCheck(Matrix4 matrix) { + if (widget.params.scrollPhysics != null && _viewSize != null) { + // When scrollPhysics is enabled, use boundary clamping for direct manipulations + _txController.value = _calcMatrixForClampedToNearestBoundary(matrix, viewSize: _viewSize!); + } else { + // When scrollPhysics is disabled, use existing normalization + _txController.value = matrix; + } + } + Matrix4 _normalizeMatrix(Matrix4 newValue) { final layout = _layout; final viewSize = _viewSize; @@ -3279,6 +3292,11 @@ class PdfViewerController extends ValueListenable { /// Restrict matrix to the safe range. Matrix4 makeMatrixInSafeRange(Matrix4 newValue) => _state._makeMatrixInSafeRange(newValue); + /// Set matrix with boundary checking for direct manipulations (mouse wheel, scroll thumb, etc.) + /// This preserves InteractiveViewer's ScrollPhysics behavior while enforcing boundaries + /// for operations that bypass the gesture system. + void setMatrixWithBoundaryCheck(Matrix4 matrix) => _state._setMatrixWithBoundaryCheck(matrix); + double getNextZoom({bool loop = true}) => _state._findNextZoomStop(currentZoom, zoomUp: true, loop: loop); double getPreviousZoom({bool loop = true}) => _state._findNextZoomStop(currentZoom, zoomUp: false, loop: loop); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index e41a0b1d..56103e89 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -88,7 +88,7 @@ class _PdfViewerScrollThumbState extends State { final y = (_panStartOffset + details.localPosition.dy) / vh; final m = widget.controller.value.clone(); m.y = -y * (all.height - view.height); - widget.controller.value = m; + widget.controller.setMatrixWithBoundaryCheck(m); }, ), ); @@ -133,7 +133,7 @@ class _PdfViewerScrollThumbState extends State { final x = (_panStartOffset + details.localPosition.dx) / vw; final m = widget.controller.value.clone(); m.x = -x * (all.width - view.width); - widget.controller.value = m; + widget.controller.setMatrixWithBoundaryCheck(m); }, ), ); From e44b7c51ee33726cdf0dd7326303f05b8e7aaebc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 16:57:53 +0900 Subject: [PATCH 346/663] Remove broken docImport not to crash dartdoc (dart-lang/dartdoc#4106) --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index ba11a150..4d507a09 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1,8 +1,6 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first /// Pdfrx API -/// @docImport './pdfrx_initialize_dart.dart'; -/// @docImport 'package:pdfrx/pdfrx_flutter.dart'; library; import 'dart:async'; From 19f8c511b60856ec2d7390ed5d037e9ad6314ada Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 17:01:29 +0900 Subject: [PATCH 347/663] pdfrx 2.1.18/pdfrx_engine 0.1.19 (fixing pub.dev issue) --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 3 +-- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index bc7ade85..7d045038 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.18 + +- Remove broken docImport not to crash dartdoc ([dart-lang/dartdoc#4106](https://github.com/dart-lang/dartdoc/issues/4106)) + # 2.1.17 - FIXED: `dart run pdfrx:remove_wasm_modules` could fail with "Too many open files" during WASM cleanup ([#476](https://github.com/espresso3389/pdfrx/issues/476)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index a7b950df..ae064f99 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.17 + pdfrx: ^2.1.18 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. @@ -249,4 +249,3 @@ For advanced use cases requiring direct PDF manipulation without Flutter widgets - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Core document interface - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page rendering and manipulation - diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 008f0113..1747b087 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.17 +version: 2.1.18 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.18 + pdfrx_engine: ^0.1.19 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index c3941fa8..761f26cc 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.19 + +- Remove broken docImport not to crash dartdoc ([dart-lang/dartdoc#4106](https://github.com/dart-lang/dartdoc/issues/4106)) + ## 0.1.18 - FIXED: `dart run pdfrx:remove_wasm_modules` could hit "Too many open files" on some platforms ([#476](https://github.com/espresso3389/pdfrx/issues/476)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 7b33f14e..20305cef 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.18 +version: 0.1.19 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 09cf072eb290547c4ef869f386a31cd4329bde68 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 17:14:46 +0900 Subject: [PATCH 348/663] dart format --- packages/pdfrx/example/viewer/lib/main.dart | 2 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 29 ++++++++++--------- .../lib/src/widgets/pdf_viewer_params.dart | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 5ba4f0b6..9e9bf593 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -326,7 +326,7 @@ class _MainPageState extends State with WidgetsBindingObserver { // during widget-tree's build process. Future.delayed(const Duration(milliseconds: 200), () => controller.goTo(newMatrix)); } - }, + }, viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c21b7e22..41d00438 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -451,7 +451,8 @@ class _PdfViewerState extends State iv.InteractiveViewer( transformationController: _txController, constrained: false, - boundaryMargin: _adjustedBoundaryMargins ?? + boundaryMargin: + _adjustedBoundaryMargins ?? (widget.params.scrollPhysics == null ? const EdgeInsets.all(double.infinity) : EdgeInsets.zero), @@ -775,7 +776,7 @@ class _PdfViewerState extends State final params = widget.params; final bmh = params.boundaryMargin?.horizontal == double.infinity ? 0 : params.boundaryMargin?.horizontal ?? 0; final bmv = params.boundaryMargin?.vertical == double.infinity ? 0 : params.boundaryMargin?.vertical ?? 0; - + if (_viewSize != null) { final s1 = _viewSize!.width / (_layout!.documentSize.width + bmh); final s2 = _viewSize!.height / (_layout!.documentSize.height + bmv); @@ -871,10 +872,9 @@ class _PdfViewerState extends State void _adjustBoundaryMargins(Size viewSize, double zoom) { if (widget.params.scrollPhysics == null) return; - final boundaryMargin = - widget.params.boundaryMargin == null || widget.params.boundaryMargin!.horizontal.isInfinite - ? EdgeInsets.zero - : widget.params.boundaryMargin!; + final boundaryMargin = widget.params.boundaryMargin == null || widget.params.boundaryMargin!.horizontal.isInfinite + ? EdgeInsets.zero + : widget.params.boundaryMargin!; final currentDocumentSize = boundaryMargin.inflateSize(_layout!.documentSize); @@ -1379,7 +1379,11 @@ class _PdfViewerState extends State return Matrix4.compose( vec.Vector3(-position.dx * zoom + hw, -position.dy * zoom + hh, 0), vec.Quaternion.identity(), - vec.Vector3(zoom, zoom, zoom), // setting zoom of 1 on z caused a call to matrix.maxScaleOnAxis() to return 1 even when x and y are < 1 + vec.Vector3( + zoom, + zoom, + zoom, + ), // setting zoom of 1 on z caused a call to matrix.maxScaleOnAxis() to return 1 even when x and y are < 1 ); } @@ -1439,7 +1443,7 @@ class _PdfViewerState extends State } } - Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) =>_calcMatrixForArea( + Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) => _calcMatrixForArea( rect: (widget.params.boundaryMargin ?? EdgeInsets.zero).inflateRect( _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin), ), @@ -3406,12 +3410,11 @@ class PdfViewerController extends ValueListenable { for (int i = 0; i < layout.pageLayouts.length; i++) { final page = layout.pageLayouts[i]; if (page.intersect(viewRect).isEmpty) continue; - final EdgeInsets boundaryMargin = - params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity - ? EdgeInsets.zero - : params.boundaryMargin!; + final EdgeInsets boundaryMargin = params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity + ? EdgeInsets.zero + : params.boundaryMargin!; final zoom = viewSize.width / (page.width + (params.margin * 2) + boundaryMargin.horizontal); - + // NOTE: keep the y-position but center the x-position final newMatrix = calcMatrixFor(Offset(page.left + page.width / 2, pos.dy), zoom: zoom); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 0d558622..59d2f9a8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -535,7 +535,7 @@ class PdfViewerParams { /// sometimes it is useful to force reload the viewer by setting this to true. final bool forceReload; - /// Scroll physics for the viewer. + /// Scroll physics for the viewer. final ScrollPhysics? scrollPhysics; /// Determine whether the viewer needs to be reloaded or not. From 8ee626420ea09157bd9d607a066eea57eb1d1be6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 17:49:25 +0900 Subject: [PATCH 349/663] Introducing pana for code analysis --- .github/workflows/pana-analysis.yml | 48 +++++++++++++++++++++++++++++ AGENTS.md | 14 +++++---- 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/pana-analysis.yml diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml new file mode 100644 index 00000000..a35b8939 --- /dev/null +++ b/.github/workflows/pana-analysis.yml @@ -0,0 +1,48 @@ +name: Pana Analysis + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + workflow_dispatch: + +jobs: + pana: + name: Run pana analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - package_name: pdfrx_engine + package_path: packages/pdfrx_engine + pub_command: dart pub get + - package_name: pdfrx + package_path: packages/pdfrx + pub_command: flutter pub get + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup Flutter + run: | + git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter + echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter channel stable + ~/flutter/bin/flutter doctor -v + + - name: Pub get (workspace) + run: dart pub get + + - name: Activate pana + run: | + dart pub global activate pana + echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH + + - name: Get dependencies (${{ matrix.package_name }}) + working-directory: ${{ matrix.package_path }} + run: ${{ matrix.pub_command }} + + - name: Run pana (${{ matrix.package_name }}) + working-directory: ${{ matrix.package_path }} + run: ~/.pub-cache/bin/pana --no-warning --exit-code-threshold 0 diff --git a/AGENTS.md b/AGENTS.md index 1c83ca23..5986fb04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,7 +81,8 @@ Both packages may need to be released when changes are made: - Don't mention CI/CD changes and `CLAUDE.md`/`AGENTS.md` related changes (unless they are significant) 3. Update `packages/pdfrx_engine/README.md` if needed 4. Update `README.md` on the repo root if needed -5. Run `dart pub publish` in `packages/pdfrx_engine/` +5. Run `pana` in `packages/pdfrx_engine` to validate code integrity +6. Run `dart pub publish` in `packages/pdfrx_engine/` ### For pdfrx package updates @@ -99,11 +100,12 @@ Both packages may need to be released when changes are made: - Run `flutter test` in `packages/pdfrx/` 7. Ensure the example app builds correctly - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app -8. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" -9. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` -10. Push changes and tags to remote -11. Run `flutter pub publish` in `packages/pdfrx/` -12. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release +8. Run `pana` in `packages/pdfrx` (and any other packages being released) to validate code integrity +9. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" +10. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` +11. Push changes and tags to remote +12. Run `flutter pub publish` in `packages/pdfrx/` +13. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - If the PR references issues, please also comment on the issues - Follow the template below for comments (but modify it as needed): From 70e89132ee62fa9b4c70420531fce9156f55a07e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 17:52:34 +0900 Subject: [PATCH 350/663] pana analysis don't need android/linux dev env. --- .github/workflows/pana-analysis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index a35b8939..8029f557 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -28,6 +28,7 @@ jobs: run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter echo "$HOME/flutter/bin" >> $GITHUB_PATH + ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v From 36292280adeac81372bd84f9ff4420f2769face7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 19:12:06 +0900 Subject: [PATCH 351/663] pdfrx_engine 0.1.20 --- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 761f26cc..34c04ae9 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.20 + +- Maintenance release to keep version alignment and ensure code integrity alongside pdfrx 2.1.19. + ## 0.1.19 - Remove broken docImport not to crash dartdoc ([dart-lang/dartdoc#4106](https://github.com/dart-lang/dartdoc/issues/4106)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 20305cef..d0e3b82b 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.19 +version: 0.1.20 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From ad27c6ba24aa00cfe6ad71ba2a0b2fb3a90d270e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 20:14:53 +0900 Subject: [PATCH 352/663] pdfrx 2.1.19 --- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 7d045038..c0bcce02 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.19 + +- Maintenance release: applied `dart format` to keep code integrity with Dart 3.9/Flutter 3.29 tooling. + # 2.1.18 - Remove broken docImport not to crash dartdoc ([dart-lang/dartdoc#4106](https://github.com/dart-lang/dartdoc/issues/4106)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index ae064f99..31f315e4 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.18 + pdfrx: ^2.1.19 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 1747b087..4ef01c6b 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.18 +version: 2.1.19 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.19 + pdfrx_engine: ^0.1.20 collection: crypto: ^3.0.6 ffi: From dbde25a88a02a77df5dbb52ece638a1aa303b86b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Sep 2025 20:31:07 +0900 Subject: [PATCH 353/663] Document updates. --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 4d507a09..04fe6d5d 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -410,6 +410,7 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { /// Event that is triggered when the list of missing fonts in the PDF document has changed. class PdfDocumentMissingFontsEvent implements PdfDocumentEvent { + /// Create a [PdfDocumentMissingFontsEvent]. PdfDocumentMissingFontsEvent(this.document, this.missingFonts); @override @@ -1398,6 +1399,7 @@ class PdfTextFragmentBoundingRect { /// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. class PdfDest { + /// Create a [PdfDest]. const PdfDest(this.pageNumber, this.command, this.params); /// Page number to jump to. @@ -1446,6 +1448,7 @@ enum PdfDestCommand { fitBH('fitbh'), fitBV('fitbv'); + /// Create a [PdfDestCommand] with the specified command name. const PdfDestCommand(this.name); /// Command name. From 9cb8bf51243b5eed77f54274f28536f106b57507 Mon Sep 17 00:00:00 2001 From: james Date: Thu, 25 Sep 2025 12:38:18 +0930 Subject: [PATCH 354/663] preserve precise position on page for layout and view size changes --- packages/pdfrx/example/viewer/lib/main.dart | 15 -- .../lib/src/widgets/interactive_viewer.dart | 101 ++++++++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 163 +++++++++++++++++- 3 files changed, 254 insertions(+), 25 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 9e9bf593..f071a01a 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -312,21 +312,6 @@ class _MainPageState extends State with WidgetsBindingObserver { useAlternativeFitScaleAsMinScale: false, maxScale: 8, //scrollPhysics: const BouncingScrollPhysics(), - onViewSizeChanged: (viewSize, oldViewSize, controller) { - if (oldViewSize != null) { - // - // Calculate the matrix to keep the center position during device - // screen rotation - // - // The most important thing here is that the transformation matrix - // is not changed on the view change. - final centerPosition = controller.value.calcPosition(oldViewSize); - final newMatrix = controller.calcMatrixFor(centerPosition); - // Don't change the matrix in sync; the callback might be called - // during widget-tree's build process. - Future.delayed(const Duration(milliseconds: 200), () => controller.goTo(newMatrix)); - } - }, viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index c9870d6b..8faca161 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -60,6 +60,55 @@ typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Q /// {@end-tool} @immutable class InteractiveViewer extends StatefulWidget { + + /// Create InteractiveViewer with animation control capability + InteractiveViewer.withAnimationControl({ + required Widget child, + Key? key, + Clip clipBehavior = Clip.hardEdge, + PanAxis panAxis = PanAxis.free, + EdgeInsets boundaryMargin = EdgeInsets.zero, + bool constrained = true, + double maxScale = 8.0, + double minScale = 0.8, + double interactionEndFrictionCoefficient = _kDrag, + GestureScaleEndCallback? onInteractionEnd, + GestureScaleStartCallback? onInteractionStart, + GestureScaleUpdateCallback? onInteractionUpdate, + bool panEnabled = true, + bool scaleEnabled = true, + double scaleFactor = kDefaultMouseScrollToScaleFactor, + TransformationController? transformationController, + Alignment? alignment, + bool trackpadScrollCausesScale = false, + void Function(PointerScrollEvent event)? onWheelDelta, + ScrollPhysics? scrollPhysics, + ScrollPhysics? scrollPhysicsScale, + bool scrollPhysicsAutoAdjustBoundaries = true, + }) : this( + key: _globalKey, + child: child, + clipBehavior: clipBehavior, + panAxis: panAxis, + boundaryMargin: boundaryMargin, + constrained: constrained, + maxScale: maxScale, + minScale: minScale, + interactionEndFrictionCoefficient: interactionEndFrictionCoefficient, + onInteractionEnd: onInteractionEnd, + onInteractionStart: onInteractionStart, + onInteractionUpdate: onInteractionUpdate, + panEnabled: panEnabled, + scaleEnabled: scaleEnabled, + scaleFactor: scaleFactor, + transformationController: transformationController, + alignment: alignment, + trackpadScrollCausesScale: trackpadScrollCausesScale, + onWheelDelta: onWheelDelta, + scrollPhysics: scrollPhysics, + scrollPhysicsScale: scrollPhysicsScale, + scrollPhysicsAutoAdjustBoundaries: scrollPhysicsAutoAdjustBoundaries, + ); /// Create an InteractiveViewer. InteractiveViewer({ required this.child, @@ -398,6 +447,13 @@ class InteractiveViewer extends StatefulWidget { // This value was eyeballed to give a feel similar to Google Photos. static const double _kDrag = 0.0000135; + /// GlobalKey to access the InteractiveViewer state for animation control + static final GlobalKey<_InteractiveViewerState> _globalKey = GlobalKey<_InteractiveViewerState>(); + + /// Static methods for external animation control + static bool get hasActiveAnimations => _globalKey.currentState?.hasActiveAnimations ?? false; + static void stopAnimations() => _globalKey.currentState?._stopAllAnimations(); + /// ScrollPhysics to use for panning final ScrollPhysics? scrollPhysics; @@ -1382,12 +1438,6 @@ class _InteractiveViewerState extends State with TickerProvid } } - void _stopAnimation() { - _controller.stop(); - _animation?.removeListener(_handleInertiaAnimation); - _animation = null; - } - Simulation? _getCombinedSimulation(Simulation? simulationX, Simulation? simulationY) { if (simulationX == null && simulationY == null) { return null; @@ -1406,9 +1456,45 @@ class _InteractiveViewerState extends State with TickerProvid // ability to stop a in-progress pan fling is particularly important // when scroll physics is enabled as the duration and distance of the // pan can be considerable. - _stopAnimation(); + _stopAllAnimations(); + } + } + + /// Check if any animations are currently active + bool get hasActiveAnimations => + _controller.isAnimating || _scaleController.isAnimating || _snapController.isAnimating; + + /// Stop all active animations without saving state + void _stopAllAnimations() { + // Stop pan animations + if (_controller.isAnimating) { + _controller.stop(); + _controller.reset(); + _animation?.removeListener(_handleInertiaAnimation); + _animation = null; + } + + // Stop scale animations + if (_scaleController.isAnimating) { + _scaleController.stop(); + _scaleController.reset(); + _scaleAnimation?.removeListener(_handleScaleAnimation); + _scaleAnimation = null; + } + + // Stop snap animations + if (_snapController.isAnimating) { + _snapController.stop(); + _snapController.reset(); + _snapTargetMatrix = null; } + + // Clear simulations + simulationX = null; + simulationY = null; + combinedSimulation = null; } + // end ScrollPhysics // Handle inertia scale animation. @@ -1573,6 +1659,7 @@ class _InteractiveViewerBuilt extends StatelessWidget { // represented by exactly one _GestureType. enum _GestureType { pan, scale, rotate } + // Given a velocity and drag, calculate the time at which motion will come to // a stop, within the margin of effectivelyMotionless. double _getFinalTime(double velocity, double drag, {double effectivelyMotionless = 0.5}) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 41d00438..a5b4f6ac 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -448,7 +448,7 @@ class _PdfViewerState extends State onPointerHover: (event) => _handlePointerEvent(event, event.localPosition, event.kind), child: Stack( children: [ - iv.InteractiveViewer( + iv.InteractiveViewer.withAnimationControl( transformationController: _txController, constrained: false, boundaryMargin: @@ -557,6 +557,9 @@ class _PdfViewerState extends State void _updateLayout(Size viewSize) { if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); + final Rect oldVisibleRect = _initialized ? _visibleRect : Rect.zero; + final PdfPageLayout? oldLayout = _layout; + final oldMinScale = _minScale; final oldSize = _viewSize; final isViewSizeChanged = oldSize != viewSize; _viewSize = viewSize; @@ -594,8 +597,55 @@ class _PdfViewerState extends State } else if (isLayoutChanged || isViewSizeChanged) { Future.microtask(() async { if (mounted) { - await _goToPage(pageNumber: currentPageNumber ?? _calcInitialPageNumber()); - callOnViewerSizeChanged(); + // preserve the current zoom whilst respecting the new minScale + final zoomTo = _currentZoom < _minScale || _currentZoom == oldMinScale ? _minScale : _currentZoom; + if (isLayoutChanged) { + // if the layout changed, calculate the top-left position in the document + // before the layout change and go to that position in the new layout + + if (oldLayout != null && currentPageNumber != null) { + // The top-left position of the screen (oldVisibleRect.topLeft) may be + // in the boundary margin, or a margin between pages, and it could be + // the current page or one of the neighboring pages + final PdfPageHitTestResult? hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); + final pageNumber = hit?.page.pageNumber ?? currentPageNumber; + + // Compute relative position within the old pageRect + final Rect oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; + final Rect newPageRect = _layout!.pageLayouts[pageNumber - 1]; + final Offset oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; + final double fracX = oldOffset.dx / oldPageRect.width; + final double fracY = oldOffset.dy / oldPageRect.height; + + // Map into new layoutRect + final Offset newOffset = Offset( + newPageRect.left + fracX * newPageRect.width, + newPageRect.top + fracY * newPageRect.height, + ); + + // preseve the position after a layout change + await _goToPosition(documentOffset: newOffset, zoom: zoomTo); + } + } else { + if (zoomTo != _currentZoom) { + // layout hasn't changed, but size and zoom has + final double zoomChange = zoomTo / _currentZoom; + final vec.Vector3 pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); + + final Matrix4 pivotScale = Matrix4.identity() + ..translateByVector3(pivot) + ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) + ..translateByVector3(-pivot / zoomChange); + + final Matrix4 zoomPivoted = pivotScale * _txController.value; + _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); + } else { + // size changes (e.g. rotation) can still cause out-of-bounds matricies + // so clamp here + _clampToNearestBoundary(_txController.value, viewSize: viewSize); + } + callOnViewerSizeChanged(); + } } }); } else if (currentPageNumber != null && _pageNumber != currentPageNumber) { @@ -603,10 +653,84 @@ class _PdfViewerState extends State } } + /// Shift any overshoot back to the nearest content boundary + void _clampToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { + _stopAnimationsAndClampBoundaries(candidate, viewSize: viewSize); + } + + /// Stop InteractiveViewer animations and apply boundary clamping + void _stopAnimationsAndClampBoundaries(Matrix4 candidate, {required Size viewSize}) { + if (_isInteractionGoingOn) return; + + // Stop any active animations and apply the clamped matrix + if (iv.InteractiveViewer.hasActiveAnimations) { + iv.InteractiveViewer.stopAnimations(); + } + + // Apply the clamped matrix + _txController.value = _calcMatrixForClampedToNearestBoundary(candidate, viewSize: viewSize); + } + int _calcInitialPageNumber() { return widget.params.calculateInitialPageNumber?.call(_document!, _controller!) ?? widget.initialPageNumber; } + PdfPageHitTestResult? _getClosestPageHit(int currentPageNumber, PdfPageLayout oldLayout, ui.Rect oldVisibleRect) { + for (final pageIndex in [currentPageNumber, currentPageNumber - 1, currentPageNumber + 1]) { + if (pageIndex >= 1 && pageIndex <= oldLayout.pageLayouts.length) { + final rec = _nudgeHitTest(oldVisibleRect.topLeft, layout: oldLayout, pageNumber: pageIndex); + if (rec != null) { + return rec.hit; + } + } + } + return null; + } + + /// Hit-tests a point against a given layout and optional page number. + PdfPageHitTestResult? _hitTestWithLayout({ + required Offset point, + required PdfPageLayout layout, + required int pageNumber, + }) { + final pages = _document?.pages; + if (pages == null) return null; + if (pageNumber >= layout.pageLayouts.length) { + return null; + } + + final rect = layout.pageLayouts[pageNumber]; + if (rect.contains(point)) { + final page = pages[pageNumber]; + final Offset local = point - rect.topLeft; + final PdfPoint pdfOffset = local.toPdfPoint(page: page, scaledPageSize: rect.size); + return PdfPageHitTestResult(page: page, offset: pdfOffset); + } else { + return null; + } + } + + // Attempts to nudge the point on the x axis until a valid page hit is found. + ({Offset point, PdfPageHitTestResult hit})? _nudgeHitTest(Offset start, {PdfPageLayout? layout, int? pageNumber}) { + const double epsViewPx = 1.0; + final double epsDoc = epsViewPx / _currentZoom; + + Offset tryPoint = start; + Offset tryOffset = Offset.zero; + final PdfPageLayout? useLayout = layout; + for (int i = 0; i < 500; i++) { + final PdfPageHitTestResult? result = useLayout != null && pageNumber != null + ? _hitTestWithLayout(point: tryPoint, layout: useLayout, pageNumber: pageNumber) + : _getPdfPageHitTestResult(tryPoint, useDocumentLayoutCoordinates: true); + if (result != null) { + return (point: tryOffset, hit: result); + } + tryOffset += Offset(epsDoc, 0); + tryPoint = tryPoint.translate(epsDoc, 0); + } + return null; + } + void _startInteraction() { _interactionEndedTimer?.cancel(); _interactionEndedTimer = null; @@ -1622,6 +1746,28 @@ class _PdfViewerState extends State _setCurrentPageNumber(targetPageNumber); } + /// Scrolls/zooms so that the specified PDF document coordinate appears at + /// the top-left corner of the viewport. + Future _goToPosition({ + required Offset documentOffset, + Duration duration = const Duration(milliseconds: 0), + double? zoom, + }) async { + // Clear any cached partial images to avoid stale tiles after + // going to the new matrix + _imageCache.releasePartialImages(); + + zoom = zoom ?? _currentZoom; + final double tx = -documentOffset.dx * zoom; + final double ty = -documentOffset.dy * zoom; + + final Matrix4 m = Matrix4.compose(vec.Vector3(tx, ty, 0), vec.Quaternion.identity(), vec.Vector3(zoom, zoom, zoom)); + + _adjustBoundaryMargins(_viewSize!, zoom); + final Matrix4 clamped = _calcMatrixForClampedToNearestBoundary(m, viewSize: _viewSize!); + await _goTo(clamped, duration: duration); + } + Future _goToRectInsidePage({ required int pageNumber, required PdfRect rect, @@ -2873,6 +3019,17 @@ class _PdfPageImageCache { tokens.add(token); } + void releasePartialImages() { + for (final request in pageImagePartialRenderingRequests.values) { + request.cancel(); + } + pageImagePartialRenderingRequests.clear(); + for (final image in pageImagesPartial.values) { + image.image.dispose(); + } + pageImagesPartial.clear(); + } + void releaseAllImages() { for (final timer in pageImageRenderingTimers.values) { timer.cancel(); From b1f67a0beb587708694b4576e22fa5452680a715 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 25 Sep 2025 17:34:05 +0900 Subject: [PATCH 355/663] WIP: fixing pana on pdfrx --- .github/workflows/pana-analysis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index 8029f557..f0ae9ef5 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -17,13 +17,16 @@ jobs: include: - package_name: pdfrx_engine package_path: packages/pdfrx_engine - pub_command: dart pub get - package_name: pdfrx package_path: packages/pdfrx - pub_command: flutter pub get steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Install Linux dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y webp + - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -33,17 +36,13 @@ jobs: ~/flutter/bin/flutter doctor -v - name: Pub get (workspace) - run: dart pub get + run: flutter pub get - name: Activate pana run: | dart pub global activate pana echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH - - name: Get dependencies (${{ matrix.package_name }}) - working-directory: ${{ matrix.package_path }} - run: ${{ matrix.pub_command }} - - name: Run pana (${{ matrix.package_name }}) working-directory: ${{ matrix.package_path }} run: ~/.pub-cache/bin/pana --no-warning --exit-code-threshold 0 From 5e808144785943bae217b7acc761930d0ca6433f Mon Sep 17 00:00:00 2001 From: james Date: Thu, 25 Sep 2025 19:24:10 +0930 Subject: [PATCH 356/663] format fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 6 ++ .../lib/src/widgets/interactive_viewer.dart | 101 +++++++++--------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 4f807b50..a8d8da46 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,9 @@ pubspec_overrides.yaml .mcp.json .serena/ + +# iOS/macOS build artifacts that change frequently +**/ios/Podfile.lock +**/macos/Podfile.lock +**/ios/**/*.xcodeproj/project.pbxproj +**/macos/**/*.xcodeproj/project.pbxproj diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 8faca161..e583af09 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -60,55 +60,6 @@ typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Q /// {@end-tool} @immutable class InteractiveViewer extends StatefulWidget { - - /// Create InteractiveViewer with animation control capability - InteractiveViewer.withAnimationControl({ - required Widget child, - Key? key, - Clip clipBehavior = Clip.hardEdge, - PanAxis panAxis = PanAxis.free, - EdgeInsets boundaryMargin = EdgeInsets.zero, - bool constrained = true, - double maxScale = 8.0, - double minScale = 0.8, - double interactionEndFrictionCoefficient = _kDrag, - GestureScaleEndCallback? onInteractionEnd, - GestureScaleStartCallback? onInteractionStart, - GestureScaleUpdateCallback? onInteractionUpdate, - bool panEnabled = true, - bool scaleEnabled = true, - double scaleFactor = kDefaultMouseScrollToScaleFactor, - TransformationController? transformationController, - Alignment? alignment, - bool trackpadScrollCausesScale = false, - void Function(PointerScrollEvent event)? onWheelDelta, - ScrollPhysics? scrollPhysics, - ScrollPhysics? scrollPhysicsScale, - bool scrollPhysicsAutoAdjustBoundaries = true, - }) : this( - key: _globalKey, - child: child, - clipBehavior: clipBehavior, - panAxis: panAxis, - boundaryMargin: boundaryMargin, - constrained: constrained, - maxScale: maxScale, - minScale: minScale, - interactionEndFrictionCoefficient: interactionEndFrictionCoefficient, - onInteractionEnd: onInteractionEnd, - onInteractionStart: onInteractionStart, - onInteractionUpdate: onInteractionUpdate, - panEnabled: panEnabled, - scaleEnabled: scaleEnabled, - scaleFactor: scaleFactor, - transformationController: transformationController, - alignment: alignment, - trackpadScrollCausesScale: trackpadScrollCausesScale, - onWheelDelta: onWheelDelta, - scrollPhysics: scrollPhysics, - scrollPhysicsScale: scrollPhysicsScale, - scrollPhysicsAutoAdjustBoundaries: scrollPhysicsAutoAdjustBoundaries, - ); /// Create an InteractiveViewer. InteractiveViewer({ required this.child, @@ -152,6 +103,55 @@ class InteractiveViewer extends StatefulWidget { ), builder = null; + /// Create InteractiveViewer with animation control capability + InteractiveViewer.withAnimationControl({ + required Widget child, + Key? key, + Clip clipBehavior = Clip.hardEdge, + PanAxis panAxis = PanAxis.free, + EdgeInsets boundaryMargin = EdgeInsets.zero, + bool constrained = true, + double maxScale = 8.0, + double minScale = 0.8, + double interactionEndFrictionCoefficient = _kDrag, + GestureScaleEndCallback? onInteractionEnd, + GestureScaleStartCallback? onInteractionStart, + GestureScaleUpdateCallback? onInteractionUpdate, + bool panEnabled = true, + bool scaleEnabled = true, + double scaleFactor = kDefaultMouseScrollToScaleFactor, + TransformationController? transformationController, + Alignment? alignment, + bool trackpadScrollCausesScale = false, + void Function(PointerScrollEvent event)? onWheelDelta, + ScrollPhysics? scrollPhysics, + ScrollPhysics? scrollPhysicsScale, + bool scrollPhysicsAutoAdjustBoundaries = true, + }) : this( + key: _globalKey, + child: child, + clipBehavior: clipBehavior, + panAxis: panAxis, + boundaryMargin: boundaryMargin, + constrained: constrained, + maxScale: maxScale, + minScale: minScale, + interactionEndFrictionCoefficient: interactionEndFrictionCoefficient, + onInteractionEnd: onInteractionEnd, + onInteractionStart: onInteractionStart, + onInteractionUpdate: onInteractionUpdate, + panEnabled: panEnabled, + scaleEnabled: scaleEnabled, + scaleFactor: scaleFactor, + transformationController: transformationController, + alignment: alignment, + trackpadScrollCausesScale: trackpadScrollCausesScale, + onWheelDelta: onWheelDelta, + scrollPhysics: scrollPhysics, + scrollPhysicsScale: scrollPhysicsScale, + scrollPhysicsAutoAdjustBoundaries: scrollPhysicsAutoAdjustBoundaries, + ); + /// Creates an InteractiveViewer for a child that is created on demand. /// /// Can be used to render a child that changes in response to the current @@ -1462,7 +1462,7 @@ class _InteractiveViewerState extends State with TickerProvid /// Check if any animations are currently active bool get hasActiveAnimations => - _controller.isAnimating || _scaleController.isAnimating || _snapController.isAnimating; + _controller.isAnimating || _scaleController.isAnimating || _snapController.isAnimating; /// Stop all active animations without saving state void _stopAllAnimations() { @@ -1659,7 +1659,6 @@ class _InteractiveViewerBuilt extends StatelessWidget { // represented by exactly one _GestureType. enum _GestureType { pan, scale, rotate } - // Given a velocity and drag, calculate the time at which motion will come to // a stop, within the margin of effectivelyMotionless. double _getFinalTime(double velocity, double drag, {double effectivelyMotionless = 0.5}) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a5b4f6ac..a0b8c1c8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -643,7 +643,7 @@ class _PdfViewerState extends State // size changes (e.g. rotation) can still cause out-of-bounds matricies // so clamp here _clampToNearestBoundary(_txController.value, viewSize: viewSize); - } + } callOnViewerSizeChanged(); } } From 896653c9b347f9567dd2c50ab0d98d480df1cd39 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 25 Sep 2025 21:51:23 +0900 Subject: [PATCH 357/663] Adding several linter rules to enforce coding rules It may break someone's fork branches... --- packages/pdfrx/analysis_options.yaml | 3 + packages/pdfrx/bin/remove_wasm_modules.dart | 4 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 6 +- .../lib/src/widgets/interactive_viewer.dart | 284 +++++++++--------- .../lib/src/widgets/pdf_text_searcher.dart | 4 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 110 +++---- packages/pdfrx_engine/analysis_options.yaml | 4 +- packages/pdfrx_engine/example/main.dart | 2 +- .../lib/src/native/pdf_file_cache.dart | 10 +- .../lib/src/native/pdfrx_pdfium.dart | 22 +- .../pdfrx_engine/lib/src/native/worker.dart | 2 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 30 +- 12 files changed, 243 insertions(+), 238 deletions(-) diff --git a/packages/pdfrx/analysis_options.yaml b/packages/pdfrx/analysis_options.yaml index 6be920c2..22448175 100644 --- a/packages/pdfrx/analysis_options.yaml +++ b/packages/pdfrx/analysis_options.yaml @@ -34,6 +34,9 @@ linter: sort_constructors_first: true always_put_required_named_parameters_first: true invalid_runtime_check_with_js_interop_types: true + type_annotate_public_apis: true + omit_local_variable_types: true + omit_obvious_local_variable_types: true # Additional information about this file can be found at diff --git a/packages/pdfrx/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart index c93fb36e..2a14e0f2 100644 --- a/packages/pdfrx/bin/remove_wasm_modules.dart +++ b/packages/pdfrx/bin/remove_wasm_modules.dart @@ -8,8 +8,8 @@ import 'package:path/path.dart' as path; Future main(List args) async { try { // Parse arguments - bool revert = false; - String projectRoot = '.'; + var revert = false; + var projectRoot = '.'; for (final arg in args) { if (arg == '--revert' || arg == '-r') { diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index a3827fdb..5e16ee6e 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -265,7 +265,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { }) async { await initPdfium(); - for (int i = 0; ; i++) { + for (var i = 0; ; i++) { final String? password; if (firstAttemptByEmptyPassword && i == 0) { password = null; @@ -370,7 +370,7 @@ class _PdfDocumentWasm extends PdfDocument { }) async { if (isDisposed) return; await synchronized(() async { - int firstPageIndex = pages.indexWhere((page) => !page.isLoaded); + var firstPageIndex = pages.indexWhere((page) => !page.isLoaded); if (firstPageIndex < 0) return; // All pages are already loaded for (; firstPageIndex < pages.length;) { @@ -617,7 +617,7 @@ class _PdfPageWasm extends PdfPage { if ((flags & PdfPageRenderFlags.premultipliedAlpha) != 0) { final count = width * height; - for (int i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { final b = pixels[i * 4]; final g = pixels[i * 4 + 1]; final r = pixels[i * 4 + 2]; diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index e583af09..8b3c6c9f 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -467,7 +467,7 @@ class InteractiveViewer extends StatefulWidget { /// Returns the closest point to the given point on the given line segment. @visibleForTesting static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) { - final double lengthSquared = math.pow(l2.x - l1.x, 2.0).toDouble() + math.pow(l2.y - l1.y, 2.0).toDouble(); + final lengthSquared = math.pow(l2.x - l1.x, 2.0).toDouble() + math.pow(l2.y - l1.y, 2.0).toDouble(); // In this case, l1 == l2. if (lengthSquared == 0) { @@ -476,9 +476,9 @@ class InteractiveViewer extends StatefulWidget { // Calculate how far down the line segment the closest point is and return // the point. - final Vector3 l1P = point - l1; - final Vector3 l1L2 = l2 - l1; - final double fraction = clampDouble(l1P.dot(l1L2) / lengthSquared, 0.0, 1.0); + final l1P = point - l1; + final l1L2 = l2 - l1; + final fraction = clampDouble(l1P.dot(l1L2) / lengthSquared, 0.0, 1.0); return l1 + l1L2 * fraction; } @@ -497,14 +497,14 @@ class InteractiveViewer extends StatefulWidget { /// Algorithm from https://math.stackexchange.com/a/190373. @visibleForTesting static bool pointIsInside(Vector3 point, Quad quad) { - final Vector3 aM = point - quad.point0; - final Vector3 aB = quad.point1 - quad.point0; - final Vector3 aD = quad.point3 - quad.point0; + final aM = point - quad.point0; + final aB = quad.point1 - quad.point0; + final aD = quad.point3 - quad.point0; - final double aMAB = aM.dot(aB); - final double aBAB = aB.dot(aB); - final double aMAD = aM.dot(aD); - final double aDAD = aD.dot(aD); + final aMAB = aM.dot(aB); + final aBAB = aB.dot(aB); + final aMAD = aM.dot(aD); + final aDAD = aD.dot(aD); return 0 <= aMAB && aMAB <= aBAB && 0 <= aMAD && aMAD <= aDAD; } @@ -520,16 +520,16 @@ class InteractiveViewer extends StatefulWidget { } // Otherwise, return the nearest point on the quad. - final List closestPoints = [ + final closestPoints = [ InteractiveViewer.getNearestPointOnLine(point, quad.point0, quad.point1), InteractiveViewer.getNearestPointOnLine(point, quad.point1, quad.point2), InteractiveViewer.getNearestPointOnLine(point, quad.point2, quad.point3), InteractiveViewer.getNearestPointOnLine(point, quad.point3, quad.point0), ]; - double minDistance = double.infinity; + var minDistance = double.infinity; late Vector3 closestOverall; - for (final Vector3 closePoint in closestPoints) { - final double distance = math.sqrt(math.pow(point.x - closePoint.x, 2) + math.pow(point.y - closePoint.y, 2)); + for (final closePoint in closestPoints) { + final distance = math.sqrt(math.pow(point.x - closePoint.x, 2) + math.pow(point.y - closePoint.y, 2)); if (distance < minDistance) { minDistance = distance; closestOverall = closePoint; @@ -587,9 +587,9 @@ class _InteractiveViewerState extends State with TickerProvid assert(!widget.boundaryMargin.top.isNaN); assert(!widget.boundaryMargin.bottom.isNaN); - final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; - final Size childSize = childRenderBox.size; - final Rect boundaryRect = widget.boundaryMargin.inflateRect(Offset.zero & childSize); + final childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; + final childSize = childRenderBox.size; + final boundaryRect = widget.boundaryMargin.inflateRect(Offset.zero & childSize); assert(!boundaryRect.isEmpty, "InteractiveViewer's child must have nonzero dimensions."); // Boundaries that are partially infinite are not allowed because Matrix4's // rotation and translation methods don't handle infinites well. @@ -607,7 +607,7 @@ class _InteractiveViewerState extends State with TickerProvid // The Rect representing the child's parent. Rect get _viewport { assert(_parentKey.currentContext != null); - final RenderBox parentRenderBox = _parentKey.currentContext!.findRenderObject()! as RenderBox; + final parentRenderBox = _parentKey.currentContext!.findRenderObject()! as RenderBox; return Offset.zero & parentRenderBox.size; } @@ -631,11 +631,11 @@ class _InteractiveViewerState extends State with TickerProvid alignedTranslation = translation; } - final Matrix4 nextMatrix = matrix.clone()..translateByDouble(alignedTranslation.dx, alignedTranslation.dy, 0, 1); + final nextMatrix = matrix.clone()..translateByDouble(alignedTranslation.dx, alignedTranslation.dy, 0, 1); // Transform the viewport to determine where its four corners will be after // the child has been transformed. - final Quad nextViewport = _transformViewport(nextMatrix, _viewport); + final nextViewport = _transformViewport(nextMatrix, _viewport); // If the boundaries are infinite, then no need to check if the translation // fits within them. @@ -650,18 +650,18 @@ class _InteractiveViewerState extends State with TickerProvid ? (widget.scrollPhysicsScale ?? widget.scrollPhysics!) : widget.scrollPhysics!; // current translation in scene coordinates (negative because controller stores inverse) - final Offset currentOffset = _getMatrixTranslation(_transformer.value) * -1; + final currentOffset = _getMatrixTranslation(_transformer.value) * -1; // build scroll metrics - final ScrollMetrics metricsX = _calculateScrollMetrics(currentOffset.dx, AxisDirection.right); - final ScrollMetrics metricsY = _calculateScrollMetrics(currentOffset.dy, AxisDirection.down); + final metricsX = _calculateScrollMetrics(currentOffset.dx, AxisDirection.right); + final metricsY = _calculateScrollMetrics(currentOffset.dy, AxisDirection.down); - final double proposedX = currentOffset.dx - alignedTranslation.dx; - final double proposedY = currentOffset.dy - alignedTranslation.dy; + final proposedX = currentOffset.dx - alignedTranslation.dx; + final proposedY = currentOffset.dy - alignedTranslation.dy; - final double overscrollX = proposedX == currentOffset.dx + final overscrollX = proposedX == currentOffset.dx ? 0 : physics.applyBoundaryConditions(metricsX, proposedX); // : 0. - final double overscrollY = proposedY == currentOffset.dy + final overscrollY = proposedY == currentOffset.dy ? 0 : physics.applyBoundaryConditions(metricsY, proposedY); // : 0. @@ -676,11 +676,11 @@ class _InteractiveViewerState extends State with TickerProvid return nextMatrix; } // Check if the offset is accepted by the ScrollPhysics, and so apply it. - double dx = 0.0; + var dx = 0.0; if (alignedTranslation.dx != 0 && physics.shouldAcceptUserOffset(_normalizeScrollMetrics(metricsX))) { dx = physics.applyPhysicsToUserOffset(metricsX, alignedTranslation.dx); } - double dy = 0.0; + var dy = 0.0; if (alignedTranslation.dy != 0 && physics.shouldAcceptUserOffset(_normalizeScrollMetrics(metricsY))) { dy = physics.applyPhysicsToUserOffset(metricsY, alignedTranslation.dy); } @@ -696,19 +696,19 @@ class _InteractiveViewerState extends State with TickerProvid // mismatch in orientation between the viewport and boundaries effectively // limits translation. With this approach, all points that are visible with // no rotation are visible after rotation. - final Quad boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation(_boundaryRect, _currentRotation); + final boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation(_boundaryRect, _currentRotation); // If the given translation fits completely within the boundaries, allow it. - final Offset offendingDistance = _exceedsBy(boundariesAabbQuad, nextViewport); + final offendingDistance = _exceedsBy(boundariesAabbQuad, nextViewport); if (offendingDistance == Offset.zero) { return nextMatrix; } // Desired translation goes out of bounds, so translate to the nearest // in-bounds point instead. - final Offset nextTotalTranslation = _getMatrixTranslation(nextMatrix); - final double currentScale = matrix.getMaxScaleOnAxis(); - final Offset correctedTotalTranslation = Offset( + final nextTotalTranslation = _getMatrixTranslation(nextMatrix); + final currentScale = matrix.getMaxScaleOnAxis(); + final correctedTotalTranslation = Offset( nextTotalTranslation.dx - offendingDistance.dx * currentScale, nextTotalTranslation.dy - offendingDistance.dy * currentScale, ); @@ -717,12 +717,12 @@ class _InteractiveViewerState extends State with TickerProvid // calculating the translation to put the viewport inside that Quad is more // complicated than this when rotated. // https://github.com/flutter/flutter/issues/57698 - final Matrix4 correctedMatrix = matrix.clone() + final correctedMatrix = matrix.clone() ..setTranslation(Vector3(correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0.0)); // Double check that the corrected translation fits. - final Quad correctedViewport = _transformViewport(correctedMatrix, _viewport); - final Offset offendingCorrectedDistance = _exceedsBy(boundariesAabbQuad, correctedViewport); + final correctedViewport = _transformViewport(correctedMatrix, _viewport); + final offendingCorrectedDistance = _exceedsBy(boundariesAabbQuad, correctedViewport); if (offendingCorrectedDistance == Offset.zero) { return correctedMatrix; } @@ -736,7 +736,7 @@ class _InteractiveViewerState extends State with TickerProvid // Otherwise, allow translation in only the direction that fits. This // happens when the viewport is larger than the boundary in one direction. - final Offset unidirectionalCorrectedTotalTranslation = Offset( + final unidirectionalCorrectedTotalTranslation = Offset( offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0, offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0, ); @@ -759,13 +759,13 @@ class _InteractiveViewerState extends State with TickerProvid if (scrollPhysics != null) { // Compute current and desired scales - final double currentScale = _transformer.value.getMaxScaleOnAxis(); + final currentScale = _transformer.value.getMaxScaleOnAxis(); // scale provided is a desired change in scale between the current scale // and the start of the gesture - final double scaleChange = scale; + final scaleChange = scale; // desired but not necessarily achieved if physics is applied - final double desiredScale = currentScale * scale; + final desiredScale = currentScale * scale; final allowedScale = _getAllowedScale(desiredScale); // Early return if not allowed to zoom outside bounds @@ -774,23 +774,23 @@ class _InteractiveViewerState extends State with TickerProvid } // Compute ratio of this update's scale to the previous update - final double scaleRatio = scaleChange / _lastScale; + final scaleRatio = scaleChange / _lastScale; // Store for next frame _lastScale = scaleChange; // Physics requires the incremental scale change since last update - final double incrementalScale = currentScale * scaleRatio; + final incrementalScale = currentScale * scaleRatio; // Content-space-based scrollPhysics for scale overscroll and undershoot if (_gestureType == _GestureType.scale && !_snapController.isAnimating && ((desiredScale < widget.minScale) || (desiredScale > widget.maxScale))) { - final Size contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; + final contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; // Compute current and desired absolute scale - final double contentWidth = contentSize.width * currentScale; - final double desiredContentWidth = contentSize.width * incrementalScale; - final double contentHeight = contentSize.height * currentScale; - final double desiredContentHeight = contentSize.height * incrementalScale; + final contentWidth = contentSize.width * currentScale; + final desiredContentWidth = contentSize.width * incrementalScale; + final contentHeight = contentSize.height * currentScale; + final desiredContentHeight = contentSize.height * incrementalScale; // Build horizontal and vertical metrics final ScrollMetrics metricsX = FixedScrollMetrics( @@ -811,23 +811,23 @@ class _InteractiveViewerState extends State with TickerProvid ); // Compute content deltas - final double deltaX = desiredContentWidth - contentWidth; - final double deltaY = desiredContentHeight - contentHeight; + final deltaX = desiredContentWidth - contentWidth; + final deltaY = desiredContentHeight - contentHeight; // Apply scroll physics half the delta to simulate exceeding a boundary // on one side - final double adjustedX = scrollPhysics.applyPhysicsToUserOffset(metricsX, deltaX / 2) * 2; - final double adjustedY = scrollPhysics.applyPhysicsToUserOffset(metricsY, deltaY / 2) * 2; + final adjustedX = scrollPhysics.applyPhysicsToUserOffset(metricsX, deltaX / 2) * 2; + final adjustedY = scrollPhysics.applyPhysicsToUserOffset(metricsY, deltaY / 2) * 2; // Convert back to scale factors - final double newScaleX = (contentWidth + adjustedX) / contentWidth; - final double newScaleY = (contentHeight + adjustedY) / contentHeight; - final double factor = (newScaleX + newScaleY) / 2; + final newScaleX = (contentWidth + adjustedX) / contentWidth; + final newScaleY = (contentHeight + adjustedY) / contentHeight; + final factor = (newScaleX + newScaleY) / 2; return matrix.clone()..scaleByDouble(factor, factor, factor, 1); } else { - final double clampedTotalScale = clampDouble(desiredScale, widget.minScale, widget.maxScale); - final double clampedScale = clampedTotalScale / currentScale; + final clampedTotalScale = clampDouble(desiredScale, widget.minScale, widget.maxScale); + final clampedScale = clampedTotalScale / currentScale; // Apply the scale factor to the matrix return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); @@ -835,15 +835,15 @@ class _InteractiveViewerState extends State with TickerProvid } else { // Don't allow a scale that results in an overall scale beyond min/max // scale. - final double currentScale = _transformer.value.getMaxScaleOnAxis(); + final currentScale = _transformer.value.getMaxScaleOnAxis(); final double totalScale = math.max( currentScale * scale, // Ensure that the scale cannot make the child so big that it can't fit // inside the boundaries (in either direction). math.max(_viewport.width / _boundaryRect.width, _viewport.height / _boundaryRect.height), ); - final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); - final double clampedScale = clampedTotalScale / currentScale; + final clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale); + final clampedScale = clampedTotalScale / currentScale; return matrix.clone()..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); } } @@ -854,7 +854,7 @@ class _InteractiveViewerState extends State with TickerProvid if (rotation == 0) { return matrix.clone(); } - final Offset focalPointScene = _transformer.toScene(focalPoint); + final focalPointScene = _transformer.toScene(focalPoint); return matrix.clone() ..translateByDouble(focalPointScene.dx, focalPointScene.dy, 0, 1) ..rotateZ(-rotation) @@ -875,8 +875,8 @@ class _InteractiveViewerState extends State with TickerProvid // starts at 0. Pan will have no scale and no rotation because it uses only one // finger. _GestureType _getGestureType(ScaleUpdateDetails details) { - final double scale = !widget.scaleEnabled ? 1.0 : details.scale; - final double rotation = !_rotateEnabled ? 0.0 : details.rotation; + final scale = !widget.scaleEnabled ? 1.0 : details.scale; + final rotation = !_rotateEnabled ? 0.0 : details.rotation; if ((scale - 1).abs() > rotation.abs()) { return _GestureType.scale; } else if (rotation != 0.0) { @@ -917,9 +917,9 @@ class _InteractiveViewerState extends State with TickerProvid // Handle an update to an ongoing gesture. All of pan, scale, and rotate are // handled with GestureDetector's scale gesture. void _onScaleUpdate(ScaleUpdateDetails details) { - final double scale = _transformer.value.getMaxScaleOnAxis(); + final scale = _transformer.value.getMaxScaleOnAxis(); _scaleAnimationFocalPoint = details.localFocalPoint; - final Offset focalPointScene = _transformer.toScene(details.localFocalPoint); + final focalPointScene = _transformer.toScene(details.localFocalPoint); if (_gestureType == _GestureType.pan) { // When a gesture first starts, it sometimes has no change in scale and @@ -941,8 +941,8 @@ class _InteractiveViewerState extends State with TickerProvid // details.scale gives us the amount to change the scale as of the // start of this gesture, so calculate the amount to scale as of the // previous call to _onScaleUpdate. - final double desiredScale = _scaleStart! * details.scale; - final double scaleChange = desiredScale / scale; + final desiredScale = _scaleStart! * details.scale; + final scaleChange = desiredScale / scale; _snapFocalPoint = details.localFocalPoint; _transformer.value = _matrixScale(_transformer.value, scaleChange); @@ -950,7 +950,7 @@ class _InteractiveViewerState extends State with TickerProvid // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. - final Offset focalPointSceneScaled = _transformer.toScene(details.localFocalPoint); + final focalPointSceneScaled = _transformer.toScene(details.localFocalPoint); _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - _referenceFocalPoint!); // details.localFocalPoint should now be at the same location as the @@ -958,7 +958,7 @@ class _InteractiveViewerState extends State with TickerProvid // the translate came in contact with a boundary. In that case, update // _referenceFocalPoint so subsequent updates happen in relation to // the new effective focal point. - final Offset focalPointSceneCheck = _transformer.toScene(details.localFocalPoint); + final focalPointSceneCheck = _transformer.toScene(details.localFocalPoint); if (_referenceFocalPoint!.round10BitFrac() != focalPointSceneCheck.round10BitFrac()) { _referenceFocalPoint = focalPointSceneCheck; } @@ -968,7 +968,7 @@ class _InteractiveViewerState extends State with TickerProvid widget.onInteractionUpdate?.call(details); return; } - final double desiredRotation = _rotationStart! + details.rotation; + final desiredRotation = _rotationStart! + details.rotation; _transformer.value = _matrixRotate( _transformer.value, _currentRotation - desiredRotation, @@ -988,7 +988,7 @@ class _InteractiveViewerState extends State with TickerProvid _currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene); // Translate so that the same point in the scene is underneath the // focal point before and after the movement. - final Offset translationChange = focalPointScene - _referenceFocalPoint!; + final translationChange = focalPointScene - _referenceFocalPoint!; _transformer.value = _matrixTranslate(_transformer.value, translationChange); _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); } @@ -1017,8 +1017,8 @@ class _InteractiveViewerState extends State with TickerProvid if (widget.scrollPhysics != null) { if (_snapController.isAnimating) return; - final Vector3 currentTranslation = _transformer.value.getTranslation(); - final Offset currentOffset = Offset(currentTranslation.x, currentTranslation.y); + final currentTranslation = _transformer.value.getTranslation(); + final currentOffset = Offset(currentTranslation.x, currentTranslation.y); final adjustedOffset = currentOffset * -1; final flingVelocityX = @@ -1052,20 +1052,20 @@ class _InteractiveViewerState extends State with TickerProvid _currentAxis = null; return; } - final Vector3 translationVector = _transformer.value.getTranslation(); - final Offset translation = Offset(translationVector.x, translationVector.y); + final translationVector = _transformer.value.getTranslation(); + final translation = Offset(translationVector.x, translationVector.y); // (Removed FrictionSimulation logic for scale; only pan uses it.) - final FrictionSimulation frictionSimulationX = FrictionSimulation( + final frictionSimulationX = FrictionSimulation( widget.interactionEndFrictionCoefficient, translation.dx, details.velocity.pixelsPerSecond.dx, ); - final FrictionSimulation frictionSimulationY = FrictionSimulation( + final frictionSimulationY = FrictionSimulation( widget.interactionEndFrictionCoefficient, translation.dy, details.velocity.pixelsPerSecond.dy, ); - final double tFinal = _getFinalTime( + final tFinal = _getFinalTime( details.velocity.pixelsPerSecond.distance, widget.interactionEndFrictionCoefficient, ); @@ -1080,8 +1080,8 @@ class _InteractiveViewerState extends State with TickerProvid break; case _GestureType.scale: if (widget.scrollPhysics != null) { - final double endScale = _transformer.value.getMaxScaleOnAxis(); - final double clampedScale = endScale.clamp(widget.minScale, widget.maxScale); + final endScale = _transformer.value.getMaxScaleOnAxis(); + final clampedScale = endScale.clamp(widget.minScale, widget.maxScale); if (clampedScale != endScale) { HapticFeedback.lightImpact(); @@ -1089,8 +1089,8 @@ class _InteractiveViewerState extends State with TickerProvid // even if the the scale doesn't change, we may be out of bounds, and // want to animate the snap back to bounds _snapStartMatrix = _transformer.value.clone(); - final Offset pivotScene = _transformer.toScene(_snapFocalPoint); - final Matrix4 endMatrix = _snapStartMatrix.clone() + final pivotScene = _transformer.toScene(_snapFocalPoint); + final endMatrix = _snapStartMatrix.clone() ..translateByDouble(pivotScene.dx, pivotScene.dy, 0, 1) ..scaleByDouble(clampedScale / endScale, clampedScale / endScale, clampedScale / endScale, 1) ..translateByDouble(-pivotScene.dx, -pivotScene.dy, 0, 1); @@ -1108,13 +1108,13 @@ class _InteractiveViewerState extends State with TickerProvid _currentAxis = null; return; } - final double scale = _transformer.value.getMaxScaleOnAxis(); - final FrictionSimulation frictionSimulation = FrictionSimulation( + final scale = _transformer.value.getMaxScaleOnAxis(); + final frictionSimulation = FrictionSimulation( widget.interactionEndFrictionCoefficient * widget.scaleFactor, scale, details.scaleVelocity / 10, ); - final double tFinal = _getFinalTime( + final tFinal = _getFinalTime( details.scaleVelocity.abs(), widget.interactionEndFrictionCoefficient, effectivelyMotionless: 0.1, @@ -1137,15 +1137,15 @@ class _InteractiveViewerState extends State with TickerProvid // Handle mousewheel and web trackpad scroll events. void _receivedPointerSignal(PointerSignalEvent event) { - final Offset local = event.localPosition; - final Offset global = event.position; + final local = event.localPosition; + final global = event.position; final double scaleChange; if (event is PointerScrollEvent) { if (event.kind == PointerDeviceKind.trackpad && !widget.trackpadScrollCausesScale) { // Trackpad scroll, so treat it as a pan. widget.onInteractionStart?.call(ScaleStartDetails(focalPoint: global, localFocalPoint: local)); - final Offset localDelta = PointerEvent.transformDeltaViaPositions( + final localDelta = PointerEvent.transformDeltaViaPositions( untransformedEndPosition: global + event.scrollDelta, untransformedDelta: event.scrollDelta, transform: event.transform, @@ -1163,8 +1163,8 @@ class _InteractiveViewerState extends State with TickerProvid return; } - final Offset focalPointScene = _transformer.toScene(local); - final Offset newFocalPointScene = _transformer.toScene(local - localDelta); + final focalPointScene = _transformer.toScene(local); + final newFocalPointScene = _transformer.toScene(local - localDelta); _transformer.value = _matrixTranslate(_transformer.value, newFocalPointScene - focalPointScene); @@ -1205,12 +1205,12 @@ class _InteractiveViewerState extends State with TickerProvid return; } - final Offset focalPointScene = _transformer.toScene(local); + final focalPointScene = _transformer.toScene(local); _transformer.value = _matrixScale(_transformer.value, scaleChange); // After scaling, translate such that the event's position is at the // same scene point before and after the scale. - final Offset focalPointSceneScaled = _transformer.toScene(local); + final focalPointSceneScaled = _transformer.toScene(local); _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - focalPointScene); widget.onInteractionUpdate?.call( @@ -1232,23 +1232,23 @@ class _InteractiveViewerState extends State with TickerProvid return; } // Translate such that the resulting translation is _animation.value. - final Vector3 translationVector = _transformer.value.getTranslation(); - final Offset translation = Offset(translationVector.x, translationVector.y); - final Offset translationScene = _transformer.toScene(translation); + final translationVector = _transformer.value.getTranslation(); + final translation = Offset(translationVector.x, translationVector.y); + final translationScene = _transformer.toScene(translation); if (widget.scrollPhysics != null) { /// When using scrollPhysics, we apply a simulation rather than an animation to the offsets - final double t = _controller.lastElapsedDuration!.inMilliseconds / 1000.0; - final double simulationOffsetX = simulationX != null ? -simulationX!.x(t) : translationVector.x; - final double simulationOffsetY = simulationY != null ? -simulationY!.x(t) : translationVector.y; - final Offset simulationOffset = Offset(simulationOffsetX, simulationOffsetY); - final Offset simulationScene = _transformer.toScene(simulationOffset); - final Offset translationChangeScene = simulationScene - translationScene; + final t = _controller.lastElapsedDuration!.inMilliseconds / 1000.0; + final simulationOffsetX = simulationX != null ? -simulationX!.x(t) : translationVector.x; + final simulationOffsetY = simulationY != null ? -simulationY!.x(t) : translationVector.y; + final simulationOffset = Offset(simulationOffsetX, simulationOffsetY); + final simulationScene = _transformer.toScene(simulationOffset); + final translationChangeScene = simulationScene - translationScene; _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); } else { // Translate such that the resulting translation is _animation.value. - final Offset animationScene = _transformer.toScene(_animation!.value); - final Offset translationChangeScene = animationScene - translationScene; + final animationScene = _transformer.toScene(_animation!.value); + final translationChangeScene = animationScene - translationScene; _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); } } @@ -1256,8 +1256,8 @@ class _InteractiveViewerState extends State with TickerProvid /// ScrollPhysics helpers /// ChildSize is the size of the child (without the boundary margin). Size _childSize() { - final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; - final Size childSize = childRenderBox.size; + final childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; + final childSize = childRenderBox.size; return childSize; } @@ -1269,7 +1269,7 @@ class _InteractiveViewerState extends State with TickerProvid bool overrideAutoAdjustBoundaries = false, }) { // Use original boundaryMargin unless a specific one is passed for override. - final EdgeInsets baseMargin = + final baseMargin = (overrideAutoAdjustBoundaries && !widget.scrollPhysicsAutoAdjustBoundaries) || boundaryMargin == null ? _originalBoundaryMargin : boundaryMargin; @@ -1279,15 +1279,15 @@ class _InteractiveViewerState extends State with TickerProvid return const Rect.fromLTRB(-double.maxFinite, -double.maxFinite, double.maxFinite, double.maxFinite); } // Compute the raw boundary rect using the baseMargin, then scale it - final Rect baseBoundaryRect = baseMargin.inflateRect(Offset.zero & _childSize()); - final double effectiveWidth = baseBoundaryRect.width * scale; - final double effectiveHeight = baseBoundaryRect.height * scale; + final baseBoundaryRect = baseMargin.inflateRect(Offset.zero & _childSize()); + final effectiveWidth = baseBoundaryRect.width * scale; + final effectiveHeight = baseBoundaryRect.height * scale; final extraWidth = effectiveWidth - viewportSize.width; final extraHeight = effectiveHeight - viewportSize.height; // Always center when content is smaller than viewport, using a small tolerance for floating imprecision. - const double kOverflowTolerance = 0.1; // logical pixels + const kOverflowTolerance = 0.1; // logical pixels final extraBoundaryHorizontal = extraWidth < -kOverflowTolerance ? (extraWidth.abs() / 2) : 0.0; final extraBoundaryVertical = extraHeight < -kOverflowTolerance ? (extraHeight.abs() / 2) : 0.0; @@ -1302,11 +1302,11 @@ class _InteractiveViewerState extends State with TickerProvid // ScrollPhysics.shouldAcceptUserOffset() does not work where minScrollExtent and pixels // are both the same value but not 0.0. ScrollMetrics _normalizeScrollMetrics(ScrollMetrics scrollMetrics) { - double range = scrollMetrics.maxScrollExtent - scrollMetrics.minScrollExtent; - double pixels = scrollMetrics.pixels - scrollMetrics.minScrollExtent; + var range = scrollMetrics.maxScrollExtent - scrollMetrics.minScrollExtent; + var pixels = scrollMetrics.pixels - scrollMetrics.minScrollExtent; // Define a small tolerance around zero to ignore tiny drifts - const double kTolerance = 0.01; + const kTolerance = 0.01; range = range.abs() < kTolerance ? 0.0 : range; pixels = pixels.abs() < kTolerance ? 0.0 : pixels; @@ -1329,7 +1329,7 @@ class _InteractiveViewerState extends State with TickerProvid boundaryMargin: widget.boundaryMargin, ); - final Axis axis = switch (axisDirection) { + final axis = switch (axisDirection) { AxisDirection.left => Axis.horizontal, AxisDirection.right => Axis.horizontal, AxisDirection.up => Axis.vertical, @@ -1382,8 +1382,8 @@ class _InteractiveViewerState extends State with TickerProvid if (_snapTargetMatrix == null) { return; } - final double t = Curves.ease.transform(_snapController.value); - final Matrix4 lerped = Matrix4Tween(begin: _snapStartMatrix, end: _snapTargetMatrix!).transform(t); + final t = Curves.ease.transform(_snapController.value); + final lerped = Matrix4Tween(begin: _snapStartMatrix, end: _snapTargetMatrix!).transform(t); _transformer.value = lerped; } @@ -1395,13 +1395,13 @@ class _InteractiveViewerState extends State with TickerProvid return proposedScale.clamp(widget.minScale, widget.maxScale); } - final Size contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; + final contentSize = _boundaryRect.isInfinite ? _childSize() : _boundaryRect.size; - final double currentScale = _transformer.value.getMaxScaleOnAxis(); - final double contentWidth = contentSize.width * currentScale; - final double desiredContentWidth = contentSize.width * proposedScale; - final double contentHeight = contentSize.height * currentScale; - final double desiredContentHeight = contentSize.height * proposedScale; + final currentScale = _transformer.value.getMaxScaleOnAxis(); + final contentWidth = contentSize.width * currentScale; + final desiredContentWidth = contentSize.width * proposedScale; + final contentHeight = contentSize.height * currentScale; + final desiredContentHeight = contentSize.height * proposedScale; final ScrollMetrics metricsX = FixedScrollMetrics( pixels: contentWidth, @@ -1420,8 +1420,8 @@ class _InteractiveViewerState extends State with TickerProvid devicePixelRatio: 1.0, ); - final double adjustmentX = scrollPhysics.applyBoundaryConditions(metricsX, desiredContentWidth); - final double adjustmentY = scrollPhysics.applyBoundaryConditions(metricsY, desiredContentHeight); + final adjustmentX = scrollPhysics.applyBoundaryConditions(metricsX, desiredContentWidth); + final adjustmentY = scrollPhysics.applyBoundaryConditions(metricsY, desiredContentHeight); if (adjustmentX == 0.0 && adjustmentY == 0.0) { // No adjustment needed, so the proposed scale is allowed. @@ -1506,16 +1506,16 @@ class _InteractiveViewerState extends State with TickerProvid _scaleController.reset(); return; } - final double desiredScale = _scaleAnimation!.value; - final double scaleChange = desiredScale / _transformer.value.getMaxScaleOnAxis(); - final Offset referenceFocalPoint = _transformer.toScene(_scaleAnimationFocalPoint); + final desiredScale = _scaleAnimation!.value; + final scaleChange = desiredScale / _transformer.value.getMaxScaleOnAxis(); + final referenceFocalPoint = _transformer.toScene(_scaleAnimationFocalPoint); _transformer.value = _matrixScale(_transformer.value, scaleChange); // While scaling, translate such that the user's two fingers stay on // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. - final Offset focalPointSceneScaled = _transformer.toScene(_scaleAnimationFocalPoint); + final focalPointSceneScaled = _transformer.toScene(_scaleAnimationFocalPoint); _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - referenceFocalPoint); } @@ -1539,7 +1539,7 @@ class _InteractiveViewerState extends State with TickerProvid void didUpdateWidget(InteractiveViewer oldWidget) { super.didUpdateWidget(oldWidget); - final TransformationController? newController = widget.transformationController; + final newController = widget.transformationController; if (newController == oldWidget.transformationController) { return; } @@ -1582,7 +1582,7 @@ class _InteractiveViewerState extends State with TickerProvid assert(!widget.constrained); child = LayoutBuilder( builder: (context, constraints) { - final Matrix4 matrix = _transformer.value; + final matrix = _transformer.value; return _InteractiveViewerBuilt( childKey: _childKey, clipBehavior: widget.clipBehavior, @@ -1667,7 +1667,7 @@ double _getFinalTime(double velocity, double drag, {double effectivelyMotionless // Return the translation from the given Matrix4 as an Offset. Offset _getMatrixTranslation(Matrix4 matrix) { - final Vector3 nextTranslation = matrix.getTranslation(); + final nextTranslation = matrix.getTranslation(); return Offset(nextTranslation.x, nextTranslation.y); } @@ -1676,7 +1676,7 @@ Offset _getMatrixTranslation(Matrix4 matrix) { // given matrix. The viewport transforms as the inverse of the child (i.e. // moving the child left is equivalent to moving the viewport right). Quad _transformViewport(Matrix4 matrix, Rect viewport) { - final Matrix4 inverseMatrix = matrix.clone()..invert(); + final inverseMatrix = matrix.clone()..invert(); return Quad.points( inverseMatrix.transform3(Vector3(viewport.topLeft.dx, viewport.topLeft.dy, 0.0)), inverseMatrix.transform3(Vector3(viewport.topRight.dx, viewport.topRight.dy, 0.0)), @@ -1688,11 +1688,11 @@ Quad _transformViewport(Matrix4 matrix, Rect viewport) { // Find the axis aligned bounding box for the rect rotated about its center by // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { - final Matrix4 rotationMatrix = Matrix4.identity() + final rotationMatrix = Matrix4.identity() ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) ..rotateZ(rotation) ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); - final Quad boundariesRotated = Quad.points( + final boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)), rotationMatrix.transform3(Vector3(rect.right, rect.bottom, 0.0)), @@ -1705,11 +1705,11 @@ Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { // is completely contained within the boundary (inclusively), then returns // Offset.zero. Offset _exceedsBy(Quad boundary, Quad viewport) { - final List viewportPoints = [viewport.point0, viewport.point1, viewport.point2, viewport.point3]; - Offset largestExcess = Offset.zero; - for (final Vector3 point in viewportPoints) { - final Vector3 pointInside = InteractiveViewer.getNearestPointInside(point, boundary); - final Offset excess = Offset(pointInside.x - point.x, pointInside.y - point.y); + final viewportPoints = [viewport.point0, viewport.point1, viewport.point2, viewport.point3]; + var largestExcess = Offset.zero; + for (final point in viewportPoints) { + final pointInside = InteractiveViewer.getNearestPointInside(point, boundary); + final excess = Offset(pointInside.x - point.x, pointInside.y - point.y); if (excess.dx.abs() > largestExcess.dx.abs()) { largestExcess = Offset(excess.dx, largestExcess.dy); } @@ -1756,8 +1756,8 @@ class CombinedSimulation extends Simulation { // Returns the combined velocity magnitude of the two simulations. @override double dx(double time) { - final double dxX = simulationX.dx(time); - final double dxY = simulationY.dx(time); + final dxX = simulationX.dx(time); + final dxY = simulationY.dx(time); return math.sqrt(dxX * dxX + dxY * dxY); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index efbddcf6..e6671c3e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -152,7 +152,7 @@ class PdfTextSearcher extends Listenable { await controller?.useDocument((document) async { final textMatches = []; final textMatchesPageStartIndex = []; - bool first = true; + var first = true; _isSearching = true; _totalPageCount = document.pages.length; for (final page in document.pages) { @@ -255,7 +255,7 @@ class PdfTextSearcher extends Listenable { final matchTextColor = controller?.params.matchTextColor ?? Colors.yellow.withAlpha(127); final activeMatchTextColor = controller?.params.activeMatchTextColor ?? Colors.orange.withAlpha(127); - for (int i = range.start; i < range.end; i++) { + for (var i = range.start; i < range.end; i++) { final m = _matches[i]; final rect = m.bounds.toRect(page: page, scaledPageSize: pageRect.size).translate(pageRect.left, pageRect.top); canvas.drawRect(rect, Paint()..color = m == _currentMatch ? activeMatchTextColor : matchTextColor); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a0b8c1c8..4ef4bfab 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -535,10 +535,10 @@ class _PdfViewerState extends State return candidate; } - final PdfPageLayout layout = _layout!; - final Rect visible = candidate.calcVisibleRect(viewSize); - double dxDoc = 0.0; - double dyDoc = 0.0; + final layout = _layout!; + final visible = candidate.calcVisibleRect(viewSize); + var dxDoc = 0.0; + var dyDoc = 0.0; if (visible.left < 0) { dxDoc = -visible.left - boundaryMargin.left; @@ -557,8 +557,8 @@ class _PdfViewerState extends State void _updateLayout(Size viewSize) { if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); - final Rect oldVisibleRect = _initialized ? _visibleRect : Rect.zero; - final PdfPageLayout? oldLayout = _layout; + final oldVisibleRect = _initialized ? _visibleRect : Rect.zero; + final oldLayout = _layout; final oldMinScale = _minScale; final oldSize = _viewSize; final isViewSizeChanged = oldSize != viewSize; @@ -607,18 +607,18 @@ class _PdfViewerState extends State // The top-left position of the screen (oldVisibleRect.topLeft) may be // in the boundary margin, or a margin between pages, and it could be // the current page or one of the neighboring pages - final PdfPageHitTestResult? hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); + final hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); final pageNumber = hit?.page.pageNumber ?? currentPageNumber; // Compute relative position within the old pageRect - final Rect oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; - final Rect newPageRect = _layout!.pageLayouts[pageNumber - 1]; - final Offset oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; - final double fracX = oldOffset.dx / oldPageRect.width; - final double fracY = oldOffset.dy / oldPageRect.height; + final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; + final newPageRect = _layout!.pageLayouts[pageNumber - 1]; + final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; + final fracX = oldOffset.dx / oldPageRect.width; + final fracY = oldOffset.dy / oldPageRect.height; // Map into new layoutRect - final Offset newOffset = Offset( + final newOffset = Offset( newPageRect.left + fracX * newPageRect.width, newPageRect.top + fracY * newPageRect.height, ); @@ -629,10 +629,10 @@ class _PdfViewerState extends State } else { if (zoomTo != _currentZoom) { // layout hasn't changed, but size and zoom has - final double zoomChange = zoomTo / _currentZoom; - final vec.Vector3 pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); + final zoomChange = zoomTo / _currentZoom; + final pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); - final Matrix4 pivotScale = Matrix4.identity() + final pivotScale = Matrix4.identity() ..translateByVector3(pivot) ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) ..translateByVector3(-pivot / zoomChange); @@ -702,8 +702,8 @@ class _PdfViewerState extends State final rect = layout.pageLayouts[pageNumber]; if (rect.contains(point)) { final page = pages[pageNumber]; - final Offset local = point - rect.topLeft; - final PdfPoint pdfOffset = local.toPdfPoint(page: page, scaledPageSize: rect.size); + final local = point - rect.topLeft; + final pdfOffset = local.toPdfPoint(page: page, scaledPageSize: rect.size); return PdfPageHitTestResult(page: page, offset: pdfOffset); } else { return null; @@ -712,14 +712,14 @@ class _PdfViewerState extends State // Attempts to nudge the point on the x axis until a valid page hit is found. ({Offset point, PdfPageHitTestResult hit})? _nudgeHitTest(Offset start, {PdfPageLayout? layout, int? pageNumber}) { - const double epsViewPx = 1.0; - final double epsDoc = epsViewPx / _currentZoom; - - Offset tryPoint = start; - Offset tryOffset = Offset.zero; - final PdfPageLayout? useLayout = layout; - for (int i = 0; i < 500; i++) { - final PdfPageHitTestResult? result = useLayout != null && pageNumber != null + const epsViewPx = 1.0; + final epsDoc = epsViewPx / _currentZoom; + + var tryPoint = start; + var tryOffset = Offset.zero; + final useLayout = layout; + for (var i = 0; i < 500; i++) { + final result = useLayout != null && pageNumber != null ? _hitTestWithLayout(point: tryPoint, layout: useLayout, pageNumber: pageNumber) : _getPdfPageHitTestResult(tryPoint, useDocumentLayoutCoordinates: true); if (result != null) { @@ -870,7 +870,7 @@ class _PdfViewerState extends State int? pageNumber; double maxRatio = 0; - for (int i = 1; i <= _document!.pages.length; i++) { + for (var i = 1; i <= _document!.pages.length; i++) { final ratio = calcIntersectionArea(i); if (ratio == 0) continue; if (ratio > maxRatio) { @@ -977,7 +977,7 @@ class _PdfViewerState extends State return _zoomStops.last; } } else { - for (int i = _zoomStops.length - 1; i >= 0; i--) { + for (var i = _zoomStops.length - 1; i >= 0; i--) { final z = _zoomStops[i]; if (z < zoom && !_areZoomsAlmostIdentical(z, zoom)) return z; } @@ -1002,12 +1002,12 @@ class _PdfViewerState extends State final currentDocumentSize = boundaryMargin.inflateSize(_layout!.documentSize); - final double effectiveWidth = currentDocumentSize.width * zoom; - final double effectiveHeight = currentDocumentSize.height * zoom; - final double extraWidth = effectiveWidth - viewSize.width; - final double extraBoundaryHorizontal = extraWidth < 0 ? (-extraWidth / 2) / zoom : 0.0; - final double extraHeight = effectiveHeight - viewSize.height; - final double extraBoundaryVertical = extraHeight < 0 ? (-extraHeight / 2) / zoom : 0.0; + final effectiveWidth = currentDocumentSize.width * zoom; + final effectiveHeight = currentDocumentSize.height * zoom; + final extraWidth = effectiveWidth - viewSize.width; + final extraBoundaryHorizontal = extraWidth < 0 ? (-extraWidth / 2) / zoom : 0.0; + final extraHeight = effectiveHeight - viewSize.height; + final extraBoundaryVertical = extraHeight < 0 ? (-extraHeight / 2) / zoom : 0.0; _adjustedBoundaryMargins = boundaryMargin + @@ -1027,7 +1027,7 @@ class _PdfViewerState extends State final overlayWidgets = []; final targetRect = _getCacheExtentRect(); - for (int i = 0; i < _document!.pages.length; i++) { + for (var i = 0; i < _document!.pages.length; i++) { final rect = _layout!.pageLayouts[i]; final intersection = rect.intersect(targetRect); if (intersection.isEmpty) continue; @@ -1127,7 +1127,7 @@ class _PdfViewerState extends State final dropShadowPaint = widget.params.pageDropShadow?.toPaint()?..style = PaintingStyle.fill; cacheTargetRect ??= targetRect; - for (int i = 0; i < _document!.pages.length; i++) { + for (var i = 0; i < _document!.pages.length; i++) { final rect = _layout!.pageLayouts[i]; final intersection = rect.intersect(cacheTargetRect); if (intersection.isEmpty) { @@ -1282,7 +1282,7 @@ class _PdfViewerState extends State bool _hitTestForTextSelection(ui.Offset position) { if (_selPartMoving != _TextSelectionPart.free && enableSelectionHandles) return false; if (_document == null || _layout == null) return false; - for (int i = 0; i < _document!.pages.length; i++) { + for (var i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(position)) continue; final page = _document!.pages[i]; @@ -1306,7 +1306,7 @@ class _PdfViewerState extends State final pageLayout = []; var y = params.margin; - for (int i = 0; i < pages.length; i++) { + for (var i = 0; i < pages.length; i++) { final page = pages[i]; final rect = Rect.fromLTWH((width - page.width) / 2, y, page.width, page.height); pageLayout.add(rect); @@ -1421,7 +1421,7 @@ class _PdfViewerState extends State final height = (inPageRect.height * scale).toInt(); if (width < 1 || height < 1) return null; - int flags = 0; + var flags = 0; if (widget.params.limitRenderingCache) flags |= PdfPageRenderFlags.limitedImageCache; PdfImage? img; @@ -1758,13 +1758,13 @@ class _PdfViewerState extends State _imageCache.releasePartialImages(); zoom = zoom ?? _currentZoom; - final double tx = -documentOffset.dx * zoom; - final double ty = -documentOffset.dy * zoom; + final tx = -documentOffset.dx * zoom; + final ty = -documentOffset.dy * zoom; - final Matrix4 m = Matrix4.compose(vec.Vector3(tx, ty, 0), vec.Quaternion.identity(), vec.Vector3(zoom, zoom, zoom)); + final m = Matrix4.compose(vec.Vector3(tx, ty, 0), vec.Quaternion.identity(), vec.Vector3(zoom, zoom, zoom)); _adjustBoundaryMargins(_viewSize!, zoom); - final Matrix4 clamped = _calcMatrixForClampedToNearestBoundary(m, viewSize: _viewSize!); + final clamped = _calcMatrixForClampedToNearestBoundary(m, viewSize: _viewSize!); await _goTo(clamped, duration: duration); } @@ -1805,7 +1805,7 @@ class _PdfViewerState extends State final r = Matrix4.inverted(_txController.value); offset = r.transformOffset(offset); } - for (int i = 0; i < pages.length; i++) { + for (var i = 0; i < pages.length; i++) { final page = pages[i]; final pageRect = pageLayouts[i]; if (pageRect.contains(offset)) { @@ -2051,7 +2051,7 @@ class _PdfViewerState extends State /// [point] is in the document coordinates. _TextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { if (point == null) return null; - for (int pageIndex = 0; pageIndex < _document!.pages.length; pageIndex++) { + for (var pageIndex = 0; pageIndex < _document!.pages.length; pageIndex++) { final pageRect = _layout!.pageLayouts[pageIndex]; if (!pageRect.contains(point)) { continue; @@ -2060,9 +2060,9 @@ class _PdfViewerState extends State final text = _getCachedTextOrDelayLoadText(pageIndex + 1, onTextLoaded: () => _updateTextSelection()); if (text == null) continue; final pt = point.translate(-pageRect.left, -pageRect.top).toPdfPoint(page: page, scaledPageSize: pageRect.size); - double d2Min = double.infinity; + var d2Min = double.infinity; int? closestIndex; - for (int i = 0; i < text.charRects.length; i++) { + for (var i = 0; i < text.charRects.length; i++) { final charRect = text.charRects[i]; if (charRect.containsPoint(pt)) { return _TextSelectionPoint(text, i, point); @@ -2329,7 +2329,7 @@ class _PdfViewerState extends State final showContextMenuAutomatically = widget.params.textSelectionParams?.showContextMenuAutomatically ?? _selectionPointerDeviceKind == PointerDeviceKind.touch; - bool showContextMenu = false; + var showContextMenu = false; if (_contextMenuDocumentPosition != null) { showContextMenu = true; } else if (showContextMenuAutomatically && @@ -2821,7 +2821,7 @@ class _PdfViewerState extends State } final selections = [a.text.getRangeFromAB(a.index, a.text.charRects.length - 1)]; - for (int i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { + for (var i = first.text.pageNumber + 1; i < second.text.pageNumber; i++) { final text = await _loadTextAsync(i); if (text == null || text.fullText.isEmpty) continue; selections.add(text.getRangeFromAB(0, text.charRects.length - 1)); @@ -2851,14 +2851,14 @@ class _PdfViewerState extends State Future selectAllText() async { if (_document!.pages.isEmpty && _layout != null) return; PdfPageText? first; - for (int i = 1; i <= _document!.pages.length; i++) { + for (var i = 1; i <= _document!.pages.length; i++) { final text = await _loadTextAsync(i); if (text == null || text.fullText.isEmpty) continue; first = text; break; } PdfPageText? last; - for (int i = _document!.pages.length; i >= 1; i--) { + for (var i = _document!.pages.length; i >= 1; i--) { final text = await _loadTextAsync(i); if (text == null || text.fullText.isEmpty) continue; last = text; @@ -2890,7 +2890,7 @@ class _PdfViewerState extends State @override Future selectWord(Offset offset, {PointerDeviceKind? deviceKind}) async { - for (int i = 0; i < _document!.pages.length; i++) { + for (var i = 0; i < _document!.pages.length; i++) { final pageRect = _layout!.pageLayouts[i]; if (!pageRect.contains(offset)) { continue; @@ -3082,7 +3082,7 @@ class _PdfPageImageCache { }) { pageNumbers.sort((a, b) => dist(b).compareTo(dist(a))); int getBytesConsumed(ui.Image? image) => image == null ? 0 : (image.width * image.height * 4).toInt(); - int bytesConsumed = + var bytesConsumed = pageImages.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)) + pageImagesPartial.values.fold(0, (sum, e) => sum + getBytesConsumed(e.image)); for (final key in pageNumbers) { @@ -3564,10 +3564,10 @@ class PdfViewerController extends ValueListenable { final viewRect = visibleRect; final result = []; final pos = centerPosition; - for (int i = 0; i < layout.pageLayouts.length; i++) { + for (var i = 0; i < layout.pageLayouts.length; i++) { final page = layout.pageLayouts[i]; if (page.intersect(viewRect).isEmpty) continue; - final EdgeInsets boundaryMargin = params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity + final boundaryMargin = params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity ? EdgeInsets.zero : params.boundaryMargin!; final zoom = viewSize.width / (page.width + (params.margin * 2) + boundaryMargin.horizontal); diff --git a/packages/pdfrx_engine/analysis_options.yaml b/packages/pdfrx_engine/analysis_options.yaml index 940ed13a..ab72b708 100644 --- a/packages/pdfrx_engine/analysis_options.yaml +++ b/packages/pdfrx_engine/analysis_options.yaml @@ -38,7 +38,9 @@ linter: sort_constructors_first: true always_put_required_named_parameters_first: true invalid_runtime_check_with_js_interop_types: true - + type_annotate_public_apis: true + omit_local_variable_types: true + omit_obvious_local_variable_types: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/packages/pdfrx_engine/example/main.dart b/packages/pdfrx_engine/example/main.dart index 5017cfb8..b95e8c6b 100644 --- a/packages/pdfrx_engine/example/main.dart +++ b/packages/pdfrx_engine/example/main.dart @@ -31,7 +31,7 @@ Future main(List args) async { print('PDF opened successfully. Pages: ${document.pages.length}'); // Process each page - for (int i = 0; i < document.pages.length; i++) { + for (var i = 0; i < document.pages.length; i++) { final pageNumber = i + 1; print('Processing page $pageNumber/${document.pages.length}...'); diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 6b034636..235078fe 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -66,7 +66,7 @@ class PdfFileCache { int get cachedBytes { if (!isInitialized) return 0; var countCached = 0; - for (int i = 0; i < totalBlocks; i++) { + for (var i = 0; i < totalBlocks; i++) { if (isCached(i)) { countCached++; } @@ -126,7 +126,7 @@ class PdfFileCache { Future setCached(int startBlock, {int? lastBlock}) async { lastBlock ??= startBlock; - for (int i = startBlock; i <= lastBlock; i++) { + for (var i = startBlock; i <= lastBlock; i++) { _cacheState[i >> 3] |= 1 << (i & 7); } await _saveCacheState(); @@ -353,8 +353,8 @@ Future pdfDocumentFromUri( read: (buffer, position, size) async { final totalSize = size; final end = position + size; - int bufferPosition = 0; - for (int p = position; p < end;) { + var bufferPosition = 0; + for (var p = position; p < end;) { final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { @@ -452,7 +452,7 @@ Future<_DownloadResult> _downloadBlock( } final contentRange = response.headers['content-range']; - bool isFullDownload = false; + var isFullDownload = false; if (response.statusCode == 206 && contentRange != null) { final m = RegExp(r'bytes (\d+)-(\d+)/(\d+)').firstMatch(contentRange); fileSize = int.parse(m!.group(3)!); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 270381c9..5ac75355 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -38,7 +38,7 @@ Future _init() async { if (fontPaths.isNotEmpty) { // NOTE: m_pUserFontPaths must not be freed until FPDF_DestroyLibrary is called; on pdfrx, it's never freed. final fontPathArray = malloc>(sizeOf>() * (fontPaths.length + 1)); - for (int i = 0; i < fontPaths.length; i++) { + for (var i = 0; i < fontPaths.length; i++) { fontPathArray[i] = fontPaths[i] .toNativeUtf8() .cast(); // NOTE: the block allocated by toNativeUtf8 never released @@ -256,7 +256,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { size = data.length - position; if (size < 0) return -1; } - for (int i = 0; i < size; i++) { + for (var i = 0; i < size; i++) { buffer[i] = data[position + i]; } return size; @@ -375,7 +375,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { bool useProgressiveLoading = false, void Function()? disposeCallback, }) async { - for (int i = 0; ; i++) { + for (var i = 0; ; i++) { final String? password; if (firstAttemptByEmptyPassword && i == 0) { password = null; @@ -602,7 +602,7 @@ class _PdfDocumentPdfium extends PdfDocument { : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); final t = params.timeoutUs != null ? (Stopwatch()..start()) : null; final pages = <({double width, double height, int rotation, double bbLeft, double bbBottom})>[]; - for (int i = params.pagesCountLoadedSoFar; i < end; i++) { + for (var i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); try { final rect = arena.allocate(sizeOf()); @@ -633,7 +633,7 @@ class _PdfDocumentPdfium extends PdfDocument { ); final pages = [...pagesLoadedSoFar]; - for (int i = 0; i < results.pages.length; i++) { + for (var i = 0; i < results.pages.length; i++) { final pageData = results.pages[i]; pages.add( _PdfPagePdfium._( @@ -651,7 +651,7 @@ class _PdfDocumentPdfium extends PdfDocument { final pageCountLoadedTotal = pages.length; if (pageCountLoadedTotal > 0) { final last = pages.last; - for (int i = pages.length; i < results.totalPageCount; i++) { + for (var i = pages.length; i < results.totalPageCount; i++) { pages.add( _PdfPagePdfium._( document: this, @@ -890,7 +890,7 @@ class _PdfPagePdfium extends PdfPage { if ((flags & PdfPageRenderFlags.premultipliedAlpha) != 0) { final count = width * height; - for (int i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { final b = resultBuffer[i * rgbaSize]; final g = resultBuffer[i * rgbaSize + 1]; final r = resultBuffer[i * rgbaSize + 2]; @@ -927,7 +927,7 @@ class _PdfPagePdfium extends PdfPage { final charCount = pdfium.FPDFText_CountChars(textPage); final sb = StringBuffer(); final charRects = []; - for (int i = 0; i < charCount; i++) { + for (var i = 0; i < charCount; i++) { sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); pdfium.FPDFText_GetCharBox( textPage, @@ -957,7 +957,7 @@ class _PdfPagePdfium extends PdfPage { links.addAll(await _loadWebLinks()); } if (compact) { - for (int i = 0; i < links.length; i++) { + for (var i = 0; i < links.length; i++) { links[i] = links[i].compact(); } } @@ -1043,7 +1043,7 @@ class _PdfPagePdfium extends PdfPage { final count = pdfium.FPDFPage_GetAnnotCount(page); final rectf = arena.allocate(sizeOf()); final links = []; - for (int i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { final annot = pdfium.FPDFPage_GetAnnot(page, i); pdfium.FPDFAnnot_GetRect(annot, rectf); final r = rectf.ref; @@ -1110,7 +1110,7 @@ class _PdfPagePdfium extends PdfPage { final buffer = arena.allocate(size); pdfium.FPDFAction_GetURIPath(document, action, buffer.cast(), size); try { - final String newBuffer = buffer.toDartString(); + final newBuffer = buffer.toDartString(); return Uri.tryParse(newBuffer); } catch (e) { return null; diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 514bc158..c0e062d5 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -34,7 +34,7 @@ class BackgroundWorker { sendPort.send(receivePort.sendPort); late final StreamSubscription sub; final suspendingQueue = Queue<_ComputeParams>(); - int suspendingLevel = 0; + var suspendingLevel = 0; sub = receivePort.listen((message) { if (message is _SuspendRequest) { suspendingLevel++; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 04fe6d5d..5f59c2ce 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -567,12 +567,12 @@ abstract class PdfPage { case PdfTextDirection.ltr: case PdfTextDirection.rtl: case PdfTextDirection.unknown: - for (int i = wordStart; i < wordEnd; i++) { + for (var i = wordStart; i < wordEnd; i++) { final r = inputCharRects[i]; outputCharRects.add(PdfRect(r.left, bounds.top, r.right, bounds.bottom)); } case PdfTextDirection.vrtl: - for (int i = wordStart; i < wordEnd; i++) { + for (var i = wordStart; i < wordEnd; i++) { final r = inputCharRects[i]; outputCharRects.add(PdfRect(bounds.left, r.top, bounds.right, r.bottom)); } @@ -586,7 +586,7 @@ abstract class PdfPage { int addWords(int start, int end, PdfTextDirection dir, PdfRect bounds) { final firstIndex = fragmentsTmp.length; final matches = _reSpaces.allMatches(inputFullText.substring(start, end)); - int wordStart = start; + var wordStart = start; for (final match in matches) { final spaceStart = start + match.start; addWord(wordStart, spaceStart, dir, bounds); @@ -615,7 +615,7 @@ abstract class PdfPage { final last = end - 1; var curStart = start; var curVec = charVec(start, Vector2(1, 0)); - for (int next = start + 1; next < last;) { + for (var next = start + 1; next < last;) { final nextVec = charVec(next, curVec); if (curVec.angleTo(nextVec) > lineThreshold) { list.add((start: curStart, end: next + 1, dir: vector2direction(curVec))); @@ -638,7 +638,7 @@ abstract class PdfPage { final dir = getLineDirection(start, end); final segments = splitLine(start, end).toList(); if (segments.length >= 2) { - for (int i = 0; i < segments.length; i++) { + for (var i = 0; i < segments.length; i++) { final seg = segments[i]; final bounds = inputCharRects.boundingRect(start: seg.start, end: seg.end); addWords(seg.start, seg.end, seg.dir, bounds); @@ -655,7 +655,7 @@ abstract class PdfPage { } } - int lineStart = 0; + var lineStart = 0; for (final match in _reNewLine.allMatches(inputFullText)) { if (lineStart < match.start) { handleLine(lineStart, match.start, newLineEnd: match.end); @@ -671,7 +671,7 @@ abstract class PdfPage { } if (rotation.index != 0) { - for (int i = 0; i < outputCharRects.length; i++) { + for (var i = 0; i < outputCharRects.length; i++) { outputCharRects[i] = outputCharRects[i].rotateReverse(rotation.index, this); } } @@ -684,8 +684,8 @@ abstract class PdfPage { fragments: UnmodifiableListView(fragments), ); - int start = 0; - for (int i = 0; i < fragmentsTmp.length; i++) { + var start = 0; + for (var i = 0; i < fragmentsTmp.length; i++) { final length = fragmentsTmp[i].length; final direction = fragmentsTmp[i].direction; final end = start + length; @@ -713,7 +713,7 @@ abstract class PdfPage { } if (rotation.index != 0) { - for (int i = 0; i < input.charRects.length; i++) { + for (var i = 0; i < input.charRects.length; i++) { input.charRects[i] = input.charRects[i].rotate(rotation.index, this); } } @@ -723,9 +723,9 @@ abstract class PdfPage { // Process the whole text final lnMatches = _reNewLine.allMatches(input.fullText).toList(); - int lineStart = 0; - int prevEnd = 0; - for (int i = 0; i < lnMatches.length; i++) { + var lineStart = 0; + var prevEnd = 0; + for (var i = 0; i < lnMatches.length; i++) { lineStart = prevEnd; final match = lnMatches[i]; fullText.write(input.fullText.substring(lineStart, match.start)); @@ -1154,7 +1154,7 @@ class PdfPageTextRange { Iterable enumerateFragmentBoundingRects() sync* { final fStart = firstFragmentIndex; final fEnd = lastFragmentIndex; - for (int i = fStart; i <= fEnd; i++) { + for (var i = fStart; i <= fEnd; i++) { final f = pageText.fragments[i]; if (f.end <= start || end <= f.index) continue; yield PdfTextFragmentBoundingRect(f, max(start - f.index, 0), min(end - f.index, f.length)); @@ -1638,7 +1638,7 @@ bool _listEquals(List? a, List? b) { if (identical(a, b)) { return true; } - for (int index = 0; index < a.length; index += 1) { + for (var index = 0; index < a.length; index += 1) { if (a[index] != b[index]) { return false; } From 74c4ae0b22ecd66b01527e7270d548db3bb3a8f5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 25 Sep 2025 22:07:50 +0900 Subject: [PATCH 358/663] #481 Remove inproper usage of static GlobalKey on InteractiveViewer that make PdfViewer singleton --- .../lib/src/widgets/interactive_viewer.dart | 64 ++----------------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 12 ++-- 2 files changed, 11 insertions(+), 65 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 8b3c6c9f..92801f02 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -103,55 +103,6 @@ class InteractiveViewer extends StatefulWidget { ), builder = null; - /// Create InteractiveViewer with animation control capability - InteractiveViewer.withAnimationControl({ - required Widget child, - Key? key, - Clip clipBehavior = Clip.hardEdge, - PanAxis panAxis = PanAxis.free, - EdgeInsets boundaryMargin = EdgeInsets.zero, - bool constrained = true, - double maxScale = 8.0, - double minScale = 0.8, - double interactionEndFrictionCoefficient = _kDrag, - GestureScaleEndCallback? onInteractionEnd, - GestureScaleStartCallback? onInteractionStart, - GestureScaleUpdateCallback? onInteractionUpdate, - bool panEnabled = true, - bool scaleEnabled = true, - double scaleFactor = kDefaultMouseScrollToScaleFactor, - TransformationController? transformationController, - Alignment? alignment, - bool trackpadScrollCausesScale = false, - void Function(PointerScrollEvent event)? onWheelDelta, - ScrollPhysics? scrollPhysics, - ScrollPhysics? scrollPhysicsScale, - bool scrollPhysicsAutoAdjustBoundaries = true, - }) : this( - key: _globalKey, - child: child, - clipBehavior: clipBehavior, - panAxis: panAxis, - boundaryMargin: boundaryMargin, - constrained: constrained, - maxScale: maxScale, - minScale: minScale, - interactionEndFrictionCoefficient: interactionEndFrictionCoefficient, - onInteractionEnd: onInteractionEnd, - onInteractionStart: onInteractionStart, - onInteractionUpdate: onInteractionUpdate, - panEnabled: panEnabled, - scaleEnabled: scaleEnabled, - scaleFactor: scaleFactor, - transformationController: transformationController, - alignment: alignment, - trackpadScrollCausesScale: trackpadScrollCausesScale, - onWheelDelta: onWheelDelta, - scrollPhysics: scrollPhysics, - scrollPhysicsScale: scrollPhysicsScale, - scrollPhysicsAutoAdjustBoundaries: scrollPhysicsAutoAdjustBoundaries, - ); - /// Creates an InteractiveViewer for a child that is created on demand. /// /// Can be used to render a child that changes in response to the current @@ -447,13 +398,6 @@ class InteractiveViewer extends StatefulWidget { // This value was eyeballed to give a feel similar to Google Photos. static const double _kDrag = 0.0000135; - /// GlobalKey to access the InteractiveViewer state for animation control - static final GlobalKey<_InteractiveViewerState> _globalKey = GlobalKey<_InteractiveViewerState>(); - - /// Static methods for external animation control - static bool get hasActiveAnimations => _globalKey.currentState?.hasActiveAnimations ?? false; - static void stopAnimations() => _globalKey.currentState?._stopAllAnimations(); - /// ScrollPhysics to use for panning final ScrollPhysics? scrollPhysics; @@ -539,10 +483,10 @@ class InteractiveViewer extends StatefulWidget { } @override - State createState() => _InteractiveViewerState(); + State createState() => InteractiveViewerState(); } -class _InteractiveViewerState extends State with TickerProviderStateMixin { +class InteractiveViewerState extends State with TickerProviderStateMixin { // Preserve the originally provided boundaryMargin for recalculation overrides. late final EdgeInsets _originalBoundaryMargin = widget.boundaryMargin; late TransformationController _transformer = widget.transformationController ?? TransformationController(); @@ -1456,7 +1400,7 @@ class _InteractiveViewerState extends State with TickerProvid // ability to stop a in-progress pan fling is particularly important // when scroll physics is enabled as the duration and distance of the // pan can be considerable. - _stopAllAnimations(); + stopAllAnimations(); } } @@ -1465,7 +1409,7 @@ class _InteractiveViewerState extends State with TickerProvid _controller.isAnimating || _scaleController.isAnimating || _snapController.isAnimating; /// Stop all active animations without saving state - void _stopAllAnimations() { + void stopAllAnimations() { // Stop pan animations if (_controller.isAnimating) { _controller.stop(); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 4ef4bfab..bf53437c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -224,6 +224,7 @@ class _PdfViewerState extends State int? _pageNumber; bool _initialized = false; StreamSubscription? _documentSubscription; + final _interactiveViewerKey = GlobalKey(); final List _zoomStops = [1.0]; @@ -448,7 +449,8 @@ class _PdfViewerState extends State onPointerHover: (event) => _handlePointerEvent(event, event.localPosition, event.kind), child: Stack( children: [ - iv.InteractiveViewer.withAnimationControl( + iv.InteractiveViewer( + key: _interactiveViewerKey, transformationController: _txController, constrained: false, boundaryMargin: @@ -623,7 +625,7 @@ class _PdfViewerState extends State newPageRect.top + fracY * newPageRect.height, ); - // preseve the position after a layout change + // preserve the position after a layout change await _goToPosition(documentOffset: newOffset, zoom: zoomTo); } } else { @@ -640,7 +642,7 @@ class _PdfViewerState extends State final Matrix4 zoomPivoted = pivotScale * _txController.value; _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); } else { - // size changes (e.g. rotation) can still cause out-of-bounds matricies + // size changes (e.g. rotation) can still cause out-of-bounds matrices // so clamp here _clampToNearestBoundary(_txController.value, viewSize: viewSize); } @@ -663,8 +665,8 @@ class _PdfViewerState extends State if (_isInteractionGoingOn) return; // Stop any active animations and apply the clamped matrix - if (iv.InteractiveViewer.hasActiveAnimations) { - iv.InteractiveViewer.stopAnimations(); + if (_interactiveViewerKey.currentState?.hasActiveAnimations == true) { + _interactiveViewerKey.currentState?.stopAllAnimations(); } // Apply the clamped matrix From 8d5fbe048cf6173df33e9b33fd62c10ecf88d84f Mon Sep 17 00:00:00 2001 From: james Date: Thu, 25 Sep 2025 23:03:33 +0930 Subject: [PATCH 359/663] Fix alternativeFitScale and account for boundaryMargins in _normalizeMatrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 41d00438..ae1baa12 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -785,7 +785,11 @@ class _PdfViewerState extends State final pageNumber = _pageNumber ?? _gotoTargetPageNumber; if (pageNumber != null) { final rect = _layout!.pageLayouts[pageNumber - 1]; - _alternativeFitScale = min((_viewSize!.width) / (rect.width + bmh), (_viewSize!.height) / (rect.height + bmv)); + final m2 = params.margin * 2; + _alternativeFitScale = min( + (_viewSize!.width) / (rect.width + bmh + m2), + (_viewSize!.height) / (rect.height + bmv + m2), + ); if (_alternativeFitScale! <= 0) { _alternativeFitScale = null; } @@ -1366,8 +1370,15 @@ class _PdfViewerState extends State final newZoom = max(newValue.zoom, minScale); final hw = viewSize.width / 2 / newZoom; final hh = viewSize.height / 2 / newZoom; - final x = position.dx.range(hw, layout.documentSize.width - hw); - final y = position.dy.range(hh, layout.documentSize.height - hh); + + final boundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; + final left = boundaryMargin.left.isInfinite ? 0.0 : boundaryMargin.left; + final right = boundaryMargin.right.isInfinite ? 0.0 : boundaryMargin.right; + final top = boundaryMargin.top.isInfinite ? 0.0 : boundaryMargin.top; + final bottom = boundaryMargin.bottom.isInfinite ? 0.0 : boundaryMargin.bottom; + + final x = position.dx.range(hw - left, layout.documentSize.width + right - hw); + final y = position.dy.range(hh - top, layout.documentSize.height + bottom - hh); return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize); // see note in _calcMatrixFor } From 8c4a02c2dd5914331bbbc22fd6927beb5a270cf9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 26 Sep 2025 03:28:29 +0900 Subject: [PATCH 360/663] WIP: TransformationMatrix should be restricted for non-touch interactions #478 --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 99 +++++++------------ .../src/widgets/pdf_viewer_scroll_thumb.dart | 4 +- 2 files changed, 39 insertions(+), 64 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c7fc8144..64630d3e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -530,15 +530,14 @@ class _PdfViewerState extends State ); } - Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { + Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { final boundaryMargin = _adjustedBoundaryMargins ?? widget.params.boundaryMargin; - if (boundaryMargin == null || boundaryMargin.horizontal.isInfinite) { - return candidate; + return Offset.zero; } final layout = _layout!; - final visible = candidate.calcVisibleRect(viewSize); + final visible = m.calcVisibleRect(viewSize); var dxDoc = 0.0; var dyDoc = 0.0; @@ -553,7 +552,15 @@ class _PdfViewerState extends State } else if (visible.bottom > layout.documentSize.height) { dyDoc = layout.documentSize.height - visible.bottom + boundaryMargin.bottom; } - return candidate.clone()..translateByDouble(-dxDoc, -dyDoc, 0, 1); + return Offset(dxDoc, dyDoc); + } + + Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { + final overScroll = _calcOverscroll(candidate, viewSize: viewSize); + if (overScroll == Offset.zero) { + return candidate; + } + return candidate.clone()..translateByDouble(-overScroll.dx, -overScroll.dy, 0, 1); } void _updateLayout(Size viewSize) { @@ -655,24 +662,26 @@ class _PdfViewerState extends State } } - /// Shift any overshoot back to the nearest content boundary - void _clampToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { - _stopAnimationsAndClampBoundaries(candidate, viewSize: viewSize); - } - /// Stop InteractiveViewer animations and apply boundary clamping - void _stopAnimationsAndClampBoundaries(Matrix4 candidate, {required Size viewSize}) { + void _clampToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { if (_isInteractionGoingOn) return; - // Stop any active animations and apply the clamped matrix - if (_interactiveViewerKey.currentState?.hasActiveAnimations == true) { - _interactiveViewerKey.currentState?.stopAllAnimations(); - } + _stopInteractiveViewerAnimation(); // Apply the clamped matrix _txController.value = _calcMatrixForClampedToNearestBoundary(candidate, viewSize: viewSize); } + /// Get the state of the internal [iv.InteractiveViewer]. + iv.InteractiveViewerState? get _interactiveViewerState => _interactiveViewerKey.currentState; + + /// Stop any active animations + void _stopInteractiveViewerAnimation() { + if (_interactiveViewerState?.hasActiveAnimations == true) { + _interactiveViewerState?.stopAllAnimations(); + } + } + int _calcInitialPageNumber() { return widget.params.calculateInitialPageNumber?.call(_document!, _controller!) ?? widget.initialPageNumber; } @@ -826,7 +835,7 @@ class _PdfViewerState extends State void _goToManipulated(void Function(Matrix4 m) manipulate) { final m = _txController.value.clone(); manipulate(m); - _txController.value = m; + _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); } Rect get _visibleRect => _txController.value.calcVisibleRect(_viewSize!); @@ -1455,58 +1464,25 @@ class _PdfViewerState extends State void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); - final m = _txController.value.clone(); final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; + final m = _txController.value.clone(); if (widget.params.scrollHorizontallyByMouseWheel) { m.translateByDouble(dy, dx, 0, 1); } else { m.translateByDouble(dx, dy, 0, 1); } - _setMatrixWithBoundaryCheck(m); + _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); _stopInteraction(); } /// Restrict matrix to the safe range. - Matrix4 _makeMatrixInSafeRange(Matrix4 newValue) { + Matrix4 _makeMatrixInSafeRange(Matrix4 newValue, {bool forceClamp = false}) { + if (!forceClamp && (_layout == null || _viewSize == null || widget.params.scrollPhysics != null)) return newValue; if (widget.params.normalizeMatrix != null) { return widget.params.normalizeMatrix!(newValue, _viewSize!, _layout!, _controller); } - return _normalizeMatrix(newValue); - } - - /// Set matrix with boundary checking for direct manipulations (mouse wheel, scroll thumb, etc.) - /// This preserves InteractiveViewer's ScrollPhysics behavior while enforcing boundaries - /// for operations that bypass the gesture system. - void _setMatrixWithBoundaryCheck(Matrix4 matrix) { - if (widget.params.scrollPhysics != null && _viewSize != null) { - // When scrollPhysics is enabled, use boundary clamping for direct manipulations - _txController.value = _calcMatrixForClampedToNearestBoundary(matrix, viewSize: _viewSize!); - } else { - // When scrollPhysics is disabled, use existing normalization - _txController.value = matrix; - } - } - - Matrix4 _normalizeMatrix(Matrix4 newValue) { - final layout = _layout; - final viewSize = _viewSize; - if (layout == null || viewSize == null || widget.params.scrollPhysics != null) return newValue; - final position = newValue.calcPosition(viewSize); - final newZoom = max(newValue.zoom, minScale); - final hw = viewSize.width / 2 / newZoom; - final hh = viewSize.height / 2 / newZoom; - - final boundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; - final left = boundaryMargin.left.isInfinite ? 0.0 : boundaryMargin.left; - final right = boundaryMargin.right.isInfinite ? 0.0 : boundaryMargin.right; - final top = boundaryMargin.top.isInfinite ? 0.0 : boundaryMargin.top; - final bottom = boundaryMargin.bottom.isInfinite ? 0.0 : boundaryMargin.bottom; - - final x = position.dx.range(hw - left, layout.documentSize.width + right - hw); - final y = position.dy.range(hh - top, layout.documentSize.height + bottom - hh); - - return _calcMatrixFor(Offset(x, y), zoom: newZoom, viewSize: viewSize); // see note in _calcMatrixFor + return _calcMatrixForClampedToNearestBoundary(newValue, viewSize: _viewSize!); } /// Calculate matrix to center the specified position. @@ -1680,12 +1656,13 @@ class _PdfViewerState extends State try { if (destination == null) return; // do nothing + _stopInteractiveViewerAnimation(); _animationResettingGuard++; _animController.reset(); _animationResettingGuard--; _animGoTo = Matrix4Tween( begin: _txController.value, - end: _makeMatrixInSafeRange(destination), + end: _makeMatrixInSafeRange(destination, forceClamp: true), ).animate(_animController); _animGoTo!.addListener(update); await _animController.animateTo(1.0, duration: duration, curve: Curves.easeInOut); @@ -3455,7 +3432,9 @@ class PdfViewerController extends ValueListenable { @override Matrix4 get value => _state._txController.value; - set value(Matrix4 newValue) => _state._txController.value = makeMatrixInSafeRange(newValue); + set value(Matrix4 newValue) { + _state._txController.value = makeMatrixInSafeRange(newValue, forceClamp: true); + } @override void addListener(ui.VoidCallback listener) => _listeners.add(listener); @@ -3464,12 +3443,8 @@ class PdfViewerController extends ValueListenable { void removeListener(ui.VoidCallback listener) => _listeners.remove(listener); /// Restrict matrix to the safe range. - Matrix4 makeMatrixInSafeRange(Matrix4 newValue) => _state._makeMatrixInSafeRange(newValue); - - /// Set matrix with boundary checking for direct manipulations (mouse wheel, scroll thumb, etc.) - /// This preserves InteractiveViewer's ScrollPhysics behavior while enforcing boundaries - /// for operations that bypass the gesture system. - void setMatrixWithBoundaryCheck(Matrix4 matrix) => _state._setMatrixWithBoundaryCheck(matrix); + Matrix4 makeMatrixInSafeRange(Matrix4 newValue, {bool forceClamp = false}) => + _state._makeMatrixInSafeRange(newValue, forceClamp: forceClamp); double getNextZoom({bool loop = true}) => _state._findNextZoomStop(currentZoom, zoomUp: true, loop: loop); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index 56103e89..e41a0b1d 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -88,7 +88,7 @@ class _PdfViewerScrollThumbState extends State { final y = (_panStartOffset + details.localPosition.dy) / vh; final m = widget.controller.value.clone(); m.y = -y * (all.height - view.height); - widget.controller.setMatrixWithBoundaryCheck(m); + widget.controller.value = m; }, ), ); @@ -133,7 +133,7 @@ class _PdfViewerScrollThumbState extends State { final x = (_panStartOffset + details.localPosition.dx) / vw; final m = widget.controller.value.clone(); m.x = -x * (all.width - view.width); - widget.controller.setMatrixWithBoundaryCheck(m); + widget.controller.value = m; }, ), ); From 49b604a4af5907a15d258e0fe45b72f14822d45f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 26 Sep 2025 03:38:54 +0900 Subject: [PATCH 361/663] Remove unused double.range function. --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 64630d3e..6298b574 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -3781,11 +3781,6 @@ extension PdfMatrix4Ext on Matrix4 { } } -extension _RangeDouble on T { - /// Identical to [num.clamp] but it does nothing if [a] is larger or equal to [b]. - T range(T a, T b) => a < b ? clamp(a, b) as T : (a + b) / 2 as T; -} - extension RectExt on Rect { Rect operator *(double operand) => Rect.fromLTRB(left * operand, top * operand, right * operand, bottom * operand); From 5254f9880c3ce9c3ca6f3cca4d02957b0b983983 Mon Sep 17 00:00:00 2001 From: james Date: Fri, 26 Sep 2025 14:14:18 +0930 Subject: [PATCH 362/663] fixes related to boundaryMargins --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6298b574..1393bc74 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -268,8 +268,8 @@ class _PdfViewerState extends State PointerDeviceKind? _pointerDeviceKind; // boundary margins adjusted to center content that's smaller than - // the viewport - used by InteractiveViewer's scrollPhysics - EdgeInsets? _adjustedBoundaryMargins; + // the viewport + EdgeInsets _adjustedBoundaryMargins = EdgeInsets.zero; @override void initState() { @@ -453,11 +453,9 @@ class _PdfViewerState extends State key: _interactiveViewerKey, transformationController: _txController, constrained: false, - boundaryMargin: - _adjustedBoundaryMargins ?? - (widget.params.scrollPhysics == null - ? const EdgeInsets.all(double.infinity) - : EdgeInsets.zero), + boundaryMargin: (widget.params.scrollPhysics == null + ? const EdgeInsets.all(double.infinity) + : _adjustedBoundaryMargins), maxScale: widget.params.maxScale, minScale: minScale, panAxis: widget.params.panAxis, @@ -531,8 +529,8 @@ class _PdfViewerState extends State } Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { - final boundaryMargin = _adjustedBoundaryMargins ?? widget.params.boundaryMargin; - if (boundaryMargin == null || boundaryMargin.horizontal.isInfinite) { + final boundaryMargin = _adjustedBoundaryMargins; + if (_edgeInsetContainsInfinite(boundaryMargin)) { return Offset.zero; } @@ -541,16 +539,23 @@ class _PdfViewerState extends State var dxDoc = 0.0; var dyDoc = 0.0; - if (visible.left < 0) { - dxDoc = -visible.left - boundaryMargin.left; - } else if (visible.right > layout.documentSize.width) { - dxDoc = layout.documentSize.width - visible.right + boundaryMargin.right; + final leftBoundary = -boundaryMargin.left; // negative margin reduces allowed leftward scroll + final rightBoundary = + layout.documentSize.width + boundaryMargin.right; // negative margin reduces allowed rightward scroll + final topBoundary = -boundaryMargin.top; // negative margin reduces allowed upward scroll + final bottomBoundary = + layout.documentSize.height + boundaryMargin.bottom; // negative margin reduces allowed downward scroll + + if (visible.left < leftBoundary) { + dxDoc = leftBoundary - visible.left; + } else if (visible.right > rightBoundary) { + dxDoc = rightBoundary - visible.right; } - if (visible.top < 0) { - dyDoc = -visible.top - boundaryMargin.top; - } else if (visible.bottom > layout.documentSize.height) { - dyDoc = layout.documentSize.height - visible.bottom + boundaryMargin.bottom; + if (visible.top < topBoundary) { + dyDoc = topBoundary - visible.top; + } else if (visible.bottom > bottomBoundary) { + dyDoc = bottomBoundary - visible.bottom; } return Offset(dxDoc, dyDoc); } @@ -647,6 +652,7 @@ class _PdfViewerState extends State ..translateByVector3(-pivot / zoomChange); final Matrix4 zoomPivoted = pivotScale * _txController.value; + _adjustBoundaryMargins(viewSize, zoomTo); _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); } else { // size changes (e.g. rotation) can still cause out-of-bounds matrices @@ -1006,17 +1012,22 @@ class _PdfViewerState extends State static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; + bool _edgeInsetContainsInfinite(EdgeInsets? e) { + if (e == null) return false; + return e.left.isInfinite || e.right.isInfinite || e.top.isInfinite || e.bottom.isInfinite; + } + // Auto-adjust boundaries when content is smaller than the view, centering // the content and ensuring InteractiveViewer's scrollPhysics works when specified void _adjustBoundaryMargins(Size viewSize, double zoom) { - if (widget.params.scrollPhysics == null) return; + final boundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; - final boundaryMargin = widget.params.boundaryMargin == null || widget.params.boundaryMargin!.horizontal.isInfinite - ? EdgeInsets.zero - : widget.params.boundaryMargin!; + if (_edgeInsetContainsInfinite(boundaryMargin)) { + _adjustedBoundaryMargins = boundaryMargin; + return; + } final currentDocumentSize = boundaryMargin.inflateSize(_layout!.documentSize); - final effectiveWidth = currentDocumentSize.width * zoom; final effectiveHeight = currentDocumentSize.height * zoom; final extraWidth = effectiveWidth - viewSize.width; @@ -1556,13 +1567,15 @@ class _PdfViewerState extends State } } - Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) => _calcMatrixForArea( - rect: (widget.params.boundaryMargin ?? EdgeInsets.zero).inflateRect( - _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin), - ), - anchor: anchor, - zoomMax: _currentZoom, - ); + Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) { + final boundaryMargin = _adjustedBoundaryMargins; + final pageRect = _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin); + + // If boundaryMargin is infinite, don't inflate the rect + final targetRect = (_edgeInsetContainsInfinite(boundaryMargin)) ? pageRect : boundaryMargin.inflateRect(pageRect); + + return _calcMatrixForArea(rect: targetRect, anchor: anchor, zoomMax: _currentZoom); + } Rect _calcRectForRectInsidePage({required int pageNumber, required PdfRect rect}) { final page = _document!.pages[pageNumber - 1]; @@ -3555,9 +3568,7 @@ class PdfViewerController extends ValueListenable { for (var i = 0; i < layout.pageLayouts.length; i++) { final page = layout.pageLayouts[i]; if (page.intersect(viewRect).isEmpty) continue; - final boundaryMargin = params.boundaryMargin == null || params.boundaryMargin!.right == double.infinity - ? EdgeInsets.zero - : params.boundaryMargin!; + final boundaryMargin = _state._adjustedBoundaryMargins; final zoom = viewSize.width / (page.width + (params.margin * 2) + boundaryMargin.horizontal); // NOTE: keep the y-position but center the x-position From 98eb818725c046ebb033391dc850efde49ec93b4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 26 Sep 2025 16:39:58 +0900 Subject: [PATCH 363/663] WIP --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 5f59c2ce..e73120d6 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1471,13 +1471,15 @@ class PdfLink { /// Link URL. final Uri? url; - /// Link destination. - /// /// Link destination (link to page). final PdfDest? dest; - /// Link location. + /// Link location(s) inside the associated PDF page. + /// + /// Sometimes a link can span multiple rectangles, e.g., a link across multiple lines. final List rects; + + /// Annotation content if available. final String? annotationContent; /// Compact the link. From 26161a00225898ae5efd3269c0f61147325a6162 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 27 Sep 2025 00:48:59 +0900 Subject: [PATCH 364/663] Update document for PdfViewerParams.scrollPhysics --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 59d2f9a8..1f7113cf 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -113,6 +113,8 @@ class PdfViewerParams { /// /// The function is called when the matrix is changed and normally used to restrict the matrix to certain range. /// + /// If [scrollPhysics] is non-null, this function is ignored. + /// /// The following fragment is an example to restrict the matrix to the document size, which is almost identical to /// the default behavior: /// @@ -536,6 +538,12 @@ class PdfViewerParams { final bool forceReload; /// Scroll physics for the viewer. + /// + /// If null, it works like [ClampingScrollPhysics] on all platforms. + /// If you want bouncing effect, set this to [BouncingScrollPhysics]. + /// + /// If the value is set non-null, it disables [normalizeMatrix]. + /// If you set [boundaryMargin] to `EdgeInsets.all(double.infinity)`, it effectively disables [scrollPhysics]. final ScrollPhysics? scrollPhysics; /// Determine whether the viewer needs to be reloaded or not. From 8506268b31fe0cc5ac16d4da6f45e07e99a1e027 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 27 Sep 2025 12:27:34 +0930 Subject: [PATCH 365/663] add scrollPhysicsScale and FixedOverScrollPhysics --- packages/pdfrx/example/viewer/lib/main.dart | 2 +- packages/pdfrx/lib/pdfrx.dart | 1 + .../lib/src/utils/edge_insets_extensions.dart | 13 ++ .../src/utils/fixed_overscroll_physics.dart | 130 ++++++++++++++++++ .../pdfrx/lib/src/widgets/pdf_viewer.dart | 13 +- .../lib/src/widgets/pdf_viewer_params.dart | 30 +++- 6 files changed, 177 insertions(+), 12 deletions(-) create mode 100644 packages/pdfrx/lib/src/utils/edge_insets_extensions.dart create mode 100644 packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index f071a01a..3158db77 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -311,7 +311,7 @@ class _MainPageState extends State with WidgetsBindingObserver { keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, maxScale: 8, - //scrollPhysics: const BouncingScrollPhysics(), + //scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index d42339c6..af1d294e 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -8,3 +8,4 @@ export 'src/widgets/pdf_viewer.dart'; export 'src/widgets/pdf_viewer_params.dart'; export 'src/widgets/pdf_viewer_scroll_thumb.dart'; export 'src/widgets/pdf_widgets.dart'; +export 'src/utils/fixed_overscroll_physics.dart'; diff --git a/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart b/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart new file mode 100644 index 00000000..a003c517 --- /dev/null +++ b/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +/// Extensions for EdgeInsets to provide additional utility methods. +extension EdgeInsetsExtensions on EdgeInsets { + /// Returns true if any of the EdgeInsets values (left, top, right, bottom) are infinite. + bool get containsInfinite => left.isInfinite || right.isInfinite || top.isInfinite || bottom.isInfinite; + + /// Inflates a given Rect by the EdgeInsets values if all values are finite, otherwise retruns the rect + Rect inflateRectIfFinite(Rect rect) { + if (containsInfinite) return rect; + return inflateRect(rect); + } +} diff --git a/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart b/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart new file mode 100644 index 00000000..faf5cd3d --- /dev/null +++ b/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart @@ -0,0 +1,130 @@ +import 'package:flutter/widgets.dart'; + +/// A ScrollPhysics that lets you overscroll by up to [maxOverscroll] +/// and then springs back to the content bounds. +class FixedOverscrollPhysics extends ClampingScrollPhysics { + const FixedOverscrollPhysics({super.parent, this.maxOverscroll = 200.0}); + + /// How far (in logical pixels) the user can overscroll. + final double maxOverscroll; + + @override + FixedOverscrollPhysics applyTo(ScrollPhysics? ancestor) { + return FixedOverscrollPhysics(parent: buildParent(ancestor), maxOverscroll: maxOverscroll); + } + + @override + double applyBoundaryConditions(ScrollMetrics position, double value) { + // Overscroll allowed to a specific maximum [maxOverscroll] to replicate + // the behavior of popular PDF readers on Android. + + // If we're within the allowed overscroll zone, allow it (return 0). + if (value < position.minScrollExtent && value >= position.minScrollExtent - maxOverscroll) { + return 0.0; + } + if (value > position.maxScrollExtent && value <= position.maxScrollExtent + maxOverscroll) { + return 0.0; + } + + if (value <= position.minScrollExtent - maxOverscroll) { + return value - (position.minScrollExtent - maxOverscroll); + } else if (value >= position.maxScrollExtent + maxOverscroll) { + return value - (position.maxScrollExtent + maxOverscroll); + } + return 0.0; + } + + @override + Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { + final tolerance = toleranceFor(position); + + if (velocity.abs() >= tolerance.velocity || position.outOfRange) { + return _HybridScrollSimulation( + position: position, + velocity: velocity, + tolerance: tolerance, + maxOverscroll: maxOverscroll, + ); + } + return null; + } +} + +/// A simulation that behaves like ClampingScrollPhysics when within bounds +/// and switches to a custom BouncingScrollSimulation when exceeding bounds. +class _HybridScrollSimulation extends Simulation { + _HybridScrollSimulation({ + required ScrollMetrics position, + required double velocity, + required this.tolerance, + required this.maxOverscroll, + }) : _minExtent = position.minScrollExtent, + _maxExtent = position.maxScrollExtent { + final currentPosition = position.pixels; + final isOutOfBounds = currentPosition < _minExtent || currentPosition > _maxExtent; + final isInOverscrollZone = + (currentPosition >= _minExtent - maxOverscroll && currentPosition < _minExtent) || + (currentPosition > _maxExtent && currentPosition <= _maxExtent + maxOverscroll); + + // If already out of bounds or in overscroll zone, start with bouncing simulation + if (isOutOfBounds || isInOverscrollZone) { + _createBouncingSimulation(currentPosition, velocity); + } else { + // Within content bounds - use clamping behavior + _currentSimulation = ClampingScrollSimulation(position: currentPosition, velocity: velocity); + } + } + + final double _minExtent; + final double _maxExtent; + final double maxOverscroll; + final Tolerance tolerance; + + late Simulation _currentSimulation; + bool _hasSwitchedToBouncing = false; + + @override + double x(double time) { + final position = _currentSimulation.x(time); + + // Check if we need to switch to bouncing simulation + if (!_hasSwitchedToBouncing) { + final wouldExceedBounds = position < _minExtent || position > _maxExtent; + + if (wouldExceedBounds) { + _switchToBouncingSimulation(position, dx(time)); + } + } + + return _currentSimulation.x(time); + } + + @override + double dx(double time) => _currentSimulation.dx(time); + + @override + bool isDone(double time) => _currentSimulation.isDone(time); + + void _switchToBouncingSimulation(double position, double velocity) { + _hasSwitchedToBouncing = true; + _createBouncingSimulation(position, velocity); + } + + void _createBouncingSimulation(double position, double velocity) { + // A spring with considerably less mass and higher stiffness than the default + // iOS BouncingScrollSimulation which results in hardly any overscroll on + // fling, and a quick snap back to the content bounds when dragged, to replicate + // similar behavior found in several popular PDF readers on Android. + + final spring = SpringDescription.withDampingRatio(mass: 0.3, stiffness: 1500.0, ratio: 3); + + _currentSimulation = BouncingScrollSimulation( + spring: spring, + position: position, + velocity: velocity, + leadingExtent: _minExtent, + trailingExtent: _maxExtent, + tolerance: tolerance, + ); + } +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 1393bc74..fa0ffb84 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -13,6 +13,7 @@ import 'package:synchronized/extension.dart'; import 'package:vector_math/vector_math_64.dart' as vec; import '../../pdfrx.dart'; +import '../utils/edge_insets_extensions.dart'; import '../utils/platform.dart'; import 'interactive_viewer.dart' as iv; import 'internals/pdf_error_widget.dart'; @@ -467,6 +468,7 @@ class _PdfViewerState extends State interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, scrollPhysics: widget.params.scrollPhysics, + scrollPhysicsScale: widget.params.scrollPhysicsScale, scrollPhysicsAutoAdjustBoundaries: false, // PDF pages child: GestureDetector( @@ -530,7 +532,7 @@ class _PdfViewerState extends State Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { final boundaryMargin = _adjustedBoundaryMargins; - if (_edgeInsetContainsInfinite(boundaryMargin)) { + if (boundaryMargin?.containsInfinite == true) { return Offset.zero; } @@ -1012,17 +1014,12 @@ class _PdfViewerState extends State static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; - bool _edgeInsetContainsInfinite(EdgeInsets? e) { - if (e == null) return false; - return e.left.isInfinite || e.right.isInfinite || e.top.isInfinite || e.bottom.isInfinite; - } - // Auto-adjust boundaries when content is smaller than the view, centering // the content and ensuring InteractiveViewer's scrollPhysics works when specified void _adjustBoundaryMargins(Size viewSize, double zoom) { final boundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; - if (_edgeInsetContainsInfinite(boundaryMargin)) { + if (boundaryMargin.containsInfinite) { _adjustedBoundaryMargins = boundaryMargin; return; } @@ -1572,7 +1569,7 @@ class _PdfViewerState extends State final pageRect = _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin); // If boundaryMargin is infinite, don't inflate the rect - final targetRect = (_edgeInsetContainsInfinite(boundaryMargin)) ? pageRect : boundaryMargin.inflateRect(pageRect); + final targetRect = boundaryMargin.inflateRectIfFinite(pageRect); return _calcMatrixForArea(rect: targetRect, anchor: anchor, zoomMax: _currentZoom); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 1f7113cf..578765d1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -71,6 +72,7 @@ class PdfViewerParams { this.behaviorControlParams = const PdfViewerBehaviorControlParams(), this.forceReload = false, this.scrollPhysics, + this.scrollPhysicsScale, }); /// Margin around the page. @@ -539,13 +541,35 @@ class PdfViewerParams { /// Scroll physics for the viewer. /// - /// If null, it works like [ClampingScrollPhysics] on all platforms. - /// If you want bouncing effect, set this to [BouncingScrollPhysics]. + /// If null, default InteractiveViewer physics is used on all platforms. This physics clamps to boundaries, + /// does not allow zooming beyond the min/max scale, and flings on panning come to rest quickly relative to + /// Scrollables in Flutter (such as SingleChildScrollView). + /// + /// A convenience function [getScrollPhysics] is provided to get platform-specific default scroll physics. + /// If you want no overscroll, but still want the physics for panning to be similar to other Scrollables, + /// you can use + /// [ClampingScrollPhysics()]. /// /// If the value is set non-null, it disables [normalizeMatrix]. - /// If you set [boundaryMargin] to `EdgeInsets.all(double.infinity)`, it effectively disables [scrollPhysics]. + /// + /// If you set [boundaryMargin] to `EdgeInsets.all(double.infinity)`, this will enable scrolling + /// beyond the boundaries regardless of which [ScrollPhysics] is used. final ScrollPhysics? scrollPhysics; + /// Scroll physics for scaling within the viewer. If null, it uses the same value as [scrollPhysics]. + final ScrollPhysics? scrollPhysicsScale; + + /// A convenience function to get platform-specific default scroll physics. + /// On iOS/MacOS this is [BouncingScrollPhysics()], and on Android this is [FixedOverscrollPhysics()], a + /// custom ScrollPhysics that allows fixed overscroll on pan/zoom and snapback. + static ScrollPhysics getScrollPhysics(BuildContext context) { + if (Platform.isAndroid) { + return const FixedOverscrollPhysics(); + } else { + return ScrollConfiguration.of(context).getScrollPhysics(context); + } + } + /// Determine whether the viewer needs to be reloaded or not. /// bool doChangesRequireReload(PdfViewerParams? other) { From 28f8d10f388369505c59f85754db9f9015c5cc52 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 27 Sep 2025 12:52:13 +0930 Subject: [PATCH 366/663] Fix analysis issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../pdfrx/lib/src/utils/fixed_overscroll_physics.dart | 9 ++++++--- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 ++-- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart b/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart index faf5cd3d..97f9116a 100644 --- a/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart +++ b/packages/pdfrx/lib/src/utils/fixed_overscroll_physics.dart @@ -56,10 +56,11 @@ class _HybridScrollSimulation extends Simulation { _HybridScrollSimulation({ required ScrollMetrics position, required double velocity, - required this.tolerance, + required Tolerance tolerance, required this.maxOverscroll, }) : _minExtent = position.minScrollExtent, - _maxExtent = position.maxScrollExtent { + _maxExtent = position.maxScrollExtent, + _tolerance = tolerance { final currentPosition = position.pixels; final isOutOfBounds = currentPosition < _minExtent || currentPosition > _maxExtent; final isInOverscrollZone = @@ -78,7 +79,9 @@ class _HybridScrollSimulation extends Simulation { final double _minExtent; final double _maxExtent; final double maxOverscroll; - final Tolerance tolerance; + @override + Tolerance get tolerance => _tolerance; + final Tolerance _tolerance; late Simulation _currentSimulation; bool _hasSwitchedToBouncing = false; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index fa0ffb84..f6a6025f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -532,7 +532,7 @@ class _PdfViewerState extends State Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { final boundaryMargin = _adjustedBoundaryMargins; - if (boundaryMargin?.containsInfinite == true) { + if (boundaryMargin.containsInfinite) { return Offset.zero; } @@ -3276,7 +3276,7 @@ class PdfTextSelectionAnchor { } @override - operator ==(Object other) { + bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! PdfTextSelectionAnchor) return false; return rect == other.rect && diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 578765d1..8431d6aa 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1407,7 +1407,7 @@ class PdfViewerKeyHandlerParams { final FocusNode? parentNode; @override - operator ==(covariant PdfViewerKeyHandlerParams other) { + bool operator ==(covariant PdfViewerKeyHandlerParams other) { if (identical(this, other)) return true; return other.autofocus == autofocus && From 3bb6ef9252052b749dda9f8f2caf353b1b9f5517 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 27 Sep 2025 13:51:09 +0930 Subject: [PATCH 367/663] update PdfViewerScrollThumb to consider boundaryMargins --- .../src/widgets/pdf_viewer_scroll_thumb.dart | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index e41a0b1d..274d28dd 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -53,8 +53,18 @@ class _PdfViewerScrollThumbState extends State { final thumbSize = widget.thumbSize ?? const Size(25, 40); final view = widget.controller.visibleRect; final all = widget.controller.documentSize; - if (all.height <= view.height) return const SizedBox(); - final y = -widget.controller.value.y / (all.height - view.height); + final boundaryMargin = widget.controller.params.boundaryMargin; + + final effectiveDocHeight = boundaryMargin == null || boundaryMargin.vertical.isInfinite + ? all.height + : all.height + boundaryMargin.vertical; + + if (effectiveDocHeight <= view.height) return const SizedBox(); + + final scrollRange = effectiveDocHeight - view.height; + final minScrollY = boundaryMargin == null || boundaryMargin.vertical.isInfinite ? 0.0 : -boundaryMargin.top; + + final y = (-widget.controller.value.y - minScrollY) / scrollRange; final vh = view.height * widget.controller.currentZoom - thumbSize.height; final top = y * vh; return Positioned( @@ -87,7 +97,7 @@ class _PdfViewerScrollThumbState extends State { onPanUpdate: (details) { final y = (_panStartOffset + details.localPosition.dy) / vh; final m = widget.controller.value.clone(); - m.y = -y * (all.height - view.height); + m.y = -(y * scrollRange + minScrollY); widget.controller.value = m; }, ), @@ -98,8 +108,18 @@ class _PdfViewerScrollThumbState extends State { final thumbSize = widget.thumbSize ?? const Size(40, 25); final view = widget.controller.visibleRect; final all = widget.controller.documentSize; - if (all.width <= view.width) return const SizedBox(); - final x = -widget.controller.value.x / (all.width - view.width); + final boundaryMargin = widget.controller.params.boundaryMargin; + + final effectiveDocWidth = boundaryMargin == null || boundaryMargin.horizontal.isInfinite + ? all.width + : all.width + boundaryMargin.horizontal; + + if (effectiveDocWidth <= view.width) return const SizedBox(); + + final scrollRange = effectiveDocWidth - view.width; + final minScrollX = boundaryMargin == null || boundaryMargin.horizontal.isInfinite ? 0.0 : -boundaryMargin.left; + + final x = (-widget.controller.value.x - minScrollX) / scrollRange; final vw = view.width * widget.controller.currentZoom - thumbSize.width; final left = x * vw; return Positioned( @@ -132,7 +152,7 @@ class _PdfViewerScrollThumbState extends State { onPanUpdate: (details) { final x = (_panStartOffset + details.localPosition.dx) / vw; final m = widget.controller.value.clone(); - m.x = -x * (all.width - view.width); + m.x = -(x * scrollRange + minScrollX); widget.controller.value = m; }, ), From 4ef57328107df836857012fa6997ab995d31d6f7 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 27 Sep 2025 14:02:05 +0930 Subject: [PATCH 368/663] doc fixes --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 8431d6aa..140b18e3 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -548,7 +548,7 @@ class PdfViewerParams { /// A convenience function [getScrollPhysics] is provided to get platform-specific default scroll physics. /// If you want no overscroll, but still want the physics for panning to be similar to other Scrollables, /// you can use - /// [ClampingScrollPhysics()]. + /// `ClampingScrollPhysics()`. /// /// If the value is set non-null, it disables [normalizeMatrix]. /// @@ -560,7 +560,7 @@ class PdfViewerParams { final ScrollPhysics? scrollPhysicsScale; /// A convenience function to get platform-specific default scroll physics. - /// On iOS/MacOS this is [BouncingScrollPhysics()], and on Android this is [FixedOverscrollPhysics()], a + /// On iOS/MacOS this is `BouncingScrollPhysics()`, and on Android this is `FixedOverscrollPhysics()`, a /// custom ScrollPhysics that allows fixed overscroll on pan/zoom and snapback. static ScrollPhysics getScrollPhysics(BuildContext context) { if (Platform.isAndroid) { From 0e90b30ce126eb8767ca9b5fadc8cbcc2f9f0a66 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 27 Sep 2025 18:05:00 +0900 Subject: [PATCH 369/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 140b18e3..4468fe53 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -560,8 +560,9 @@ class PdfViewerParams { final ScrollPhysics? scrollPhysicsScale; /// A convenience function to get platform-specific default scroll physics. - /// On iOS/MacOS this is `BouncingScrollPhysics()`, and on Android this is `FixedOverscrollPhysics()`, a - /// custom ScrollPhysics that allows fixed overscroll on pan/zoom and snapback. + /// + /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a + /// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. static ScrollPhysics getScrollPhysics(BuildContext context) { if (Platform.isAndroid) { return const FixedOverscrollPhysics(); From f2df4c023ebd38ba99dc38c69f1bdc3bb8cce933 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 03:37:24 +0900 Subject: [PATCH 370/663] FIXED: regression: #443 by dart_pubspec_licenses 3.0.12 --- .vscode/launch.json | 7 +++++++ packages/pdfrx/bin/remove_wasm_modules.dart | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 36054036..8ad875f4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,13 @@ "type": "dart", "program": "packages/pdfrx/example/viewer/lib/main.dart", "args": ["-d", "web-server", "--wasm", "--web-port", "8080", "--web-hostname", "127.0.0.1"] + }, + { + "name": "pdfrx:remove_wasm_modules", + "request": "launch", + "type": "dart", + "program": "packages/pdfrx/bin/remove_wasm_modules.dart", + "args": [] } ] } diff --git a/packages/pdfrx/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart index 2a14e0f2..0744e1fb 100644 --- a/packages/pdfrx/bin/remove_wasm_modules.dart +++ b/packages/pdfrx/bin/remove_wasm_modules.dart @@ -29,7 +29,7 @@ Future main(List args) async { } final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); - final pdfrxWasmPackage = deps.allDependencies.firstWhere((p) => p.name == 'pdfrx'); + final pdfrxWasmPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfrx'); print('Found: ${pdfrxWasmPackage.name} ${pdfrxWasmPackage.version}: ${pdfrxWasmPackage.pubspecYamlPath}'); final pubspecPath = pdfrxWasmPackage.pubspecYamlPath; From e3c99d2e8888c8102949474572ac22e3b1414f67 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 04:01:19 +0900 Subject: [PATCH 371/663] Preparing for releasing pdfrx 2.1.20 --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 10 ++++++++-- packages/pdfrx/README.md | 15 ++++++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4de387f1..650f36ec 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.12 + pdfrx: ^2.1.20 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.15 + pdfrx_engine: ^0.1.20 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index c0bcce02..b7533303 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.1.20 + +- Added [PdfViewerParams.scrollPhysics](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/scrollPhysics.html) and [PdfViewerParams.scrollPhysicsScale](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/scrollPhysicsScale.html) so you can plug in custom [ScrollPhysics](https://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html) for both panning and pinch-zoom interactions + - PR [#481](https://github.com/espresso3389/pdfrx/issues/481), [#482](https://github.com/espresso3389/pdfrx/issues/482), [#484](https://github.com/espresso3389/pdfrx/issues/484), [#485](https://github.com/espresso3389/pdfrx/issues/485) by [enhancient](https://github.com/enhancient) +- FIXED: regression where `dart run pdfrx:remove_wasm_modules` failed with dart_pubspec_licenses 3.0.12 ([#443](https://github.com/espresso3389/pdfrx/issues/443)). + # 2.1.19 - Maintenance release: applied `dart format` to keep code integrity with Dart 3.9/Flutter 3.29 tooling. @@ -13,8 +19,8 @@ # 2.1.16 -- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.initPdfium to explicitly call FPDF_InitLibraryWithConfig and pdfrxInitialize/pdfrxFlutterInitialize internally call it -- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add [PdfrxEntryFunctions.initPdfium](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions/initPdfium.html) to explicitly call FPDF_InitLibraryWithConfig and [pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html)/[pdfrxFlutterInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxFlutterInitialize.html) internally call it +- [#474](https://github.com/espresso3389/pdfrx/issues/474) Add [PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions/suspendPdfiumWorkerDuringAction.html) - Documentation improvements for low-level PDFium bindings access/PDFium interoperability and initialization - Updated to pdfrx_engine 0.1.17 diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 31f315e4..734f55bf 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.19 + pdfrx: ^2.1.20 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. @@ -130,6 +130,18 @@ For opening PDF files from various sources, there are several constructors avail You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html). +```dart +PdfViewer.asset( + 'assets/test.pdf', + params: const PdfViewerParams( + scrollPhysics: FixedOverscrollPhysics(maxOverscroll: 120), + scrollPhysicsScale: BouncingScrollPhysics(), + ), +); +``` + +The `scrollPhysics` and `scrollPhysicsScale` hooks let you plug in your own [ScrollPhysics](https://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html) (or the bundled [FixedOverscrollPhysics](https://pub.dev/documentation/pdfrx/latest/pdfrx/FixedOverscrollPhysics-class.html)) to tune drag and zoom behavior per platform. + ## Deal with Password Protected PDF Files ```dart @@ -183,6 +195,7 @@ For more text selection customization, see [Text Selection](https://github.com/e - [Dark/Night Mode Support](https://github.com/espresso3389/pdfrx/blob/master/doc/Dark-Night-Mode-Support.md) - [Document Loading Indicator](https://github.com/espresso3389/pdfrx/blob/master/doc/Document-Loading-Indicator.md) - [Viewer Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html) +- [Custom Scroll Physics for Drag/Zoom](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/scrollPhysics.html) ### Additional Customizations From 1c47a76ddc3bdc50ee380053e0660a1b1811025c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 04:07:23 +0900 Subject: [PATCH 372/663] Release pdfrx v2.1.20 --- packages/pdfrx/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4ef01c6b..14373d74 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.19 +version: 2.1.20 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From ccf37ac43368ca85c87b185526d5ed337caf0b4a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 08:18:16 +0900 Subject: [PATCH 373/663] Don't import dart:io on public codes. --- .vscode/settings.json | 1 + packages/pdfrx/lib/src/utils/native/native.dart | 16 ++++++++++++++++ packages/pdfrx/lib/src/utils/web/web.dart | 13 ++++++++++++- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 15 ++++----------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7ee8a285..fb2d570e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -170,6 +170,7 @@ "SCHHANDLE", "Schyler", "scrollable", + "Scrollables", "selectable", "selectables", "SIZEF", diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index e89d66fe..3cce4750 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; @@ -20,6 +21,21 @@ void setClipboardData(String text) { Clipboard.setData(ClipboardData(text: text)); } +/// A convenience function to get platform-specific default scroll physics. +/// +/// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a +/// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. +ScrollPhysics getScrollPhysicsOfPlatform(BuildContext context) { + if (Platform.isAndroid) { + return const FixedOverscrollPhysics(); + } else { + return ScrollConfiguration.of(context).getScrollPhysics(context); + } +} + +/// Gets the cache directory path for the current platform. +/// +/// For web, this function throws an [UnimplementedError] since there is no temporary directory available. Future getCacheDirectory() async => (await getTemporaryDirectory()).path; /// Override for the [PdfrxEntryFunctions] for native platforms; it is null. diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index c64bd46f..ccbba1bd 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -1,6 +1,6 @@ import 'dart:js_interop'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:web/web.dart' as web; @@ -21,6 +21,17 @@ void setClipboardData(String text) { web.window.navigator.clipboard.writeText(text); } +/// A convenience function to get platform-specific default scroll physics. +/// +/// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a +/// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. +ScrollPhysics getScrollPhysicsOfPlatform(BuildContext context) { + return ScrollConfiguration.of(context).getScrollPhysics(context); +} + +/// Gets the cache directory path for the current platform. +/// +/// For web, this function throws an [UnimplementedError] since there is no temporary directory available. Future getCacheDirectory() async => throw UnimplementedError('No temporary directory available for web.'); /// Override for the [PdfrxEntryFunctions] for web platforms to use WASM implementation. diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 4468fe53..5612bd58 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../pdfrx.dart'; +import '../utils/platform.dart'; /// Viewer customization parameters. /// @@ -543,12 +543,11 @@ class PdfViewerParams { /// /// If null, default InteractiveViewer physics is used on all platforms. This physics clamps to boundaries, /// does not allow zooming beyond the min/max scale, and flings on panning come to rest quickly relative to - /// Scrollables in Flutter (such as SingleChildScrollView). + /// Scrollables in Flutter (such as [SingleChildScrollView]). /// /// A convenience function [getScrollPhysics] is provided to get platform-specific default scroll physics. /// If you want no overscroll, but still want the physics for panning to be similar to other Scrollables, - /// you can use - /// `ClampingScrollPhysics()`. + /// you can use [ClampingScrollPhysics]. /// /// If the value is set non-null, it disables [normalizeMatrix]. /// @@ -563,13 +562,7 @@ class PdfViewerParams { /// /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a /// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. - static ScrollPhysics getScrollPhysics(BuildContext context) { - if (Platform.isAndroid) { - return const FixedOverscrollPhysics(); - } else { - return ScrollConfiguration.of(context).getScrollPhysics(context); - } - } + static ScrollPhysics getScrollPhysics(BuildContext context) => getScrollPhysicsOfPlatform(context); /// Determine whether the viewer needs to be reloaded or not. /// From a65c9a0f99cdbe33d1e88f19981a66e07917ba1a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 08:33:10 +0900 Subject: [PATCH 374/663] Revival of CLAUDE.md but it imports AGENTS.md now --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From e3c909c981cfdc2701445f74623142808b3f5ebc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 08:41:57 +0900 Subject: [PATCH 375/663] Release pdfrx v2.1.21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- AGENTS.md | 1 + README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5986fb04..f198cc99 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -101,6 +101,7 @@ Both packages may need to be released when changes are made: 7. Ensure the example app builds correctly - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app 8. Run `pana` in `packages/pdfrx` (and any other packages being released) to validate code integrity + - Warn me if `pana` warns about WASM incompatibility issues 9. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" 10. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` 11. Push changes and tags to remote diff --git a/README.md b/README.md index 650f36ec..8f667ef2 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.20 + pdfrx: ^2.1.21 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index b7533303..7829e901 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.21 + +- FIXED: Web compatibility issue where `dart:io` was imported in public-facing code + # 2.1.20 - Added [PdfViewerParams.scrollPhysics](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/scrollPhysics.html) and [PdfViewerParams.scrollPhysicsScale](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/scrollPhysicsScale.html) so you can plug in custom [ScrollPhysics](https://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html) for both panning and pinch-zoom interactions diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 734f55bf..b1a0db3d 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.20 + pdfrx: ^2.1.21 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 14373d74..b23c58f9 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.20 +version: 2.1.21 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 78bc47f6f278bfc11969fd29e52c8fc529680b9e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 16:32:34 +0900 Subject: [PATCH 376/663] Document updates --- doc/Loading-Fonts-Dynamically.md | 140 ++++++++++++++++++++++++ doc/Low-Level-PDFium-Bindings-Access.md | 22 ++-- doc/README.md | 1 + 3 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 doc/Loading-Fonts-Dynamically.md diff --git a/doc/Loading-Fonts-Dynamically.md b/doc/Loading-Fonts-Dynamically.md new file mode 100644 index 00000000..33602c74 --- /dev/null +++ b/doc/Loading-Fonts-Dynamically.md @@ -0,0 +1,140 @@ +# Loading Fonts Dynamically + +When rendering PDFs, PDFium may require fonts that are not embedded in the PDF file itself. This is especially important for iOS and Web because PDFium does not have access to system's preinstalled fonts on these platforms due to security sandbox. + +## Overview + +pdfrx provides APIs to dynamically load font data at runtime. The fonts are cached and used by PDFium when rendering PDF pages that reference those fonts. + +Please note that PDFium's font system basically supports TrueType/OpenType font files (`ttf`/`ttc`/`otf`/`otc`). Fonts of other formats may not be supported. + +## Basic Usage + +### Loading Font Data + +Use [PdfrxEntryFunctions.addFontData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/addFontData.html) to add font data dynamically: + +```dart +import 'package:pdfrx/pdfrx.dart'; +import 'package:http/http.dart' as http; + +Future loadFont() async { + // Download font from a URL + final response = await http.get(Uri.parse('https://example.com/fonts/MyFont.ttf')); + final fontData = response.bodyBytes; + + // Add font data to PDFium + await PPdfrxEntryFunctions.instance.addFontData( + face: 'MyFont', // font name should be unique but don't have to be meaningful name + data: fontData, + ); + + // Instruct PDFium to reload fonts + await PdfrxEntryFunctions.instance.reloadFonts(); +} +``` + +The fonts loaded by [PdfrxEntryFunctions.addFontData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/addFontData.html) are **cached on memory**. So don't load so many/large fonts. + +For non-Web platforms, you can alternatively [place fonts on file system](#place-fonts-on-file-system). + +### Reload PdfDocument Instances + +[PdfrxEntryFunctions.reloadFonts](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/reloadFonts.html) instructs PDFium to reload the fonts but it does not reload [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) instances already loaded. You must close/re-open these loaded documents by yourself. + +For [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) or such widgets, you can use [PdfDocumentRef](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentRef-class.html) to reload the loaded document: + +```dart +// loading fonts +await PdfrxEntryFunctions.instance.addFontData(...); +await PdfrxEntryFunctions.instance.reloadFonts(); + +// and reload document using PdfViewerController.documentRef +await controller.documentRef.resolveListenable().load(forceReload: true); +``` + +### Clearing Font Cache on Memory + +To clear all fonts loaded on memory: + +```dart +await PdfrxEntryFunctions.instance.clearAllFontData(); +await PdfrxEntryFunctions.instance.reloadFonts(); +``` + +## Place Fonts on File System + +On native platforms (non-Web), you can places fonts on PDFium's font path. + +The following fragment illustrates how to add a directory path to [Pdfrx.fontPaths](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/Pdfrx/fontPaths.html): + +```dart +import 'package:pdfrx/pdfrx.dart'; +import 'package:path_provider/path_provider.dart'; + +... + +// Pdfrx.fontPaths must be set **before** calling any pdfrx functions (ideally in main) +final appDocDir = await getApplicationDocumentsDirectory(); +final fontsDir = Directory('${appDocDir.path}/fonts'); +await fontsDir.create(recursive: true); + +// Add to PDFium font paths +Pdfrx.fontPaths.add(fontsDir.path); + +// Initialize pdfrx +pdfrxFlutterInitialize(); +``` + +You can add fonts anytime on your program but you should call [PdfrxEntryFunctions.reloadFonts](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/reloadFonts.html) and reload the documents as explained above. + +## Handling Missing Fonts + +You can listen to [PdfDocument.events](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/events.html) to get [PdfDocumentMissingFontsEvent](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentMissingFontsEvent-class.html) on missing fonts and load the required fonts dynamically: + +```dart +document.events.listen((event) async { + if (event is PdfDocumentMissingFontsEvent) { + for (final query in event.missingFonts) { + print('Missing font: ${query.face}, charset: ${query.charset}'); + + // Load the font based on the query + final fontData = await fetchFontForQuery(query); + if (fontData != null) { + await addFontData(face: query.face, data: fontData); + } + } + + // and reload the document + .... + } +}); + +... + +Future fetchFontForQuery(PdfFontQuery query) async { + // Implement your font loading logic here + // You can use query.charset, query.weight, query.isItalic, etc. + return null; +} +``` + +The [`PdfFontQuery`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfFontQuery-class.html) object provides information about the requested font: + +- `face`: Font family name +- `weight`: Font weight (100-900) +- `isItalic`: Whether italic style is needed +- `charset`: Character set (e.g., [`PdfFontCharset.shiftJis`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfFontCharset.html) for Japanese) +- `pitchFamily`: Font pitch and family flags + +## Advanced Example + +For a more sophisticated implementation with caching and multiple font sources, see the example app's [main.dart](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx/example/viewer/lib/main.dart#L406-L427) and [noto_google_fonts.dart](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx/example/viewer/lib/noto_google_fonts.dart). + +## See Also + +- [PdfrxEntryFunctions.addFontData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/addFontData.html) - Add font data to PDFium +- [PdfrxEntryFunctions.clearAllFontData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/clearAllFontData.html) - Clear all cached fonts +- [PdfrxEntryFunctions.reloadFonts](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/reloadFonts.html) - Reload fonts from file system +- [Pdfrx.fontPaths](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/Pdfrx/fontPaths.html) - Font directory paths +- [PdfFontQuery](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfFontQuery-class.html) - Font query information diff --git a/doc/Low-Level-PDFium-Bindings-Access.md b/doc/Low-Level-PDFium-Bindings-Access.md index 02212a76..bd2a0d21 100644 --- a/doc/Low-Level-PDFium-Bindings-Access.md +++ b/doc/Low-Level-PDFium-Bindings-Access.md @@ -36,28 +36,30 @@ This import provides: PDFium must be initialized before use. The high-level API handles this automatically, but when using raw bindings directly, you may need to ensure initialization. +There are basically three ways to initialize PDFium: + +#### Manual Initialization + ```dart -// -// Manual initialization -// import 'package:pdfrx_engine/pdfrx_engine.dart'; // or import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx_engine/src/native/pdfium.dart'; import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; -// Low level initialization with PDFium's FPDF_InitLibrary Pdfrx.pdfiumModulePath = 'somewhere/in/your/filesystem/libpdfium.so'; pdfium.FPDF_InitLibrary(); // or pdfium.FPDF_InitLibraryWithConfig(...) +``` + +#### Initialization for Flutter App -// -// Flutter app -// +```dart import 'package:pdfrx/pdfrx.dart'; pdfrxFlutterInitialize(); +``` -// -// Pure Dart -// +#### Initialization for pure Dart + +```dart import 'package:pdfrx_engine/pdfrx_engine.dart'; await pdfrxInitialize(); diff --git a/doc/README.md b/doc/README.md index f54e6126..ad5e7335 100644 --- a/doc/README.md +++ b/doc/README.md @@ -32,6 +32,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays +- [Loading Fonts Dynamically](Loading-Fonts-Dynamically.md) - Add custom fonts for Web and iOS - [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - Using PDFium function directly - [Interoperability with other PDFium Libraries](Interoperability-with-other-PDFium-Libraries.md) - Using pdfrx alongside other PDFium-based libraries From 4365cf12e14c078b79860c0f98e72d659e583048 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 16:34:02 +0900 Subject: [PATCH 377/663] WIP --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index ad5e7335..a0f7c882 100644 --- a/doc/README.md +++ b/doc/README.md @@ -32,7 +32,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays -- [Loading Fonts Dynamically](Loading-Fonts-Dynamically.md) - Add custom fonts for Web and iOS +- [Loading Fonts Dynamically](Loading-Fonts-Dynamically.md) - Add custom fonts - [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - Using PDFium function directly - [Interoperability with other PDFium Libraries](Interoperability-with-other-PDFium-Libraries.md) - Using pdfrx alongside other PDFium-based libraries From 6b45d05c9005354b6af053348d7a11d9a1ee34cb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 16:38:25 +0900 Subject: [PATCH 378/663] GitHub Actions don't have to run on document updates. --- .github/workflows/build-test.yml | 7 +++++++ .github/workflows/github-pages.yml | 3 +++ .github/workflows/pana-analysis.yml | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f961d842..a86bc2fe 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -3,8 +3,15 @@ name: Build Test on: push: branches: [ master, main ] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: branches: [ master, main ] + paths-ignore: + - '**.md' + - 'doc/**' workflow_dispatch: inputs: build_android: diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 35f30a74..f0bbd703 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -4,6 +4,9 @@ on: push: branches: - master + paths-ignore: + - '**.md' + - 'doc/**' jobs: build-and-deploy: diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index f0ae9ef5..eac0ed6f 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -3,8 +3,14 @@ name: Pana Analysis on: push: branches: [ master, main ] + paths-ignore: + - '**.md' + - 'doc/**' pull_request: branches: [ master, main ] + paths-ignore: + - '**.md' + - 'doc/**' workflow_dispatch: jobs: From b8fd5a7c495e27a8371068613a7260bab4fce225 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 30 Sep 2025 23:18:20 +0900 Subject: [PATCH 379/663] WIP --- packages/pdfrx/lib/src/widgets/interactive_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 92801f02..fe4af8fc 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -698,7 +698,7 @@ class InteractiveViewerState extends State with TickerProvide } assert(scale != 0.0); - // fallback to widget.scrollPhysics if widgry.scrollPhysicsScale not specified + // fallback to widget.scrollPhysics if widget.scrollPhysicsScale not specified final scrollPhysics = widget.scrollPhysicsScale ?? widget.scrollPhysics; if (scrollPhysics != null) { From 255440c4e4c88095089f8d4efef6ef4d98133843 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 1 Oct 2025 00:55:08 +0900 Subject: [PATCH 380/663] WIP: #486 Option+Wheel to zoom --- packages/pdfrx/example/viewer/.metadata | 27 ++++++++++++++++ .../pdfrx/example/viewer/macos/Podfile.lock | 10 +++--- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 31 +++++++++++++------ 3 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 packages/pdfrx/example/viewer/.metadata diff --git a/packages/pdfrx/example/viewer/.metadata b/packages/pdfrx/example/viewer/.metadata new file mode 100644 index 00000000..b1c3f10b --- /dev/null +++ b/packages/pdfrx/example/viewer/.metadata @@ -0,0 +1,27 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index ea61c8ad..62bff544 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.6): + - pdfrx (0.0.8): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 7d42fd227c1ea6a48d7e687cfe27d503238c7f97 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f6a6025f..0b0b5198 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1472,16 +1472,29 @@ class _PdfViewerState extends State void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); - final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; - final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; - final m = _txController.value.clone(); - if (widget.params.scrollHorizontallyByMouseWheel) { - m.translateByDouble(dy, dx, 0, 1); - } else { - m.translateByDouble(dx, dy, 0, 1); + try { + if (isApple && event.original is PointerScrollEvent) { + // macOS: treat horizontal wheel scroll as zoom (it's initiated by Option+Wheel) + final scrollEvent = event.original as PointerScrollEvent; + final newZoom = (_currentZoom * (pow(2, scrollEvent.scrollDelta.dx / 120))).clamp( + widget.params.minScale, + widget.params.maxScale, + ); + if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; + _setZoom(event.localPosition, newZoom); + } + final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; + final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; + final m = _txController.value.clone(); + if (widget.params.scrollHorizontallyByMouseWheel) { + m.translateByDouble(dy, dx, 0, 1); + } else { + m.translateByDouble(dx, dy, 0, 1); + } + _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); + } finally { + _stopInteraction(); } - _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); - _stopInteraction(); } /// Restrict matrix to the safe range. From 4438f09e9d85b65f32ffc688c063a3c1fed32314 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 1 Oct 2025 00:58:40 +0900 Subject: [PATCH 381/663] WIP: remove getScrollPhysicsOfPlatform to simplify the code --- packages/pdfrx/lib/src/utils/native/native.dart | 14 +------------- packages/pdfrx/lib/src/utils/web/web.dart | 9 +-------- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 8 +++++++- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 3cce4750..33d25b50 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,12 +1,12 @@ import 'dart:io'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import '../../../pdfrx.dart'; final isApple = Platform.isMacOS || Platform.isIOS; +final isAndroid = Platform.isAndroid; final isWindows = Platform.isWindows; /// Whether the current platform is mobile (Android, iOS, or Fuchsia). @@ -21,18 +21,6 @@ void setClipboardData(String text) { Clipboard.setData(ClipboardData(text: text)); } -/// A convenience function to get platform-specific default scroll physics. -/// -/// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a -/// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. -ScrollPhysics getScrollPhysicsOfPlatform(BuildContext context) { - if (Platform.isAndroid) { - return const FixedOverscrollPhysics(); - } else { - return ScrollConfiguration.of(context).getScrollPhysics(context); - } -} - /// Gets the cache directory path for the current platform. /// /// For web, this function throws an [UnimplementedError] since there is no temporary directory available. diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index ccbba1bd..fed78f2d 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -8,6 +8,7 @@ import '../../../pdfrx.dart'; import '../../wasm/pdfrx_wasm.dart'; final isApple = false; +final isAndroid = false; final isWindows = false; /// Whether the current platform is mobile (Android, iOS, or Fuchsia). @@ -21,14 +22,6 @@ void setClipboardData(String text) { web.window.navigator.clipboard.writeText(text); } -/// A convenience function to get platform-specific default scroll physics. -/// -/// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a -/// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. -ScrollPhysics getScrollPhysicsOfPlatform(BuildContext context) { - return ScrollConfiguration.of(context).getScrollPhysics(context); -} - /// Gets the cache directory path for the current platform. /// /// For web, this function throws an [UnimplementedError] since there is no temporary directory available. diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 5612bd58..13f5fd94 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -562,7 +562,13 @@ class PdfViewerParams { /// /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a /// custom [ScrollPhysics] that allows fixed overscroll on pan/zoom and snapback. - static ScrollPhysics getScrollPhysics(BuildContext context) => getScrollPhysicsOfPlatform(context); + static ScrollPhysics getScrollPhysics(BuildContext context) { + if (isAndroid) { + return FixedOverscrollPhysics(); + } else { + return ScrollConfiguration.of(context).getScrollPhysics(context); + } + } /// Determine whether the viewer needs to be reloaded or not. /// From f45be2b68f992c1cf4f3ca2cb4b4079ea709aa3a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 1 Oct 2025 03:12:13 +0900 Subject: [PATCH 382/663] WIP: #486 --- .../pdfrx/example/viewer/ios/Podfile.lock | 4 ++-- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 67e482f1..9f8e38dc 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.7): + - pdfrx (0.0.8): - Flutter - FlutterMacOS - url_launcher_ios (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: aab50d03b29bd4fbfec302e13e72178d0a0ed3d5 + pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 0b0b5198..8300f90d 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1473,15 +1473,19 @@ class _PdfViewerState extends State void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); try { - if (isApple && event.original is PointerScrollEvent) { - // macOS: treat horizontal wheel scroll as zoom (it's initiated by Option+Wheel) - final scrollEvent = event.original as PointerScrollEvent; - final newZoom = (_currentZoom * (pow(2, scrollEvent.scrollDelta.dx / 120))).clamp( - widget.params.minScale, - widget.params.maxScale, - ); - if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; - _setZoom(event.localPosition, newZoom); + if (!kIsWeb) { + // for Web, Ctrl+wheel is already supported on the framework side and should not be handled here. + // on Apple platforms, Option+wheel is used instead of Ctrl+wheel. + if ((isApple && HardwareKeyboard.instance.isAltPressed) || + (!isApple && HardwareKeyboard.instance.isControlPressed)) { + // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. + // So, I just add both values. + final zoomFactor = (event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; + final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); + if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; + _setZoom(_controller!.globalToDocument(event.position)!, newZoom, duration: Duration.zero); + return; + } } final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; From 1f7a326786ef7f19259980d3aedfdc4a2bed1053 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 1 Oct 2025 03:39:14 +0900 Subject: [PATCH 383/663] #486: consistent behavior across platforms; use Ctrl+wheel for zoom and same wheel-direction to zoom up/down --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8300f90d..a71a4e94 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1474,13 +1474,11 @@ class _PdfViewerState extends State _startInteraction(); try { if (!kIsWeb) { - // for Web, Ctrl+wheel is already supported on the framework side and should not be handled here. - // on Apple platforms, Option+wheel is used instead of Ctrl+wheel. - if ((isApple && HardwareKeyboard.instance.isAltPressed) || - (!isApple && HardwareKeyboard.instance.isControlPressed)) { + // To make the behavior consistent across platforms, we only handle zooming on web via Ctrl+wheel. + if (HardwareKeyboard.instance.isControlPressed) { // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. // So, I just add both values. - final zoomFactor = (event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; + var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; _setZoom(_controller!.globalToDocument(event.position)!, newZoom, duration: Duration.zero); From 78ffce525a455e132eb03ffdf0f8908f1e98afc5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 2 Oct 2025 03:48:11 +0900 Subject: [PATCH 384/663] #486, #462: introducing PdfViewerController.zoomOnLocalPosition and its variants for consistent zooming on cursor/finger position --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a71a4e94..a7f0aa98 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1481,7 +1481,12 @@ class _PdfViewerState extends State var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; - _setZoom(_controller!.globalToDocument(event.position)!, newZoom, duration: Duration.zero); + // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. + _controller!.zoomOnLocalPosition( + localPosition: _controller!.globalToLocal(event.position)!, + newZoom: newZoom, + duration: Duration.zero, + ); return; } } @@ -1841,6 +1846,12 @@ class _PdfViewerState extends State duration: duration, ); + Offset _localPositionToZoomCenter(Offset localPosition, double newZoom) { + final toCenter = (_viewSize!.center(Offset.zero) - localPosition) / newZoom; + final zoomPosition = _controller!.globalToDocument(_controller!.localToGlobal(localPosition)!)!; + return zoomPosition.translate(toCenter.dx, toCenter.dy); + } + Offset get _centerPosition => _txController.value.calcPosition(_viewSize!); Future _zoomUp({ @@ -3665,20 +3676,83 @@ class PdfViewerController extends ValueListenable { /// This function does not scroll/zoom to the specified page but changes the current page number. void setCurrentPageNumber(int pageNumber) => _state._setCurrentPageNumber(pageNumber); + /// The current zoom ratio. double get currentZoom => value.zoom; + /// Set the zoom ratio with the specified position as the zoom center. + /// + /// [position] specifies the zoom center in the document coordinates. + /// [zoom] specifies the new zoom ratio. + /// [duration] specifies the duration of the animation. Future setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => _state._setZoom(position, zoom, duration: duration); + /// Zoom in with the specified position as the zoom center. + /// + /// [zoomCenter] specifies the zoom center in the document coordinates; if null, the center of the view is used. + /// [loop] specifies whether to loop the zoom stops. + /// [duration] specifies the duration of the animation. Future zoomUp({bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200)}) => _state._zoomUp(loop: loop, zoomCenter: zoomCenter, duration: duration); + /// Zoom out with the specified position as the zoom center. + /// + /// [zoomCenter] specifies the zoom center in the document coordinates; if null, the center of the view is used. + /// [loop] specifies whether to loop the zoom stops. + /// [duration] specifies the duration of the animation. Future zoomDown({ bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200), }) => _state._zoomDown(loop: loop, zoomCenter: zoomCenter, duration: duration); + /// Set the zoom ratio with the document point corresponding to the specified local position is kept unmoved + /// on the view. + /// + /// [localPosition] specifies the position in the widget's local coordinates. + /// [newZoom] specifies the new zoom ratio. + /// [duration] specifies the duration of the animation. + Future zoomOnLocalPosition({ + required Offset localPosition, + required double newZoom, + Duration duration = const Duration(milliseconds: 200), + }) async { + final center = _state._localPositionToZoomCenter(localPosition, newZoom); + await _state._setZoom(center, newZoom, duration: duration); + } + + /// Zoom in with the document point corresponding to the specified local position is kept unmoved + /// on the view. + /// + /// [localPosition] specifies the position in the widget's local coordinates. + /// [loop] specifies whether to loop the zoom stops. + /// [duration] specifies the duration of the animation. + Future zoomUpOnLocalPosition({ + required Offset localPosition, + bool loop = false, + Duration duration = const Duration(milliseconds: 200), + }) async { + final newZoom = _state._findNextZoomStop(currentZoom, zoomUp: true, loop: loop); + final center = _state._localPositionToZoomCenter(localPosition, newZoom); + await _state._setZoom(center, newZoom, duration: duration); + } + + /// Zoom out with the document point corresponding to the specified local position is kept unmoved + /// on the view. + /// + /// [localPosition] specifies the position in the widget's local coordinates. + /// [loop] specifies whether to loop the zoom stops. + /// [duration] specifies the duration of the animation. + Future zoomDownOnLocalPosition({ + required Offset localPosition, + bool loop = false, + Duration duration = const Duration(milliseconds: 200), + }) async { + final newZoom = _state._findNextZoomStop(currentZoom, zoomUp: false, loop: loop); + final center = _state._localPositionToZoomCenter(localPosition, newZoom); + await _state._setZoom(center, newZoom, duration: duration); + } + RenderBox? get renderBox => _state._renderBox; /// Converts the global position to the local position in the widget. From 5278fb49cfb4fc56aa8db659a909e11b470c0e95 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 2 Oct 2025 04:10:16 +0900 Subject: [PATCH 385/663] Release pdfrx v2.1.22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f667ef2..7baeec0e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.21 + pdfrx: ^2.1.22 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 7829e901..2cb36a46 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.22 + +- NEW: Introducing [PdfViewerController.zoomOnLocalPosition](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/zoomOnLocalPosition.html) and its variants for consistent zooming on cursor/finger position ([#486](https://github.com/espresso3389/pdfrx/issues/486), [#462](https://github.com/espresso3389/pdfrx/issues/462)) +- PdfViewer now handles Ctrl+wheel to zoom up/down ([#486](https://github.com/espresso3389/pdfrx/issues/486)) + # 2.1.21 - FIXED: Web compatibility issue where `dart:io` was imported in public-facing code diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b1a0db3d..52627a84 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.21 + pdfrx: ^2.1.22 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b23c58f9..4b908cda 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.21 +version: 2.1.22 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 6e9bd65b02bba50f520ef9cae3acdfd2d4e0964a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 3 Oct 2025 03:37:13 +0900 Subject: [PATCH 386/663] Enhance pana analysis step to check for wasm readiness in output --- .github/workflows/pana-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index eac0ed6f..3e1095bc 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -51,4 +51,4 @@ jobs: - name: Run pana (${{ matrix.package_name }}) working-directory: ${{ matrix.package_path }} - run: ~/.pub-cache/bin/pana --no-warning --exit-code-threshold 0 + run: ~/.pub-cache/bin/pana --json --no-warning --exit-code-threshold 0 | jq -e '.tags | any(. == "is:wasm-ready")' From 23c969d1b9e57cdc51808e6fcd31f240eaefeed7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 3 Oct 2025 15:39:55 +0900 Subject: [PATCH 387/663] WIP --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index e73120d6..c95359f3 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -1,4 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +/// @docImport 'native/pdfrx_pdfium.dart'; /// Pdfrx API library; From 7b53893eb34b44c800bd40b74920be20d12d7a29 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 4 Oct 2025 04:19:12 +0900 Subject: [PATCH 388/663] Introduces PdfDocumentRefKey for more flexible PdfDocumentRef identification --- packages/pdfrx/example/viewer/lib/main.dart | 6 +- packages/pdfrx/lib/src/pdf_document_ref.dart | 177 +++++++++--------- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 16 +- .../lib/src/native/pdfrx_pdfium.dart | 12 +- 4 files changed, 116 insertions(+), 95 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 3158db77..fba3bb1b 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -103,7 +103,7 @@ class _MainPageState extends State with WidgetsBindingObserver { return Row( children: [ if (!isMobileDevice) ...[ - Expanded(child: Text(_fileName(documentRef?.sourceName) ?? 'No document loaded')), + Expanded(child: Text(_fileName(documentRef?.key.sourceName) ?? 'No document loaded')), SizedBox(width: 10), FilledButton(onPressed: () => openFile(), child: Text('Open File')), SizedBox(width: 20), @@ -198,7 +198,7 @@ class _MainPageState extends State with WidgetsBindingObserver { valueListenable: documentRef, builder: (context, documentRef, child) => Expanded( child: Text( - _fileName(documentRef?.sourceName) ?? 'No document loaded', + _fileName(documentRef?.key.sourceName) ?? 'No document loaded', softWrap: false, ), ), @@ -606,7 +606,7 @@ class _MainPageState extends State with WidgetsBindingObserver { final bytes = await file.readAsBytes(); documentRef.value = PdfDocumentRefData( bytes, - sourceName: file.name, + sourceName: 'web-open-file%${file.name}', passwordProvider: () => passwordDialog(context), useProgressiveLoading: useProgressiveLoading, ); diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index c6c5675c..1cbc1c57 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:synchronized/extension.dart'; @@ -11,7 +12,36 @@ import '../pdfrx.dart'; /// [totalBytes] is the total number of bytes to download. It may be null if the total size is unknown. typedef PdfDocumentLoaderProgressCallback = void Function(int downloadedBytes, [int? totalBytes]); -/// PdfDocumentRef controls loading of a [PdfDocument] and it also provide you with a way to use [PdfDocument] +/// A key that identifies the source of a [PdfDocumentRef]. +/// +/// It is used to cache and share [PdfDocumentListenable] instances for [PdfDocumentRef]s that refer to the same document. +/// +/// This class supercedes the previous [sourceName] property of [PdfDocumentRef] to provide a more flexible way to +/// identify the source. +class PdfDocumentRefKey { + PdfDocumentRefKey(this.sourceName, [Iterable parts = const []]) : parts = List.unmodifiable(parts); + + /// A name that identifies the source of the document. + final String sourceName; + + /// Additional parts to identify the source uniquely. + /// + /// For example, if the document is identified not only by the URI but also by some HTTP headers + /// (for example, authentication/authorization headers), you can include the headers in the parts. + final List parts; + + @override + bool operator ==(Object other) => + other is PdfDocumentRefKey && sourceName == other.sourceName && listEquals(parts, other.parts); + + @override + int get hashCode => Object.hash(sourceName, const ListEquality().hash(parts)); + + @override + String toString() => 'PdfDocumentRefKey($sourceName)'; +} + +/// PdfDocumentRef controls loading/caching of a [PdfDocument] and it also provide you with a way to use [PdfDocument] /// safely in your long running async operations. /// /// There are several types of [PdfDocumentRef]s predefined: @@ -35,15 +65,32 @@ typedef PdfDocumentLoaderProgressCallback = void Function(int downloadedBytes, [ /// ``` /// abstract class PdfDocumentRef { - const PdfDocumentRef({this.autoDispose = true}); + /// Creates a new instance of [PdfDocumentRef]. + const PdfDocumentRef({required this.key, this.autoDispose = true}); /// Whether to dispose the document on reference dispose or not. final bool autoDispose; - /// Source name to identify the reference. - String get sourceName; + /// A name that identifies the source of the document. + /// + /// [PdfDocument] is cached based on the [PdfDocumentRefKey]. If you create multiple [PdfDocumentRef]s with the same + /// key, they share the same [PdfDocumentListenable] and thus the same [PdfDocument] instance. + /// + /// By default, the key is created from the source name (for example, file path or URI) of the document. But it may + /// be insufficient to identify the document uniquely in some cases. For example, if the document is not only + /// identified by the URI but also by some HTTP headers (for example, authentication/authorization headers), + /// you should create a custom key that includes the headers as well. + final PdfDocumentRefKey key; - static final _listenables = {}; + /// The name that identifies the source of the document. + /// + /// This is for compatibility. Use [key] instead. See [PdfDocumentRefKey] for more info. + @Deprecated('For compatibility. Use key for source identification') + String get sourceName => key.sourceName; + + static final _listenables = CanonicalizedMap( + (ref) => ref.key, + ); /// Resolve the [PdfDocumentListenable] for this reference. PdfDocumentListenable resolveListenable() => _listenables.putIfAbsent(this, () => PdfDocumentListenable._(this)); @@ -63,34 +110,24 @@ abstract class PdfDocumentRef { /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. bool get firstAttemptByEmptyPassword; - - /// Classes that extends [PdfDocumentRef] should override this function to compare the equality by [sourceName] - /// or such. - @override - bool operator ==(Object other) => throw UnimplementedError(); - - /// Classes that extends [PdfDocumentRef] should override this function. - @override - int get hashCode => throw UnimplementedError(); } /// A [PdfDocumentRef] that loads the document from asset. class PdfDocumentRefAsset extends PdfDocumentRef { - const PdfDocumentRefAsset( + PdfDocumentRefAsset( this.name, { this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, this.useProgressiveLoading = true, - }); + PdfDocumentRefKey? key, + }) : super(key: key ?? PdfDocumentRefKey(name)); final String name; @override final PdfPasswordProvider? passwordProvider; @override final bool firstAttemptByEmptyPassword; - @override - String get sourceName => name; /// Whether to use progressive loading or not. final bool useProgressiveLoading; @@ -102,17 +139,11 @@ class PdfDocumentRefAsset extends PdfDocumentRef { firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, ); - - @override - bool operator ==(Object other) => other is PdfDocumentRefAsset && name == other.name; - - @override - int get hashCode => name.hashCode; } /// A [PdfDocumentRef] that loads the document from network. class PdfDocumentRefUri extends PdfDocumentRef { - const PdfDocumentRefUri( + PdfDocumentRefUri( this.uri, { this.passwordProvider, this.firstAttemptByEmptyPassword = true, @@ -121,7 +152,8 @@ class PdfDocumentRefUri extends PdfDocumentRef { this.headers, this.withCredentials = false, this.useProgressiveLoading = true, - }); + PdfDocumentRefKey? key, + }) : super(key: key ?? PdfDocumentRefKey(uri.toString())); /// The URI to load the document. final Uri uri; @@ -142,9 +174,6 @@ class PdfDocumentRefUri extends PdfDocumentRef { /// Whether to use progressive loading or not. final bool useProgressiveLoading; - @override - String get sourceName => uri.toString(); - @override Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openUri( uri, @@ -156,31 +185,24 @@ class PdfDocumentRefUri extends PdfDocumentRef { headers: headers, withCredentials: withCredentials, ); - - @override - bool operator ==(Object other) => other is PdfDocumentRefUri && uri == other.uri; - - @override - int get hashCode => uri.hashCode; } /// A [PdfDocumentRef] that loads the document from file. class PdfDocumentRefFile extends PdfDocumentRef { - const PdfDocumentRefFile( + PdfDocumentRefFile( this.file, { this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, this.useProgressiveLoading = true, - }); + PdfDocumentRefKey? key, + }) : super(key: key ?? PdfDocumentRefKey(file)); final String file; @override final PdfPasswordProvider? passwordProvider; @override final bool firstAttemptByEmptyPassword; - @override - String get sourceName => file; /// Whether to use progressive loading or not. final bool useProgressiveLoading; @@ -192,28 +214,23 @@ class PdfDocumentRefFile extends PdfDocumentRef { firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, ); - - @override - bool operator ==(Object other) => other is PdfDocumentRefFile && file == other.file; - - @override - int get hashCode => file.hashCode; } /// A [PdfDocumentRef] that loads the document from data. /// /// For [allowDataOwnershipTransfer], see [PdfDocument.openData]. class PdfDocumentRefData extends PdfDocumentRef { - const PdfDocumentRefData( + PdfDocumentRefData( this.data, { - required this.sourceName, + required String sourceName, this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, this.allowDataOwnershipTransfer = false, this.onDispose, this.useProgressiveLoading = true, - }); + PdfDocumentRefKey? key, + }) : super(key: key ?? PdfDocumentRefKey(sourceName)); final Uint8List data; @override @@ -223,9 +240,6 @@ class PdfDocumentRefData extends PdfDocumentRef { final bool allowDataOwnershipTransfer; final void Function()? onDispose; - @override - final String sourceName; - /// Whether to use progressive loading or not. final bool useProgressiveLoading; @@ -235,31 +249,26 @@ class PdfDocumentRefData extends PdfDocumentRef { passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, - sourceName: sourceName, + sourceName: key.sourceName, allowDataOwnershipTransfer: allowDataOwnershipTransfer, onDispose: onDispose, ); - - @override - bool operator ==(Object other) => other is PdfDocumentRefData && sourceName == other.sourceName; - - @override - int get hashCode => sourceName.hashCode; } /// A [PdfDocumentRef] that loads the document from custom source. class PdfDocumentRefCustom extends PdfDocumentRef { - const PdfDocumentRefCustom({ + PdfDocumentRefCustom({ required this.fileSize, required this.read, - required this.sourceName, + required String sourceName, this.passwordProvider, this.firstAttemptByEmptyPassword = true, super.autoDispose = true, this.maxSizeToCacheOnMemory, this.onDispose, this.useProgressiveLoading = true, - }); + PdfDocumentRefKey? key, + }) : super(key: key ?? PdfDocumentRefKey(sourceName)); final int fileSize; final FutureOr Function(Uint8List buffer, int position, int size) read; @@ -270,9 +279,6 @@ class PdfDocumentRefCustom extends PdfDocumentRef { final int? maxSizeToCacheOnMemory; final void Function()? onDispose; - @override - final String sourceName; - /// Whether to use progressive loading or not. final bool useProgressiveLoading; @@ -280,39 +286,25 @@ class PdfDocumentRefCustom extends PdfDocumentRef { Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openCustom( read: read, fileSize: fileSize, - sourceName: sourceName, + sourceName: key.sourceName, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, onDispose: onDispose, ); - - @override - bool operator ==(Object other) => other is PdfDocumentRefCustom && sourceName == other.sourceName; - - @override - int get hashCode => sourceName.hashCode; } /// A [PdfDocumentRef] that directly contains [PdfDocument]. class PdfDocumentRefDirect extends PdfDocumentRef { - const PdfDocumentRefDirect(this.document, {super.autoDispose = true}); + PdfDocumentRefDirect(this.document, {super.autoDispose = true, PdfDocumentRefKey? key}) + : super(key: key ?? PdfDocumentRefKey(document.sourceName)); final PdfDocument document; - @override - String get sourceName => document.sourceName; - @override Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => Future.value(document); - @override - bool operator ==(Object other) => other is PdfDocumentRefDirect && sourceName == other.sourceName; - - @override - int get hashCode => sourceName.hashCode; - @override bool get firstAttemptByEmptyPassword => throw UnimplementedError('Not applicable for PdfDocumentRefDirect'); @@ -379,20 +371,25 @@ class PdfDocumentListenable extends Listenable { if (!forceReload && loadAttempted) { return null; } + final stopwatch = Stopwatch()..start(); return await synchronized(() async { if (!forceReload && loadAttempted) return null; final PdfDocument document; PdfDownloadReport? report; try { - final stopwatch = Stopwatch()..start(); - document = await ref.loadDocument(_progress); - debugPrint('PdfDocument initial load: ${ref.sourceName} (${stopwatch.elapsedMilliseconds} ms)'); + document = await ref.loadDocument((cur, [total]) { + _progress(cur, total); + if (total != null) { + report = PdfDownloadReport(downloaded: cur, total: total, elapsedTime: stopwatch.elapsed); + } + }); + debugPrint('PdfDocument initial load: ${ref.key} (${stopwatch.elapsedMilliseconds} ms)'); } catch (err, stackTrace) { setError(err, stackTrace); - return report; + return report?.copyWith(elapsedTime: stopwatch.elapsed); } setDocument(document); - return report; + return report?.copyWith(elapsedTime: stopwatch.elapsed); }); } @@ -525,6 +522,14 @@ class PdfDownloadReport { other.elapsedTime == elapsedTime; } + PdfDownloadReport copyWith({int? downloaded, int? total, Duration? elapsedTime}) { + return PdfDownloadReport( + downloaded: downloaded ?? this.downloaded, + total: total ?? this.total, + elapsedTime: elapsedTime ?? this.elapsedTime, + ); + } + @override int get hashCode => downloaded.hashCode ^ total.hashCode ^ elapsedTime.hashCode; } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 5e16ee6e..be190366 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -3,6 +3,7 @@ import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui_web' as ui_web; +import 'package:crypto/crypto.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; @@ -154,7 +155,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, - sourceName: 'asset:$name', + sourceName: 'asset%$name', allowDataOwnershipTransfer: true, ); } @@ -187,12 +188,19 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { 'loadDocumentFromData', parameters: {'data': data, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), - sourceName: sourceName ?? 'data', + sourceName: sourceName ?? _sourceNameFromData(data), passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: onDispose, ); + /// Generates a pseudo-unique source name for the given data using its SHA-256 hash. + /// + /// This may be sometimes slow for large data, so it's better to provide a meaningful source name when possible. + static String _sourceNameFromData(Uint8List data) { + return 'data%${sha256.convert(data)}'; + } + @override Future openFile( String filePath, { @@ -204,7 +212,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { 'loadDocumentFromUrl', parameters: {'url': filePath, 'password': password, 'useProgressiveLoading': useProgressiveLoading}, ), - sourceName: filePath, + sourceName: 'file%$filePath', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: null, @@ -245,7 +253,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { 'withCredentials': withCredentials, }, ), - sourceName: uri.toString(), + sourceName: 'uri%$uri', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, onDispose: cleanupCallbacks, diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 5ac75355..30af8f36 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; +import 'package:crypto/crypto.dart'; import 'package:ffi/ffi.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; @@ -213,7 +214,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { void Function()? onDispose, }) => _openData( data, - sourceName ?? 'memory-${data.hashCode}', + sourceName ?? _sourceNameFromData(data), passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, @@ -221,6 +222,13 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { onDispose: onDispose, ); + /// Generates a pseudo-unique source name for the given data using its SHA-256 hash. + /// + /// This may be sometimes slow for large data, so it's better to provide a meaningful source name when possible. + static String _sourceNameFromData(Uint8List data) { + return 'data%${sha256.convert(data)}'; + } + @override Future openFile( String filePath, { @@ -234,7 +242,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); return doc.address; }, (filePath: filePath, password: password)), - sourceName: filePath, + sourceName: 'file%$filePath', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, useProgressiveLoading: useProgressiveLoading, From 575c2d664cf466c7346d56c52c312ed838b277e6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 5 Oct 2025 02:53:03 +0900 Subject: [PATCH 389/663] Ugly workaround for WASM+Safari StringBuffer issue (#483) --- .../lib/src/mock/string_buffer_wrapper.dart | 19 +++++++++++++++++++ .../lib/src/native/string_buffer_wrapper.dart | 8 ++++++++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 5 ++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart create mode 100644 packages/pdfrx_engine/lib/src/native/string_buffer_wrapper.dart diff --git a/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart b/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart new file mode 100644 index 00000000..83309236 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart @@ -0,0 +1,19 @@ +/// A workaround for WASM+Safari StringBuffer issue. +class StringBufferWrapper { + String buffer = ''; + + void write(Object? str) { + buffer += str?.toString() ?? ''; + } + + int get length => buffer.length; + + @override + String toString() => buffer; +} + +/// This is a workaround for WASM+Safari StringBuffer issue (#483). +/// +/// - for native code, use [StringBuffer] directly +/// - for Flutter Web, use this [StringBufferWrapper] that internally uses [String] instead. +StringBufferWrapper createStringBufferForWorkaroundSafariWasm() => StringBufferWrapper(); diff --git a/packages/pdfrx_engine/lib/src/native/string_buffer_wrapper.dart b/packages/pdfrx_engine/lib/src/native/string_buffer_wrapper.dart new file mode 100644 index 00000000..2006005a --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/string_buffer_wrapper.dart @@ -0,0 +1,8 @@ +/// This is a workaround for WASM+Safari StringBuffer issue. For native code, use StringBuffer directly. +typedef StringBufferWrapper = StringBuffer; + +/// This is a workaround for WASM+Safari StringBuffer issue (#483). +/// +/// - for native code, use [StringBuffer] directly +/// - for Flutter Web, use this [StringBufferWrapper] that internally uses [String] instead. +StringBufferWrapper createStringBufferForWorkaroundSafariWasm() => StringBuffer(); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index c95359f3..fb939e3a 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -13,6 +13,7 @@ import 'package:http/http.dart' as http; import 'package:vector_math/vector_math_64.dart' hide Colors; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; +import './mock/string_buffer_wrapper.dart' if (dart.library.io) './native/string_buffer_wrapper.dart'; import 'utils/unmodifiable_list.dart'; /// Class to provide Pdfrx's configuration. @@ -506,7 +507,9 @@ abstract class PdfPage { final inputFullText = raw.fullText; final fragmentsTmp = <({int length, PdfTextDirection direction})>[]; - final outputText = StringBuffer(); + + /// Ugly workaround for WASM+Safari StringBuffer issue (#483). + final outputText = createStringBufferForWorkaroundSafariWasm(); final outputCharRects = []; PdfTextDirection vector2direction(Vector2 v) { From cfc9524c7826e6f26aa9d0cd65b3de6694d9c76a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 5 Oct 2025 03:03:47 +0900 Subject: [PATCH 390/663] WIP --- .../pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart b/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart index 83309236..21024905 100644 --- a/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart +++ b/packages/pdfrx_engine/lib/src/mock/string_buffer_wrapper.dart @@ -16,4 +16,7 @@ class StringBufferWrapper { /// /// - for native code, use [StringBuffer] directly /// - for Flutter Web, use this [StringBufferWrapper] that internally uses [String] instead. -StringBufferWrapper createStringBufferForWorkaroundSafariWasm() => StringBufferWrapper(); +StringBufferWrapper createStringBufferForWorkaroundSafariWasm() { + // FIXME: we need some kind of kIsWeb && kIsSafari check here but it's not easy to do that correctly on pdfrx_engine. + return StringBufferWrapper(); +} From 0e9d980a97919972d1a4d42bc5b2a720dc3da2f1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 5 Oct 2025 03:34:13 +0900 Subject: [PATCH 391/663] Release pdfrx_engine v0.1.21 and pdfrx v2.1.23 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7baeec0e..ac8fe015 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.22 + pdfrx: ^2.1.23 ``` ### For Pure Dart Applications @@ -44,7 +44,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.20 + pdfrx_engine: ^0.1.21 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 2cb36a46..e93da098 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.1.23 + +- FIXED: WASM+Safari StringBuffer issue with workaround ([#483](https://github.com/espresso3389/pdfrx/issues/483)) +- Introduces `PdfDocumentRefKey` for more flexible `PdfDocumentRef` identification +- Updated to pdfrx_engine 0.1.21 + # 2.1.22 - NEW: Introducing [PdfViewerController.zoomOnLocalPosition](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/zoomOnLocalPosition.html) and its variants for consistent zooming on cursor/finger position ([#486](https://github.com/espresso3389/pdfrx/issues/486), [#462](https://github.com/espresso3389/pdfrx/issues/462)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 52627a84..1bc5f89a 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.22 + pdfrx: ^2.1.23 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4b908cda..116c0f99 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.22 +version: 2.1.23 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.20 + pdfrx_engine: ^0.1.21 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 34c04ae9..ea1891a9 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.21 + +- FIXED: WASM+Safari StringBuffer issue with workaround ([#483](https://github.com/espresso3389/pdfrx/issues/483)) +- Introduces `PdfDocumentRefKey` for more flexible `PdfDocumentRef` identification + ## 0.1.20 - Maintenance release to keep version alignment and ensure code integrity alongside pdfrx 2.1.19. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index d0e3b82b..ab7844f5 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.20 +version: 0.1.21 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 69419b468d84128c5bd54ccd63e68b618acfa767 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 02:43:59 +0900 Subject: [PATCH 392/663] #490 --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 13 ++++++------- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a7f0aa98..c46c683c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -454,9 +454,9 @@ class _PdfViewerState extends State key: _interactiveViewerKey, transformationController: _txController, constrained: false, - boundaryMargin: (widget.params.scrollPhysics == null - ? const EdgeInsets.all(double.infinity) - : _adjustedBoundaryMargins), + boundaryMargin: widget.params.scrollPhysics == null + ? const EdgeInsets.all(double.infinity) // NOTE: boundaryMargin is handled manually + : _adjustedBoundaryMargins, maxScale: widget.params.maxScale, minScale: minScale, panAxis: widget.params.panAxis, @@ -563,6 +563,7 @@ class _PdfViewerState extends State } Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { + _adjustBoundaryMargins(_viewSize!, candidate.zoom); final overScroll = _calcOverscroll(candidate, viewSize: viewSize); if (overScroll == Offset.zero) { return candidate; @@ -1523,8 +1524,8 @@ class _PdfViewerState extends State vec.Vector3( zoom, zoom, - zoom, - ), // setting zoom of 1 on z caused a call to matrix.maxScaleOnAxis() to return 1 even when x and y are < 1 + zoom, // setting zoom of 1 on z caused a call to Matrix4.getMaxScaleOnAxis() to return 1 even when x and y are < 1 + ), ); } @@ -1546,8 +1547,6 @@ class _PdfViewerState extends State ); /// The function calculate the rectangle which should be shown in the view. - /// - /// If the rect is smaller than the view size, it will Rect _calcRectForArea({required Rect rect, required PdfPageAnchor anchor}) { final viewSize = _visibleRect.size; final w = min(rect.width, viewSize.width); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 13f5fd94..25a956a8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -164,7 +164,7 @@ class PdfViewerParams { /// See [InteractiveViewer.boundaryMargin] for details. /// - /// The default is `EdgeInsets.all(double.infinity)`. + /// The default is `EdgeInsets.zero`. final EdgeInsets? boundaryMargin; /// Annotation rendering mode. From 3b5152f20b015126c28d88dda32de5a91212dbbd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 15:44:39 +0900 Subject: [PATCH 393/663] #490 --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c46c683c..1428868c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -563,7 +563,9 @@ class _PdfViewerState extends State } Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { - _adjustBoundaryMargins(_viewSize!, candidate.zoom); + if (widget.params.scrollPhysics == null) { + _adjustBoundaryMargins(_viewSize!, candidate.zoom); + } final overScroll = _calcOverscroll(candidate, viewSize: viewSize); if (overScroll == Offset.zero) { return candidate; From 0dd8d2e26949d1f974210bf96ae4b1838500c6a5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 16:03:03 +0900 Subject: [PATCH 394/663] Release pdfrx v2.1.24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ac8fe015..bc419af9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.23 + pdfrx: ^2.1.24 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e93da098..62de464a 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.24 + +- FIXED: Strange zooming out behavior ([#490](https://github.com/espresso3389/pdfrx/issues/490)) + # 2.1.23 - FIXED: WASM+Safari StringBuffer issue with workaround ([#483](https://github.com/espresso3389/pdfrx/issues/483)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 1bc5f89a..f1c81ea4 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.23 + pdfrx: ^2.1.24 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 116c0f99..a839b0f5 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.23 +version: 2.1.24 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From a2f586fa3eafa12cb143994169c30b6d5100d169 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 16:14:09 +0900 Subject: [PATCH 395/663] Update AGENTS.md --- AGENTS.md | 178 ++++++++++++++------------------------------------- RELEASING.md | 57 +++++++++++++++++ 2 files changed, 104 insertions(+), 131 deletions(-) create mode 100644 RELEASING.md diff --git a/AGENTS.md b/AGENTS.md index f198cc99..a4ea022f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,13 @@ This file provides guidance to AI agents and developers when working with code in this repository. +## Quick Start for Agents + +- Keep existing user changes intact; if you notice unexpected edits you didn't make, pause and ask the user how to proceed. +- Prefer fast, non-destructive tools (`rg`, `rg --files`, targeted tests) and run commands with an explicit `workdir`; avoid wandering `cd` commands. +- Leave release artifacts (`CHANGELOG.md`, version numbers, tags) untouched unless the task is explicitly about publishing. +- Default to ASCII output and add only brief clarifying comments when the code is non-obvious. + ## Project Overview pdfrx is a monorepo containing two packages: @@ -17,160 +24,75 @@ pdfrx is a monorepo containing two packages: - Supports iOS, Android, Windows, macOS, Linux, and Web - Uses PDFium for native platforms and PDFium WASM for web platforms -## Development Commands +## Command and Tooling Expectations + +- Run commands directly in the repository environment with the correct `workdir`; coordinate with the user before escalating privileges or leaving the workspace. +- Prefer `rg`/`rg --files` for search and discovery tasks; they are significantly faster than alternatives. +- Use Flutter/Dart tooling for formatting (`dart format`, `flutter format`) and keep the 120 character width. +- On Windows, use `pwsh.exe -Command ...` if a command fails due to script launching quirks. -### Monorepo Management +### Pub Workspace Basics -This project uses pub workspace for managing the multi-package repository. All you have to do is to run `dart pub get` on somewhere in the repo directory. +This project uses a pub workspace. Running `dart pub get` in any directory inside the repository fetches dependencies for all packages. -### Basic Flutter Commands +### Common Commands ```bash -# For the main pdfrx package +# Flutter plugin (packages/pdfrx) cd packages/pdfrx -flutter pub get # Install dependencies -flutter analyze # Run static analysis -flutter test # Run all tests -flutter format . # Format code (120 char line width) +flutter pub get +flutter analyze +flutter test +flutter format . -# For the pdfrx_engine package +# Core engine (packages/pdfrx_engine) cd packages/pdfrx_engine -dart pub get # Install dependencies -dart analyze # Run static analysis -dart test # Run all tests -dart format . # Format code (120 char line width) +dart pub get +dart analyze +dart test +dart format . ``` -### Platform-Specific Builds +### Platform Builds ```bash -# Example app cd packages/pdfrx/example/viewer -flutter run # Run on connected device/emulator -flutter build appbundle # Build Android App Bundle -flutter build ios # Build iOS (requires macOS) -flutter build web --wasm # Build for web -flutter build linux # Build for Linux -flutter build windows # Build for Windows -flutter build macos # Build for macOS +flutter run +flutter build appbundle +flutter build ios +flutter build web --wasm +flutter build linux +flutter build windows +flutter build macos ``` -### FFI Bindings Generation (pdfrx_engine) +### FFI Bindings (pdfrx_engine) -- FFI bindings for PDFium are generated using `ffigen` in the pdfrx_engine package. -- FFI bindings depends on the Pdfium headers which are downloaded during `dart test` on pdfrx_engine (Linux only). +- Bindings are generated with `ffigen`. +- Pdfium headers download automatically when `dart test` runs on Linux. ```bash -# Run on Linux cd packages/pdfrx_engine dart test -dart run ffigen # Regenerate PDFium FFI bindings +dart run ffigen ``` ## Release Process -Both packages may need to be released when changes are made: - -### For pdfrx_engine package updates - -1. Update version in `packages/pdfrx_engine/pubspec.yaml` - - Basically, if the changes are not breaking (or relatively small breaking changes), increment the patch version (X.Y.Z -> X.Y.Z+1) - - If there are breaking changes, increment the minor version (X.Y.Z -> X.Y+1.0) - - If there are major changes, increment the major version (X.Y.Z -> X+1.0.0) -2. Update `packages/pdfrx_engine/CHANGELOG.md` with changes - - Don't mention CI/CD changes and `CLAUDE.md`/`AGENTS.md` related changes (unless they are significant) -3. Update `packages/pdfrx_engine/README.md` if needed -4. Update `README.md` on the repo root if needed -5. Run `pana` in `packages/pdfrx_engine` to validate code integrity -6. Run `dart pub publish` in `packages/pdfrx_engine/` - -### For pdfrx package updates - -1. Update version in `packages/pdfrx/pubspec.yaml` - - If pdfrx_engine was updated, update the dependency version -2. Update `packages/pdfrx/CHANGELOG.md` with changes -3. Update `packages/pdfrx/README.md` with new version information - - Changes version in example fragments - - Consider to add notes for new features or breaking changes - - Notify the owner if you find any issues with the example app or documentation -4. Update `README.md` on the repo root if needed -5. Run `dart pub get` to update all dependencies -6. Run tests to ensure everything works - - Run `dart test` in `packages/pdfrx_engine/` - - Run `flutter test` in `packages/pdfrx/` -7. Ensure the example app builds correctly - - Run `flutter build web --wasm` in `packages/pdfrx/example/viewer` to test the example app -8. Run `pana` in `packages/pdfrx` (and any other packages being released) to validate code integrity - - Warn me if `pana` warns about WASM incompatibility issues -9. Commit changes with message "Release pdfrx vX.Y.Z" or "Release pdfrx_engine vX.Y.Z" -10. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z` -11. Push changes and tags to remote -12. Run `flutter pub publish` in `packages/pdfrx/` -13. If the changes reference GitHub issues or PRs, add comments on them notifying about the new release - - Use `gh issue comment` or `gh pr comment` to notify that the issue/PR has been addressed in the new release - - If the PR references issues, please also comment on the issues - - Follow the template below for comments (but modify it as needed): - - ```md - The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). - - ...Fix/update summary... - - Written by [AGENT SIGNATURE] - ``` - - - Focus on the release notes and what was fixed/changed rather than upgrade instructions - - Include a link to the changelog for the specific version - -## Architecture Overview - -### Package Architecture - -The project is split into two packages with clear separation of concerns: +See `RELEASING.md` for the full checklist. Agents should avoid editing release metadata unless the task explicitly covers publishing. -#### pdfrx_engine (`packages/pdfrx_engine/`) +- Never bump versions or changelog entries preemptively. +- Surface blockers or uncertainties to the user before continuing a release flow. -- Platform-agnostic PDF rendering engine -- Conditional imports to support different platforms: - - `lib/src/native/` - Native platform implementation using PDFium via FFI - - `lib/src/web/` - Web implementation using PDFium WASM - - Platform-specific code determined at import time based on `dart:library.io` availability -- Main exports: - - `pdf_api.dart` - Core PDF document interfaces - -#### pdfrx (`packages/pdfrx/`) - -- Flutter plugin built on top of pdfrx_engine -- Contains all Flutter-specific code: - - Widget layer - - Platform channel implementations - - UI components and overlays - -### Core Components - -1. **Document API** (in `packages/pdfrx_engine/lib/src/pdf_api.dart`) - - `PdfDocument` - Main document interface - - `PdfPage` - Page representation - - `PdfDocumentRef` - Reference counting for document lifecycle - - Platform-agnostic interfaces implemented differently per platform - -2. **Widget Layer** (in `packages/pdfrx/lib/src/widgets/`) - - `PdfViewer` - Main viewer widget with multiple constructors - - `PdfPageView` - Single page display - - `PdfDocumentViewBuilder` - Safe document loading pattern - - Overlay widgets for text selection, links, search +## Architecture Overview -3. **Native Integration** - - pdfrx_engine uses Dart FFI for PDFium integration - - Native code in `packages/pdfrx_engine/src/pdfium_interop.cpp` - - Platform folders in `packages/pdfrx/` contain Flutter plugin build configurations +For architectural details and API surface breakdowns, refer to: -### Key Patterns +- `README.md` for a high-level overview of both packages. +- `packages/pdfrx_engine/README.md` for engine internals and FFI notes. +- `packages/pdfrx/README.md` for Flutter plugin structure, widgets, and overlays. -- **Factory Pattern**: `PdfDocumentFactory` creates platform-specific implementations -- **Builder Pattern**: `PdfDocumentViewBuilder` for safe async document loading -- **Overlay System**: Composable overlays for text, links, annotations -- **Conditional Imports**: Web vs native determined at compile time +These documents live alongside the code and stay in sync with implementation changes. ## Testing @@ -263,12 +185,6 @@ The following guidelines should be followed when writing documentation including - Use sections for different versions - Use bullet points for changes -## Command Execution Guidelines - -- Run commands directly in the repository environment; do not rely on any agent sandbox when executing them. -- If a command cannot be executed without sandboxing, pause and coordinate with the user so it runs on their machine as needed. -- On Windows, use `pwsh.exe -Command ...` to run any commands to reduce issues caused by missing .bat/.cmd and shebang on shell-scripts - ## Special Notes - `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..eb0451e2 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,57 @@ +# Releasing pdfrx and pdfrx_engine + +This guide covers the full release checklist for both packages in the monorepo. Follow the steps that apply to the package you are releasing. + +## pdfrx_engine Releases + +1. Update the version in `packages/pdfrx_engine/pubspec.yaml`. + - For non-breaking or small breaking changes, bump the patch version (`X.Y.Z -> X.Y.Z+1`). + - For breaking changes, bump the minor version (`X.Y.Z -> X.Y+1.0`). + - For major changes, bump the major version (`X.Y.Z -> X+1.0.0`). +2. Update `packages/pdfrx_engine/CHANGELOG.md` with user-facing changes. + - Skip CI/CD updates and meta-doc changes (`CLAUDE.md`, `AGENTS.md`) unless significant. +3. Update `packages/pdfrx_engine/README.md` (at least, the versions hard-coded on it). +4. Update the root `README.md` if necessary. +5. Run `pana` inside `packages/pdfrx_engine` to validate the package. +6. Publish with `dart pub publish` inside `packages/pdfrx_engine/`. + +## pdfrx Releases + +1. Update the version in `packages/pdfrx/pubspec.yaml`. + - If `pdfrx_engine` was updated, update the dependency version here as well. +2. Update `packages/pdfrx/CHANGELOG.md` with user-facing changes. +3. Update `packages/pdfrx/README.md` with the new version. + - Update version numbers in sample snippets. + - Note new features or breaking changes when relevant. + - Report any issues found in the example app or documentation to the owner. +4. Update the root `README.md` (at least, the versions hard-coded on it). +5. Run `dart pub get` to refresh dependencies. +6. Run tests: + - `dart test` inside `packages/pdfrx_engine/`. + - `flutter test` inside `packages/pdfrx/`. +7. Validate the example app builds: `flutter build web --wasm` in `packages/pdfrx/example/viewer`. +8. Run `pana` in `packages/pdfrx` (and other packages being released) to validate code integrity. + - Flag any WASM compatibility warnings emitted by `pana`. +9. Commit changes with `Release pdfrx vX.Y.Z` or `Release pdfrx_engine vX.Y.Z`. +10. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z`. +11. Push commits and tags. +12. Publish with `flutter pub publish` inside `packages/pdfrx/`. +13. Comment on related GitHub issues/PRs once the release is live. + - Use `gh issue comment` or `gh pr comment` as appropriate. + - If a PR references issues, comment on those issues as well. + - Template: + + ```md + The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). + + ...Fix/update summary... + + Written by [AGENT SIGNATURE] + ``` + + - Focus on release notes and what changed; link to the version-specific changelog entry. + +## General Notes + +- Keep `CHANGELOG.md` entries user-focused and concise. +- Coordinate with the repository owner if any release blockers appear. From 7311088bf47cb10fa57395b1efcf283698ebf827 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 16:36:42 +0900 Subject: [PATCH 396/663] #405, #493: After considering a little, I finally decided to add a code to workaround the server issue but it also show warning on Chrome's console. --- packages/pdfrx/assets/pdfium_worker.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index e37a13b1..c0397c62 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1606,10 +1606,22 @@ async function initializePdfium(params = {}) { fetchOptions.headers = params.headers; } - const result = await WebAssembly.instantiateStreaming(fetch(pdfiumWasmUrl, fetchOptions), { - env: emEnv, - wasi_snapshot_preview1: wasi, - }); + let result; + try { + result = await WebAssembly.instantiateStreaming(fetch(pdfiumWasmUrl, fetchOptions), { + env: emEnv, + wasi_snapshot_preview1: wasi, + }); + } catch (e) { + // Fallback for browsers that do not support instantiateStreaming + console.warn('%cWebAssembly.instantiateStreaming failed, falling back to ArrayBuffer instantiation. Consider to configure your server to serve wasm files as application/wasm', 'background: red; color: white', e); + const response = await fetch(pdfiumWasmUrl, fetchOptions); + const buffer = await response.arrayBuffer(); + result = await WebAssembly.instantiate(buffer, { + env: emEnv, + wasi_snapshot_preview1: wasi, + }); + } Pdfium.initWith(result.instance.exports); Pdfium.wasmExports.FPDF_InitLibrary(); From 9868c478148444a8f141d52ca5480c1727b8e661 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 6 Oct 2025 16:57:27 +0900 Subject: [PATCH 397/663] Release pdfrx v2.1.25 --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 3 +-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bc419af9..a1a0cf6f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.24 + pdfrx: ^2.1.25 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 62de464a..4b7408bf 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.25 + +- FIXED: Added ArrayBuffer fallback when `WebAssembly.instantiateStreaming` fails (e.g. missing `application/wasm` MIME type) ([#405](https://github.com/espresso3389/pdfrx/issues/405), [#493](https://github.com/espresso3389/pdfrx/issues/493)) + # 2.1.24 - FIXED: Strange zooming out behavior ([#490](https://github.com/espresso3389/pdfrx/issues/490)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index f1c81ea4..3bb320c0 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.24 + pdfrx: ^2.1.25 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index a839b0f5..b36e8d28 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.24 +version: 2.1.25 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -57,4 +57,3 @@ flutter: assets: - assets/ - From e007d57fae7b9c958b5bca37e42722a1e00bb283 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:55:22 +0000 Subject: [PATCH 398/663] Bump softprops/action-gh-release from 2.3.3 to 2.3.4 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.3 to 2.3.4. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/6cbd405e2c4e67a21c47fa9e383d020e4e28b836...62c96d0c4e8a889135c1f3a25910db8dbe0e85f7) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.3.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 4b384244..b9dccf80 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -35,7 +35,7 @@ jobs: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v0.1.15 + uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From ff338bc3c6b747672c6332c71b50aea4188cfda5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 10 Oct 2025 03:49:20 +0900 Subject: [PATCH 399/663] WIP --- .vscode/settings.json | 1 + .../pdfrx/example/viewer/ios/Podfile.lock | 7 + packages/pdfrx/example/viewer/lib/main.dart | 4 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + .../pdfrx/example/viewer/macos/Podfile.lock | 7 + packages/pdfrx/example/viewer/pubspec.yaml | 2 + packages/pdfrx_coregraphics/.gitignore | 33 + packages/pdfrx_coregraphics/.metadata | 33 + packages/pdfrx_coregraphics/CHANGELOG.md | 3 + packages/pdfrx_coregraphics/LICENSE | 11 + packages/pdfrx_coregraphics/README.md | 48 ++ .../pdfrx_coregraphics/analysis_options.yaml | 4 + .../pdfrx_coregraphics/darwin/Assets/.gitkeep | 0 .../Classes/PdfrxCoregraphicsPlugin.swift | 224 ++++++ .../darwin/Resources/PrivacyInfo.xcprivacy | 14 + .../darwin/pdfrx_coregraphics.podspec | 37 + .../lib/pdfrx_coregraphics.dart | 691 ++++++++++++++++++ packages/pdfrx_coregraphics/pubspec.yaml | 30 + pubspec.yaml | 1 + 19 files changed, 1152 insertions(+) create mode 100644 packages/pdfrx_coregraphics/.gitignore create mode 100644 packages/pdfrx_coregraphics/.metadata create mode 100644 packages/pdfrx_coregraphics/CHANGELOG.md create mode 100644 packages/pdfrx_coregraphics/LICENSE create mode 100644 packages/pdfrx_coregraphics/README.md create mode 100644 packages/pdfrx_coregraphics/analysis_options.yaml create mode 100644 packages/pdfrx_coregraphics/darwin/Assets/.gitkeep create mode 100644 packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift create mode 100644 packages/pdfrx_coregraphics/darwin/Resources/PrivacyInfo.xcprivacy create mode 100644 packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec create mode 100644 packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart create mode 100644 packages/pdfrx_coregraphics/pubspec.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json index fb2d570e..88ad2eed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "COLORTYPE", "COMBOBOX", "contextmenu", + "coregraphics", "credentialless", "Cupertino", "cwrap", diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 9f8e38dc..797d8061 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -8,6 +8,9 @@ PODS: - pdfrx (0.0.8): - Flutter - FlutterMacOS + - pdfrx_coregraphics (0.0.1): + - Flutter + - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter @@ -16,6 +19,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) + - pdfrx_coregraphics (from `.symlinks/plugins/pdfrx_coregraphics/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: @@ -27,6 +31,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" pdfrx: :path: ".symlinks/plugins/pdfrx/darwin" + pdfrx_coregraphics: + :path: ".symlinks/plugins/pdfrx_coregraphics/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" @@ -35,6 +41,7 @@ SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 + pdfrx_coregraphics: 3d4ab0240897ec65b4e41d0de1a51eddf7abe90d url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index fba3bb1b..b87e1231 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_coregraphics/pdfrx_coregraphics.dart'; import 'package:url_launcher/url_launcher.dart'; import 'markers_view.dart'; @@ -15,6 +16,9 @@ import 'search_view.dart'; import 'thumbnails_view.dart'; void main(List args) { + WidgetsFlutterBinding.ensureInitialized(); + PdfrxEntryFunctions.instance = PdfrxCoreGraphicsEntryFunctions(); + pdfrxFlutterInitialize(); runApp(MyApp(fileOrUri: args.isNotEmpty ? args[0] : null)); } diff --git a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift index 05b351d6..86ff0ffb 100644 --- a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import file_selector_macos import path_provider_foundation +import pdfrx_coregraphics import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PdfrxCoregraphicsPlugin.register(with: registry.registrar(forPlugin: "PdfrxCoregraphicsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 62bff544..688800ef 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -8,6 +8,9 @@ PODS: - pdfrx (0.0.8): - Flutter - FlutterMacOS + - pdfrx_coregraphics (0.0.1): + - Flutter + - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS @@ -16,6 +19,7 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) + - pdfrx_coregraphics (from `Flutter/ephemeral/.symlinks/plugins/pdfrx_coregraphics/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: @@ -27,6 +31,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin pdfrx: :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin + pdfrx_coregraphics: + :path: Flutter/ephemeral/.symlinks/plugins/pdfrx_coregraphics/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos @@ -35,6 +41,7 @@ SPEC CHECKSUMS: FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 + pdfrx_coregraphics: 3d4ab0240897ec65b4e41d0de1a51eddf7abe90d url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 902ed5ab..0e5e03ee 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -15,6 +15,8 @@ dependencies: pdfrx: path: ../../ + pdfrx_coregraphics: + path: ../../../pdfrx_coregraphics cupertino_icons: ^1.0.8 rxdart: ^0.28.0 diff --git a/packages/pdfrx_coregraphics/.gitignore b/packages/pdfrx_coregraphics/.gitignore new file mode 100644 index 00000000..b9d7f25b --- /dev/null +++ b/packages/pdfrx_coregraphics/.gitignore @@ -0,0 +1,33 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +/build/ +/coverage/ diff --git a/packages/pdfrx_coregraphics/.metadata b/packages/pdfrx_coregraphics/.metadata new file mode 100644 index 00000000..7b98f660 --- /dev/null +++ b/packages/pdfrx_coregraphics/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "9f455d2486bcb28cad87b062475f42edc959f636" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + base_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + - platform: ios + create_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + base_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + - platform: macos + create_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + base_revision: 9f455d2486bcb28cad87b062475f42edc959f636 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md new file mode 100644 index 00000000..7dc69ab5 --- /dev/null +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +- Initial CoreGraphics-backed Pdfrx entry implementation for iOS/macOS diff --git a/packages/pdfrx_coregraphics/LICENSE b/packages/pdfrx_coregraphics/LICENSE new file mode 100644 index 00000000..575416cb --- /dev/null +++ b/packages/pdfrx_coregraphics/LICENSE @@ -0,0 +1,11 @@ + +The MIT License (MIT) +=============== + +Copyright (c) 2018 @espresso3389 (Takashi Kawasaki) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md new file mode 100644 index 00000000..4720be0d --- /dev/null +++ b/packages/pdfrx_coregraphics/README.md @@ -0,0 +1,48 @@ +# pdfrx_coregraphics + +CoreGraphics-backed renderer for [pdfrx](https://pub.dev/packages/pdfrx) on iOS and macOS. + +This plugin provides a `PdfrxEntryFunctions` implementation that uses PDFKit/CoreGraphics instead of the bundled PDFium +runtime. It is intended for teams that prefer the system PDF stack on Apple platforms while keeping the pdfrx widget +API. + +## Installation + +Add the package to your Flutter app: + +```yaml +dependencies: + pdfrx: any + pdfrx_coregraphics: + path: ../packages/pdfrx_coregraphics +``` + +Call `installPdfrxCoreGraphics()` before interacting with pdfrx: + +```dart +import 'package:flutter/material.dart'; +import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfrx_coregraphics/pdfrx_coregraphics.dart'; + +void main() { + installPdfrxCoreGraphics(); + runApp(const MyApp()); +} +``` + +After installation, use pdfrx as usual. All `PdfDocument` and widget APIs continue to work, but rendering is routed +through CoreGraphics. + +## Current capabilities + +- Document loading from files, memory buffers, and URIs +- Page rendering with background color control and annotation drawing +- Basic outline, text, and link support fall back to pdfrx defaults when not available + +## Limitations + +- Incremental/custom stream loading is converted to in-memory loading +- Custom font registration is not yet supported +- Text extraction and outline/link metadata currently fall back to empty results + +Contributions and issue reports are welcome. diff --git a/packages/pdfrx_coregraphics/analysis_options.yaml b/packages/pdfrx_coregraphics/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/pdfrx_coregraphics/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/pdfrx_coregraphics/darwin/Assets/.gitkeep b/packages/pdfrx_coregraphics/darwin/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift new file mode 100644 index 00000000..ebefa803 --- /dev/null +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -0,0 +1,224 @@ +import Foundation +import PDFKit + +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#endif + +public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { + private var nextHandle: Int64 = 1 + private var documents: [Int64: PDFDocument] = [:] + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "pdfrx_coregraphics", + binaryMessenger: registrar.pdfrxCoreGraphicsMessenger + ) + let instance = PdfrxCoregraphicsPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "initialize": + result(nil) + case "openDocument": + openDocument(arguments: call.arguments, result: result) + case "renderPage": + renderPage(arguments: call.arguments, result: result) + case "closeDocument": + closeDocument(arguments: call.arguments, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + private func openDocument(arguments: Any?, result: @escaping FlutterResult) { + guard let args = arguments as? [String: Any] else { + result(FlutterError(code: "bad-arguments", message: "Invalid arguments for openDocument.", details: nil)) + return + } + guard let sourceType = args["sourceType"] as? String else { + result(FlutterError(code: "missing-source", message: "sourceType is required.", details: nil)) + return + } + let password = args["password"] as? String + + let document: PDFDocument? + switch sourceType { + case "file": + guard let path = args["path"] as? String else { + result(FlutterError(code: "missing-path", message: "File path is required for openDocument.", details: nil)) + return + } + document = PDFDocument(url: URL(fileURLWithPath: path)) + case "bytes": + guard let data = args["bytes"] as? FlutterStandardTypedData else { + result(FlutterError(code: "missing-bytes", message: "PDF bytes are required for openDocument.", details: nil)) + return + } + document = PDFDocument(data: data.data) + default: + result(FlutterError(code: "unsupported-source", message: "Unsupported sourceType \(sourceType).", details: nil)) + return + } + + guard let pdfDocument = document else { + result(FlutterError(code: "open-failed", message: "Failed to open PDF document.", details: nil)) + return + } + + if pdfDocument.isLocked { + let candidatePassword = password ?? "" + if !pdfDocument.unlock(withPassword: candidatePassword) || pdfDocument.isLocked { + result(FlutterError(code: "wrong-password", message: "Password is required or incorrect.", details: nil)) + return + } + } + + guard pdfDocument.pageCount > 0 else { + result(FlutterError(code: "empty-document", message: "PDF document does not contain any pages.", details: nil)) + return + } + + let handle = nextHandle + nextHandle += 1 + documents[handle] = pdfDocument + + var pageInfos: [[String: Any]] = [] + for index in 0..= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { + result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + return + } + + let x = args["x"] as? Int ?? 0 + let y = args["y"] as? Int ?? 0 + let backgroundColor = args["backgroundColor"] as? Int ?? 0xffffffff + let renderAnnotations = args["renderAnnotations"] as? Bool ?? true + + guard width > 0, height > 0, fullWidth > 0, fullHeight > 0 else { + result(FlutterError(code: "invalid-size", message: "Invalid render dimensions.", details: nil)) + return + } + + let bytesPerPixel = 4 + let bytesPerRow = width * bytesPerPixel + let dataSize = bytesPerRow * height + + guard + let context = CGContext( + data: nil, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue + ) + else { + result(FlutterError(code: "context-failure", message: "Failed to create bitmap context.", details: nil)) + return + } + + context.setBlendMode(.normal) + context.interpolationQuality = .high + let components = colorComponents(from: backgroundColor) + context.setFillColor(red: components.red, green: components.green, blue: components.blue, alpha: components.alpha) + context.fill(CGRect(x: 0, y: 0, width: width, height: height)) + + let bounds = page.bounds(for: .mediaBox) + let scaleX = CGFloat(fullWidth) / bounds.width + let scaleY = CGFloat(fullHeight) / bounds.height + let pdfX = CGFloat(x) + let pdfBottom = CGFloat(fullHeight - (y + height)) + + context.translateBy(x: -pdfX, y: -pdfBottom) + context.scaleBy(x: scaleX, y: scaleY) + + let originalDisplaysAnnotations = page.displaysAnnotations + page.displaysAnnotations = renderAnnotations + page.draw(with: .mediaBox, to: context) + page.displaysAnnotations = originalDisplaysAnnotations + + guard let contextData = context.data else { + result(FlutterError(code: "render-failure", message: "Failed to access rendered bitmap.", details: nil)) + return + } + + let buffer = Data(bytes: contextData, count: dataSize) + result([ + "width": width, + "height": height, + "pixels": FlutterStandardTypedData(bytes: buffer), + ]) + } + + private func closeDocument(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let handleValue = args["handle"] + else { + result(FlutterError(code: "bad-arguments", message: "Invalid arguments for closeDocument.", details: nil)) + return + } + let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) + documents.removeValue(forKey: handle) + result(nil) + } + + private func colorComponents(from argb: Int) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + let value = UInt32(bitPattern: Int32(truncatingIfNeeded: argb)) + let alpha = CGFloat((value >> 24) & 0xff) / 255.0 + let red = CGFloat((value >> 16) & 0xff) / 255.0 + let green = CGFloat((value >> 8) & 0xff) / 255.0 + let blue = CGFloat(value & 0xff) / 255.0 + return (red, green, blue, alpha) + } +} + +private extension FlutterPluginRegistrar { + #if os(iOS) + var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger() + } + #elseif os(macOS) + var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger + } + #endif +} diff --git a/packages/pdfrx_coregraphics/darwin/Resources/PrivacyInfo.xcprivacy b/packages/pdfrx_coregraphics/darwin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..a34b7e2e --- /dev/null +++ b/packages/pdfrx_coregraphics/darwin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec new file mode 100644 index 00000000..8d33e4a6 --- /dev/null +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec @@ -0,0 +1,37 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint pdfrx_coregraphics.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'pdfrx_coregraphics' + s.version = '0.0.1' + s.summary = 'CoreGraphics-backed renderer for pdfrx on Apple platforms.' + s.description = <<-DESC +Provides a PdfrxEntryFunctions implementation that uses PDFKit/CoreGraphics instead of PDFium. + DESC + s.homepage = 'https://github.com/espresso3389/pdfrx' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } + s.source = { :path => '.' } + s.source_files = [ 'Classes/PdfrxCoregraphicsPlugin.swift' ] + + s.ios.deployment_target = '13.0' + s.ios.dependency 'Flutter' + # Flutter.framework does not contain a i386 slice. + s.ios.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + } + + s.osx.deployment_target = '10.13' + s.osx.dependency 'FlutterMacOS' + s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + + s.swift_version = '5.0' + + # If your plugin requires a privacy manifest, for example if it uses any + # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your + # plugin's privacy impact, and then uncomment this line. For more information, + # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + # s.resource_bundles = {'pdfrx_coregraphics_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart new file mode 100644 index 00000000..2102be84 --- /dev/null +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -0,0 +1,691 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +const _kPasswordErrorCode = 'wrong-password'; + +/// CoreGraphics backed implementation of [PdfrxEntryFunctions]. +class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { + static final _channel = MethodChannel('pdfrx_coregraphics'); + static bool _initialized = false; + + PdfrxCoreGraphicsEntryFunctions(); + + @override + Future initPdfium() async { + if (_initialized) { + return; + } + try { + await _channel.invokeMethod('initialize'); + } on MissingPluginException { + // Older platform code may not implement an explicit initializer; that's fine. + } + _initialized = true; + } + + @override + Future suspendPdfiumWorkerDuringAction( + FutureOr Function() action, + ) async { + return await Future.sync(action); + } + + @override + Future openAsset( + String name, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) async { + if (Pdfrx.loadAsset == null) { + throw StateError( + 'Pdfrx.loadAsset is not set. Please set it before calling openAsset.', + ); + } + final data = await Pdfrx.loadAsset!(name); + return openData( + data, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + sourceName: 'asset:$name', + ); + } + + @override + Future openData( + Uint8List data, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + String? sourceName, + bool allowDataOwnershipTransfer = false, + bool useProgressiveLoading = false, + void Function()? onDispose, + }) async { + await initPdfium(); + sourceName ??= _sourceNameFromData(data); + return _openWithPassword( + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + sourceName: sourceName, + useProgressiveLoading: useProgressiveLoading, + onDispose: onDispose, + opener: (password) async { + final result = await _channel + .invokeMapMethod('openDocument', { + 'sourceType': 'bytes', + 'bytes': data, + 'password': password, + 'sourceName': sourceName, + }); + return result; + }, + ); + } + + static String _sourceNameFromData(Uint8List data) { + final hash = data.fold( + 0, + (value, element) => ((value * 31) ^ element) & 0xFFFFFFFF, + ); + return 'memory:$hash'; + } + + @override + Future openFile( + String filePath, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) async { + await initPdfium(); + return _openWithPassword( + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + sourceName: 'file:$filePath', + useProgressiveLoading: useProgressiveLoading, + opener: (password) => + _channel.invokeMapMethod('openDocument', { + 'sourceType': 'file', + 'path': filePath, + 'password': password, + 'sourceName': 'file:$filePath', + }), + ); + } + + @override + Future openCustom({ + required FutureOr Function(Uint8List buffer, int position, int size) + read, + required int fileSize, + required String sourceName, + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + int? maxSizeToCacheOnMemory, + void Function()? onDispose, + }) async { + final buffer = Uint8List(fileSize); + final chunk = Uint8List(min(fileSize, 128 * 1024)); + var offset = 0; + while (offset < fileSize) { + final currentSize = min(chunk.length, fileSize - offset); + final readCount = await read(chunk, offset, currentSize); + if (readCount <= 0) { + throw PdfException( + 'Unexpected end of custom stream. Expected $currentSize bytes at offset $offset.', + ); + } + buffer.setRange(offset, offset + readCount, chunk); + offset += readCount; + } + return openData( + buffer, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + sourceName: sourceName, + onDispose: onDispose, + ); + } + + @override + Future openUri( + Uri uri, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + PdfDownloadProgressCallback? progressCallback, + bool preferRangeAccess = false, + Map? headers, + bool withCredentials = false, + }) async { + final clientFactory = Pdfrx.createHttpClient ?? () => http.Client(); + final client = clientFactory(); + try { + final request = http.Request('GET', uri); + if (headers != null && headers.isNotEmpty) { + request.headers.addAll(headers); + } + final response = await client.send(request); + if (response.statusCode < 200 || response.statusCode >= 300) { + throw PdfException( + 'Failed to download PDF from $uri (HTTP ${response.statusCode}).', + ); + } + final total = response.contentLength; + progressCallback?.call(0, total); + + final builder = BytesBuilder(copy: false); + var downloaded = 0; + await for (final chunk in response.stream) { + builder.add(chunk); + downloaded += chunk.length; + progressCallback?.call(downloaded, total); + } + final data = builder.takeBytes(); + return openData( + data, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + sourceName: uri.toString(), + ); + } finally { + client.close(); + } + } + + @override + Future reloadFonts() async { + // CoreGraphics reuses system font registrations; nothing to do. + } + + @override + Future addFontData({ + required String face, + required Uint8List data, + }) async { + // Custom font registration is not currently supported by the CoreGraphics bridge. + } + + @override + Future clearAllFontData() async { + // Custom font registration is not currently supported by the CoreGraphics bridge. + } + + Future _openWithPassword({ + required PdfPasswordProvider? passwordProvider, + required bool firstAttemptByEmptyPassword, + required String sourceName, + required bool useProgressiveLoading, + required Future?> Function(String? password) opener, + void Function()? onDispose, + }) async { + for (var attempt = 0; ; attempt++) { + final String? password; + if (firstAttemptByEmptyPassword && attempt == 0) { + password = null; + } else { + password = await passwordProvider?.call(); + if (password == null) { + throw const PdfPasswordException( + 'No password supplied by PasswordProvider.', + ); + } + } + try { + final result = await opener(password); + if (result == null) { + throw const PdfException('Failed to open PDF document.'); + } + return _CoreGraphicsPdfDocument.fromPlatformMap( + channel: _channel, + result: result, + sourceName: sourceName, + useProgressiveLoading: useProgressiveLoading, + onDispose: onDispose, + ); + } on PlatformException catch (e) { + if (e.code == _kPasswordErrorCode) { + // try again with the next password + continue; + } + rethrow; + } + } + } +} + +class _CoreGraphicsPdfDocument extends PdfDocument { + _CoreGraphicsPdfDocument._({ + required this.channel, + required this.handle, + required super.sourceName, + required this.isEncrypted, + required this.permissions, + required this.useProgressiveLoading, + this.onDispose, + }) : subject = StreamController.broadcast(); + + factory _CoreGraphicsPdfDocument.fromPlatformMap({ + required MethodChannel channel, + required Map result, + required String sourceName, + required bool useProgressiveLoading, + void Function()? onDispose, + }) { + final map = result.cast(); + final handle = + map['handle'] as int? ?? + (throw const PdfException( + 'Platform response missing document handle.', + )); + final isEncrypted = map['isEncrypted'] as bool? ?? false; + final permissionsValue = map['permissions'] as Map?; + final permissions = permissionsValue == null + ? null + : PdfPermissions( + (permissionsValue['flags'] as int?) ?? 0, + (permissionsValue['revision'] as int?) ?? -1, + ); + final doc = _CoreGraphicsPdfDocument._( + channel: channel, + handle: handle, + sourceName: sourceName, + isEncrypted: isEncrypted, + permissions: permissions, + useProgressiveLoading: useProgressiveLoading, + onDispose: onDispose, + ); + final pageInfos = (map['pages'] as List? ?? const []) + .map((e) => (e as Map).cast()) + .toList(growable: false); + doc._pages = List.unmodifiable([ + for (var i = 0; i < pageInfos.length; i++) + _CoreGraphicsPdfPage( + document: doc, + index: i, + width: (pageInfos[i]['width'] as num?)?.toDouble() ?? 0.0, + height: (pageInfos[i]['height'] as num?)?.toDouble() ?? 0.0, + rotation: _rotationFromDegrees(pageInfos[i]['rotation'] as int? ?? 0), + ), + ]); + return doc; + } + + static PdfPageRotation _rotationFromDegrees(int degrees) { + switch (degrees % 360) { + case 90: + return PdfPageRotation.clockwise90; + case 180: + return PdfPageRotation.clockwise180; + case 270: + return PdfPageRotation.clockwise270; + default: + return PdfPageRotation.none; + } + } + + final MethodChannel channel; + final int handle; + final bool useProgressiveLoading; + final void Function()? onDispose; + final StreamController subject; + bool _disposed = false; + + List _pages = const []; + + @override + final bool isEncrypted; + + @override + final PdfPermissions? permissions; + + @override + Stream get events => subject.stream; + + @override + List get pages => _pages; + + @override + Future dispose() async { + if (_disposed) { + return; + } + _disposed = true; + subject.close(); + onDispose?.call(); + try { + await channel.invokeMethod('closeDocument', {'handle': handle}); + } on MissingPluginException { + // Ignore if the platform does not provide explicit disposal. + } + } + + @override + Future loadPagesProgressively({ + PdfPageLoadingCallback? onPageLoadProgress, + T? data, + Duration loadUnitDuration = const Duration(milliseconds: 250), + }) async { + if (onPageLoadProgress != null) { + await onPageLoadProgress(_pages.length, _pages.length, data); + } + } + + @override + Future> loadOutline() async { + try { + final result = await channel.invokeListMethod('loadOutline', { + 'handle': handle, + }); + if (result == null) { + return const []; + } + return _parseOutline(result); + } on MissingPluginException { + return const []; + } + } + + List _parseOutline(List nodes) { + return List.unmodifiable( + nodes.map((node) { + final map = (node as Map).cast(); + return PdfOutlineNode( + title: map['title'] as String? ?? '', + dest: _parseDest(map['dest'] as Map?), + children: _parseOutline( + (map['children'] as List?) ?? const [], + ), + ); + }), + ); + } + + PdfDest? _parseDest(Map? dest) { + if (dest == null) { + return null; + } + final map = dest.cast(); + final page = map['page'] as int? ?? 1; + final params = (map['params'] as List?) + ?.map((value) => (value as num?)?.toDouble()) + .toList(growable: false); + final commandName = map['command'] as String?; + final command = commandName == null + ? PdfDestCommand.unknown + : PdfDestCommand.parse(commandName); + return PdfDest(page, command, params); + } + + @override + bool isIdenticalDocumentHandle(Object? other) { + return other is _CoreGraphicsPdfDocument && other.handle == handle; + } +} + +class _CoreGraphicsPdfPage extends PdfPage { + _CoreGraphicsPdfPage({ + required _CoreGraphicsPdfDocument document, + required this.index, + required double width, + required double height, + required PdfPageRotation rotation, + }) : _document = document, + _width = width, + _height = height, + _rotation = rotation; + + final _CoreGraphicsPdfDocument _document; + final int index; + final double _width; + final double _height; + final PdfPageRotation _rotation; + + @override + PdfDocument get document => _document; + + @override + double get width => _width; + + @override + double get height => _height; + + @override + PdfPageRotation get rotation => _rotation; + + @override + int get pageNumber => index + 1; + + @override + bool get isLoaded => true; + + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfAnnotationRenderingMode annotationRenderingMode = + PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) async { + if (_document._disposed) { + return null; + } + final token = cancellationToken as _CoreGraphicsCancellationToken?; + if (token?.isCanceled == true) { + return null; + } + + final targetFullWidth = max(1, (fullWidth ?? width ?? this.width).round()); + final targetFullHeight = max( + 1, + (fullHeight ?? height ?? this.height).round(), + ); + final targetWidth = max(1, (width ?? targetFullWidth)); + final targetHeight = max(1, (height ?? targetFullHeight)); + + final Map? result; + try { + result = await _document.channel + .invokeMapMethod('renderPage', { + 'handle': _document.handle, + 'pageIndex': index, + 'x': x, + 'y': y, + 'width': targetWidth, + 'height': targetHeight, + 'fullWidth': targetFullWidth, + 'fullHeight': targetFullHeight, + 'backgroundColor': backgroundColor ?? 0xffffffff, + 'flags': flags, + 'renderAnnotations': + annotationRenderingMode != PdfAnnotationRenderingMode.none, + 'renderForms': + annotationRenderingMode == + PdfAnnotationRenderingMode.annotationAndForms, + }); + } on MissingPluginException { + return null; + } + if (token?.isCanceled == true) { + return null; + } + if (result == null) { + return null; + } + final map = result.cast(); + final pixels = map['pixels']; + if (pixels is! Uint8List) { + throw const PdfException('renderPage did not return pixel data.'); + } + final renderedWidth = map['width'] as int? ?? targetWidth; + final renderedHeight = map['height'] as int? ?? targetHeight; + return _CoreGraphicsPdfImage( + width: renderedWidth, + height: renderedHeight, + pixels: pixels, + ); + } + + @override + PdfPageRenderCancellationToken createCancellationToken() => + _CoreGraphicsCancellationToken._(); + + @override + Future loadText() async { + try { + final result = await _document.channel.invokeMapMethod( + 'loadPageText', + {'handle': _document.handle, 'pageIndex': index}, + ); + if (result == null) { + return null; + } + final map = result.cast(); + final text = map['text'] as String?; + final rects = (map['rects'] as List?) + ?.map((e) => (e as Map).cast()) + .map( + (rect) => PdfRect( + (rect['left'] as num).toDouble(), + (rect['top'] as num).toDouble(), + (rect['right'] as num).toDouble(), + (rect['bottom'] as num).toDouble(), + ), + ) + .toList(growable: false); + if (text == null || rects == null) { + return null; + } + return PdfPageRawText(text, rects); + } on MissingPluginException { + return null; + } + } + + @override + Future> loadLinks({ + bool compact = false, + bool enableAutoLinkDetection = true, + }) async { + try { + final result = await _document.channel + .invokeListMethod('loadPageLinks', { + 'handle': _document.handle, + 'pageIndex': index, + 'enableAutoLinkDetection': enableAutoLinkDetection, + }); + if (result == null) { + return const []; + } + final links = result + .map((entry) { + final map = (entry as Map) + .cast(); + final rects = + (map['rects'] as List?) + ?.map( + (rect) => (rect as Map) + .cast(), + ) + .map( + (rect) => PdfRect( + (rect['left'] as num).toDouble(), + (rect['top'] as num).toDouble(), + (rect['right'] as num).toDouble(), + (rect['bottom'] as num).toDouble(), + ), + ) + .toList(growable: false) ?? + const []; + final url = map['url'] as String?; + final destMap = map['dest'] as Map?; + final dest = destMap == null + ? null + : PdfDest( + (destMap['page'] as int?) ?? pageNumber, + destMap['command'] is String + ? PdfDestCommand.parse(destMap['command'] as String) + : PdfDestCommand.unknown, + (destMap['params'] as List?) + ?.map((value) => (value as num?)?.toDouble()) + .toList(growable: false), + ); + final link = PdfLink( + rects, + url: url == null ? null : Uri.tryParse(url), + dest: dest, + annotationContent: map['annotationContent'] as String?, + ); + return compact ? link.compact() : link; + }) + .toList(growable: false); + return List.unmodifiable(links); + } on MissingPluginException { + return const []; + } + } +} + +class _CoreGraphicsPdfImage implements PdfImage { + _CoreGraphicsPdfImage({ + required int width, + required int height, + required Uint8List pixels, + }) : _width = width, + _height = height, + _pixels = pixels; + + final int _width; + final int _height; + Uint8List _pixels; + bool _disposed = false; + + @override + int get width => _width; + + @override + int get height => _height; + + @override + Uint8List get pixels { + if (_disposed) { + throw StateError('PdfImage has been disposed.'); + } + return _pixels; + } + + @override + void dispose() { + _disposed = true; + _pixels = Uint8List(0); + } +} + +class _CoreGraphicsCancellationToken implements PdfPageRenderCancellationToken { + _CoreGraphicsCancellationToken._(); + + bool _isCanceled = false; + + @override + void cancel() { + _isCanceled = true; + } + + @override + bool get isCanceled => _isCanceled; +} diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml new file mode 100644 index 00000000..80a960fc --- /dev/null +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -0,0 +1,30 @@ +name: pdfrx_coregraphics +description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. +version: 0.0.1 +homepage: https://github.com/espresso3389/pdfrx + +environment: + sdk: '>=3.9.0 <4.0.0' + flutter: '>=3.29.0' +resolution: workspace + +dependencies: + flutter: + sdk: flutter + pdfrx_engine: ^0.1.21 + http: + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + plugin: + platforms: + ios: + pluginClass: PdfrxCoregraphicsPlugin + sharedDarwinSource: true + macos: + pluginClass: PdfrxCoregraphicsPlugin + sharedDarwinSource: true diff --git a/pubspec.yaml b/pubspec.yaml index be7db1ba..0f03b046 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,4 +4,5 @@ environment: workspace: - packages/pdfrx - packages/pdfrx_engine + - packages/pdfrx_coregraphics - packages/pdfrx/example/viewer From c6706ac1dae0f3dce26aa832d3f63bb07e5315f6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 10 Oct 2025 18:15:41 +0900 Subject: [PATCH 400/663] WIP: loadOutline/loadPageLinks --- .../Classes/PdfrxCoregraphicsPlugin.swift | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index ebefa803..1ac653ea 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -30,6 +30,10 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { renderPage(arguments: call.arguments, result: result) case "closeDocument": closeDocument(arguments: call.arguments, result: result) + case "loadOutline": + loadOutline(arguments: call.arguments, result: result) + case "loadPageLinks": + loadPageLinks(arguments: call.arguments, result: result) default: result(FlutterMethodNotImplemented) } @@ -188,6 +192,274 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } + private func loadOutline(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let handleValue = args["handle"] + else { + result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadOutline.", details: nil)) + return + } + let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) + guard handle >= 0, let document = documents[handle] else { + result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + return + } + guard let root = document.outlineRoot else { + result([]) + return + } + result(outlineChildren(of: root, document: document)) + } + + private func loadPageLinks(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let handleValue = args["handle"], + let pageIndex = args["pageIndex"] as? Int + else { + result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadPageLinks.", details: nil)) + return + } + let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { + result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + return + } + + var (links, occupiedRects) = annotationLinks(on: page, document: document) + let enableAutoLinkDetection = args["enableAutoLinkDetection"] as? Bool ?? true + if enableAutoLinkDetection { + links.append(contentsOf: autodetectedLinks(on: page, excluding: occupiedRects)) + } + result(links) + } + + private func outlineChildren(of outline: PDFOutline, document: PDFDocument) -> [[String: Any]] { + let count = outline.numberOfChildren + guard count > 0 else { return [] } + var nodes: [[String: Any]] = [] + nodes.reserveCapacity(count) + for index in 0.. [String: Any] { + var result: [String: Any] = [ + "title": node.label ?? "", + "children": outlineChildren(of: node, document: document), + ] + if let dest = outlineDestinationMap(node, document: document) { + result["dest"] = dest + } + return result + } + + private func outlineDestinationMap(_ node: PDFOutline, document: PDFDocument) -> [String: Any]? { + if let destination = node.destination { + return destinationMap(destination, document: document) + } + if let action = node.action as? PDFActionGoTo { + return destinationMap(action.destination, document: document) + } + return nil + } + + private func annotationDestinationMap(_ annotation: PDFAnnotation, document: PDFDocument) -> [String: Any]? { + if let destination = annotation.destination { + return destinationMap(destination, document: document) + } + if let action = annotation.action as? PDFActionGoTo { + return destinationMap(action.destination, document: document) + } + return nil + } + + private func isLinkAnnotation(_ annotation: PDFAnnotation) -> Bool { + if let subtypeValue = annotation.value(forAnnotationKey: PDFAnnotationKey.subtype) as? String { + let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + let linkRaw = PDFAnnotationSubtype.link.rawValue + let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { + return true + } + } + if annotation.url != nil { return true } + if annotation.action is PDFActionURL { return true } + if annotation.action is PDFActionGoTo { return true } + return false + } + + private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: Any]? { + guard let destination = destination, let page = destination.page else { + return nil + } + let pageIndex = document.index(for: page) + if pageIndex == NSNotFound || pageIndex < 0 { + return nil + } + var params: [Double] = [ + Double(destination.point.x), + Double(destination.point.y), + ] + let zoom = destination.zoom + params.append(zoom.isFinite && zoom > 0 && zoom < 10.0 ? Double(zoom) : 0.0) + return [ + "page": pageIndex + 1, + "command": "xyz", + "params": params, + ] + } + + private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ([[String: Any]], [CGRect]) { + var links: [[String: Any]] = [] + var rects: [CGRect] = [] + for annotation in page.annotations { + guard isLinkAnnotation(annotation) else { continue } + let annotationRects = annotationRectangles(annotation) + guard !annotationRects.isEmpty else { continue } + let content = annotation.contents + let dest = annotationDestinationMap(annotation, document: document) + var urlString: String? + if let url = annotation.url { + urlString = url.absoluteString + } else if let actionURL = annotation.action as? PDFActionURL { + if let url = actionURL.url { + urlString = url.absoluteString + } + } + + if dest == nil && urlString == nil && content == nil { + continue + } + + var linkEntry: [String: Any] = [ + "rects": annotationRects.map(rectDictionary), + ] + if let dest = dest { + linkEntry["dest"] = dest + } + if let urlString { + linkEntry["url"] = urlString + } + if let content { + linkEntry["annotationContent"] = content + } + links.append(linkEntry) + rects.append(contentsOf: annotationRects) + } + return (links, rects) + } + + private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: Any]] { + guard let text = page.string, !text.isEmpty else { return [] } + guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { + return [] + } + + var links: [[String: Any]] = [] + var occupied = occupiedRects + let fullRange = NSRange(location: 0, length: (text as NSString).length) + let matches = detector.matches(in: text, options: [], range: fullRange) + + for match in matches { + guard let url = match.url else { continue } + guard let selection = page.selection(for: match.range) else { continue } + let selectionsByLine = selection.selectionsByLine() + let lineSelections = selectionsByLine.isEmpty ? [selection] : selectionsByLine + var rectDictionaries: [[String: Double]] = [] + var rectsForMatch: [CGRect] = [] + for lineSelection in lineSelections { + let bounds = lineSelection.bounds(for: page) + if bounds.isNull || bounds.isEmpty { + continue + } + if intersects(bounds, with: occupied) { + rectDictionaries.removeAll() + break + } + rectDictionaries.append(rectDictionary(bounds)) + rectsForMatch.append(bounds) + } + guard !rectDictionaries.isEmpty else { continue } + links.append([ + "rects": rectDictionaries, + "url": url.absoluteString, + ]) + occupied.append(contentsOf: rectsForMatch) + } + return links + } + + private func annotationRectangles(_ annotation: PDFAnnotation) -> [CGRect] { + let quadPoints = annotation.quadrilateralPoints + if let quadPoints, quadPoints.count >= 4 { + var rects: [CGRect] = [] + rects.reserveCapacity(quadPoints.count / 4) + var index = 0 + while index + 3 < quadPoints.count { + let points = [ + quadPoints[index].pointValue, + quadPoints[index + 1].pointValue, + quadPoints[index + 2].pointValue, + quadPoints[index + 3].pointValue, + ] + index += 4 + if let rect = rectangle(from: points) { + rects.append(rect) + } + } + if !rects.isEmpty { + return rects + } + } + let bounds = annotation.bounds + return bounds.isNull || bounds.isEmpty ? [] : [bounds] + } + + private func rectangle(from points: [CGPoint]) -> CGRect? { + guard !points.isEmpty else { return nil } + var minX = CGFloat.greatestFiniteMagnitude + var minY = CGFloat.greatestFiniteMagnitude + var maxX = -CGFloat.greatestFiniteMagnitude + var maxY = -CGFloat.greatestFiniteMagnitude + for point in points { + if point.x < minX { minX = point.x } + if point.y < minY { minY = point.y } + if point.x > maxX { maxX = point.x } + if point.y > maxY { maxY = point.y } + } + let width = maxX - minX + let height = maxY - minY + if width <= 0 || height <= 0 { + return nil + } + return CGRect(x: minX, y: minY, width: width, height: height) + } + + private func intersects(_ rect: CGRect, with others: [CGRect]) -> Bool { + for other in others where rect.intersects(other) { + return true + } + return false + } + + private func rectDictionary(_ rect: CGRect) -> [String: Double] { + let left = Double(rect.minX) + let right = Double(rect.maxX) + let bottom = Double(rect.minY) + let top = Double(rect.maxY) + return [ + "left": left, + "top": top, + "right": right, + "bottom": bottom, + ] + } + private func closeDocument(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], From 8f9389f36c3b82ad31d4509b4c60eb242a3fc2e4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 10 Oct 2025 18:16:29 +0900 Subject: [PATCH 401/663] WIP --- .../darwin/Classes/PdfrxCoregraphicsPlugin.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index 1ac653ea..a1e05aae 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -306,6 +306,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { Double(destination.point.y), ] let zoom = destination.zoom + /// NOTE: sometimes zoom contains invalid values like NaN or Inf. params.append(zoom.isFinite && zoom > 0 && zoom < 10.0 ? Double(zoom) : 0.0) return [ "page": pageIndex + 1, From a135826e3d579d17f218be2e6fc6a2d7562048cc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 01:39:30 +0900 Subject: [PATCH 402/663] WIP --- .../Classes/PdfrxCoregraphicsPlugin.swift | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index a1e05aae..a7d64639 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -28,6 +28,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { openDocument(arguments: call.arguments, result: result) case "renderPage": renderPage(arguments: call.arguments, result: result) + case "loadPageText": + loadPageText(arguments: call.arguments, result: result) case "closeDocument": closeDocument(arguments: call.arguments, result: result) case "loadOutline": @@ -235,6 +237,69 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result(links) } + private func loadPageText(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let handleValue = args["handle"], + let pageIndex = args["pageIndex"] as? Int + else { + result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadPageText.", details: nil)) + return + } + let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { + result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + return + } + + guard let fullText = page.string else { + result(["text": "", "rects": []]) + return + } + + if fullText.isEmpty || page.numberOfCharacters <= 0 { + result(["text": "", "rects": []]) + return + } + + let reportedCount = page.numberOfCharacters + print("Reported character count: \(page.numberOfCharacters): '\(fullText.count)'") + let characterCount = max(0, reportedCount) + + var rects: [[String: Double]] = [] + rects.reserveCapacity(characterCount) + + let mediaBounds = page.bounds(for: .mediaBox) + let offsetX = mediaBounds.minX + let offsetY = mediaBounds.minY + + for index in 0.. [[String: Any]] { let count = outline.numberOfChildren guard count > 0 else { return [] } From 7824a38a40f7acd1fedb735f772ce095612d5c05 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 03:19:20 +0900 Subject: [PATCH 403/663] WIP: outline, links, texts --- .../Classes/PdfrxCoregraphicsPlugin.swift | 189 ++++++++++++------ 1 file changed, 129 insertions(+), 60 deletions(-) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index a7d64639..443b966e 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -2,9 +2,9 @@ import Foundation import PDFKit #if os(iOS) -import Flutter + import Flutter #elseif os(macOS) -import FlutterMacOS + import FlutterMacOS #endif public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { @@ -43,7 +43,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { private func openDocument(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any] else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for openDocument.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for openDocument.", details: nil)) return } guard let sourceType = args["sourceType"] as? String else { @@ -56,36 +58,50 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { switch sourceType { case "file": guard let path = args["path"] as? String else { - result(FlutterError(code: "missing-path", message: "File path is required for openDocument.", details: nil)) + result( + FlutterError( + code: "missing-path", message: "File path is required for openDocument.", details: nil)) return } document = PDFDocument(url: URL(fileURLWithPath: path)) case "bytes": guard let data = args["bytes"] as? FlutterStandardTypedData else { - result(FlutterError(code: "missing-bytes", message: "PDF bytes are required for openDocument.", details: nil)) + result( + FlutterError( + code: "missing-bytes", message: "PDF bytes are required for openDocument.", details: nil + )) return } document = PDFDocument(data: data.data) default: - result(FlutterError(code: "unsupported-source", message: "Unsupported sourceType \(sourceType).", details: nil)) + result( + FlutterError( + code: "unsupported-source", message: "Unsupported sourceType \(sourceType).", details: nil + )) return } guard let pdfDocument = document else { - result(FlutterError(code: "open-failed", message: "Failed to open PDF document.", details: nil)) + result( + FlutterError(code: "open-failed", message: "Failed to open PDF document.", details: nil)) return } if pdfDocument.isLocked { let candidatePassword = password ?? "" if !pdfDocument.unlock(withPassword: candidatePassword) || pdfDocument.isLocked { - result(FlutterError(code: "wrong-password", message: "Password is required or incorrect.", details: nil)) + result( + FlutterError( + code: "wrong-password", message: "Password is required or incorrect.", details: nil)) return } } guard pdfDocument.pageCount > 0 else { - result(FlutterError(code: "empty-document", message: "PDF document does not contain any pages.", details: nil)) + result( + FlutterError( + code: "empty-document", message: "PDF document does not contain any pages.", details: nil) + ) return } @@ -123,22 +139,29 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let fullWidth = args["fullWidth"] as? Int, let fullHeight = args["fullHeight"] as? Int else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for renderPage.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for renderPage.", details: nil)) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { - result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) + else { + result( + FlutterError( + code: "unknown-document", message: "Document not found for handle \(handle).", + details: nil)) return } let x = args["x"] as? Int ?? 0 let y = args["y"] as? Int ?? 0 - let backgroundColor = args["backgroundColor"] as? Int ?? 0xffffffff + let backgroundColor = args["backgroundColor"] as? Int ?? 0xffff_ffff let renderAnnotations = args["renderAnnotations"] as? Bool ?? true guard width > 0, height > 0, fullWidth > 0, fullHeight > 0 else { - result(FlutterError(code: "invalid-size", message: "Invalid render dimensions.", details: nil)) + result( + FlutterError(code: "invalid-size", message: "Invalid render dimensions.", details: nil)) return } @@ -154,17 +177,21 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue + bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue + | CGBitmapInfo.byteOrder32Little.rawValue ) else { - result(FlutterError(code: "context-failure", message: "Failed to create bitmap context.", details: nil)) + result( + FlutterError( + code: "context-failure", message: "Failed to create bitmap context.", details: nil)) return } context.setBlendMode(.normal) context.interpolationQuality = .high let components = colorComponents(from: backgroundColor) - context.setFillColor(red: components.red, green: components.green, blue: components.blue, alpha: components.alpha) + context.setFillColor( + red: components.red, green: components.green, blue: components.blue, alpha: components.alpha) context.fill(CGRect(x: 0, y: 0, width: width, height: height)) let bounds = page.bounds(for: .mediaBox) @@ -182,7 +209,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { page.displaysAnnotations = originalDisplaysAnnotations guard let contextData = context.data else { - result(FlutterError(code: "render-failure", message: "Failed to access rendered bitmap.", details: nil)) + result( + FlutterError( + code: "render-failure", message: "Failed to access rendered bitmap.", details: nil)) return } @@ -199,12 +228,17 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let args = arguments as? [String: Any], let handleValue = args["handle"] else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadOutline.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for loadOutline.", details: nil)) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) guard handle >= 0, let document = documents[handle] else { - result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + result( + FlutterError( + code: "unknown-document", message: "Document not found for handle \(handle).", + details: nil)) return } guard let root = document.outlineRoot else { @@ -220,12 +254,18 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let handleValue = args["handle"], let pageIndex = args["pageIndex"] as? Int else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadPageLinks.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for loadPageLinks.", details: nil)) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { - result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) + else { + result( + FlutterError( + code: "unknown-document", message: "Document not found for handle \(handle).", + details: nil)) return } @@ -243,12 +283,18 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let handleValue = args["handle"], let pageIndex = args["pageIndex"] as? Int else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for loadPageText.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for loadPageText.", details: nil)) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { - result(FlutterError(code: "unknown-document", message: "Document not found for handle \(handle).", details: nil)) + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) + else { + result( + FlutterError( + code: "unknown-document", message: "Document not found for handle \(handle).", + details: nil)) return } @@ -257,31 +303,38 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return } - if fullText.isEmpty || page.numberOfCharacters <= 0 { + let nsText = fullText as NSString + let length = nsText.length + if length <= 0 { result(["text": "", "rects": []]) return } - let reportedCount = page.numberOfCharacters - print("Reported character count: \(page.numberOfCharacters): '\(fullText.count)'") - let characterCount = max(0, reportedCount) - - var rects: [[String: Double]] = [] - rects.reserveCapacity(characterCount) - let mediaBounds = page.bounds(for: .mediaBox) let offsetX = mediaBounds.minX let offsetY = mediaBounds.minY + let zeroRect: [String: Double] = [ + "left": 0.0, + "top": 0.0, + "right": 0.0, + "bottom": 0.0, + ] + + var rects: [[String: Double]] = [] + rects.reserveCapacity(length) + var charIndex = 0 + for index in 0.. [String: Any]? { + private func annotationDestinationMap(_ annotation: PDFAnnotation, document: PDFDocument) + -> [String: Any]? + { if let destination = annotation.destination { return destinationMap(destination, document: document) } @@ -345,10 +400,13 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { private func isLinkAnnotation(_ annotation: PDFAnnotation) -> Bool { if let subtypeValue = annotation.value(forAnnotationKey: PDFAnnotationKey.subtype) as? String { - let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + .lowercased() let linkRaw = PDFAnnotationSubtype.link.rawValue - let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() - if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { + let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + .lowercased() + if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" + { return true } } @@ -358,7 +416,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return false } - private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: Any]? { + private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: + Any]? + { guard let destination = destination, let page = destination.page else { return nil } @@ -380,7 +440,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ] } - private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ([[String: Any]], [CGRect]) { + private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ( + [[String: Any]], [CGRect] + ) { var links: [[String: Any]] = [] var rects: [CGRect] = [] for annotation in page.annotations { @@ -403,7 +465,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } var linkEntry: [String: Any] = [ - "rects": annotationRects.map(rectDictionary), + "rects": annotationRects.map(rectDictionary) ] if let dest = dest { linkEntry["dest"] = dest @@ -420,9 +482,12 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return (links, rects) } - private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: Any]] { + private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: + Any]] + { guard let text = page.string, !text.isEmpty else { return [] } - guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { + guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) + else { return [] } @@ -531,7 +596,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let args = arguments as? [String: Any], let handleValue = args["handle"] else { - result(FlutterError(code: "bad-arguments", message: "Invalid arguments for closeDocument.", details: nil)) + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for closeDocument.", details: nil)) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) @@ -539,7 +606,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result(nil) } - private func colorComponents(from argb: Int) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + private func colorComponents(from argb: Int) -> ( + red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat + ) { let value = UInt32(bitPattern: Int32(truncatingIfNeeded: argb)) let alpha = CGFloat((value >> 24) & 0xff) / 255.0 let red = CGFloat((value >> 16) & 0xff) / 255.0 @@ -549,14 +618,14 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } } -private extension FlutterPluginRegistrar { +extension FlutterPluginRegistrar { #if os(iOS) - var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { - messenger() - } + fileprivate var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger() + } #elseif os(macOS) - var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { - messenger - } + fileprivate var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger + } #endif } From 2e61b48a759b7e3a3db6d97dbfab58c0d57e71fd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 03:21:07 +0900 Subject: [PATCH 404/663] WIP --- .../darwin/Classes/PdfrxCoregraphicsPlugin.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index 443b966e..758f55d8 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -322,16 +322,16 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var rects: [[String: Double]] = [] rects.reserveCapacity(length) - var charIndex = 0 - for index in 0.. Date: Sat, 11 Oct 2025 03:41:43 +0900 Subject: [PATCH 405/663] Yeah!!! It's almost working now! --- packages/pdfrx_coregraphics/README.md | 21 ++++++++++++------- .../Classes/PdfrxCoregraphicsPlugin.swift | 9 ++++++++ packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 4720be0d..950778e7 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -2,7 +2,9 @@ CoreGraphics-backed renderer for [pdfrx](https://pub.dev/packages/pdfrx) on iOS and macOS. -This plugin provides a `PdfrxEntryFunctions` implementation that uses PDFKit/CoreGraphics instead of the bundled PDFium +**⚠️ EXPERIMENTAL: This package is in very early experimental stage. APIs and functionality may change significantly.** + +This plugin provides a [`PdfrxEntryFunctions`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions-class.html) implementation that uses PDFKit/CoreGraphics instead of the bundled PDFium runtime. It is intended for teams that prefer the system PDF stack on Apple platforms while keeping the pdfrx widget API. @@ -13,11 +15,10 @@ Add the package to your Flutter app: ```yaml dependencies: pdfrx: any - pdfrx_coregraphics: - path: ../packages/pdfrx_coregraphics + pdfrx_coregraphics: ^0.1.1 ``` -Call `installPdfrxCoreGraphics()` before interacting with pdfrx: +Set the CoreGraphics entry functions before initializing pdfrx: ```dart import 'package:flutter/material.dart'; @@ -25,12 +26,14 @@ import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx_coregraphics/pdfrx_coregraphics.dart'; void main() { - installPdfrxCoreGraphics(); + WidgetsFlutterBinding.ensureInitialized(); + PdfrxEntryFunctions.instance = PdfrxCoreGraphicsEntryFunctions(); + pdfrxFlutterInitialize(); runApp(const MyApp()); } ``` -After installation, use pdfrx as usual. All `PdfDocument` and widget APIs continue to work, but rendering is routed +After installation, use pdfrx as usual. All [`PdfDocument`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) and widget APIs continue to work, but rendering is routed through CoreGraphics. ## Current capabilities @@ -43,6 +46,8 @@ through CoreGraphics. - Incremental/custom stream loading is converted to in-memory loading - Custom font registration is not yet supported -- Text extraction and outline/link metadata currently fall back to empty results +- In document links are always `xyz` and zoom is not reliable (or omitted) in certain situations +- Text extraction does not cover certain scenarios like vertical texts or R-to-L texts so far +- If you just use CoreGraphics backend only, pdfrx can work without PDFium shared library; but it is still bundled with the apps -Contributions and issue reports are welcome. +Contributions and issue reports are always welcome. diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index 758f55d8..ceb9fbc6 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -532,12 +532,21 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { rects.reserveCapacity(quadPoints.count / 4) var index = 0 while index + 3 < quadPoints.count { + #if os(iOS) + let points = [ + quadPoints[index].cgPointValue, + quadPoints[index + 1].cgPointValue, + quadPoints[index + 2].cgPointValue, + quadPoints[index + 3].cgPointValue, + ] + #else let points = [ quadPoints[index].pointValue, quadPoints[index + 1].pointValue, quadPoints[index + 2].pointValue, quadPoints[index + 3].pointValue, ] + #endif index += 4 if let rect = rectangle(from: points) { rects.append(rect) diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 80a960fc..9847b57b 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.0.1 +version: 0.1.1 homepage: https://github.com/espresso3389/pdfrx environment: From 11b7bfedab48cec683c6e61f659730dac80a5e03 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 03:47:15 +0900 Subject: [PATCH 406/663] Release pdfrx_coregraphics v0.1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx_coregraphics/CHANGELOG.md | 2 +- packages/pdfrx_coregraphics/README.md | 6 ------ packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 7dc69ab5..6ace19f8 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.0.1 +## 0.1.1 - Initial CoreGraphics-backed Pdfrx entry implementation for iOS/macOS diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 950778e7..7a64c6a6 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -36,12 +36,6 @@ void main() { After installation, use pdfrx as usual. All [`PdfDocument`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) and widget APIs continue to work, but rendering is routed through CoreGraphics. -## Current capabilities - -- Document loading from files, memory buffers, and URIs -- Page rendering with background color control and annotation drawing -- Basic outline, text, and link support fall back to pdfrx defaults when not available - ## Limitations - Incremental/custom stream loading is converted to in-memory loading diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 9847b57b..efd2a59d 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter pdfrx_engine: ^0.1.21 - http: + http: ^1.5.0 dev_dependencies: flutter_test: From f5ff1e091db00b9288a7a41e0a8cfc456dc70578 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 03:51:35 +0900 Subject: [PATCH 407/663] WIP: remove pdfrx_coregraphics dependency from main.dart of example viewer --- packages/pdfrx/example/viewer/lib/main.dart | 4 ---- packages/pdfrx/example/viewer/pubspec.yaml | 2 -- 2 files changed, 6 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index b87e1231..fba3bb1b 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_coregraphics/pdfrx_coregraphics.dart'; import 'package:url_launcher/url_launcher.dart'; import 'markers_view.dart'; @@ -16,9 +15,6 @@ import 'search_view.dart'; import 'thumbnails_view.dart'; void main(List args) { - WidgetsFlutterBinding.ensureInitialized(); - PdfrxEntryFunctions.instance = PdfrxCoreGraphicsEntryFunctions(); - pdfrxFlutterInitialize(); runApp(MyApp(fileOrUri: args.isNotEmpty ? args[0] : null)); } diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 0e5e03ee..902ed5ab 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -15,8 +15,6 @@ dependencies: pdfrx: path: ../../ - pdfrx_coregraphics: - path: ../../../pdfrx_coregraphics cupertino_icons: ^1.0.8 rxdart: ^0.28.0 From 9609e85343d8c2500c75c9fe896ae94f8c54da8c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 04:17:17 +0900 Subject: [PATCH 408/663] WIP --- .vscode/extensions.json | 8 ++ .vscode/settings.json | 10 ++ packages/pdfrx_coregraphics/.swiftformat | 37 +++++ .../Classes/PdfrxCoregraphicsPlugin.swift | 131 ++++++++++-------- 4 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 packages/pdfrx_coregraphics/.swiftformat diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..e57075c2 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dart-code.dart-code", + "dart-code.flutter", + "vknabel.vscode-swiftformat", + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 88ad2eed..b8a097f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -305,5 +305,15 @@ 120 ] }, + "[swift]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "vknabel.vscode-swiftformat", + "editor.rulers": [ + 120 + ] + }, + "swiftformat.enable": true, + "swiftformat.onlyEnableOnSwiftPMProjects": false, + "swiftformat.onlyEnableWithConfig": false, "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable" } diff --git a/packages/pdfrx_coregraphics/.swiftformat b/packages/pdfrx_coregraphics/.swiftformat new file mode 100644 index 00000000..838bc469 --- /dev/null +++ b/packages/pdfrx_coregraphics/.swiftformat @@ -0,0 +1,37 @@ +# SwiftFormat configuration for pdfrx_coregraphics +--swiftversion 5.0 + +# Indentation +--indent 2 +--tabwidth 2 +--xcodeindentation enabled + +# Spacing +--trimwhitespace always +--stripunusedargs closure-only + +# Wrapping +--maxwidth 120 +--wraparguments before-first +--wrapparameters before-first +--wrapcollections before-first + +# Line breaks +--elseposition next-line +--guardelse next-line + +# Other formatting +--self remove +--importgrouping testable-bottom +--commas inline +--decimalgrouping 3,4 +--binarygrouping 4,8 +--octalgrouping 4,8 +--hexgrouping 4,8 +--fractiongrouping disabled +--exponentgrouping disabled +--hexliteralcase uppercase +--ifdef no-indent + +# Disabled rules (if any specific rules need to be disabled) +# --disable diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift index ceb9fbc6..781fe2d9 100644 --- a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift @@ -2,9 +2,9 @@ import Foundation import PDFKit #if os(iOS) - import Flutter +import Flutter #elseif os(macOS) - import FlutterMacOS +import FlutterMacOS #endif public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { @@ -45,7 +45,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { guard let args = arguments as? [String: Any] else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for openDocument.", details: nil)) + code: "bad-arguments", message: "Invalid arguments for openDocument.", details: nil + )) return } guard let sourceType = args["sourceType"] as? String else { @@ -60,7 +61,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { guard let path = args["path"] as? String else { result( FlutterError( - code: "missing-path", message: "File path is required for openDocument.", details: nil)) + code: "missing-path", message: "File path is required for openDocument.", details: nil + )) return } document = PDFDocument(url: URL(fileURLWithPath: path)) @@ -92,7 +94,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { if !pdfDocument.unlock(withPassword: candidatePassword) || pdfDocument.isLocked { result( FlutterError( - code: "wrong-password", message: "Password is required or incorrect.", details: nil)) + code: "wrong-password", message: "Password is required or incorrect.", details: nil + )) return } } @@ -100,7 +103,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { guard pdfDocument.pageCount > 0 else { result( FlutterError( - code: "empty-document", message: "PDF document does not contain any pages.", details: nil) + code: "empty-document", message: "PDF document does not contain any pages.", details: nil + ) ) return } @@ -110,7 +114,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { documents[handle] = pdfDocument var pageInfos: [[String: Any]] = [] - for index in 0..= 0, let document = documents[handle], let page = document.page(at: pageIndex) - else { + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { result( FlutterError( code: "unknown-document", message: "Document not found for handle \(handle).", - details: nil)) + details: nil + )) return } let x = args["x"] as? Int ?? 0 let y = args["y"] as? Int ?? 0 - let backgroundColor = args["backgroundColor"] as? Int ?? 0xffff_ffff + let backgroundColor = args["backgroundColor"] as? Int ?? 0xFFFF_FFFF let renderAnnotations = args["renderAnnotations"] as? Bool ?? true guard width > 0, height > 0, fullWidth > 0, fullHeight > 0 else { @@ -183,7 +188,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { else { result( FlutterError( - code: "context-failure", message: "Failed to create bitmap context.", details: nil)) + code: "context-failure", message: "Failed to create bitmap context.", details: nil + )) return } @@ -191,7 +197,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { context.interpolationQuality = .high let components = colorComponents(from: backgroundColor) context.setFillColor( - red: components.red, green: components.green, blue: components.blue, alpha: components.alpha) + red: components.red, green: components.green, blue: components.blue, alpha: components.alpha + ) context.fill(CGRect(x: 0, y: 0, width: width, height: height)) let bounds = page.bounds(for: .mediaBox) @@ -211,7 +218,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { guard let contextData = context.data else { result( FlutterError( - code: "render-failure", message: "Failed to access rendered bitmap.", details: nil)) + code: "render-failure", message: "Failed to access rendered bitmap.", details: nil + )) return } @@ -219,7 +227,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result([ "width": width, "height": height, - "pixels": FlutterStandardTypedData(bytes: buffer), + "pixels": FlutterStandardTypedData(bytes: buffer) ]) } @@ -230,7 +238,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for loadOutline.", details: nil)) + code: "bad-arguments", message: "Invalid arguments for loadOutline.", details: nil + )) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) @@ -238,7 +247,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result( FlutterError( code: "unknown-document", message: "Document not found for handle \(handle).", - details: nil)) + details: nil + )) return } guard let root = document.outlineRoot else { @@ -256,16 +266,17 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for loadPageLinks.", details: nil)) + code: "bad-arguments", message: "Invalid arguments for loadPageLinks.", details: nil + )) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) - else { + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { result( FlutterError( code: "unknown-document", message: "Document not found for handle \(handle).", - details: nil)) + details: nil + )) return } @@ -285,16 +296,17 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for loadPageText.", details: nil)) + code: "bad-arguments", message: "Invalid arguments for loadPageText.", details: nil + )) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) - else { + guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { result( FlutterError( code: "unknown-document", message: "Document not found for handle \(handle).", - details: nil)) + details: nil + )) return } @@ -317,13 +329,13 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { "left": 0.0, "top": 0.0, "right": 0.0, - "bottom": 0.0, + "bottom": 0.0 ] var rects: [[String: Double]] = [] rects.reserveCapacity(length) var boundsIndex = 0 - for charIndex in 0.. 0 else { return [] } var nodes: [[String: Any]] = [] nodes.reserveCapacity(count) - for index in 0.. [String: Any] { var result: [String: Any] = [ "title": node.label ?? "", - "children": outlineChildren(of: node, document: document), + "children": outlineChildren(of: node, document: document) ] if let dest = outlineDestinationMap(node, document: document) { result["dest"] = dest @@ -405,8 +417,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let linkRaw = PDFAnnotationSubtype.link.rawValue let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")) .lowercased() - if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" - { + if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { return true } } @@ -428,7 +439,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } var params: [Double] = [ Double(destination.point.x), - Double(destination.point.y), + Double(destination.point.y) ] let zoom = destination.zoom /// NOTE: sometimes zoom contains invalid values like NaN or Inf. @@ -436,7 +447,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return [ "page": pageIndex + 1, "command": "xyz", - "params": params, + "params": params ] } @@ -454,13 +465,14 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var urlString: String? if let url = annotation.url { urlString = url.absoluteString - } else if let actionURL = annotation.action as? PDFActionURL { + } + else if let actionURL = annotation.action as? PDFActionURL { if let url = actionURL.url { urlString = url.absoluteString } } - if dest == nil && urlString == nil && content == nil { + if dest == nil, urlString == nil, content == nil { continue } @@ -483,11 +495,10 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: - Any]] + Any]] { guard let text = page.string, !text.isEmpty else { return [] } - guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) - else { + guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { return [] } @@ -518,7 +529,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { guard !rectDictionaries.isEmpty else { continue } links.append([ "rects": rectDictionaries, - "url": url.absoluteString, + "url": url.absoluteString ]) occupied.append(contentsOf: rectsForMatch) } @@ -537,14 +548,14 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { quadPoints[index].cgPointValue, quadPoints[index + 1].cgPointValue, quadPoints[index + 2].cgPointValue, - quadPoints[index + 3].cgPointValue, + quadPoints[index + 3].cgPointValue ] #else let points = [ quadPoints[index].pointValue, quadPoints[index + 1].pointValue, quadPoints[index + 2].pointValue, - quadPoints[index + 3].pointValue, + quadPoints[index + 3].pointValue ] #endif index += 4 @@ -596,7 +607,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { "left": left, "top": top, "right": right, - "bottom": bottom, + "bottom": bottom ] } @@ -607,7 +618,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for closeDocument.", details: nil)) + code: "bad-arguments", message: "Invalid arguments for closeDocument.", details: nil + )) return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) @@ -619,22 +631,23 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat ) { let value = UInt32(bitPattern: Int32(truncatingIfNeeded: argb)) - let alpha = CGFloat((value >> 24) & 0xff) / 255.0 - let red = CGFloat((value >> 16) & 0xff) / 255.0 - let green = CGFloat((value >> 8) & 0xff) / 255.0 - let blue = CGFloat(value & 0xff) / 255.0 + let alpha = CGFloat((value >> 24) & 0xFF) / 255.0 + let red = CGFloat((value >> 16) & 0xFF) / 255.0 + let green = CGFloat((value >> 8) & 0xFF) / 255.0 + let blue = CGFloat(value & 0xFF) / 255.0 return (red, green, blue, alpha) } } -extension FlutterPluginRegistrar { +private extension FlutterPluginRegistrar { #if os(iOS) - fileprivate var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { - messenger() - } + var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger() + } + #elseif os(macOS) - fileprivate var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { - messenger - } + var pdfrxCoreGraphicsMessenger: FlutterBinaryMessenger { + messenger + } #endif } From 6a368cf6ed92feeefd7084f48f11f10ba006c35a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 13:12:43 +0900 Subject: [PATCH 409/663] pdfrx_coregraphics: support SwiftPM --- packages/pdfrx/darwin/pdfrx.podspec | 2 +- packages/pdfrx/darwin/pdfrx/Package.swift | 8 +++---- packages/pdfrx_coregraphics/README.md | 4 ++-- .../darwin/pdfrx_coregraphics.podspec | 4 ++-- .../darwin/pdfrx_coregraphics/Package.swift | 24 +++++++++++++++++++ .../Sources}/PdfrxCoregraphicsPlugin.swift | 0 packages/pdfrx_coregraphics/pubspec.yaml | 6 +++-- 7 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Package.swift rename packages/pdfrx_coregraphics/darwin/{Classes => pdfrx_coregraphics/Sources}/PdfrxCoregraphicsPlugin.swift (100%) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index a8f613c7..eac541fa 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -6,7 +6,7 @@ lib_tag = 'pdfium-apple-v11' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.8' + s.version = '0.0.11' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift index 42dc67c4..e493b292 100644 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ b/packages/pdfrx/darwin/pdfrx/Package.swift @@ -4,8 +4,8 @@ import PackageDescription let package = Package( name: "pdfrx", platforms: [ - .iOS(.v11), - .macOS(.v10_11) + .iOS(.v12), + .macOS(.v10_13), ], products: [ .library( @@ -18,7 +18,7 @@ let package = Package( name: "pdfrx", dependencies: [ .target(name: "pdfium", condition: .when(platforms: [.iOS])), - .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])) + .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])), ] ), .binaryTarget( @@ -30,6 +30,6 @@ let package = Package( name: "pdfium-macos", url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-macos.zip", checksum: "682ebbbb750fc185295e5b803f497e6ce25ab967476478253a1911977fe22c93" - ) + ), ] ) diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 7a64c6a6..b775e8bc 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: any - pdfrx_coregraphics: ^0.1.1 + pdfrx: ^2.1.25 + pdfrx_coregraphics: ^0.1.2 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec index 8d33e4a6..be7247f6 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'pdfrx_coregraphics' - s.version = '0.0.1' + s.version = '0.1.2' s.summary = 'CoreGraphics-backed renderer for pdfrx on Apple platforms.' s.description = <<-DESC Provides a PdfrxEntryFunctions implementation that uses PDFKit/CoreGraphics instead of PDFium. @@ -13,7 +13,7 @@ Provides a PdfrxEntryFunctions implementation that uses PDFKit/CoreGraphics inst s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = [ 'Classes/PdfrxCoregraphicsPlugin.swift' ] + s.source_files = [ 'pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift' ] s.ios.deployment_target = '13.0' s.ios.dependency 'Flutter' diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Package.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Package.swift new file mode 100644 index 00000000..a0f99113 --- /dev/null +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:5.6 +import PackageDescription + +let package = Package( + name: "pdfrx_coregraphics", + platforms: [ + .iOS(.v13), + .macOS(.v10_13) + ], + products: [ + .library( + name: "pdfrx-coregraphics", + targets: ["pdfrx_coregraphics"] + ) + ], + targets: [ + .target( + name: "pdfrx_coregraphics", + dependencies: [], + path: ".", + sources: ["Sources"] + ) + ] +) diff --git a/packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift similarity index 100% rename from packages/pdfrx_coregraphics/darwin/Classes/PdfrxCoregraphicsPlugin.swift rename to packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index efd2a59d..72263c89 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,7 +1,9 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx +issue_tracker: https://github.com/espresso3389/pdfrx/issues environment: sdk: '>=3.9.0 <4.0.0' @@ -12,7 +14,7 @@ dependencies: flutter: sdk: flutter pdfrx_engine: ^0.1.21 - http: ^1.5.0 + http: dev_dependencies: flutter_test: From 5b943d7ce38aedff6be6ff153f20426f0b3ca302 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 13:28:50 +0900 Subject: [PATCH 410/663] FIXED: #495 if loading takes relatively long, setState is not called timely --- .../pdfrx/example/viewer/ios/Podfile.lock | 33 ---------------- .../ios/Runner.xcodeproj/project.pbxproj | 20 +--------- .../Flutter/GeneratedPluginRegistrant.swift | 2 - .../pdfrx/example/viewer/macos/Podfile.lock | 11 +----- .../macos/Runner.xcodeproj/project.pbxproj | 38 ++++++++++--------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 1 + 6 files changed, 25 insertions(+), 80 deletions(-) diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 797d8061..c3dcb254 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,48 +1,15 @@ PODS: - - file_selector_ios (0.0.1): - - Flutter - Flutter (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.0.8): - - Flutter - - FlutterMacOS - - pdfrx_coregraphics (0.0.1): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter DEPENDENCIES: - - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) - - pdfrx_coregraphics (from `.symlinks/plugins/pdfrx_coregraphics/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: - file_selector_ios: - :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - pdfrx: - :path: ".symlinks/plugins/pdfrx/darwin" - pdfrx_coregraphics: - :path: ".symlinks/plugins/pdfrx_coregraphics/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 - pdfrx_coregraphics: 3d4ab0240897ec65b4e41d0de1a51eddf7abe90d - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 7be7e647..96fca8b5 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AB3913973095D5E19AA88E2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 85C9F234D9DF12C65EB18206 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; @@ -113,6 +114,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -199,7 +201,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 41B2057A58D2AC93EC82BCC3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -293,23 +294,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 41B2057A58D2AC93EC82BCC3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 475BC99F98491F877D325517 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift index 86ff0ffb..05b351d6 100644 --- a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,10 @@ import Foundation import file_selector_macos import path_provider_foundation -import pdfrx_coregraphics import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - PdfrxCoregraphicsPlugin.register(with: registry.registrar(forPlugin: "PdfrxCoregraphicsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 688800ef..54ab7043 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -5,10 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.8): - - Flutter - - FlutterMacOS - - pdfrx_coregraphics (0.0.1): + - pdfrx (0.0.11): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -19,7 +16,6 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) - - pdfrx_coregraphics (from `Flutter/ephemeral/.symlinks/plugins/pdfrx_coregraphics/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: @@ -31,8 +27,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin pdfrx: :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin - pdfrx_coregraphics: - :path: Flutter/ephemeral/.symlinks/plugins/pdfrx_coregraphics/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos @@ -40,8 +34,7 @@ SPEC CHECKSUMS: file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 04c7feb7d21c416d5ff89de5d7a9a35caa0c95c2 - pdfrx_coregraphics: 3d4ab0240897ec65b4e41d0de1a51eddf7abe90d + pdfrx: 4d84cd0897c1a01d974258c0f2fe211b79954fa0 url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index c62bf808..f5ce6f1e 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 5F27827074D121E8149E1E36 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; A392714C36B02B18E76DEBDE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -180,6 +181,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -242,7 +244,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 6A47EF1A6FB31E6B5F759E4B /* [CP] Embed Pods Frameworks */, + B85BC795505CC27018AFCA24 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -368,23 +370,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 6A47EF1A6FB31E6B5F759E4B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 9C1586A419FF5DDDD0DFC35A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -429,6 +414,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + B85BC795505CC27018AFCA24 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 1428868c..2b03d931 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -360,6 +360,7 @@ class _PdfViewerState extends State onPageLoadProgress: (pageNumber, totalPageCount, document) { if (document == _document && mounted) { debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); + setState(() {}); return true; } return false; From fe2c816ad5d42f14d130b886de5d4474d6f9e668 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 13:43:28 +0900 Subject: [PATCH 411/663] Release pdfrx_coregraphics v0.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx_coregraphics/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 6ace19f8..22230452 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.2 + +- Added Swift Package Manager (SwiftPM) support for easier integration +- Internal code structure reorganization and formatting improvements + ## 0.1.1 - Initial CoreGraphics-backed Pdfrx entry implementation for iOS/macOS From 2f07de1218df537a1a31d410c66977c163a4e0e2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 13:53:32 +0900 Subject: [PATCH 412/663] Release pdfrx v2.1.26 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1a0cf6f..5146e078 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.25 + pdfrx: ^2.1.26 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 4b7408bf..65d83606 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.26 + +- FIXED: PDF not visible initially when loading takes relatively long ([#495](https://github.com/espresso3389/pdfrx/issues/495)) + # 2.1.25 - FIXED: Added ArrayBuffer fallback when `WebAssembly.instantiateStreaming` fails (e.g. missing `application/wasm` MIME type) ([#405](https://github.com/espresso3389/pdfrx/issues/405), [#493](https://github.com/espresso3389/pdfrx/issues/493)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 3bb320c0..e6eed1fb 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.25 + pdfrx: ^2.1.26 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b36e8d28..5662b963 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.25 +version: 2.1.26 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 29f0645b57bed69626e8dc743fb573930efbf4a0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 13:56:36 +0900 Subject: [PATCH 413/663] Update root README to document pdfrx_coregraphics package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5146e078..c1ff7b0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdfrx -This repository contains two Dart/Flutter packages for PDF rendering and viewing: +This repository contains three Dart/Flutter packages for PDF rendering and viewing: ## Packages @@ -22,10 +22,20 @@ A cross-platform PDF viewer plugin for Flutter. - Provides high-level viewer widgets and overlays - Includes text selection, search, zoom controls, and more +### [pdfrx_coregraphics](packages/pdfrx_coregraphics/) + +**⚠️ EXPERIMENTAL** - CoreGraphics-backed renderer for pdfrx on iOS/macOS. + +- Uses PDFKit/CoreGraphics instead of PDFium on Apple platforms +- Drop-in replacement for teams preferring the system PDF stack +- Maintains full compatibility with pdfrx widget API +- iOS and macOS only + ## When to Use Which Package - **Use `pdfrx`** if you're building a Flutter application and need PDF viewing capabilities with UI - **Use `pdfrx_engine`** if you need PDF rendering without Flutter dependencies (e.g., server-side PDF processing, CLI tools) +- **Use `pdfrx_coregraphics`** (experimental) if you want to use CoreGraphics/PDFKit instead of PDFium on iOS/macOS ## Getting Started From fb3585ef88537cce6daacd8b762c57a2deaa480d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 15:05:26 +0900 Subject: [PATCH 414/663] pdfrx: remove_darwin_pdfium_modules command for integrating only pdfrx_coregraphics (removing pdfium) --- .../bin/remove_darwin_pdfium_modules.dart | 118 ++++++++++++++++++ packages/pdfrx/pubspec.yaml | 6 +- 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/pdfrx/bin/remove_darwin_pdfium_modules.dart diff --git a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart new file mode 100644 index 00000000..40f0e2d2 --- /dev/null +++ b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart @@ -0,0 +1,118 @@ +// ignore_for_file: avoid_print + +import 'dart:io'; + +import 'package:dart_pubspec_licenses/dart_pubspec_licenses.dart' as oss; +import 'package:path/path.dart' as path; + +Future main(List args) async { + try { + // Parse arguments + var revert = false; + var projectRoot = '.'; + + for (final arg in args) { + if (arg == '--revert' || arg == '-r') { + revert = true; + } else if (!arg.startsWith('-')) { + projectRoot = arg; + } else if (arg == '--help' || arg == '-h') { + _printUsage(); + return 0; + } + } + + final projectPubspecYaml = File(path.join(projectRoot, 'pubspec.yaml')); + if (!projectPubspecYaml.existsSync()) { + print('No pubspec.yaml found in $projectRoot'); + return 2; + } + + final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); + final pdfrxPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfrx'); + print('Found: ${pdfrxPackage.name} ${pdfrxPackage.version}: ${pdfrxPackage.pubspecYamlPath}'); + + final pubspecPath = pdfrxPackage.pubspecYamlPath; + final pubspecFile = File(pubspecPath); + + if (!pubspecFile.existsSync()) { + print('pubspec.yaml not found at: $pubspecPath'); + return 3; + } + + // Read the pubspec.yaml content + var pubspecYaml = pubspecFile.readAsStringSync(); + + // Comment/uncomment iOS and macOS ffiPlugin configurations + final modifiedYaml = revert ? _uncommentPlatforms(pubspecYaml) : _commentPlatforms(pubspecYaml); + + if (modifiedYaml == pubspecYaml) { + print('No changes needed.'); + } else { + pubspecFile.writeAsStringSync(modifiedYaml); + print('Successfully ${revert ? "reverted" : "modified"}.'); + } + return 0; + } catch (e, s) { + print('Error: $e\n$s'); + return 1; + } +} + +String _commentPlatforms(String yaml) { + // Comment out iOS platform configuration + yaml = yaml.replaceAllMapped( + RegExp(r'^(\s*ios:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', + ); + + // Comment out macOS platform configuration + yaml = yaml.replaceAllMapped( + RegExp(r'^(\s*macos:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', + ); + + return yaml; +} + +String _uncommentPlatforms(String yaml) { + // Uncomment iOS platform configuration + yaml = yaml.replaceAllMapped( + RegExp(r'^# (\s*ios:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '${match[1]}${match[2]}${match[3]}', + ); + + // Uncomment macOS platform configuration + yaml = yaml.replaceAllMapped( + RegExp(r'^# (\s*macos:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '${match[1]}${match[2]}${match[3]}', + ); + + return yaml; +} + +void _printUsage() { + print(''' +Usage: dart run pdfrx:remove_darwin_pdfium_modules [options] [project_root] + +This tool comments out the iOS and macOS ffiPlugin configurations in pdfrx's +pubspec.yaml to remove PDFium dependencies when using pdfrx_coregraphics. + +Options: + -r, --revert Revert the changes (uncomment the platform configurations) + -h, --help Show this help message + +Arguments: + project_root Path to the project root (default: current directory) + +Examples: + # Comment out iOS/macOS PDFium dependencies + dart run pdfrx:remove_darwin_pdfium_modules + + # Revert changes (uncomment platform configurations) + dart run pdfrx:remove_darwin_pdfium_modules --revert + + # Specify a different project root + dart run pdfrx:remove_darwin_pdfium_modules ../my_project +'''); +} diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 5662b963..dd817eae 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -28,7 +28,6 @@ dependencies: url_launcher: ^6.3.2 vector_math: ^2.2.0 web: ^1.1.1 - dart_pubspec_licenses: ^3.0.12 dev_dependencies: ffigen: ^19.1.0 @@ -36,6 +35,11 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 archive: ^4.0.7 + dart_pubspec_licenses: ^3.0.12 + +executables: + remove_wasm_modules: + remove_darwin_pdfium_modules: flutter: plugin: From 4a3b98b6e3b2feebf7dbaffbb698a3120b5748c5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 15:26:59 +0900 Subject: [PATCH 415/663] refactor: rename initPdfium to init across the codebase for consistency --- packages/pdfrx/lib/src/pdfrx_flutter.dart | 4 ++-- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 12 ++++++------ .../pdfrx_coregraphics/lib/pdfrx_coregraphics.dart | 6 +++--- .../lib/src/mock/pdfrx_initialize_mock.dart | 2 +- packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 2 +- .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 2 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 2 +- .../pdfrx_engine/lib/src/pdfrx_initialize_dart.dart | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index d11c740a..5c533ecb 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -14,7 +14,7 @@ bool _isInitialized = false; /// This function actually sets up the following functions: /// - [Pdfrx.loadAsset]: Loads an asset by name and returns its byte data. /// - [Pdfrx.getCacheDirectory]: Returns the path to the temporary directory for caching. -/// - Call [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. +/// - Call [PdfrxEntryFunctions.init] to initialize the library. /// /// For Dart (non-Flutter) programs, you should call [pdfrxInitialize] instead. /// @@ -61,7 +61,7 @@ void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { } /// NOTE: it's actually async, but hopefully, it finishes quickly... - PdfrxEntryFunctions.instance.initPdfium(); + PdfrxEntryFunctions.instance.init(); _isInitialized = true; } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index be190366..139a060a 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -66,7 +66,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { bool _initialized = false; @override - Future initPdfium() async { + Future init() async { if (_initialized) return; await synchronized(() async { if (_initialized) return; @@ -234,7 +234,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { try { if (progressCallback != null) { - await initPdfium(); + await init(); progressCallbackReg = _PdfiumWasmCallback.register( ((int bytesReceived, int bytesTotal) => progressCallback(bytesReceived, bytesTotal)).toJS, ); @@ -271,7 +271,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { required bool firstAttemptByEmptyPassword, required void Function()? onDispose, }) async { - await initPdfium(); + await init(); for (var i = 0; ; i++) { final String? password; @@ -301,20 +301,20 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { @override Future reloadFonts() async { - await initPdfium(); + await init(); await _sendCommand('reloadFonts', parameters: {'dummy': true}); } @override Future addFontData({required String face, required Uint8List data}) async { - await initPdfium(); + await init(); final jsData = data.buffer.toJS; await _sendCommand('addFontData', parameters: {'face': face, 'data': jsData}, transfer: [jsData].toJS); } @override Future clearAllFontData() async { - await initPdfium(); + await init(); await _sendCommand('clearAllFontData', parameters: {'dummy': true}); } } diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 2102be84..39a31381 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -16,7 +16,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { PdfrxCoreGraphicsEntryFunctions(); @override - Future initPdfium() async { + Future init() async { if (_initialized) { return; } @@ -67,7 +67,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { bool useProgressiveLoading = false, void Function()? onDispose, }) async { - await initPdfium(); + await init(); sourceName ??= _sourceNameFromData(data); return _openWithPassword( passwordProvider: passwordProvider, @@ -103,7 +103,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, }) async { - await initPdfium(); + await init(); return _openWithPassword( passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart index db3f8053..5600bfc1 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart @@ -10,7 +10,7 @@ library; /// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. /// - If Pdfium module is not found, it will be downloaded from the internet. /// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). -/// - Calls [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. +/// - Calls [PdfrxEntryFunctions.init] to initialize the library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease}) async { diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 6fa3e3d2..1862f5a7 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -17,7 +17,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } @override - Future initPdfium() => unimplemented(); + Future init() => unimplemented(); @override Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 30af8f36..d2be445d 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -174,7 +174,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { PdfrxEntryFunctionsImpl(); @override - Future initPdfium() => _init(); + Future init() => _init(); @override Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index fb939e3a..395e2f50 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -76,7 +76,7 @@ abstract class PdfrxEntryFunctions { /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. /// /// For actual apps, call `pdfrxFlutterInitialize` (for Flutter) or [pdfrxInitialize] (for Dart only) instead of this function. - Future initPdfium(); + Future init(); /// This function blocks pdfrx internally calls PDFium functions during the execution of [action]. /// diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 4d1f5029..37025a4c 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -16,7 +16,7 @@ bool _isInitialized = false; /// - The function checks for the `PDFIUM_PATH` environment variable to find an existing pdfium module. /// - If Pdfium module is not found, it will be downloaded from the internet. /// - [Pdfrx.loadAsset] is set to throw an error by default (Dart does not support assets like Flutter does). -/// - Calls [PdfrxEntryFunctions.initPdfium] to initialize the PDFium library. +/// - Calls [PdfrxEntryFunctions.init] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. Future pdfrxInitialize({ @@ -39,7 +39,7 @@ Future pdfrxInitialize({ Pdfrx.pdfiumModulePath = await _PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); } - await PdfrxEntryFunctions.instance.initPdfium(); + await PdfrxEntryFunctions.instance.init(); _isInitialized = true; } From d04dec7508b3a9d694aae7df266d2a68adb8b700 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 15:27:04 +0900 Subject: [PATCH 416/663] docs: update README to clarify removal of PDFium dependencies for app size reduction --- packages/pdfrx_coregraphics/README.md | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index b775e8bc..2196ca2a 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -36,12 +36,33 @@ void main() { After installation, use pdfrx as usual. All [`PdfDocument`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) and widget APIs continue to work, but rendering is routed through CoreGraphics. +## Removing PDFium Dependencies (Reducing App Size) + +By default, pdfrx bundles PDFium shared libraries for iOS and macOS even when using `pdfrx_coregraphics`. If you're only using the CoreGraphics backend, you can remove these PDFium dependencies to reduce your app size. + +Run this command from your project root: + +```bash +flutter clean # if the environment is not clean +flutter pub get +dart run pdfrx:remove_darwin_pdfium_modules +``` + +This will comment out the iOS and macOS ffiPlugin configurations in pdfrx's `pubspec.yaml`, preventing PDFium binaries from being bundled with your app. + +To revert the changes (restore PDFium dependencies): + +```bash +flutter clean # if the environment is not clean +flutter pub get +dart run pdfrx:remove_darwin_pdfium_modules --revert +``` + +After executing it, you can run `flutter build` or `flutter run` for iOS/macOS. + ## Limitations - Incremental/custom stream loading is converted to in-memory loading - Custom font registration is not yet supported - In document links are always `xyz` and zoom is not reliable (or omitted) in certain situations -- Text extraction does not cover certain scenarios like vertical texts or R-to-L texts so far -- If you just use CoreGraphics backend only, pdfrx can work without PDFium shared library; but it is still bundled with the apps - -Contributions and issue reports are always welcome. +- Text extraction does not fully cover certain scenarios like vertical texts or R-to-L texts so far From a59baa1599c61a9f49cf5152b70a1caf85944afe Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 15:40:43 +0900 Subject: [PATCH 417/663] Release pdfrx_engine v0.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 6 +++--- packages/pdfrx_coregraphics/CHANGELOG.md | 6 ++++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 9 files changed, 26 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c1ff7b0a..ae171347 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.1.26 + pdfrx: ^2.2.0 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 65d83606..7f91c595 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.0 + +- **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency +- NEW: Added `dart run pdfrx:remove_darwin_pdfium_modules` command to remove PDFium dependencies from iOS/macOS when using alternative backends like pdfrx_coregraphics +- Updated to pdfrx_engine 0.2.0 + # 2.1.26 - FIXED: PDF not visible initially when loading takes relatively long ([#495](https://github.com/espresso3389/pdfrx/issues/495)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index e6eed1fb..b47aaf74 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.1.26 + pdfrx: ^2.2.0 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index dd817eae..a4eeab55 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.1.26 +version: 2.2.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,9 +14,10 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.1.21 + pdfrx_engine: ^0.2.0 collection: crypto: ^3.0.6 + dart_pubspec_licenses: ^3.0.12 ffi: flutter: sdk: flutter @@ -35,7 +36,6 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 archive: ^4.0.7 - dart_pubspec_licenses: ^3.0.12 executables: remove_wasm_modules: diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 22230452..57fa72b9 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.3 + +- **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency +- Updated README with documentation for `dart run pdfrx:remove_darwin_pdfium_modules` command to reduce app size +- Updated to pdfrx_engine 0.2.0 + ## 0.1.2 - Added Swift Package Manager (SwiftPM) support for easier integration diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 2196ca2a..57efcf63 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.1.25 - pdfrx_coregraphics: ^0.1.2 + pdfrx: ^2.2.0 + pdfrx_coregraphics: ^0.1.3 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 72263c89..a67feed5 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.2 +version: 0.1.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.1.21 + pdfrx_engine: ^0.2.0 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index ea1891a9..f52e1eaf 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +- **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency + ## 0.1.21 - FIXED: WASM+Safari StringBuffer issue with workaround ([#483](https://github.com/espresso3389/pdfrx/issues/483)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index ab7844f5..b2ab5eeb 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.1.21 +version: 0.2.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From fd46c5542bc7935fc57098b828465dec5257aebc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 16:11:18 +0900 Subject: [PATCH 418/663] docs: add note on using CoreGraphics for iOS/macOS to reduce app size --- packages/pdfrx/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b47aaf74..bf34b428 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -117,6 +117,20 @@ To restore the WASM binaries, run the following command: dart run pdfrx:remove_wasm_modules --revert ``` +## Note for iOS/macOS: Using CoreGraphics Instead of PDFium + +For iOS and macOS apps, you can optionally use [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) to render PDFs with Apple's native CoreGraphics/PDFKit instead of the bundled PDFium library. This can significantly reduce your app size by removing PDFium dependencies on Darwin platforms. + +**⚠️ Note: `pdfrx_coregraphics` is experimental and has some limitations. See the [package documentation](https://pub.dev/packages/pdfrx_coregraphics#limitations) for details.** + +To use CoreGraphics rendering: + +1. Add `pdfrx_coregraphics` to your dependencies +2. Set the CoreGraphics entry functions before initializing pdfrx +3. Optionally remove PDFium dependencies to reduce app size + +For complete installation instructions and app size reduction steps, see the [pdfrx_coregraphics README](https://pub.dev/packages/pdfrx_coregraphics). + ## PdfViewer constructors For opening PDF files from various sources, there are several constructors available in [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html): From 25d8726b8a70df0b67b864a85c75dbda268ba785 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 16:43:58 +0900 Subject: [PATCH 419/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 6 +++--- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 2b03d931..a4503d56 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -208,7 +208,7 @@ class PdfViewer extends StatefulWidget { class _PdfViewerState extends State with SingleTickerProviderStateMixin - implements PdfTextSelectionDelegate, DocumentCoordinateConverter { + implements PdfTextSelectionDelegate, PdfViewerCoordinateConverter { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; @@ -3024,7 +3024,7 @@ class _PdfViewerState extends State } @override - DocumentCoordinateConverter get doc2local => this; + PdfViewerCoordinateConverter get doc2local => this; void forceRepaintAllPageImages() { _imageCache.cancelAllPendingRenderings(); @@ -3770,7 +3770,7 @@ class PdfViewerController extends ValueListenable { Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); /// Converts document coordinates to local coordinates. - DocumentCoordinateConverter get doc2local => _state; + PdfViewerCoordinateConverter get doc2local => _state; /// Provided to workaround certain widgets eating wheel events. Use with [Listener.onPointerSignal]. void handlePointerSignalEvent(PointerSignalEvent event) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 25a956a8..aee36c76 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -962,11 +962,11 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { Future selectWord(Offset position); /// Convert document coordinates to local coordinates and vice versa. - DocumentCoordinateConverter get doc2local; + PdfViewerCoordinateConverter get doc2local; } /// Utility class to convert document coordinates to local coordinates and vice versa. -abstract class DocumentCoordinateConverter { +abstract class PdfViewerCoordinateConverter { /// Convert a document position to a local position in the specified [context]. Offset? offsetToLocal(BuildContext context, Offset? position); From dbf7cb3dffdbee94865327ad2864f505f232653c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 11 Oct 2025 22:32:57 +0900 Subject: [PATCH 420/663] Use CoreGraphics directly to extract outline/links --- .vscode/settings.json | 1 + .../Sources/PdfrxCoregraphicsPlugin.swift | 927 +++++++++++++++--- 2 files changed, 803 insertions(+), 125 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b8a097f4..7a2d0c87 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "calloc", "CALRGB", "ccall", + "CGPDF", "Charcodes", "clippath", "cmap", diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index 781fe2d9..aa7be293 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -7,6 +7,8 @@ import Flutter import FlutterMacOS #endif +/// Flutter-side bridge that mirrors the PdfRx engine API by combining PDFKit conveniences with +/// CoreGraphics access. public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { private var nextHandle: Int64 = 1 private var documents: [Int64: PDFDocument] = [:] @@ -41,6 +43,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } } + /// Opens a PDF document from file, bytes, or custom providers and registers it under a handle. private func openDocument(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any] else { result( @@ -133,6 +136,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } + /// Renders the requested page using a CoreGraphics bitmap context and returns ARGB pixels. private func renderPage(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -231,6 +235,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } + /// Builds the document outline. private func loadOutline(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -251,13 +256,38 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { )) return } - guard let root = document.outlineRoot else { + + guard let cgDocument = document.documentRef else { + result([]) + return + } + + guard let catalog = cgDocument.catalog else { + result([]) + return + } + + var outlinesDict: CGPDFDictionaryRef? + guard CGPDFDictionaryGetDictionary(catalog, "Outlines", &outlinesDict), + let outlines = outlinesDict + else { result([]) return } - result(outlineChildren(of: root, document: document)) + + var firstOutline: CGPDFDictionaryRef? + guard CGPDFDictionaryGetDictionary(outlines, "First", &firstOutline), + let first = firstOutline + else { + result([]) + return + } + + result(parseCGOutlineNodes(first, document: document)) } + /// Loads page links by merging PDFKit annotation data with CoreGraphics parsing and optional + /// text-based auto-detection. Duplicate links are filtered via a stable hash. private func loadPageLinks(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -271,7 +301,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return } let handle = (handleValue as? Int64) ?? Int64((handleValue as? Int) ?? -1) - guard handle >= 0, let document = documents[handle], let page = document.page(at: pageIndex) else { + guard handle >= 0, let document = documents[handle], + let page = document.page(at: pageIndex) + else { result( FlutterError( code: "unknown-document", message: "Document not found for handle \(handle).", @@ -280,14 +312,174 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return } - var (links, occupiedRects) = annotationLinks(on: page, document: document) + var links: [[String: Any]] = [] + var occupiedRects: [CGRect] = [] + var seenKeys = Set() + + let (pdfKitLinks, pdfKitRects) = annotationLinks(on: page, document: document) + for link in pdfKitLinks { + let key = linkKey(link) + if seenKeys.insert(key).inserted { + links.append(link) + } + } + occupiedRects.append(contentsOf: pdfKitRects) + + if let cgDocument = document.documentRef { + let (cgLinks, cgRects) = cgAnnotationLinks( + cgDocument: cgDocument, + pageIndex: pageIndex, + document: document + ) + for link in cgLinks { + let key = linkKey(link) + if seenKeys.insert(key).inserted { + links.append(link) + } + } + occupiedRects.append(contentsOf: cgRects) + } + let enableAutoLinkDetection = args["enableAutoLinkDetection"] as? Bool ?? true if enableAutoLinkDetection { - links.append(contentsOf: autodetectedLinks(on: page, excluding: occupiedRects)) + for link in autodetectedLinks(on: page, excluding: occupiedRects) { + let key = linkKey(link) + if seenKeys.insert(key).inserted { + links.append(link) + } + } } result(links) } + private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ([[String: Any]], [CGRect]) { + var links: [[String: Any]] = [] + var rects: [CGRect] = [] + for annotation in page.annotations { + guard isLinkAnnotation(annotation) else { continue } + let annotationRects = annotationRectangles(annotation) + guard !annotationRects.isEmpty else { continue } + let content = annotation.contents + let dest = annotationDestinationMap(annotation, document: document) + var urlString: String? + if let url = annotation.url { + urlString = url.absoluteString + } + else if let actionURL = annotation.action as? PDFActionURL, let url = actionURL.url { + urlString = url.absoluteString + } + + if dest == nil, urlString == nil, content == nil { + continue + } + + var linkEntry: [String: Any] = [ + "rects": annotationRects.map(rectDictionary) + ] + if let dest = dest { + linkEntry["dest"] = dest + } + if let urlString { + linkEntry["url"] = urlString + } + if let content { + linkEntry["annotationContent"] = content + } + links.append(linkEntry) + rects.append(contentsOf: annotationRects) + } + return (links, rects) + } + + private func annotationDestinationMap(_ annotation: PDFAnnotation, document: PDFDocument) -> [String: Any]? { + if let destination = annotation.destination { + return destinationMap(destination, document: document) + } + if let action = annotation.action as? PDFActionGoTo { + return destinationMap(action.destination, document: document) + } + return nil + } + + private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: Any]? { + guard let destination = destination, let page = destination.page else { + return nil + } + let pageIndex = document.index(for: page) + if pageIndex == NSNotFound || pageIndex < 0 { + return nil + } + var params: [Double] = [ + Double(destination.point.x), + Double(destination.point.y) + ] + let zoom = destination.zoom + // Some PDFs store invalid zoom values such as NaN or Infinity; clamp to 0 to indicate "use current". + params.append(zoom.isFinite && zoom > 0 && zoom < 10.0 ? Double(zoom) : 0.0) + return [ + "page": pageIndex + 1, + "command": "xyz", + "params": params + ] + } + + private func isLinkAnnotation(_ annotation: PDFAnnotation) -> Bool { + if let subtypeValue = annotation.value(forAnnotationKey: PDFAnnotationKey.subtype) as? String { + let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + let linkRaw = PDFAnnotationSubtype.link.rawValue + let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { + return true + } + } + if annotation.url != nil { return true } + if annotation.action is PDFActionURL { return true } + if annotation.action is PDFActionGoTo { return true } + return false + } + + private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: Any]] { + guard let text = page.string, !text.isEmpty else { return [] } + guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { + return [] + } + + var links: [[String: Any]] = [] + var occupied = occupiedRects + let fullRange = NSRange(location: 0, length: (text as NSString).length) + let matches = detector.matches(in: text, options: [], range: fullRange) + + for match in matches { + guard let url = match.url else { continue } + guard let selection = page.selection(for: match.range) else { continue } + let selectionsByLine = selection.selectionsByLine() + let lineSelections = selectionsByLine.isEmpty ? [selection] : selectionsByLine + var rectDictionaries: [[String: Double]] = [] + var rectsForMatch: [CGRect] = [] + for lineSelection in lineSelections { + let bounds = lineSelection.bounds(for: page) + if bounds.isNull || bounds.isEmpty { + continue + } + if intersects(bounds, with: occupied) { + rectDictionaries.removeAll() + break + } + rectDictionaries.append(rectDictionary(bounds)) + rectsForMatch.append(bounds) + } + guard !rectDictionaries.isEmpty else { continue } + links.append([ + "rects": rectDictionaries, + "url": url.absoluteString + ]) + occupied.append(contentsOf: rectsForMatch) + } + return links + } + + /// Extracts raw page text along with bounding boxes for each character so the engine can run its + /// own text-selection heuristics. private func loadPageText(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -365,175 +557,589 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } - private func outlineChildren(of outline: PDFOutline, document: PDFDocument) -> [[String: Any]] { - let count = outline.numberOfChildren - guard count > 0 else { return [] } - var nodes: [[String: Any]] = [] - nodes.reserveCapacity(count) - for index in 0 ..< count { - guard let child = outline.child(at: index) else { continue } - nodes.append(outlineNode(child, document: document)) + private func parseCGOutlineNodes(_ outlineDict: CGPDFDictionaryRef, document: PDFDocument) -> [[String: Any]] { + var result: [[String: Any]] = [] + var currentDict: CGPDFDictionaryRef? = outlineDict + + while let current = currentDict { + if let node = parseCGOutlineNode(current, document: document) { + result.append(node) + } + + var nextDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(current, "Next", &nextDict), let next = nextDict { + currentDict = next + } + else { + break + } } - return nodes + + return result } - private func outlineNode(_ node: PDFOutline, document: PDFDocument) -> [String: Any] { - var result: [String: Any] = [ - "title": node.label ?? "", - "children": outlineChildren(of: node, document: document) - ] - if let dest = outlineDestinationMap(node, document: document) { - result["dest"] = dest + private func parseCGOutlineNode(_ outlineDict: CGPDFDictionaryRef, document: PDFDocument) -> [String: Any]? { + // Extract title + var titleString: CGPDFStringRef? + let title: String + if CGPDFDictionaryGetString(outlineDict, "Title", &titleString), let titleStr = titleString { + if let cfString = CGPDFStringCopyTextString(titleStr) { + title = cfString as String + } + else { + title = "" + } } - return result + else { + title = "" + } + + var node: [String: Any] = ["title": title] + + // Extract destination + if let dest = parseCGDestination(outlineDict, document: document) { + node["dest"] = dest + } + + // Extract children (First child) + var firstChild: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(outlineDict, "First", &firstChild), let first = firstChild { + node["children"] = parseCGOutlineNodes(first, document: document) + } + else { + node["children"] = [] + } + + return node } - private func outlineDestinationMap(_ node: PDFOutline, document: PDFDocument) -> [String: Any]? { - if let destination = node.destination { - return destinationMap(destination, document: document) + private func parseCGDestination(_ dict: CGPDFDictionaryRef, document: PDFDocument) -> [String: Any]? { + // Check for "Dest" key (explicit destination) + var destObject: CGPDFObjectRef? + if CGPDFDictionaryGetObject(dict, "Dest", &destObject), let dest = destObject { + return parseCGDestinationObject(dest, document: document) } - if let action = node.action as? PDFActionGoTo { - return destinationMap(action.destination, document: document) + + // Check for "A" key (action dictionary) + var actionDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(dict, "A", &actionDict), let action = actionDict { + if let actionType = actionType(from: action), + actionType.caseInsensitiveCompare("GoTo") == .orderedSame, + let dest = parseCGDestinationFromAction(action, document: document) + { + return dest + } } + return nil } - private func annotationDestinationMap(_ annotation: PDFAnnotation, document: PDFDocument) - -> [String: Any]? - { - if let destination = annotation.destination { - return destinationMap(destination, document: document) + private func actionType(from action: CGPDFDictionaryRef) -> String? { + var actionTypeString: CGPDFStringRef? + if CGPDFDictionaryGetString(action, "S", &actionTypeString), let typeStr = actionTypeString { + if let cfString = CGPDFStringCopyTextString(typeStr) { + return (cfString as String).trimmingCharacters(in: CharacterSet(charactersIn: "/")) + } } - if let action = annotation.action as? PDFActionGoTo { - return destinationMap(action.destination, document: document) + var actionTypeName: UnsafePointer? + if CGPDFDictionaryGetName(action, "S", &actionTypeName), let name = actionTypeName { + return String(cString: name).trimmingCharacters(in: CharacterSet(charactersIn: "/")) } return nil } - private func isLinkAnnotation(_ annotation: PDFAnnotation) -> Bool { - if let subtypeValue = annotation.value(forAnnotationKey: PDFAnnotationKey.subtype) as? String { - let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")) - .lowercased() - let linkRaw = PDFAnnotationSubtype.link.rawValue - let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")) - .lowercased() - if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { - return true + private func parseCGDestinationFromAction(_ action: CGPDFDictionaryRef, document: PDFDocument) -> [String: Any]? { + var destObject: CGPDFObjectRef? + if CGPDFDictionaryGetObject(action, "D", &destObject), let dest = destObject { + return parseCGDestinationObject(dest, document: document) + } + return nil + } + + private func parseCGDestinationObject(_ destObject: CGPDFObjectRef, document: PDFDocument) -> [String: Any]? { + // Try array-type destination first (explicit destination) + var destArray: CGPDFArrayRef? + if CGPDFObjectGetValue(destObject, .array, &destArray), let array = destArray { + return parseDestinationArray(array, document: document) + } + + // Try string-type destination (named destination) + var destString: CGPDFStringRef? + if CGPDFObjectGetValue(destObject, .string, &destString), let string = destString { + if let cfString = CGPDFStringCopyTextString(string) { + let destName = cfString as String + return lookupNamedDestination(destName, document: document) } } - if annotation.url != nil { return true } - if annotation.action is PDFActionURL { return true } - if annotation.action is PDFActionGoTo { return true } - return false + + // Try name-type destination (named destination) + var destName: UnsafePointer? + if CGPDFObjectGetValue(destObject, .name, &destName), let name = destName { + let destNameStr = String(cString: name).trimmingCharacters(in: CharacterSet(charactersIn: "/")) + return lookupNamedDestination(destNameStr, document: document) + } + + return nil } - private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: - Any]? - { - guard let destination = destination, let page = destination.page else { - return nil + private func parseDestinationArray(_ array: CGPDFArrayRef, document: PDFDocument) -> [String: Any]? { + let count = CGPDFArrayGetCount(array) + guard count >= 1 else { return nil } + + // First element should be a page reference (dictionary or integer) + var pageIndex: Int? + var pageRef: CGPDFDictionaryRef? + if CGPDFArrayGetDictionary(array, 0, &pageRef), let pageDict = pageRef { + // Find the page index from the page dictionary + pageIndex = findPageIndex(pageDict, document: document) } - let pageIndex = document.index(for: page) - if pageIndex == NSNotFound || pageIndex < 0 { - return nil + else { + var rawIndex: CGPDFInteger = 0 + if CGPDFArrayGetInteger(array, 0, &rawIndex) { + pageIndex = Int(rawIndex) + } } - var params: [Double] = [ - Double(destination.point.x), - Double(destination.point.y) - ] - let zoom = destination.zoom - /// NOTE: sometimes zoom contains invalid values like NaN or Inf. - params.append(zoom.isFinite && zoom > 0 && zoom < 10.0 ? Double(zoom) : 0.0) + + guard let pageIndex, pageIndex >= 0, pageIndex < document.pageCount else { return nil } + + // Extract command type (XYZ, Fit, FitH, etc.) + var commandRaw = "xyz" + if count >= 2 { + var commandName: UnsafePointer? + if CGPDFArrayGetName(array, 1, &commandName), let name = commandName { + commandRaw = String(cString: name) + } + else { + var commandString: CGPDFStringRef? + if CGPDFArrayGetString(array, 1, &commandString), let str = commandString, + let cfString = CGPDFStringCopyTextString(str) + { + commandRaw = cfString as String + } + } + } + let command = commandRaw + .trimmingCharacters(in: CharacterSet(charactersIn: "/")) + .lowercased() + + // Extract parameters (x, y, zoom) + var params: [Any] = [] + for i in 2 ..< count { + var object: CGPDFObjectRef? + guard CGPDFArrayGetObject(array, i, &object), let obj = object else { + params.append(NSNull()) + continue + } + switch CGPDFObjectGetType(obj) { + case .null: + params.append(NSNull()) + case .integer: + var intValue: CGPDFInteger = 0 + params.append( + CGPDFObjectGetValue(obj, .integer, &intValue) ? Double(intValue) : NSNull() + ) + case .real: + var realValue: CGPDFReal = 0 + params.append( + CGPDFObjectGetValue(obj, .real, &realValue) ? Double(realValue) : NSNull() + ) + default: + params.append(NSNull()) + } + } + return [ - "page": pageIndex + 1, - "command": "xyz", + "page": pageIndex + 1, // Convert to 1-based + "command": command, "params": params ] } - private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ( - [[String: Any]], [CGRect] - ) { + private func lookupNamedDestination(_ name: String, document: PDFDocument) -> [String: Any]? { + guard let cgDocument = document.documentRef else { + return nil + } + + guard let catalog = cgDocument.catalog else { + return nil + } + + // Try to get Dests dictionary (old-style named destinations) + var destsDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(catalog, "Dests", &destsDict), let dests = destsDict { + var destObject: CGPDFObjectRef? + let found = name.withCString { cName -> Bool in + CGPDFDictionaryGetObject(dests, cName, &destObject) + } + if found, let dest = destObject { + return parseCGDestinationObject(dest, document: document) + } + } + + // Try to get Names dictionary (new-style named destinations) + var namesDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(catalog, "Names", &namesDict), let names = namesDict { + var destsNameTreeDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(names, "Dests", &destsNameTreeDict), let destsTree = destsNameTreeDict { + if let dest = lookupInNameTree(name, nameTree: destsTree) { + return parseCGDestinationObject(dest, document: document) + } + } + } + + return nil + } + + private func lookupInNameTree(_ name: String, nameTree: CGPDFDictionaryRef) -> CGPDFObjectRef? { + // Check if this node has a Names array (leaf node) + var namesArray: CGPDFArrayRef? + if CGPDFDictionaryGetArray(nameTree, "Names", &namesArray), let names = namesArray { + let count = CGPDFArrayGetCount(names) + // Names array contains pairs: [name1, value1, name2, value2, ...] + var i: size_t = 0 + while i + 1 < count { + var nameString: CGPDFStringRef? + if CGPDFArrayGetString(names, i, &nameString), let str = nameString { + if let cfString = CGPDFStringCopyTextString(str), (cfString as String) == name { + var valueObj: CGPDFObjectRef? + if CGPDFArrayGetObject(names, i + 1, &valueObj) { + return valueObj + } + } + } + i += 2 + } + } + + // Check if this node has Kids array (intermediate node) + var kidsArray: CGPDFArrayRef? + if CGPDFDictionaryGetArray(nameTree, "Kids", &kidsArray), let kids = kidsArray { + let count = CGPDFArrayGetCount(kids) + for i in 0 ..< count { + var kidDict: CGPDFDictionaryRef? + if CGPDFArrayGetDictionary(kids, i, &kidDict), let kid = kidDict { + if let result = lookupInNameTree(name, nameTree: kid) { + return result + } + } + } + } + + return nil + } + + private func findPageIndex(_ pageDict: CGPDFDictionaryRef, document: PDFDocument) -> Int? { + // Try to match the page dictionary reference with actual pages + guard let cgDocument = document.documentRef else { + return nil + } + + for i in 0 ..< document.pageCount { + guard let cgPage = cgDocument.page(at: i + 1) else { continue } + guard let currentPageDict = cgPage.dictionary else { continue } + + // Compare dictionary pointers - they should be the same object if it's the same page + if currentPageDict == pageDict { + return i + } + } + + // If exact match fails, try comparing by object reference indirectly + // Some PDFs use indirect references, so we need to check the page type + var pageType: UnsafePointer? + if CGPDFDictionaryGetName(pageDict, "Type", &pageType), + let type = pageType, + String(cString: type) == "Page" || String(cString: type) == "/Page" + { + // This is a valid page dictionary but we couldn't find exact match + // Fall back to checking page by content comparison (checking some unique properties) + for i in 0 ..< document.pageCount { + guard let cgPage = cgDocument.page(at: i + 1) else { continue } + guard let currentPageDict = cgPage.dictionary else { continue } + + // Compare page properties (MediaBox, Resources, etc.) + if comparePagDictionaries(pageDict, currentPageDict) { + return i + } + } + } + + return nil + } + + private func comparePagDictionaries(_ dict1: CGPDFDictionaryRef, _ dict2: CGPDFDictionaryRef) -> Bool { + // Compare MediaBox + var mediaBox1: CGPDFArrayRef? + var mediaBox2: CGPDFArrayRef? + let hasMediaBox1 = CGPDFDictionaryGetArray(dict1, "MediaBox", &mediaBox1) + let hasMediaBox2 = CGPDFDictionaryGetArray(dict2, "MediaBox", &mediaBox2) + + if hasMediaBox1 != hasMediaBox2 { + return false + } + + if hasMediaBox1, let mb1 = mediaBox1, let mb2 = mediaBox2 { + if !compareArrays(mb1, mb2) { + return false + } + } + + // Compare Rotate + var rotate1: CGPDFInteger = 0 + var rotate2: CGPDFInteger = 0 + _ = CGPDFDictionaryGetInteger(dict1, "Rotate", &rotate1) + _ = CGPDFDictionaryGetInteger(dict2, "Rotate", &rotate2) + + if rotate1 != rotate2 { + return false + } + + return true + } + + private func compareArrays(_ array1: CGPDFArrayRef, _ array2: CGPDFArrayRef) -> Bool { + let count1 = CGPDFArrayGetCount(array1) + let count2 = CGPDFArrayGetCount(array2) + + if count1 != count2 { + return false + } + + for i in 0 ..< count1 { + var num1: CGPDFReal = 0 + var num2: CGPDFReal = 0 + let hasNum1 = CGPDFArrayGetNumber(array1, i, &num1) + let hasNum2 = CGPDFArrayGetNumber(array2, i, &num2) + + if hasNum1 != hasNum2 { + return false + } + + if hasNum1, abs(num1 - num2) > 0.01 { + return false + } + } + + return true + } + + private func cgAnnotationLinks( + cgDocument: CGPDFDocument, + pageIndex: Int, + document: PDFDocument + ) -> ([[String: Any]], [CGRect]) { var links: [[String: Any]] = [] var rects: [CGRect] = [] - for annotation in page.annotations { - guard isLinkAnnotation(annotation) else { continue } - let annotationRects = annotationRectangles(annotation) - guard !annotationRects.isEmpty else { continue } - let content = annotation.contents - let dest = annotationDestinationMap(annotation, document: document) - var urlString: String? - if let url = annotation.url { - urlString = url.absoluteString + + guard let cgPage = cgDocument.page(at: pageIndex + 1) else { + return (links, rects) + } + + guard let pageDict = cgPage.dictionary else { + return (links, rects) + } + + // Get the Annots array + var annotsArray: CGPDFArrayRef? + guard CGPDFDictionaryGetArray(pageDict, "Annots", &annotsArray), let annots = annotsArray else { + return (links, rects) + } + + let count = CGPDFArrayGetCount(annots) + for i in 0 ..< count { + var annotObj: CGPDFObjectRef? + guard CGPDFArrayGetObject(annots, i, &annotObj), let obj = annotObj else { + continue } - else if let actionURL = annotation.action as? PDFActionURL { - if let url = actionURL.url { - urlString = url.absoluteString + + var annotDict: CGPDFDictionaryRef? + if CGPDFObjectGetValue(obj, .dictionary, &annotDict), let annot = annotDict { + if let link = parseCGAnnotation(annot, document: document) { + links.append(link) + if let linkRects = link["rects"] as? [[String: Double]] { + for rectDict in linkRects { + if let rect = cgRectFromDict(rectDict) { + rects.append(rect) + } + } + } } } + } - if dest == nil, urlString == nil, content == nil { - continue + return (links, rects) + } + + private func parseCGAnnotation(_ annotDict: CGPDFDictionaryRef, document: PDFDocument) -> [String: Any]? { + // Check if this is a link annotation + var subtypeString: CGPDFStringRef? + var subtypeName: UnsafePointer? + var isLink = false + + if CGPDFDictionaryGetString(annotDict, "Subtype", &subtypeString), let subtype = subtypeString { + if let cfString = CGPDFStringCopyTextString(subtype) { + let subtypeStr = (cfString as String).trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + isLink = subtypeStr == "link" } + } + else if CGPDFDictionaryGetName(annotDict, "Subtype", &subtypeName), let name = subtypeName { + let subtypeStr = String(cString: name).trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() + isLink = subtypeStr == "link" + } - var linkEntry: [String: Any] = [ - "rects": annotationRects.map(rectDictionary) - ] - if let dest = dest { - linkEntry["dest"] = dest + guard isLink else { return nil } + + // Extract rectangle + var rectArray: CGPDFArrayRef? + var annotRects: [[String: Double]] = [] + if CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray), let rect = rectArray { + if let rectDict = parseCGRect(rect) { + annotRects.append(rectDict) } - if let urlString { - linkEntry["url"] = urlString + } + + // Try to get QuadPoints for more accurate rectangles + var quadPointsArray: CGPDFArrayRef? + if CGPDFDictionaryGetArray(annotDict, "QuadPoints", &quadPointsArray), let quadPoints = quadPointsArray { + let quadRects = parseCGQuadPoints(quadPoints) + if !quadRects.isEmpty { + annotRects = quadRects } - if let content { - linkEntry["annotationContent"] = content + } + + guard !annotRects.isEmpty else { return nil } + + var linkEntry: [String: Any] = ["rects": annotRects] + + // Extract URL from action + var actionDict: CGPDFDictionaryRef? + if CGPDFDictionaryGetDictionary(annotDict, "A", &actionDict), let action = actionDict { + if let actionType = actionType(from: action)?.lowercased() { + switch actionType { + case "uri": + var uriString: CGPDFStringRef? + if CGPDFDictionaryGetString(action, "URI", &uriString), let uri = uriString, + let cfString = CGPDFStringCopyTextString(uri) + { + linkEntry["url"] = cfString as String + } + case "goto": + if let dest = parseCGDestinationFromAction(action, document: document) { + linkEntry["dest"] = dest + } + default: + break + } } - links.append(linkEntry) - rects.append(contentsOf: annotationRects) } - return (links, rects) + + // Extract destination directly from annotation (without action) + if linkEntry["dest"] == nil, linkEntry["url"] == nil { + if let dest = parseCGDestination(annotDict, document: document) { + linkEntry["dest"] = dest + } + } + + // Extract annotation content + var contentsString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "Contents", &contentsString), let contents = contentsString { + if let cfString = CGPDFStringCopyTextString(contents) { + linkEntry["annotationContent"] = cfString as String + } + } + + // Only return if we have a URL or destination + guard linkEntry["url"] != nil || linkEntry["dest"] != nil else { + return nil + } + + return linkEntry } - private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: - Any]] - { - guard let text = page.string, !text.isEmpty else { return [] } - guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { - return [] + private func parseCGRect(_ rectArray: CGPDFArrayRef) -> [String: Double]? { + let count = CGPDFArrayGetCount(rectArray) + guard count >= 4 else { return nil } + + var values: [CGFloat] = [] + for i in 0 ..< 4 { + var num: CGPDFReal = 0 + var intNum: CGPDFInteger = 0 + if CGPDFArrayGetNumber(rectArray, i, &num) { + values.append(CGFloat(num)) + } + else if CGPDFArrayGetInteger(rectArray, i, &intNum) { + values.append(CGFloat(intNum)) + } + else { + return nil + } } - var links: [[String: Any]] = [] - var occupied = occupiedRects - let fullRange = NSRange(location: 0, length: (text as NSString).length) - let matches = detector.matches(in: text, options: [], range: fullRange) + return [ + "left": Double(values[0]), + "bottom": Double(values[1]), + "right": Double(values[2]), + "top": Double(values[3]) + ] + } - for match in matches { - guard let url = match.url else { continue } - guard let selection = page.selection(for: match.range) else { continue } - let selectionsByLine = selection.selectionsByLine() - let lineSelections = selectionsByLine.isEmpty ? [selection] : selectionsByLine - var rectDictionaries: [[String: Double]] = [] - var rectsForMatch: [CGRect] = [] - for lineSelection in lineSelections { - let bounds = lineSelection.bounds(for: page) - if bounds.isNull || bounds.isEmpty { - continue + private func parseCGQuadPoints(_ quadPointsArray: CGPDFArrayRef) -> [[String: Double]] { + let count = CGPDFArrayGetCount(quadPointsArray) + guard count >= 8, count % 8 == 0 else { return [] } + + var rects: [[String: Double]] = [] + + for i in stride(from: 0, to: count, by: 8) { + var points: [CGFloat] = [] + for j in 0 ..< 8 { + var num: CGPDFReal = 0 + var intNum: CGPDFInteger = 0 + if CGPDFArrayGetNumber(quadPointsArray, i + j, &num) { + points.append(CGFloat(num)) } - if intersects(bounds, with: occupied) { - rectDictionaries.removeAll() + else if CGPDFArrayGetInteger(quadPointsArray, i + j, &intNum) { + points.append(CGFloat(intNum)) + } + else { break } - rectDictionaries.append(rectDictionary(bounds)) - rectsForMatch.append(bounds) } - guard !rectDictionaries.isEmpty else { continue } - links.append([ - "rects": rectDictionaries, - "url": url.absoluteString - ]) - occupied.append(contentsOf: rectsForMatch) + + if points.count == 8 { + // QuadPoints are in order: (x1,y1), (x2,y2), (x3,y3), (x4,y4) + // Usually represents corners of a quadrilateral + let minX = min(points[0], points[2], points[4], points[6]) + let maxX = max(points[0], points[2], points[4], points[6]) + let minY = min(points[1], points[3], points[5], points[7]) + let maxY = max(points[1], points[3], points[5], points[7]) + + rects.append([ + "left": Double(minX), + "bottom": Double(minY), + "right": Double(maxX), + "top": Double(maxY) + ]) + } } - return links + + return rects + } + + private func cgRectFromDict(_ dict: [String: Double]) -> CGRect? { + guard let left = dict["left"], + let bottom = dict["bottom"], + let right = dict["right"], + let top = dict["top"] + else { + return nil + } + + return CGRect( + x: left, + y: bottom, + width: right - left, + height: top - bottom + ) } private func annotationRectangles(_ annotation: PDFAnnotation) -> [CGRect] { @@ -611,6 +1217,77 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ] } + private func linkKey(_ link: [String: Any]) -> String { + if let dest = link["dest"] as? [String: Any] { + let page = dest["page"] as? Int ?? -1 + let command = (dest["command"] as? String ?? "").lowercased() + let paramsKey = paramsKeyString(from: dest["params"]) + return "dest:\(page):\(command):\(paramsKey)" + } + if let url = link["url"] as? String { + return "url:\(url.lowercased())" + } + if let rects = link["rects"] as? [[String: Double]] { + let rectKey = rects + .map { rect -> String in + let left = rect["left"] ?? 0 + let top = rect["top"] ?? 0 + let right = rect["right"] ?? 0 + let bottom = rect["bottom"] ?? 0 + return [ + numberKey(left), + numberKey(top), + numberKey(right), + numberKey(bottom) + ].joined(separator: ",") + } + .joined(separator: "|") + return "rect:\(rectKey)" + } + if let content = link["annotationContent"] as? String, !content.isEmpty { + return "content:\(content)" + } + return UUID().uuidString + } + + private func paramsKeyString(from value: Any?) -> String { + guard let value else { return "" } + if let doubles = value as? [Double] { + return doubles.map(numberKey).joined(separator: ",") + } + if let optionals = value as? [Double?] { + return optionals.map { $0.map(numberKey) ?? "null" }.joined(separator: ",") + } + if let numbers = value as? [NSNumber] { + return numbers.map { numberKey($0.doubleValue) }.joined(separator: ",") + } + if let anys = value as? [Any] { + return anys.map { valueKey($0) }.joined(separator: ",") + } + return valueKey(value) + } + + private func valueKey(_ value: Any?) -> String { + guard let value else { return "null" } + if value is NSNull { + return "null" + } + if let number = value as? NSNumber { + return numberKey(number.doubleValue) + } + if let doubleValue = value as? Double { + return numberKey(doubleValue) + } + if let intValue = value as? Int { + return numberKey(Double(intValue)) + } + return "null" + } + + private func numberKey(_ value: Double) -> String { + return String(format: "%.4f", value) + } + private func closeDocument(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], From 6f04faa2ee2c82e19b1f8d0993abd0bb662438aa Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 13 Oct 2025 04:35:33 +0900 Subject: [PATCH 421/663] WIP: conditioning context menu position on desktop with/without touch --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a4503d56..b34756f8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -256,6 +256,7 @@ class _PdfViewerState extends State _TextSelectionPart _selPartLastMoved = _TextSelectionPart.none; bool _isSelectingAllText = false; + bool _isSelectingAWord = false; PointerDeviceKind? _selectionPointerDeviceKind; Offset? _contextMenuDocumentPosition; @@ -2382,12 +2383,31 @@ class _PdfViewerState extends State : null; Offset? a, b; - switch (Theme.of(context).platform) { - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - case TargetPlatform.macOS: - a = _pointerOffset; + if (isMobile) { + a = localOffset; + switch (_textSelA?.direction) { + case PdfTextDirection.ltr: + a ??= _anchorARect?.topLeft; + b = localOffset == null ? _anchorBRect?.bottomLeft : null; + case PdfTextDirection.rtl: + case PdfTextDirection.vrtl: + a ??= _anchorARect?.topRight; + b = localOffset == null ? _anchorBRect?.bottomRight : null; + default: + } + } else { + // NOTE: + // On Desktop, AdaptiveTextSelectionToolbar determines the context menu position by only the first anchor (a). + // So, we need to be careful about where to place the anchor (a). + if (!_isSelectingAWord && localOffset != null) { + a = localOffset; + } else { + // NOTE: it's still a little strange behavior when selecting a word by long-pressing on it on Desktop + if (_isSelectingAWord) { + _isSelectingAWord = false; + a = _anchorBRect?.bottomRight ?? _anchorARect?.center; + } + a ??= _pointerOffset; if (_anchorARect != null && _anchorBRect != null) { switch (_textSelA?.direction) { case PdfTextDirection.ltr: @@ -2403,8 +2423,8 @@ class _PdfViewerState extends State } case PdfTextDirection.rtl: case PdfTextDirection.vrtl: - final distA = (_pointerOffset - _anchorARect!.center).distanceSquared; - final distB = (_pointerOffset - _anchorBRect!.center).distanceSquared; + final distA = (a - _anchorARect!.center).distanceSquared; + final distB = (a - _anchorBRect!.center).distanceSquared; if (distA < distB) { a = _anchorARect!.bottomLeft.translate(8, 8); } else { @@ -2412,19 +2432,9 @@ class _PdfViewerState extends State } default: } + _contextMenuDocumentPosition = offsetToDocument(context, a); } - default: - a = localOffset; - switch (_textSelA?.direction) { - case PdfTextDirection.ltr: - a ??= _anchorARect?.topLeft; - b = localOffset == null ? _anchorBRect?.bottomLeft : null; - case PdfTextDirection.rtl: - case PdfTextDirection.vrtl: - a ??= _anchorARect?.topRight; - b = localOffset == null ? _anchorBRect?.bottomRight : null; - default: - } + } } contextMenu = createContextMenu(a, b, _contextMenuFor); @@ -2961,9 +2971,11 @@ class _PdfViewerState extends State } _selPartMoving = _TextSelectionPart.none; - _selPartLastMoved = _TextSelectionPart.a; + _selPartLastMoved = _TextSelectionPart.b; _isSelectingAllText = false; _selectionPointerDeviceKind = deviceKind; + _contextMenuDocumentPosition = null; + _isSelectingAWord = true; _notifyTextSelectionChange(); } From 222158e2cd4931e555e50da1eb23f6aab7277886 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:23:39 +0000 Subject: [PATCH 422/663] Bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index b9dccf80..1be45c3b 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -35,7 +35,7 @@ jobs: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v0.1.15 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From 4bb96b5a745fff1422406a1755b13f86556a96c6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 14 Oct 2025 15:39:38 +0900 Subject: [PATCH 423/663] FIXED: #495 if _alternativeFitScale is null, view is not refreshed on start --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index b34756f8..3cc0a8df 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -361,7 +361,6 @@ class _PdfViewerState extends State onPageLoadProgress: (pageNumber, totalPageCount, document) { if (document == _document && mounted) { debugPrint('PdfViewer: Loaded page $pageNumber of $totalPageCount in ${stopwatch.elapsedMilliseconds} ms'); - setState(() {}); return true; } return false; @@ -598,7 +597,7 @@ class _PdfViewerState extends State } } - if (!_initialized && _layout != null && _coverScale != null && _alternativeFitScale != null) { + if (!_initialized && _layout != null && _coverScale != null) { _initialized = true; Future.microtask(() async { // forcibly calculate fit scale for the initial page @@ -606,7 +605,12 @@ class _PdfViewerState extends State _calcCoverFitScale(); _calcZoomStopTable(); final zoom = - widget.params.calculateInitialZoom?.call(_document!, _controller!, _alternativeFitScale!, _coverScale!) ?? + widget.params.calculateInitialZoom?.call( + _document!, + _controller!, + _alternativeFitScale ?? _coverScale!, + _coverScale!, + ) ?? _coverScale!; await _setZoom(Offset.zero, zoom, duration: Duration.zero); await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); From 1433da101379234b95408e6edb870797652b7131 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 14 Oct 2025 16:11:56 +0900 Subject: [PATCH 424/663] Release pdfrx v2.2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae171347..9132b084 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.0 + pdfrx: ^2.2.1 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 7f91c595..258f1263 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.1 + +- FIXED: PDF not visible initially if `_alternativeFitScale` is null ([#495](https://github.com/espresso3389/pdfrx/issues/495)) + # 2.2.0 - **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index bf34b428..d82fdb47 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.0 + pdfrx: ^2.2.1 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index a4eeab55..46dd6e7c 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.0 +version: 2.2.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 8253609ca3fb55f1e7d8f54327f2e8b03f12c7fc Mon Sep 17 00:00:00 2001 From: james Date: Thu, 16 Oct 2025 20:01:27 +1030 Subject: [PATCH 425/663] InteractiveViewer ScrollPhysics pinch-zoom centering fixes --- .../pdfrx/lib/src/widgets/interactive_viewer.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index fe4af8fc..6e9f6c36 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -1235,10 +1235,15 @@ class InteractiveViewerState extends State with TickerProvide final extraBoundaryHorizontal = extraWidth < -kOverflowTolerance ? (extraWidth.abs() / 2) : 0.0; final extraBoundaryVertical = extraHeight < -kOverflowTolerance ? (extraHeight.abs() / 2) : 0.0; + // When content is smaller than viewport, force centering by making min==max final minX = -((baseMargin.left * scale + extraBoundaryHorizontal)); - final maxX = -((baseMargin.left * scale + extraBoundaryHorizontal)) + math.max(extraWidth, 0); + final maxX = extraWidth < -kOverflowTolerance + ? minX // Force centering + : -((baseMargin.left * scale + extraBoundaryHorizontal)) + extraWidth; final minY = -((baseMargin.top * scale + extraBoundaryVertical)); - final maxY = -((baseMargin.bottom * scale + extraBoundaryVertical)) + math.max(extraHeight, 0); + final maxY = extraHeight < -kOverflowTolerance + ? minY // Force centering + : -((baseMargin.bottom * scale + extraBoundaryVertical)) + extraHeight; return Rect.fromLTRB(minX, minY, maxX, maxY).round10BitFrac(); } @@ -1307,7 +1312,7 @@ class InteractiveViewerState extends State with TickerProvide viewportSize: viewSize, scale: scale, boundaryMargin: widget.boundaryMargin, - overrideAutoAdjustBoundaries: true, + overrideAutoAdjustBoundaries: false, // Use adjusted boundaries to respect centering logic ); // Ensure bounds are ordered correctly for clamp. From 2e5dcd9b3ef6986135a7eb59c61640b351195e59 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 16 Oct 2025 22:22:25 +0900 Subject: [PATCH 426/663] Add notes for invalidation for param functions --- doc/Page-Layout-Customization.md | 4 +++- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/Page-Layout-Customization.md b/doc/Page-Layout-Customization.md index 03462ab8..412a2121 100644 --- a/doc/Page-Layout-Customization.md +++ b/doc/Page-Layout-Customization.md @@ -1,5 +1,7 @@ # Page Layout Customization +> NOTE: setting [PdfViewerParams.layoutPages](https://pub.dev/documentation/pdf_render/latest/pdf_render_widgets/PdfViewerParams/layoutPages.html) dynamically does not refresh the viewer. You should call [PdfViewerController.invalidate](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/invalidate.html), [setState](https://api.flutter.dev/flutter/widgets/State/setState.html) or some equivalent function. + ## Horizontal Scroll View By default, the pages are laid out vertically. @@ -83,4 +85,4 @@ layoutPages: (pages, params) { ), ); }, -``` \ No newline at end of file +``` diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index aee36c76..8a9b9c8f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -9,8 +9,8 @@ import '../utils/platform.dart'; /// Viewer customization parameters. /// -/// Changes to several builder functions such as [layoutPages] does not -/// take effect until the viewer is re-layout-ed. You can relayout the viewer by calling [PdfViewerController].relayout. +/// Changes to several functions such as [layoutPages] does not +/// take effect until the viewer is re-layout-ed. You can relayout the viewer by calling [PdfViewerController.invalidate]. @immutable class PdfViewerParams { const PdfViewerParams({ @@ -83,7 +83,7 @@ class PdfViewerParams { /// Function to customize the layout of the pages. /// - /// Changes to this function does not take effect until the viewer is re-layout-ed. You can relayout the viewer by calling [PdfViewerController].relayout. + /// Changes to this function does not take effect until the viewer is re-layout-ed. You can relayout the viewer by calling [PdfViewerController.invalidate]. /// /// The following fragment is an example to layout pages horizontally with margin: /// From 45ea0abbe110397ac63bba2f1e30e983f6b99fb5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 17 Oct 2025 02:53:39 +0900 Subject: [PATCH 427/663] Release pdfrx v2.2.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9132b084..ebbe06e6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.1 + pdfrx: ^2.2.2 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 258f1263..37e024fb 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.2 + +- FIXED: InteractiveViewer ScrollPhysics pinch-zoom centering issues ([#502](https://github.com/espresso3389/pdfrx/issues/502)) + # 2.2.1 - FIXED: PDF not visible initially if `_alternativeFitScale` is null ([#495](https://github.com/espresso3389/pdfrx/issues/495)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index d82fdb47..a3716154 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.1 + pdfrx: ^2.2.2 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 46dd6e7c..dbf23e8f 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.1 +version: 2.2.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7816486c1cdc6e33e4134c616ddc38c0e6ce9854 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 17 Oct 2025 03:43:04 +0900 Subject: [PATCH 428/663] #501: Add almost empty PdfrxPlugin class to keep PDFium functions on iOS --- packages/pdfrx/darwin/pdfrx.podspec | 2 +- packages/pdfrx/darwin/pdfrx/Package.swift | 4 ++- .../darwin/pdfrx/Sources/PdfrxPlugin.swift | 20 ++++++++++++++ .../pdfrx/example/viewer/ios/Podfile.lock | 26 +++++++++++++++++++ .../ios/Runner.xcodeproj/project.pbxproj | 18 +++++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 ++ packages/pdfrx/pubspec.yaml | 2 ++ packages/pdfrx/src/pdfium_interop.cpp | 9 +++++++ 8 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index eac541fa..86363c75 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = 'pdfrx/Sources/pdfrx/**/*' + s.source_files = 'pdfrx/Sources/**/*' s.ios.deployment_target = '12.0' s.ios.dependency 'Flutter' diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift index e493b292..85b49d62 100644 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ b/packages/pdfrx/darwin/pdfrx/Package.swift @@ -19,7 +19,9 @@ let package = Package( dependencies: [ .target(name: "pdfium", condition: .when(platforms: [.iOS])), .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])), - ] + ], + path: ".", + sources: ["Sources"] ), .binaryTarget( name: "pdfium", diff --git a/packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift b/packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift new file mode 100644 index 00000000..1831d86f --- /dev/null +++ b/packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift @@ -0,0 +1,20 @@ +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +// We don't want to strip these symbols out, so we declare them here. +// For the actual implementation, see pdfrx_interop.cpp. +@_silgen_name("pdfrx_binding") +func pdfrx_binding() -> UnsafePointer + +/// The PdfrxPlugin class that is used to keep PDFium exports alive. +public class PdfrxPlugin: NSObject, FlutterPlugin { + public static func register(with _: FlutterPluginRegistrar) { + // NOTE: Call the function to ensure symbols are kept alive + _ = pdfrx_binding() + } +} diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index c3dcb254..b7d68ca6 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_ios (0.0.1): + - Flutter - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfrx (0.0.11): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: + - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: + file_selector_ios: + :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + pdfrx: + :path: ".symlinks/plugins/pdfrx/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 96fca8b5..2dd02ad7 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,6 +201,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -353,6 +354,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift index 05b351d6..cc36c74b 100644 --- a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import file_selector_macos import path_provider_foundation +import pdfrx import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index dbf23e8f..eab3441a 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -47,11 +47,13 @@ flutter: android: ffiPlugin: true ios: + pluginClass: PdfrxPlugin ffiPlugin: true sharedDarwinSource: true linux: ffiPlugin: true macos: + pluginClass: PdfrxPlugin ffiPlugin: true sharedDarwinSource: true windows: diff --git a/packages/pdfrx/src/pdfium_interop.cpp b/packages/pdfrx/src/pdfium_interop.cpp index a28b92ec..e57fbffc 100644 --- a/packages/pdfrx/src/pdfium_interop.cpp +++ b/packages/pdfrx/src/pdfium_interop.cpp @@ -74,9 +74,18 @@ extern "C" PDFRX_EXPORT void PDFRX_INTEROP_API pdfrx_file_access_set_value(pdfrx #include #include +// This function is used to keep the linker from stripping out the PDFium +// functions that are not directly referenced in this file. This is necessary +// because we are dynamically loading the functions at runtime. extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() { static const void *bindings[] = { + // File access functions + reinterpret_cast(pdfrx_file_access_create), + reinterpret_cast(pdfrx_file_access_destroy), + reinterpret_cast(pdfrx_file_access_set_value), + + // PDFium functions reinterpret_cast(FPDF_InitLibraryWithConfig), reinterpret_cast(FPDF_InitLibrary), reinterpret_cast(FPDF_DestroyLibrary), From 077e98b5882e20e210433eacca467fe81b525313 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 17 Oct 2025 03:59:44 +0900 Subject: [PATCH 429/663] WIP: Try to fix C++ include path... --- .vscode/c_cpp_properties.json | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2670dd6d..55e1beca 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -6,16 +6,41 @@ { "name": "Win32", "includePath": [ - "${workspaceFolder}/android/.lib/include", - "${workspaceFolder}/iOS/Classes/*", + "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", + "${workspaceFolder}/packages/pdfrx/example/viewer/build/windows/x64/.lib/latest/include", + "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx", "${env:LOCALAPPDATA}/Android/Sdk/ndk/${ndkVersion}/sysroot/usr/include/*" ], "defines": [], "cStandard": "c17", "cppStandard": "c++17", - "intelliSenseMode": "linux-clang-arm64", - "configurationProvider": "go2sh.cmake-integration" + "intelliSenseMode": "msvc-x64" + }, + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", + "${workspaceFolder}/packages/pdfrx/example/viewer/build/linux/x64/.lib/latest/include", + "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx" + ], + "defines": [], + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "linux-clang-arm64" + }, + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", + "${workspaceFolder}/packages/pdfrx/darwin/pdfium/.lib/pdfium-apple-*/macos/pdfium.xcframework/macos-arm64_x86_64/Headers", + "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx" + ], + "defines": [], + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-arm64" } ], - "version": 4 + "version": 4, + "enableConfigurationSquiggles": true } \ No newline at end of file From 55624d2c9a2b2b06a0d39500e0fdb95594ad8ef9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 17 Oct 2025 08:27:55 +0900 Subject: [PATCH 430/663] Release pdfrx v2.2.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebbe06e6..bef4ba47 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.2 + pdfrx: ^2.2.3 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 37e024fb..90f74307 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.3 + +- POSSIBLE FIX: Error on `openFile()` or `openAsset()` on iOS production builds installed from AppStore/TestFlight ([#501](https://github.com/espresso3389/pdfrx/issues/501)) + # 2.2.2 - FIXED: InteractiveViewer ScrollPhysics pinch-zoom centering issues ([#502](https://github.com/espresso3389/pdfrx/issues/502)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index a3716154..16a7712c 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.2 + pdfrx: ^2.2.3 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index eab3441a..36b65b39 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.2 +version: 2.2.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From ce3aa5cb6a7da13212490617b8623f42764f09a9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 17 Oct 2025 08:59:24 +0900 Subject: [PATCH 431/663] #468: Some server returns 200 instead of 206 for content-range --- packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 235078fe..b02d30e4 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -453,7 +453,7 @@ Future<_DownloadResult> _downloadBlock( final contentRange = response.headers['content-range']; var isFullDownload = false; - if (response.statusCode == 206 && contentRange != null) { + if (contentRange != null) { final m = RegExp(r'bytes (\d+)-(\d+)/(\d+)').firstMatch(contentRange); fileSize = int.parse(m!.group(3)!); } else { From 52862d902199af7176b2671e6f63d6d70089fb98 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 18 Oct 2025 10:17:45 +1030 Subject: [PATCH 432/663] WIP --- .../pdfrx/example/viewer/ios/Podfile.lock | 8 +- .../ios/Runner.xcodeproj/project.pbxproj | 42 +- packages/pdfrx/example/viewer/lib/main.dart | 85 +- .../pdfrx/example/viewer/macos/Podfile.lock | 8 +- packages/pdfrx/lib/pdfrx.dart | 1 + .../lib/src/widgets/interactive_viewer.dart | 197 ++- .../lib/src/widgets/pdf_page_layout.dart | 912 +++++++++++ .../pdfrx/lib/src/widgets/pdf_viewer.dart | 1411 ++++++++++++++--- .../lib/src/widgets/pdf_viewer_params.dart | 154 +- .../src/widgets/pdf_viewer_scroll_thumb.dart | 115 +- 10 files changed, 2575 insertions(+), 358 deletions(-) create mode 100644 packages/pdfrx/lib/src/widgets/pdf_page_layout.dart diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index b7d68ca6..b31260c1 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 + file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 5d590cfc73b66a19231c9bff0367a5875aea32e8 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 2dd02ad7..19623d87 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */, + 5F441919C7BCC240DA875443 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -317,6 +317,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 5F441919C7BCC240DA875443 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 938050D037D35BB79276ADD0 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -354,23 +371,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -480,7 +480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -663,7 +663,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -690,7 +690,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index fba3bb1b..4907574d 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -103,7 +103,7 @@ class _MainPageState extends State with WidgetsBindingObserver { return Row( children: [ if (!isMobileDevice) ...[ - Expanded(child: Text(_fileName(documentRef?.key.sourceName) ?? 'No document loaded')), + Expanded(child: Text(_fileName(documentRef?.sourceName) ?? 'No document loaded')), SizedBox(width: 10), FilledButton(onPressed: () => openFile(), child: Text('Open File')), SizedBox(width: 20), @@ -198,7 +198,7 @@ class _MainPageState extends State with WidgetsBindingObserver { valueListenable: documentRef, builder: (context, documentRef, child) => Expanded( child: Text( - _fileName(documentRef?.key.sourceName) ?? 'No document loaded', + _fileName(documentRef?.sourceName) ?? 'No document loaded', softWrap: false, ), ), @@ -309,9 +309,14 @@ class _MainPageState extends State with WidgetsBindingObserver { }, ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), - useAlternativeFitScaleAsMinScale: false, maxScale: 8, - //scrollPhysics: PdfViewerParams.getScrollPhysics(context), + fitMode: FitMode.fill, + // pageDropShadow: null, + margin: 10, + boundaryMargin: const EdgeInsets.all(10), + pageTransition: PageTransition.discrete, + resetScaleOnDiscreteTransition: true, + scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures @@ -341,28 +346,28 @@ class _MainPageState extends State with WidgetsBindingObserver { controller: controller, orientation: ScrollbarOrientation.right, thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + /* thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( color: Colors.black, child: isHorizontalLayout ? null : Center( child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), ), - ), + ), */ ), // Just a simple horizontal scroll thumb on the bottom PdfViewerScrollThumb( controller: controller, orientation: ScrollbarOrientation.bottom, thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + /*thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( color: Colors.black, child: !isHorizontalLayout ? null : Center( child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), ), - ), + ), */ ), ], // @@ -470,62 +475,32 @@ class _MainPageState extends State with WidgetsBindingObserver { /// Page reading order; true to L-to-R that is commonly used by books like manga or such var isRightToLeftReadingOrder = false; - /// Use the first page as cover page - var needCoverPage = true; - late final List _layoutPages = [ - // The default layout null, - // Horizontal layout - (pages, params) { + // Horizontal layout (using built-in layout class) + (pages, params, helper) => + SinglePagesLayout.fromPages(pages, params, helper: helper, scrollDirection: Axis.horizontal), + + // Facing pages layout (using built-in layout class) + (pages, params, helper) => FacingPagesLayout.fromPages( + pages, + params, + helper: helper, + firstPageIsCoverPage: true, + isRightToLeftReadingOrder: isRightToLeftReadingOrder, + ), + + // Custom layout example - horizontal strip layout + (pages, params, helper) { final height = pages.fold(0.0, (prev, page) => max(prev, page.height)) + params.margin * 2; final pageLayouts = []; double x = params.margin; for (var page in pages) { - pageLayouts.add( - Rect.fromLTWH( - x, - (height - page.height) / 2, // center vertically - page.width, - page.height, - ), - ); + pageLayouts.add(Rect.fromLTWH(x, (height - page.height) / 2, page.width, page.height)); x += page.width + params.margin; } return PdfPageLayout(pageLayouts: pageLayouts, documentSize: Size(x, height)); }, - // Facing pages layout - (pages, params) { - final width = pages.fold(0.0, (prev, page) => max(prev, page.width)); - - final pageLayouts = []; - final offset = needCoverPage ? 1 : 0; - double y = params.margin; - for (int i = 0; i < pages.length; i++) { - final page = pages[i]; - final pos = i + offset; - final isLeft = isRightToLeftReadingOrder ? (pos & 1) == 1 : (pos & 1) == 0; - - final otherSide = (pos ^ 1) - offset; - final h = 0 <= otherSide && otherSide < pages.length ? max(page.height, pages[otherSide].height) : page.height; - - pageLayouts.add( - Rect.fromLTWH( - isLeft ? width + params.margin - page.width : params.margin * 2 + width, - y + (h - page.height) / 2, - page.width, - page.height, - ), - ); - if (pos & 1 == 1 || i + 1 == pages.length) { - y += h + params.margin; - } - } - return PdfPageLayout( - pageLayouts: pageLayouts, - documentSize: Size((params.margin + width) * 2 + params.margin, y), - ); - }, ]; void _addCurrentSelectionToMarkers(Color color) { @@ -606,7 +581,7 @@ class _MainPageState extends State with WidgetsBindingObserver { final bytes = await file.readAsBytes(); documentRef.value = PdfDocumentRefData( bytes, - sourceName: 'web-open-file%${file.name}', + sourceName: file.name, passwordProvider: () => passwordDialog(context), useProgressiveLoading: useProgressiveLoading, ); diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 54ab7043..f6e0ea09 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 4d84cd0897c1a01d974258c0f2fe211b79954fa0 - url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 5d590cfc73b66a19231c9bff0367a5875aea32e8 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index af1d294e..282c739b 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -3,6 +3,7 @@ export 'package:pdfrx_engine/pdfrx_engine.dart'; export 'src/pdf_document_ref.dart'; export 'src/pdfrx_flutter.dart'; +export 'src/widgets/pdf_page_layout.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; export 'src/widgets/pdf_viewer_params.dart'; diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 6e9f6c36..42285932 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -30,6 +30,16 @@ import '../utils/double_extensions.dart'; /// * [WidgetBuilder], which is similar, but takes no viewport. typedef InteractiveViewerWidgetBuilder = Widget Function(BuildContext context, Quad viewport); +/// A signature for providing dynamic boundary rect based on the current visible rect. +/// +/// Returns the boundary rect that should be used for the given [visibleRect] and [childSize]. +/// This allows boundaries to change based on scroll position (e.g., per-page boundaries). +/// +/// The [visibleRect] is in document coordinates (the child's coordinate space). +/// The [childSize] is the full size of the child widget. +/// Return null to use the static [InteractiveViewer.boundaryMargin] instead. +typedef BoundaryProvider = Rect? Function(Rect visibleRect, Size childSize); + /// [**FORKED VERSION**] A widget that enables pan and zoom interactions with its child. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=zrn7V3bMJvg} @@ -76,6 +86,7 @@ class InteractiveViewer extends StatefulWidget { this.onInteractionEnd, this.onInteractionStart, this.onInteractionUpdate, + this.onAnimationEnd, this.panEnabled = true, this.scaleEnabled = true, this.scaleFactor = kDefaultMouseScrollToScaleFactor, @@ -86,6 +97,7 @@ class InteractiveViewer extends StatefulWidget { this.scrollPhysics, this.scrollPhysicsScale, this.scrollPhysicsAutoAdjustBoundaries = true, + this.boundaryProvider, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), @@ -124,6 +136,7 @@ class InteractiveViewer extends StatefulWidget { this.onInteractionEnd, this.onInteractionStart, this.onInteractionUpdate, + this.onAnimationEnd, this.panEnabled = true, this.scaleEnabled = true, this.scaleFactor = 200.0, @@ -134,6 +147,7 @@ class InteractiveViewer extends StatefulWidget { this.scrollPhysics, this.scrollPhysicsScale, this.scrollPhysicsAutoAdjustBoundaries = true, + this.boundaryProvider, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), @@ -370,6 +384,11 @@ class InteractiveViewer extends StatefulWidget { /// * [onInteractionEnd], which handles the end of the same interaction. final GestureScaleUpdateCallback? onInteractionUpdate; + /// Called when all animations (inertia, scale, snap) have completed. + /// + /// This is useful for triggering UI updates after zoom or pan animations finish. + final VoidCallback? onAnimationEnd; + /// A [TransformationController] for the transformation performed on the /// child. /// @@ -408,6 +427,15 @@ class InteractiveViewer extends StatefulWidget { /// child size is smaller than the viewport size. final bool scrollPhysicsAutoAdjustBoundaries; + /// Provides dynamic boundary margins based on the current visible rect. + /// + /// When provided, this callback is called to determine the boundary margins + /// based on the current scroll position. This allows for per-page boundaries + /// in discrete scrolling modes. + /// + /// If null, [boundaryMargin] is used as a static boundary. + final BoundaryProvider? boundaryProvider; + /// Returns the closest point to the given point on the given line segment. @visibleForTesting static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) { @@ -523,20 +551,51 @@ class InteractiveViewerState extends State with TickerProvide final bool _rotateEnabled = false; // The _boundaryRect is calculated by adding the boundaryMargin to the size of - // the child. + // the child. If boundaryProvider is set, it's called to get dynamic boundaries. Rect get _boundaryRect { assert(_childKey.currentContext != null); - assert(!widget.boundaryMargin.left.isNaN); - assert(!widget.boundaryMargin.right.isNaN); - assert(!widget.boundaryMargin.top.isNaN); - assert(!widget.boundaryMargin.bottom.isNaN); final childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; final childSize = childRenderBox.size; - final boundaryRect = widget.boundaryMargin.inflateRect(Offset.zero & childSize); + + // Check if we should use dynamic boundaries from provider + if (widget.boundaryProvider != null) { + // Calculate current visible rect for provider + final translationVector = _transformer.value.getTranslation(); + final scale = _transformer.value.getMaxScaleOnAxis(); + final viewportSize = _viewport.size; + + final visibleRect = Rect.fromLTWH( + -translationVector.x / scale, + -translationVector.y / scale, + viewportSize.width / scale, + viewportSize.height / scale, + ); + + final dynamicBoundary = widget.boundaryProvider!(visibleRect, childSize); + if (dynamicBoundary != null) { + assert(!dynamicBoundary.isEmpty, "InteractiveViewer's boundary must have nonzero dimensions."); + assert( + dynamicBoundary.isFinite || + (dynamicBoundary.left.isInfinite && + dynamicBoundary.top.isInfinite && + dynamicBoundary.right.isInfinite && + dynamicBoundary.bottom.isInfinite), + 'boundaryRect must either be infinite in all directions or finite in all directions.', + ); + return dynamicBoundary; + } + } + + // Fall back to static boundary margin + final boundaryMargin = widget.boundaryMargin; + assert(!boundaryMargin.left.isNaN); + assert(!boundaryMargin.right.isNaN); + assert(!boundaryMargin.top.isNaN); + assert(!boundaryMargin.bottom.isNaN); + + final boundaryRect = boundaryMargin.inflateRect(Offset.zero & childSize); assert(!boundaryRect.isEmpty, "InteractiveViewer's child must have nonzero dimensions."); - // Boundaries that are partially infinite are not allowed because Matrix4's - // rotation and translation methods don't handle infinites well. assert( boundaryRect.isFinite || (boundaryRect.left.isInfinite && @@ -1045,6 +1104,7 @@ class InteractiveViewerState extends State with TickerProvide ..addListener(_animateSnap) ..forward(from: 0.0).then((_) { _snapTargetMatrix = null; + _checkAndNotifyAnimationEnd(); }); break; } else { @@ -1173,6 +1233,7 @@ class InteractiveViewerState extends State with TickerProvide } _currentAxis = null; _controller.reset(); + _checkAndNotifyAnimationEnd(); return; } // Translate such that the resulting translation is _animation.value. @@ -1180,21 +1241,22 @@ class InteractiveViewerState extends State with TickerProvide final translation = Offset(translationVector.x, translationVector.y); final translationScene = _transformer.toScene(translation); + Offset newTranslationVector; if (widget.scrollPhysics != null) { /// When using scrollPhysics, we apply a simulation rather than an animation to the offsets final t = _controller.lastElapsedDuration!.inMilliseconds / 1000.0; final simulationOffsetX = simulationX != null ? -simulationX!.x(t) : translationVector.x; final simulationOffsetY = simulationY != null ? -simulationY!.x(t) : translationVector.y; - final simulationOffset = Offset(simulationOffsetX, simulationOffsetY); - final simulationScene = _transformer.toScene(simulationOffset); - final translationChangeScene = simulationScene - translationScene; - _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); + newTranslationVector = Offset(simulationOffsetX, simulationOffsetY); } else { // Translate such that the resulting translation is _animation.value. - final animationScene = _transformer.toScene(_animation!.value); - final translationChangeScene = animationScene - translationScene; - _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); + newTranslationVector = _animation!.value; } + + // Apply the translation + final newTranslationScene = _transformer.toScene(newTranslationVector); + final translationChangeScene = newTranslationScene - translationScene; + _transformer.value = _matrixTranslate(_transformer.value, translationChangeScene); } /// ScrollPhysics helpers @@ -1212,7 +1274,6 @@ class InteractiveViewerState extends State with TickerProvide EdgeInsets? boundaryMargin, bool overrideAutoAdjustBoundaries = false, }) { - // Use original boundaryMargin unless a specific one is passed for override. final baseMargin = (overrideAutoAdjustBoundaries && !widget.scrollPhysicsAutoAdjustBoundaries) || boundaryMargin == null ? _originalBoundaryMargin @@ -1222,29 +1283,95 @@ class InteractiveViewerState extends State with TickerProvide if (_boundaryRect.isInfinite) { return const Rect.fromLTRB(-double.maxFinite, -double.maxFinite, double.maxFinite, double.maxFinite); } - // Compute the raw boundary rect using the baseMargin, then scale it - final baseBoundaryRect = baseMargin.inflateRect(Offset.zero & _childSize()); - final effectiveWidth = baseBoundaryRect.width * scale; - final effectiveHeight = baseBoundaryRect.height * scale; + + // Compute the raw boundary rect - use boundaryProvider if provided, otherwise use baseMargin + final baseBoundaryRect = widget.boundaryProvider != null + ? widget.boundaryProvider!(_viewport, _childSize()) + : baseMargin.inflateRect(Offset.zero & _childSize()); + + // If boundaryProvider returned null, fall back to baseMargin + final effectiveBoundaryRect = baseBoundaryRect ?? baseMargin.inflateRect(Offset.zero & _childSize()); + final effectiveWidth = effectiveBoundaryRect.width * scale; + final effectiveHeight = effectiveBoundaryRect.height * scale; final extraWidth = effectiveWidth - viewportSize.width; final extraHeight = effectiveHeight - viewportSize.height; // Always center when content is smaller than viewport, using a small tolerance for floating imprecision. - const kOverflowTolerance = 0.1; // logical pixels + // Use a larger tolerance (1.0px) for boundaryProvider because: + // - Dynamic boundary rects go through more transformations (layout, spacing, margins, scale) + // - Floating-point errors accumulate through these operations + // - Sub-pixel differences create janky scrolling when content nearly fills viewport + // - 1px is imperceptible to users but prevents false "content overflows" detection + final kOverflowTolerance = widget.boundaryProvider != null ? 1.0 : 0.1; // logical pixels final extraBoundaryHorizontal = extraWidth < -kOverflowTolerance ? (extraWidth.abs() / 2) : 0.0; final extraBoundaryVertical = extraHeight < -kOverflowTolerance ? (extraHeight.abs() / 2) : 0.0; - // When content is smaller than viewport, force centering by making min==max - final minX = -((baseMargin.left * scale + extraBoundaryHorizontal)); - final maxX = extraWidth < -kOverflowTolerance - ? minX // Force centering - : -((baseMargin.left * scale + extraBoundaryHorizontal)) + extraWidth; - final minY = -((baseMargin.top * scale + extraBoundaryVertical)); - final maxY = extraHeight < -kOverflowTolerance - ? minY // Force centering - : -((baseMargin.bottom * scale + extraBoundaryVertical)) + extraHeight; - return Rect.fromLTRB(minX, minY, maxX, maxY).round10BitFrac(); + // Calculate pan boundaries differently based on whether boundaryProvider is used + final double minX, maxX, minY, maxY; + + if (widget.boundaryProvider != null) { + // boundaryProvider: rect represents absolute document region to keep visible + // We need to calculate boundaries relative to the entire child (document) size + // NOTE: Return NEGATIVE values to match the convention used by _matrixClamp + + // When content is smaller than or nearly equal to viewport, center the region + // Use larger tolerance to lock cross-axis when content almost fills viewport + if (extraWidth < kOverflowTolerance) { + // Center horizontally: position so the region center aligns with viewport center + final regionCenterX = effectiveBoundaryRect.center.dx * scale; + final viewportCenterX = viewportSize.width / 2; + final centerTranslation = viewportCenterX - regionCenterX; + minX = -centerTranslation; // Negate for convention + maxX = -centerTranslation; // Negate for convention + } else { + // Content is larger: allow panning to see all of the region + // Calculate actual translations (before negating for convention) + final rightEdgeTranslation = viewportSize.width - effectiveBoundaryRect.right * scale; + final leftEdgeTranslation = -effectiveBoundaryRect.left * scale; + // After negating, the order swaps: more negative becomes more positive + // So what was min becomes max and vice versa + minX = -leftEdgeTranslation; // Negate and swap + maxX = -rightEdgeTranslation; // Negate and swap + } + + if (extraHeight < kOverflowTolerance) { + // Center vertically: position so the region center aligns with viewport center + final regionCenterY = effectiveBoundaryRect.center.dy * scale; + final viewportCenterY = viewportSize.height / 2; + final centerTranslation = viewportCenterY - regionCenterY; + minY = -centerTranslation; // Negate for convention + maxY = -centerTranslation; // Negate for convention + } else { + // Content is larger: allow panning to see all of the region + // Calculate actual translations (before negating for convention) + final bottomEdgeTranslation = viewportSize.height - effectiveBoundaryRect.bottom * scale; + final topEdgeTranslation = -effectiveBoundaryRect.top * scale; + // After negating, the order swaps: more negative becomes more positive + // So what was min becomes max and vice versa + minY = -topEdgeTranslation; // Negate and swap + maxY = -bottomEdgeTranslation; // Negate and swap + } + } else { + // baseMargin: use traditional EdgeInsets-based calculation + // When content is smaller than viewport, center it by making min==max + minX = -((baseMargin.left * scale + extraBoundaryHorizontal)); + maxX = extraWidth < -kOverflowTolerance + ? minX // Force centering + : -((baseMargin.left * scale - extraBoundaryHorizontal)) + extraWidth; + minY = -((baseMargin.top * scale + extraBoundaryVertical)); + maxY = extraHeight < -kOverflowTolerance + ? minY // Force centering + : -((baseMargin.top * scale - extraBoundaryVertical)) + extraHeight; + } + + // Ensure bounds are valid (min <= max) to avoid scroll physics assertion errors + // This can happen due to floating point precision issues when content is centered + final safeMinX = math.min(minX, maxX); + final safeMaxX = math.max(minX, maxX); + final safeMinY = math.min(minY, maxY); + final safeMaxY = math.max(minY, maxY); + return Rect.fromLTRB(safeMinX, safeMinY, safeMaxX, safeMaxY).round10BitFrac(); } // Normalize ScrollMetrics such that minScrollExtent = 0 and pixels shift accordingly. @@ -1413,6 +1540,13 @@ class InteractiveViewerState extends State with TickerProvide bool get hasActiveAnimations => _controller.isAnimating || _scaleController.isAnimating || _snapController.isAnimating; + /// Check if all animations have completed and call onAnimationEnd if needed + void _checkAndNotifyAnimationEnd() { + if (!hasActiveAnimations) { + widget.onAnimationEnd?.call(); + } + } + /// Stop all active animations without saving state void stopAllAnimations() { // Stop pan animations @@ -1453,6 +1587,7 @@ class InteractiveViewerState extends State with TickerProvide _scaleAnimation?.removeListener(_handleScaleAnimation); _scaleAnimation = null; _scaleController.reset(); + _checkAndNotifyAnimationEnd(); return; } final desiredScale = _scaleAnimation!.value; diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart new file mode 100644 index 00000000..a205ac82 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -0,0 +1,912 @@ +// Copyright (c) 2024 Espresso Systems Inc. +// This file is part of pdfrx. + +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import '../../pdfrx.dart'; + +/// Helper class to hold layout calculation results. +class LayoutResult { + LayoutResult({required this.pageLayouts, required this.documentSize}); + final List pageLayouts; + final Size documentSize; +} + +/// Helper class for viewport calculations with margins. +/// +/// Bundles viewport size with boundary and content margins, providing +/// convenient getters for calculating available space and inflating dimensions. +/// +/// **Example usage:** +/// ```dart +/// final helper = PdfLayoutHelper( +/// viewportSize: viewportSize, +/// boundaryMargin: params.boundaryMargin, +/// margin: params.margin, +/// ); +/// +/// // Get available space for content +/// final width = helper.availableWidth; +/// final height = helper.availableHeight; +/// +/// // Add margins to content dimensions +/// final totalWidth = helper.widthWithMargins(contentWidth); +/// ``` +class PdfLayoutHelper { + const PdfLayoutHelper({required this.viewportSize, this.boundaryMargin, this.margin = 0.0}); + + final Size viewportSize; + final EdgeInsets? boundaryMargin; + final double margin; + + /// Horizontal boundary margin (0 if infinite or null). + double get boundaryMarginHorizontal { + return boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; + } + + /// Vertical boundary margin (0 if infinite or null). + double get boundaryMarginVertical { + return boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; + } + + /// Available width after subtracting boundary margins and content margins (margin * 2). + double get availableWidth { + return viewportSize.width - boundaryMarginHorizontal - margin * 2; + } + + /// Available height after subtracting boundary margins and content margins (margin * 2). + double get availableHeight { + return viewportSize.height - boundaryMarginVertical - margin * 2; + } + + double get viewportWidth => viewportSize.width; + double get viewportHeight => viewportSize.height; + + /// Add horizontal boundary margin and content margins to a content width. + double widthWithMargins(double contentWidth) { + return contentWidth + boundaryMarginHorizontal + margin * 2; + } + + /// Add vertical boundary margin and content margins to a content height. + double heightWithMargins(double contentHeight) { + return contentHeight + boundaryMarginVertical + margin * 2; + } +} + +/// Defines page layout. +/// +/// **Simple usage (backward compatible):** +/// Create instances directly with pre-computed page layouts: +/// ```dart +/// return PdfPageLayout( +/// pageLayouts: pageLayouts, // List of page positions +/// documentSize: Size(width, height), +/// ); +/// ``` +/// +/// **Advanced usage (subclassing):** +/// For dynamic layouts that respond to viewport changes or fit modes: +/// 1. Extend this class and override [layoutBuilder] for custom positioning logic +/// 2. Override [primaryAxis] if not vertical scrolling +/// 3. Override [calculateFitScale] for custom scaling logic +/// +/// **Document size and margin handling:** +/// Use [PdfLayoutHelper.widthWithMargins] and [PdfLayoutHelper.heightWithMargins] to properly +/// include both boundary margins and content margins in your document size calculations. +/// The helper handles the complexity of margin application so you don't have to. +/// +/// **Scaling considerations:** +/// - [calculateFitScale] returns the scale based on [FitMode] strategy +/// - This is typically used as the minimum scale for the InteractiveViewer, +/// unless an explicit [PdfViewerParams.minScale] parameter is set +/// - Override only if default implementation doesn't fit your layout's needs +class PdfPageLayout { + PdfPageLayout({required this.pageLayouts, required this.documentSize}) + : primaryAxis = documentSize.height >= documentSize.width ? Axis.vertical : Axis.horizontal { + margin = impliedMargin(); + } + + /// Page rectangles positioned within the document coordinate space. + /// + /// Each rect represents a page's position and size. The rects include positioning + /// with spacing between pages, but the rect dimensions themselves are + /// the page sizes WITHOUT margins added to width/height. + /// + /// Use [getPageRectWithMargins] to get a page rect with margins included. + final List pageLayouts; + + /// Total document size including content margins. + /// + /// This is the size of the scrollable content area and includes [margin] spacing + /// on all sides. Does NOT include boundary margins - those are handled separately + /// at the viewport level. + /// + final Size documentSize; + + /// The primary scroll axis for this layout. + /// + /// In the base class, derived from document dimensions: [Axis.vertical] if + /// height >= width, otherwise [Axis.horizontal]. + /// + /// Subclasses can override this to use explicit scroll direction instead. + /// For example, [SinglePagesLayout] overrides to use its scroll direction parameter. + /// + /// Determines the direction of scrolling and page layout. + final Axis primaryAxis; + + /// Content margin around the document edges. + /// + /// Calculated automatically via [impliedMargin] based on the spacing + /// between page dimensions and document size. + /// + /// See [getPageRectWithMargins] to get page bounds including margins. + late double margin; + + /// Each layout implements its own calculation logic. + /// Optional [helper] can be used for fit mode calculations. + /// + /// The default implementation returns the existing layout, which supports + /// backward compatibility with pre-computed layouts. + LayoutResult layoutBuilder(List pages, PdfViewerParams params, {PdfLayoutHelper? helper}) { + return LayoutResult(pageLayouts: pageLayouts, documentSize: documentSize); + } + + /// Gets the maximum width across all layout units. + /// For single page layouts, this is the maximum page width. + /// For spread layouts, this can be overridden to return the maximum spread width. + double getMaxWidth({bool withMargins = false}) => + pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.width)) + (withMargins ? margin * 2 : 0); + + /// Calculates the implied margin based on document size and max page dimensions. + /// This assumes pages are centered within the document size. + double impliedMargin() => primaryAxis == Axis.vertical + ? (documentSize.width - getMaxWidth()) / 2 + : (documentSize.height - getMaxHeight()) / 2; + + Rect getPageRectWithMargins(int pageNumber) { + if (pageNumber < 1 || pageNumber > pageLayouts.length) { + throw RangeError('Invalid page number $pageNumber'); + } + final pageRect = pageLayouts[pageNumber - 1]; + return Rect.fromLTWH( + pageRect.left - margin, + pageRect.top - margin, + pageRect.width + margin * 2, + pageRect.height + margin * 2, + ); + } + + /// Gets the maximum height across all layout units. + /// For single page layouts, this is the maximum page height. + /// For spread layouts, this can be overridden to return the maximum spread height. + double getMaxHeight({bool withMargins = false}) => + pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.height)) + (withMargins ? margin * 2 : 0); + + /// Gets the dimensions for a specific page number. + /// For single page layouts, returns the page dimensions. + /// For spread layouts, should return the spread dimensions containing the page. + /// Returns null if pageNumber is invalid. + Size? getPageDimensions(int pageNumber, {bool withMargins = false}) { + if (pageNumber < 1 || pageNumber > pageLayouts.length) { + return null; + } + final pageRect = pageLayouts[pageNumber - 1]; + return pageRect.size + (withMargins ? Offset(margin * 2, margin * 2) : Offset.zero); + } + + /// Calculates page sizes based on fit mode and scroll direction, to enable independent + /// page scaling for documents with mixed page sizes to provide optimal viewing experience. + /// + /// **Scaling behavior:** + /// For each page, calculates a scale such that the page PLUS margins fits the viewport: + /// `scale = viewport / (pageSize + boundaryMargin + margin*2)` + /// + /// Returns the scaled page sizes WITHOUT margins included in the dimensions: + /// `scaledPageSize = pageSize * scale` + /// + /// The margins should then applied separately during layout positioning - for example + /// see [layoutSequentialPages]. + /// + /// **Normalization:** + /// For FitMode.fit and FitMode.fill with multiple pages, cross-axis dimensions are normalized + /// to ensure consistent visual spacing when pages have different aspect ratios. + /// + /// **Use this when:** You want standard PDF scaling behavior but custom positioning logic. + /// + /// **Example:** + /// ```dart + /// final sizes = calculatePageSizes( + /// pages: pages, + /// fitMode: FitMode.fill, + /// scrollAxis: Axis.vertical, + /// helper: helper, // Provides viewport and margin info + /// ); + /// // sizes[i] is the scaled page size (without margins included in the dimensions) + /// // Then use sizes for custom layout positioning, adding margins as needed + /// ``` + /// + /// See also: [calculateFitScale] for document-level scaling + static List calculatePageSizes({ + required List pages, + required FitMode fitMode, + required Axis scrollAxis, + required PdfLayoutHelper helper, + }) { + if (fitMode == FitMode.none || fitMode == FitMode.cover) { + // No scaling, use pdf document dimensions + return pages.map((page) => Size(page.width, page.height)).toList(); + } + + // Calculate initial scales for each page independently + final scales = pages.map((page) { + return calculateFitScaleForDimensions( + width: page.width, + height: page.height, + helper: helper, + mode: fitMode, + scrollAxis: scrollAxis, + ); + }).toList(); + + // For FitMode.fill and FitMode.fit, normalize scales to ensure uniform cross-axis dimensions + if ((fitMode == FitMode.fill || fitMode == FitMode.fit) && scales.length > 1) { + final normalizedSizes = _normalizePageSizes( + pages: pages, + scales: scales, + scrollAxis: scrollAxis, + fitMode: fitMode, + helper: helper, + ); + if (normalizedSizes != null) return normalizedSizes; + } + + // Apply calculated scales + return List.generate(pages.length, (i) => Size(pages[i].width * scales[i], pages[i].height * scales[i])); + } + + /// Normalizes page sizes to ensure uniform cross-axis dimensions for constrained pages. + /// Returns null if normalization is not needed or not applicable. + /// + /// For documents with mixed page sizes, independent page scaling results in varying margins + /// so we normalize cross-axis dimensions to be the same for constrained pages, so that the + /// margins are consistent between pages. + static List? _normalizePageSizes({ + required List pages, + required List scales, + required Axis scrollAxis, + required FitMode fitMode, + required PdfLayoutHelper helper, + }) { + // Find pages that are constrained by the cross-axis (not primary axis) + final crossAxisConstrainedPages = []; + + for (var i = 0; i < pages.length; i++) { + final page = pages[i]; + final widthWithMargins = helper.widthWithMargins(page.width); + final heightWithMargins = helper.heightWithMargins(page.height); + + final crossAxisScale = scrollAxis == Axis.vertical + ? helper.viewportWidth / widthWithMargins + : helper.viewportHeight / heightWithMargins; + final primaryAxisScale = scrollAxis == Axis.vertical + ? helper.viewportHeight / heightWithMargins + : helper.viewportWidth / widthWithMargins; + + // For fill mode: always constrained by cross-axis + // For fit mode: only if cross-axis is the limiting factor + if (fitMode == FitMode.fill || crossAxisScale <= primaryAxisScale) { + crossAxisConstrainedPages.add(i); + } + } + + if (crossAxisConstrainedPages.isEmpty) return null; + + // Calculate cross-axis sizes for constrained pages + final constrainedCrossAxisSizes = crossAxisConstrainedPages.map((i) { + final page = pages[i]; + return scrollAxis == Axis.vertical ? page.width * scales[i] : page.height * scales[i]; + }).toList(); + + final minSize = constrainedCrossAxisSizes.reduce(min); + final maxSize = constrainedCrossAxisSizes.reduce(max); + + // Only normalize if sizes differ by more than 0.5% + const similarityThreshold = 0.005; + if ((maxSize - minSize) / minSize <= similarityThreshold) return null; + + // Determine target cross-axis size + var targetSize = maxSize; + + // For fit mode, ensure no page exceeds its original fit scale + if (fitMode == FitMode.fit) { + for (var i in crossAxisConstrainedPages) { + final page = pages[i]; + final pageCrossAxisSize = scrollAxis == Axis.vertical ? page.width : page.height; + final requiredScale = targetSize / pageCrossAxisSize; + + if (requiredScale > scales[i]) { + targetSize = pageCrossAxisSize * scales[i]; + } + } + } + + // Apply normalization + return pages.asMap().entries.map((entry) { + final i = entry.key; + final page = entry.value; + + if (crossAxisConstrainedPages.contains(i)) { + // Normalize to target cross-axis size + final pageCrossAxisSize = scrollAxis == Axis.vertical ? page.width : page.height; + final newScale = targetSize / pageCrossAxisSize; + return Size(page.width * newScale, page.height * newScale); + } else { + // Keep original scale for primary-axis constrained pages + return Size(page.width * scales[i], page.height * scales[i]); + } + }).toList(); + } + + /// Calculate the scale factor for given dimensions based on fit mode. + /// This is the core logic used by both [calculatePageSizes] and [calculateFitScale]. + /// + /// **Important:** Margins are in PDF points and scale with content. The calculation accounts + /// for this by dividing viewport by (width + margin*2), ensuring the scaled page + scaled margins + /// fit within the viewport. + /// + /// **Note:** [FitMode.cover] is not supported here as it requires document-level dimensions. + /// Use [calculateFitScale] instead for cover mode. + static double calculateFitScaleForDimensions({ + required double width, + required double height, + required PdfLayoutHelper helper, + required FitMode mode, + required Axis scrollAxis, + }) { + assert(mode != FitMode.cover, 'FitMode.cover requires document-level calculation. Use calculateFitScale instead.'); + + // Both margins and boundaryMargins are in PDF points and scale with the content + // So we need: scale = viewport / (pageSize + margin*2 + boundaryMargin) + // This ensures: (pageSize + margin*2 + boundaryMargin) * scale = viewport + final widthWithMargins = helper.widthWithMargins(width); + final heightWithMargins = helper.heightWithMargins(height); + + switch (mode) { + case FitMode.fit: + // Scale to fit viewport (letterbox) - content + all margins must fit + return min(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + + case FitMode.fill: + // Scale to fill cross-axis - content + all margins on cross-axis must fit + return scrollAxis == Axis.vertical + ? helper.viewportWidth / widthWithMargins + : helper.viewportHeight / heightWithMargins; + + case FitMode.cover: + // Cover mode not supported at page level - requires full document dimensions + // This should be caught by the assertion above + throw UnsupportedError('FitMode.cover requires document-level calculation'); + + case FitMode.none: + return 1.0; + } + } + + /// Positions pre-sized pages sequentially along a scroll axis. + /// + /// This is a building block for creating simple scrolling layouts. It handles the + /// geometry of positioning pages one after another, with optional centering + /// perpendicular to the scroll direction. + /// + /// **Use this when:** You have pre-calculated page sizes and need to position them + /// sequentially (vertical or horizontal scrolling). + /// + /// **Parameters:** + /// - [pageSizes]: Pre-calculated size for each page + /// - [scrollAxis]: Direction of scrolling (vertical or horizontal) + /// - [margin]: Margin around pages + /// - [centerPerpendicular]: Whether to center pages perpendicular to scroll axis + /// + /// **Example:** + /// ```dart + /// final sizes = pages.map((p) => Size(p.width, p.height)).toList(); + /// return layoutSequentialPages( + /// pageSizes: sizes, + /// scrollAxis: Axis.vertical, + /// margin: 8.0, + /// centerPerpendicular: true, + /// ); + /// ``` + LayoutResult layoutSequentialPages({ + required List pageSizes, + required Axis scrollAxis, + required double margin, + bool centerPerpendicular = false, + }) { + final isVertical = scrollAxis == Axis.vertical; + final pageLayouts = []; + var scrollPosition = margin; + var maxCrossAxis = 0.0; + + // Track max cross-axis dimension (needed for centering and document size) + for (var size in pageSizes) { + maxCrossAxis = max(maxCrossAxis, isVertical ? size.width : size.height); + } + + // Layout pages along scroll axis + for (var size in pageSizes) { + final rect = isVertical + ? Rect.fromLTWH(margin, scrollPosition, size.width, size.height) + : Rect.fromLTWH(scrollPosition, margin, size.width, size.height); + pageLayouts.add(rect); + scrollPosition += (isVertical ? size.height : size.width) + margin; + } + + // Center perpendicular to scroll if requested + final finalLayouts = centerPerpendicular + ? pageLayouts.map((rect) { + if (isVertical) { + final xOffset = (maxCrossAxis - rect.width) / 2; + return rect.translate(xOffset, 0); + } else { + final yOffset = (maxCrossAxis - rect.height) / 2; + return rect.translate(0, yOffset); + } + }).toList() + : pageLayouts; + + final docSize = isVertical + ? Size(maxCrossAxis + margin * 2, scrollPosition) + : Size(scrollPosition, maxCrossAxis + margin * 2); + + return LayoutResult(pageLayouts: finalLayouts, documentSize: docSize); + } + + /// Calculates the scale to display content according to the [FitMode] strategy. + /// + /// This value determines how content should be scaled based on the fit mode and is + /// typically used as the minimum scale for the InteractiveViewer (though an explicit + /// [PdfViewerParams.minScale] parameter may override this). + /// + /// **Calculation:** + /// Uses the maximum page dimensions from [getMaxWidth] and [getMaxHeight], then calculates: + /// `scale = viewport / (maxPageDimension + boundaryMargin + margin*2)` + /// + /// This ensures the largest page plus all margins fits within the viewport when scaled. + /// + /// **Return value behavior by mode:** + /// - [FitMode.fit]: Scale to fit largest page + margins within viewport (both axes) + /// - [FitMode.fill]: Scale to fill viewport on cross-axis (width for vertical scroll) + /// - [FitMode.cover]: Scale to fill viewport on largest axis (may crop content) + /// - [FitMode.none]: Returns 1.0 (no scaling) + /// + /// **Custom layouts:** + /// Override this method for custom scaling logic, or override [getMaxWidth] and [getMaxHeight] + /// to change the dimensions used (e.g., spread width instead of page width). + /// + /// See also: [calculatePageSizes] for page-level scaling + double calculateFitScale( + PdfLayoutHelper helper, + FitMode mode, { + PageTransition pageTransition = PageTransition.continuous, + int? pageNumber, + }) { + // FitMode.cover is special - it uses different logic + if (mode == FitMode.cover) { + // In discrete mode, calculate cover scale for the specific page + // In continuous mode, use document dimensions to match legacy _coverScale calculation + final double width; + final double height; + + if (pageTransition == PageTransition.discrete && pageNumber != null && getPageDimensions(pageNumber) != null) { + final dimensions = getPageDimensions(pageNumber); + width = dimensions!.width; + height = dimensions.height; + // For discrete, add margins since page dimensions don't include them + final widthWithMargins = width + helper.margin * 2 + helper.boundaryMarginHorizontal; + final heightWithMargins = height + helper.margin * 2 + helper.boundaryMarginVertical; + return max(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + } else { + // Continuous mode uses document dimensions (which already include margin * 2) + // This matches legacy _coverScale calculation: viewport / (documentSize + boundaryMargin) + width = documentSize.width; + height = documentSize.height; + final widthWithMargins = width + helper.boundaryMarginHorizontal; + final heightWithMargins = height + helper.boundaryMarginVertical; + return max(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + } + } + + // For discrete mode with a specific page, calculate scale for that page + // Otherwise, use the maximum dimensions across all pages + final double width; + final double height; + + if (pageTransition == PageTransition.discrete && pageNumber != null && getPageDimensions(pageNumber) != null) { + final dimensions = getPageDimensions(pageNumber); + width = dimensions!.width; + height = dimensions.height; + } else { + width = getMaxWidth(); + height = getMaxHeight(); + } + + // Use the core calculation logic for fit/fill/none + return calculateFitScaleForDimensions( + width: width, + height: height, + helper: helper, + mode: mode, + scrollAxis: primaryAxis, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! PdfPageLayout) return false; + // Use runtimeType to ensure subclasses with additional fields aren't considered equal + if (runtimeType != other.runtimeType) return false; + return listEquals(pageLayouts, other.pageLayouts) && documentSize == other.documentSize; + } + + @override + int get hashCode => Object.hash(runtimeType, Object.hashAll(pageLayouts), documentSize); +} + +/// Spread-aware layout base class. +/// +/// This class extends [PdfPageLayout] to support layouts that group multiple pages +/// into "spreads" (e.g., facing pages). It provides spread-specific functionality +/// while maintaining compatibility with the base page layout system. +/// +/// **Key concepts:** +/// - A "spread" is a layout unit that may contain one or more pages +/// - [spreadLayouts] contains the bounds of each spread (indexed by spread index, 0-based) +/// - [pageToSpreadIndex] maps page numbers (1-based) to spread indices (0-based) +/// +/// **Example: Facing pages layout** +/// - Page 1 (cover): spread 0 +/// - Pages 2-3: spread 1 +/// - Pages 4-5: spread 2 +/// - pageToSpreadIndex = [0, 1, 1, 2, 2, ...] (0-based: index 0 = page 1) +/// - spreadLayouts = [Rect(cover bounds), Rect(pages 2-3 bounds), Rect(pages 4-5 bounds), ...] +abstract class PdfSpreadLayout extends PdfPageLayout { + PdfSpreadLayout({ + required super.pageLayouts, + required super.documentSize, + required this.spreadLayouts, + required this.pageToSpreadIndex, + }); + + /// List of spread bounds, indexed by spread index + /// + /// Each Rect represents the bounds of one spread in document coordinates. + /// Use [getSpreadBounds] to get the spread for a specific page number. + final List spreadLayouts; + + /// Maps page number to spread index. + /// + /// - Example: `pageToSpreadIndex[0]` = spread index for page 1 + final List pageToSpreadIndex; + + /// Get the spread index for a given page number. + int getSpreadIndex(int pageNumber) => pageToSpreadIndex[pageNumber - 1]; + + /// Get the spread bounds for a given page number. + Rect getSpreadBounds(int pageNumber) { + return spreadLayouts[pageToSpreadIndex[pageNumber - 1]]; + } + + /// Get the page range (first, last) for the spread containing pageNumber. + ({int first, int last}) getPageRange(int pageNumber) { + final spreadIndex = pageToSpreadIndex[pageNumber - 1]; + var first = -1; + var last = -1; + for (var i = 0; i < pageToSpreadIndex.length; i++) { + if (pageToSpreadIndex[i] == spreadIndex) { + if (first == -1) first = i + 1; // Convert to 1-based + last = i + 1; // Convert to 1-based + } + } + return (first: first, last: last); + } + + /// Get the first page number of the spread containing pageNumber. + int getSpreadFirstPage(int pageNumber) => getPageRange(pageNumber).first; + + /// Get the last page number of the spread containing pageNumber. + int getSpreadLastPage(int pageNumber) => getPageRange(pageNumber).last; + + /// Get the first page number of a spread by its index. + int? getFirstPageOfSpread(int spreadIndex) { + if (spreadIndex < 0 || spreadIndex >= spreadLayouts.length) { + return null; + } + for (var i = 0; i < pageToSpreadIndex.length; i++) { + if (pageToSpreadIndex[i] == spreadIndex) { + return i + 1; // Convert to 1-based page number + } + } + return null; + } + + /// Gets the spread dimensions for a page number + @override + Size? getPageDimensions(int pageNumber, {bool withMargins = false}) { + if (pageNumber < 1 || pageNumber > pageToSpreadIndex.length) { + return null; + } + final spreadBounds = getSpreadBounds(pageNumber); + return spreadBounds.size + (withMargins ? Offset(margin * 2, margin * 2) : Offset.zero); + } + + /// Gets the maximum spread width across all spreads. + @override + double getMaxWidth({bool withMargins = false}) { + final maxWidthNoMargins = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.width)); + return maxWidthNoMargins + (withMargins ? margin * 2 : 0); + } + + /// Gets the maximum spread height across all spreads. + @override + double getMaxHeight({bool withMargins = false}) { + final maxHeightNoMargins = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.height)); + return maxHeightNoMargins + (withMargins ? margin * 2 : 0); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! PdfSpreadLayout) return false; + return super == other && + listEquals(spreadLayouts, other.spreadLayouts) && + listEquals(pageToSpreadIndex, other.pageToSpreadIndex); + } + + @override + int get hashCode => Object.hash(super.hashCode, Object.hashAll(spreadLayouts), Object.hashAll(pageToSpreadIndex)); +} + +/// Single pages layout implementation supporting both vertical and horizontal scrolling. +/// +/// This layout displays pages one after another in either a vertical or horizontal +/// scrolling direction. The scroll direction is specified when creating the layout. +/// +/// Example usage: +/// ```dart +/// // Vertical scrolling (default) +/// layoutPages: (pages, params, {viewport}) => +/// SinglePagesLayout.fromPages(pages, params, helper: helper), +/// +/// // Horizontal scrolling +/// layoutPages: (pages, params, {viewport}) => +/// SinglePagesLayout.fromPages( +/// pages, +/// params, +/// helper: helper, +/// scrollDirection: Axis.horizontal, +/// ), +/// ``` +class SinglePagesLayout extends PdfPageLayout { + SinglePagesLayout({required super.pageLayouts, required super.documentSize, required this.scrollDirection}); + + /// Create a single pages layout from pages and parameters. + /// + /// The [scrollDirection] parameter determines whether pages scroll vertically (default) + /// or horizontally. + factory SinglePagesLayout.fromPages( + List pages, + PdfViewerParams params, { + PdfLayoutHelper? helper, + Axis scrollDirection = Axis.vertical, + }) { + final layout = SinglePagesLayout(pageLayouts: [], documentSize: Size.zero, scrollDirection: scrollDirection); + final result = layout.layoutBuilder(pages, params, helper: helper); + return SinglePagesLayout( + pageLayouts: result.pageLayouts, + documentSize: result.documentSize, + scrollDirection: scrollDirection, + ); + } + + final Axis scrollDirection; + + @override + Axis get primaryAxis => scrollDirection; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! SinglePagesLayout) return false; + return super == other && scrollDirection == other.scrollDirection; + } + + @override + int get hashCode => Object.hash(super.hashCode, scrollDirection); + + @override + LayoutResult layoutBuilder(List pages, PdfViewerParams params, {PdfLayoutHelper? helper}) { + assert(helper != null, 'SinglePagesLayout requires PdfLayoutHelper for fit modes other than none or cover'); + final pageSizes = PdfPageLayout.calculatePageSizes( + pages: pages, + fitMode: params.fitMode, + scrollAxis: scrollDirection, + helper: helper!, + ); + + final centerPerpendicular = + params.fitMode == FitMode.fit || params.fitMode == FitMode.none || params.fitMode == FitMode.cover; + + return layoutSequentialPages( + pageSizes: pageSizes, + scrollAxis: scrollDirection, + margin: params.margin, + centerPerpendicular: centerPerpendicular, + ); + } +} + +/// Facing pages layout implementation. +class FacingPagesLayout extends PdfSpreadLayout { + FacingPagesLayout({ + required super.pageLayouts, + required super.documentSize, + required super.spreadLayouts, + required super.pageToSpreadIndex, + }); + + /// Create a facing pages layout from pages and parameters. + factory FacingPagesLayout.fromPages( + List pages, + PdfViewerParams params, { + PdfLayoutHelper? helper, + bool firstPageIsCoverPage = false, + bool isRightToLeftReadingOrder = false, + double gutter = 4.0, // gap between left/right pages + }) { + if (pages.isEmpty) { + return FacingPagesLayout(pageLayouts: [], documentSize: Size.zero, spreadLayouts: [], pageToSpreadIndex: []); + } + + // For fit/fill/cover modes with viewport scaling, we need helper + final scaleToViewport = + params.fitMode == FitMode.fit || params.fitMode == FitMode.fill || params.fitMode == FitMode.cover; + assert( + !scaleToViewport || helper != null, + 'FitMode.${params.fitMode.name} requires PdfLayoutHelper for FacingPagesLayout.', + ); + if (scaleToViewport && helper == null) { + return FacingPagesLayout(pageLayouts: [], documentSize: Size.zero, spreadLayouts: [], pageToSpreadIndex: []); + } + + final pageLayouts = []; + final spreadLayouts = []; + final pageToSpreadIndex = List.filled(pages.length, 0); + var y = params.margin; + var spreadIndex = 0; + var pageIndex = 0; + + // Available space for content + final availableWidth = scaleToViewport ? helper!.availableWidth : 0.0; + final availableHeight = scaleToViewport ? helper!.availableHeight : 0.0; + + // Handle cover page if needed + if (firstPageIsCoverPage && pages.isNotEmpty) { + final coverPage = pages[0]; + final coverSize = scaleToViewport + ? _calculatePageSize( + page: coverPage, + targetWidth: availableWidth, + availableHeight: availableHeight, + fitMode: params.fitMode, + ) + : Size(coverPage.width, coverPage.height); + + final coverX = scaleToViewport ? params.margin + (availableWidth - coverSize.width) / 2 : params.margin; + pageLayouts.add(Rect.fromLTWH(coverX, y, coverSize.width, coverSize.height)); + pageToSpreadIndex[0] = spreadIndex; // Page 1 is at index 0 + spreadLayouts.add(pageLayouts.last); + + spreadIndex++; + y += coverSize.height + params.margin; + pageIndex = 1; + } + + // Process remaining pages as spreads (pairs) + while (pageIndex < pages.length) { + final leftPage = pages[pageIndex]; + final rightPageIndex = pageIndex + 1; + final rightPage = rightPageIndex < pages.length ? pages[rightPageIndex] : null; + + // Calculate page dimensions + final leftSize = scaleToViewport + ? _calculatePageSize( + page: leftPage, + targetWidth: rightPage != null ? (availableWidth - gutter) / 2 : availableWidth, + availableHeight: availableHeight, + fitMode: params.fitMode, + ) + : Size(leftPage.width, leftPage.height); + + final rightSize = rightPage != null && scaleToViewport + ? _calculatePageSize( + page: rightPage, + targetWidth: (availableWidth - gutter) / 2, + availableHeight: availableHeight, + fitMode: params.fitMode, + ) + : Size(rightPage?.width ?? 0.0, rightPage?.height ?? 0.0); + + // Calculate spread dimensions + final spreadWidth = rightPage != null ? leftSize.width + gutter + rightSize.width : leftSize.width; + final spreadHeight = max(leftSize.height, rightSize.height); + final spreadX = scaleToViewport ? params.margin + (availableWidth - spreadWidth) / 2 : params.margin; + + spreadLayouts.add(Rect.fromLTWH(spreadX, y, spreadWidth, spreadHeight)); + + // Layout pages within spread (RTL vs LTR) + final leftX = isRightToLeftReadingOrder && rightPage != null ? spreadX + rightSize.width + gutter : spreadX; + final rightX = isRightToLeftReadingOrder ? spreadX : spreadX + leftSize.width + gutter; + + pageLayouts.add(Rect.fromLTWH(leftX, y + (spreadHeight - leftSize.height) / 2, leftSize.width, leftSize.height)); + pageToSpreadIndex[pageIndex] = spreadIndex; // 0-based indexing + + if (rightPage != null) { + pageLayouts.add( + Rect.fromLTWH(rightX, y + (spreadHeight - rightSize.height) / 2, rightSize.width, rightSize.height), + ); + pageToSpreadIndex[rightPageIndex] = spreadIndex; // 0-based indexing + } + + spreadIndex++; + y += spreadHeight + params.margin; + pageIndex += rightPage != null ? 2 : 1; + } + + // Calculate document width based on content + final maxSpreadRight = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.right)); + final documentWidth = maxSpreadRight + params.margin; + + return FacingPagesLayout( + pageLayouts: pageLayouts, + documentSize: Size(documentWidth, y), + spreadLayouts: spreadLayouts, + pageToSpreadIndex: pageToSpreadIndex, + ); + } + + @override + Axis get primaryAxis => Axis.vertical; // Typically vertical for facing pages + + @override + LayoutResult layoutBuilder(List pages, PdfViewerParams params, {PdfLayoutHelper? helper}) { + // For the base layoutBuilder, use default parameters (no cover, LTR, no gutter) + final layout = FacingPagesLayout.fromPages( + pages, + params, + helper: helper, + firstPageIsCoverPage: false, + isRightToLeftReadingOrder: false, + gutter: 0.0, + ); + return LayoutResult(pageLayouts: layout.pageLayouts, documentSize: layout.documentSize); + } +} + +/// FacingPagesLayout Helper function to calculate page dimensions for a single page +Size _calculatePageSize({ + required PdfPage page, + required double targetWidth, + required double availableHeight, + required FitMode fitMode, +}) { + if (fitMode == FitMode.fit) { + final scale = min(targetWidth / page.width, availableHeight / page.height); + return Size(page.width * scale, page.height * scale); + } else { + final scale = targetWidth / page.width; + return Size(targetWidth, page.height * scale); + } +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 3cc0a8df..db581ae1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -208,7 +208,7 @@ class PdfViewer extends StatefulWidget { class _PdfViewerState extends State with SingleTickerProviderStateMixin - implements PdfTextSelectionDelegate, PdfViewerCoordinateConverter { + implements PdfTextSelectionDelegate, DocumentCoordinateConverter { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; @@ -218,11 +218,10 @@ class _PdfViewerState extends State PdfDocument? _document; PdfPageLayout? _layout; Size? _viewSize; - double? _coverScale; - double? _alternativeFitScale; static const _defaultMinScale = 0.1; - double _minScale = _defaultMinScale; + double _fitScale = _defaultMinScale; // Scale calculated based on fitMode for page positioning int? _pageNumber; + ({int first, int last})? _visiblePageRange; bool _initialized = false; StreamSubscription? _documentSubscription; final _interactiveViewerKey = GlobalKey(); @@ -256,7 +255,6 @@ class _PdfViewerState extends State _TextSelectionPart _selPartLastMoved = _TextSelectionPart.none; bool _isSelectingAllText = false; - bool _isSelectingAWord = false; PointerDeviceKind? _selectionPointerDeviceKind; Offset? _contextMenuDocumentPosition; @@ -264,6 +262,10 @@ class _PdfViewerState extends State Timer? _interactionEndedTimer; bool _isInteractionGoingOn = false; + bool _isActiveGesture = false; // True during pan/scale gestures + bool _isActivelyZooming = false; // True only during active pinch-zoom gesture + bool _hasActiveAnimations = false; // True when InteractiveViewer has active animations + bool _isTransitioningPages = false; // True during discrete page transition animation BuildContext? _contextForFocusNode; Offset _pointerOffset = Offset.zero; @@ -456,8 +458,14 @@ class _PdfViewerState extends State transformationController: _txController, constrained: false, boundaryMargin: widget.params.scrollPhysics == null - ? const EdgeInsets.all(double.infinity) // NOTE: boundaryMargin is handled manually + ? const EdgeInsets.all(double.infinity) + : widget.params.pageTransition == PageTransition.discrete + ? EdgeInsets + .zero // Discrete mode uses boundaryProvider : _adjustedBoundaryMargins, + boundaryProvider: widget.params.pageTransition == PageTransition.discrete + ? _getDiscreteBoundaryRect + : null, maxScale: widget.params.maxScale, minScale: minScale, panAxis: widget.params.panAxis, @@ -465,7 +473,8 @@ class _PdfViewerState extends State scaleEnabled: widget.params.scaleEnabled, onInteractionEnd: _onInteractionEnd, onInteractionStart: _onInteractionStart, - onInteractionUpdate: widget.params.onInteractionUpdate, + onInteractionUpdate: _onInteractionUpdate, + onAnimationEnd: _onAnimationEnd, interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, scrollPhysics: widget.params.scrollPhysics, @@ -532,16 +541,60 @@ class _PdfViewerState extends State } Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { - final boundaryMargin = _adjustedBoundaryMargins; - if (boundaryMargin.containsInfinite) { - return Offset.zero; - } - final layout = _layout!; final visible = m.calcVisibleRect(viewSize); var dxDoc = 0.0; var dyDoc = 0.0; + // Check for invalid rect (happens when matrix has incorrect values) + if (!visible.isFinite || visible.isEmpty || visible.left > visible.right || visible.top > visible.bottom) { + return Offset.zero; + } + + // In discrete mode, use the page/spread bounds directly + if (widget.params.pageTransition == PageTransition.discrete) { + final discreteBounds = _getDiscreteBoundaryRect( + visible, + Size(layout.documentSize.width, layout.documentSize.height), + ); + if (discreteBounds == null) { + return Offset.zero; + } + + // Calculate overscroll: how much to adjust to keep visible rect within discrete bounds + if (visible.left < discreteBounds.left) { + dxDoc = discreteBounds.left - visible.left; + } else if (visible.right > discreteBounds.right) { + dxDoc = discreteBounds.right - visible.right; + } + + if (visible.top < discreteBounds.top) { + dyDoc = discreteBounds.top - visible.top; + } else if (visible.bottom > discreteBounds.bottom) { + dyDoc = discreteBounds.bottom - visible.bottom; + } + + // Special case: if the discrete bounds are smaller than the viewport, center them + if (discreteBounds.width < viewSize.width) { + final desiredCenter = (discreteBounds.left + discreteBounds.right) / 2; + final currentCenter = (visible.left + visible.right) / 2; + dxDoc = desiredCenter - currentCenter; + } + if (discreteBounds.height < viewSize.height) { + final desiredCenter = (discreteBounds.top + discreteBounds.bottom) / 2; + final currentCenter = (visible.top + visible.bottom) / 2; + dyDoc = desiredCenter - currentCenter; + } + + return Offset(dxDoc, dyDoc); + } + + // Continuous mode: use EdgeInsets-based boundaries + final boundaryMargin = _adjustedBoundaryMargins; + if (boundaryMargin.containsInfinite) { + return Offset.zero; + } + final leftBoundary = -boundaryMargin.left; // negative margin reduces allowed leftward scroll final rightBoundary = layout.documentSize.width + boundaryMargin.right; // negative margin reduces allowed rightward scroll @@ -564,9 +617,7 @@ class _PdfViewerState extends State } Matrix4 _calcMatrixForClampedToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { - if (widget.params.scrollPhysics == null) { - _adjustBoundaryMargins(_viewSize!, candidate.zoom); - } + _adjustBoundaryMargins(viewSize, candidate.zoom); final overScroll = _calcOverscroll(candidate, viewSize: viewSize); if (overScroll == Offset.zero) { return candidate; @@ -579,15 +630,19 @@ class _PdfViewerState extends State final currentPageNumber = _guessCurrentPageNumber(); final oldVisibleRect = _initialized ? _visibleRect : Rect.zero; final oldLayout = _layout; - final oldMinScale = _minScale; + final oldFitScale = _fitScale; final oldSize = _viewSize; final isViewSizeChanged = oldSize != viewSize; _viewSize = viewSize; + final isLayoutChanged = _relayoutPages(); - _calcCoverFitScale(); + _calcFitScale(); _calcZoomStopTable(); - _adjustBoundaryMargins(viewSize, max(_minScale, _currentZoom)); + // Use max of current zoom and minScale for boundary calculations + // minScale getter returns widget.params.minScale ?? _fitScale, matching InteractiveViewer + final boundaryScale = max(_currentZoom, minScale); + _adjustBoundaryMargins(viewSize, boundaryScale); void callOnViewerSizeChanged() { if (isViewSizeChanged) { @@ -597,23 +652,50 @@ class _PdfViewerState extends State } } - if (!_initialized && _layout != null && _coverScale != null) { + if (!_initialized && _layout != null && _fitScale != _defaultMinScale) { _initialized = true; Future.microtask(() async { // forcibly calculate fit scale for the initial page _pageNumber = _gotoTargetPageNumber = _calcInitialPageNumber(); - _calcCoverFitScale(); + _calcFitScale(); _calcZoomStopTable(); final zoom = widget.params.calculateInitialZoom?.call( _document!, _controller!, - _alternativeFitScale ?? _coverScale!, - _coverScale!, + _calculateScaleForMode(FitMode.fit), // fitZoom (was _alternativeFitScale) + _calculateScaleForMode(FitMode.cover), // coverZoom (was _coverScale) ) ?? - _coverScale!; + _getInitialZoom(); await _setZoom(Offset.zero, zoom, duration: Duration.zero); - await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); + // Recalculate boundary margins with the correct initial zoom + _adjustBoundaryMargins(_viewSize!, zoom); + + // Determine initial anchor for discrete mode + var discreteAnchor = PdfPageAnchor.center; + if (widget.params.pageTransition == PageTransition.discrete) { + // Check if page fits in viewport on primary axis + final layout = _layout!; + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + final pageRect = layout is PdfSpreadLayout + ? layout.getSpreadBounds(_pageNumber!) + : layout.pageLayouts[_pageNumber! - 1]; + final pageSize = isPrimaryVertical ? pageRect.height : pageRect.width; + final viewportSize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; + final pageFitsInViewport = pageSize * zoom <= viewportSize; + + // If page fits, center it; otherwise use appropriate edge anchor + if (!pageFitsInViewport) { + discreteAnchor = widget.params.pageAnchor; + } + } + + await _goToPage( + pageNumber: _pageNumber!, + duration: Duration.zero, + maintainCurrentZoom: true, + anchor: widget.params.pageTransition == PageTransition.discrete ? discreteAnchor : PdfPageAnchor.topLeft, + ); if (mounted && _document != null && _controller != null) { widget.params.onViewerReady?.call(_document!, _controller!); } @@ -622,8 +704,8 @@ class _PdfViewerState extends State } else if (isLayoutChanged || isViewSizeChanged) { Future.microtask(() async { if (mounted) { - // preserve the current zoom whilst respecting the new minScale - final zoomTo = _currentZoom < _minScale || _currentZoom == oldMinScale ? _minScale : _currentZoom; + // preserve the current zoom whilst respecting the new fitScale + final zoomTo = _currentZoom < _fitScale || _currentZoom == oldFitScale ? _fitScale : _currentZoom; if (isLayoutChanged) { // if the layout changed, calculate the top-left position in the document // before the layout change and go to that position in the new layout @@ -675,7 +757,12 @@ class _PdfViewerState extends State } }); } else if (currentPageNumber != null && _pageNumber != currentPageNumber) { - _setCurrentPageNumber(currentPageNumber); + // In discrete mode, only allow page changes via _snapToPage (not guessed page number) + // Exception: during initialization when _pageNumber is null + // In continuous mode, always update page based on what's most visible + if (widget.params.pageTransition != PageTransition.discrete || _pageNumber == null) { + _setCurrentPageNumber(currentPageNumber); + } } } @@ -774,17 +861,72 @@ class _PdfViewerState extends State }); } - void _onInteractionEnd(ScaleEndDetails details) { + // State for discrete page transitions + Matrix4? _interactionStartMatrix; + double? _interactionStartScale; + int? _interactionStartPage; + bool _hadScaleChangeInInteraction = false; + + Future _onInteractionEnd(ScaleEndDetails details) async { + _isActiveGesture = false; + _isActivelyZooming = false; + widget.params.onInteractionEnd?.call(details); + + final shouldHandleDiscrete = + (widget.params.pageTransition == PageTransition.discrete && + (!_hadScaleChangeInInteraction && !_hasActiveAnimations)); + + if (shouldHandleDiscrete) { + await _handleDiscretePageTransition(details); + } + + _interactionStartMatrix = null; + _interactionStartScale = null; _stopInteraction(); } void _onInteractionStart(ScaleStartDetails details) { _startInteraction(); _requestFocus(); + _isActiveGesture = true; // User is now actively dragging + if (widget.params.pageTransition == PageTransition.discrete) { + _interactionStartMatrix = _txController.value.clone(); + _interactionStartScale = _currentZoom; + _interactionStartPage = _pageNumber; + _hadScaleChangeInInteraction = false; // Reset for new interaction + } widget.params.onInteractionStart?.call(details); } + void _onInteractionUpdate(ScaleUpdateDetails details) { + // Track if scale changed during the interaction + if (widget.params.pageTransition == PageTransition.discrete && _interactionStartScale != null) { + final currentScale = _currentZoom; + final scaleChanged = (_interactionStartScale! - currentScale).abs() > 0.01; + if (scaleChanged) { + _hadScaleChangeInInteraction = true; + _isActivelyZooming = true; + _hasActiveAnimations = true; + } + } + widget.params.onInteractionUpdate?.call(details); + } + + void _onAnimationEnd() { + // Check if all animations have completed + final ivState = _interactiveViewerKey.currentState; + final hasAnimations = + (ivState != null && (ivState as dynamic).hasActiveAnimations == true) || _animController.isAnimating; + + if (!hasAnimations) { + // Animations complete, clear flags + _hasActiveAnimations = false; + _hadScaleChangeInInteraction = false; + setState(() {}); // Trigger repaint + } + } + /// Last page number that is explicitly requested to go to. int? _gotoTargetPageNumber; @@ -855,15 +997,305 @@ class _PdfViewerState extends State _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); } + /// Handles discrete page transition logic when interaction ends. + /// + /// Determines whether to snap back to current page/spread or advance to next/previous + /// based on swipe velocity or drag threshold (50% of page/spread width). + Future _handleDiscretePageTransition(ScaleEndDetails details) async { + final startMatrix = _interactionStartMatrix; + if (startMatrix == null || _layout == null || _pageNumber == null || _interactionStartPage == null) { + return; + } + + final currentPage = _interactionStartPage!; + final layout = _layout!; + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + + // Calculate viewport movement for threshold-based page transitions + final startRect = startMatrix.calcVisibleRect(_viewSize!); + final endRect = _txController.value.calcVisibleRect(_viewSize!); + final verticalDelta = endRect.top - startRect.top; + final horizontalDelta = endRect.left - startRect.left; + final scrollDelta = isPrimaryVertical ? verticalDelta : horizontalDelta; + + // Extract velocity components + final scrollVelocity = isPrimaryVertical + ? details.velocity.pixelsPerSecond.dy + : details.velocity.pixelsPerSecond.dx; + final crossAxisVelocity = isPrimaryVertical + ? details.velocity.pixelsPerSecond.dx + : details.velocity.pixelsPerSecond.dy; + + // Ignore cross-axis dominant movements, unless there's significant velocity on primary axis + // Use velocity (not delta) to detect user's intent at release time + const minFlingVelocity = 50.0; + final hasSignificantVelocity = scrollVelocity.abs() > minFlingVelocity; + if (!hasSignificantVelocity && crossAxisVelocity.abs() > scrollVelocity.abs() * 3) { + return; + } + + // Stop animation before making snap decision + _stopInteractiveViewerAnimation(); + await Future.delayed(const Duration(milliseconds: 5)); + + // Determine target page based on fling velocity or drag threshold + int targetPage; + if (hasSignificantVelocity) { + // Flutter velocity: positive = finger down/right → content up/left → previous page + final velocityDirection = scrollVelocity > 0 ? -1 : 1; + + // Only advance if at boundary when fling started + if (_isAtBoundary(startRect, currentPage, layout, isPrimaryVertical, velocityDirection)) { + targetPage = _getAdjacentPage(currentPage, layout, velocityDirection); + } else { + return; // Not at boundary - let InteractiveViewer handle fling + } + } else { + // No fling - use visible page area threshold + targetPage = _getTargetPageBasedOnThreshold(currentPage, scrollDelta, isPrimaryVertical); + } + + final snapAnchor = _getSnapAnchor( + targetPage: targetPage, + currentPage: currentPage, + layout: layout, + isPrimaryVertical: isPrimaryVertical, + endRect: endRect, + ); + + await _snapToPage(targetPage, anchor: snapAnchor, currentPage: currentPage); + } + + /// Checks if viewport is at a page boundary in the given direction. + bool _isAtBoundary(Rect visibleRect, int pageNumber, PdfPageLayout layout, bool isPrimaryVertical, int direction) { + final isMovingForward = direction > 0; + + // Determine anchor based on direction to include boundary margins + final anchor = isMovingForward + ? (isPrimaryVertical ? PdfPageAnchor.bottomLeft : PdfPageAnchor.topRight) + : (isPrimaryVertical ? PdfPageAnchor.topLeft : PdfPageAnchor.topLeft); + + final pageRect = _getEffectivePageBounds(pageNumber, layout, anchor: anchor); + final currentScale = _txController.value.getMaxScaleOnAxis(); + // Increase tolerance for clamping physics - user may not be able to get exactly to the boundary + const baseTolerance = 10; // pixels in screen space + final tolerance = baseTolerance * currentScale; // scale to document coordinates + + // Check appropriate boundary based on direction + if (isMovingForward) { + return isPrimaryVertical + ? visibleRect.bottom >= pageRect.bottom - tolerance + : visibleRect.right >= pageRect.right - tolerance; + } else { + return isPrimaryVertical + ? visibleRect.top <= pageRect.top + tolerance + : visibleRect.left <= pageRect.left + tolerance; + } + } + + /// Gets the adjacent page/spread in the given direction. + int _getAdjacentPage(int currentPage, PdfPageLayout layout, int direction) { + if (layout is PdfSpreadLayout) { + final currentSpreadIndex = layout.pageToSpreadIndex[currentPage - 1]; // Convert to 0-based + final nextSpreadIndex = currentSpreadIndex + direction; + + return layout.getFirstPageOfSpread(nextSpreadIndex) ?? currentPage; + } else { + final candidatePage = currentPage + direction; + return candidatePage.clamp(1, _document!.pages.length); + } + } + + /// Determines the snap anchor based on target page and current position. + PdfPageAnchor _getSnapAnchor({ + required int targetPage, + required int currentPage, + required PdfPageLayout layout, + required bool isPrimaryVertical, + required Rect endRect, + }) { + // Check if page/spread overflows on primary and cross axes at current zoom + final pageRect = layout is PdfSpreadLayout + ? layout.getSpreadBounds(targetPage) + : layout.pageLayouts[targetPage - 1]; + + final pagePrimarySize = isPrimaryVertical ? pageRect.height : pageRect.width; + final pageCrossSize = isPrimaryVertical ? pageRect.width : pageRect.height; + final viewportPrimarySize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; + final viewportCrossSize = isPrimaryVertical ? _viewSize!.width : _viewSize!.height; + + final primaryOverflows = pagePrimarySize * _currentZoom > viewportPrimarySize; + final crossOverflows = pageCrossSize * _currentZoom > viewportCrossSize; + + if (!primaryOverflows && !crossOverflows) { + // Page fits entirely - center it + return PdfPageAnchor.center; + } + + // Page overflows on at least one axis + if (targetPage == currentPage) { + // Staying on current page - snap to nearest edge + final pageCenter = isPrimaryVertical + ? (pageRect.top + pageRect.bottom) / 2 + : (pageRect.left + pageRect.right) / 2; + final visibleCenter = isPrimaryVertical ? (endRect.top + endRect.bottom) / 2 : (endRect.left + endRect.right) / 2; + + if (isPrimaryVertical) { + // If cross overflows, anchor to top-left or bottom-left instead of centering + return crossOverflows + ? (visibleCenter < pageCenter ? PdfPageAnchor.topLeft : PdfPageAnchor.bottomLeft) + : (visibleCenter < pageCenter ? PdfPageAnchor.topCenter : PdfPageAnchor.bottomCenter); + } else { + // If cross overflows, anchor to top-left or top-right instead of centering + return crossOverflows + ? (visibleCenter < pageCenter ? PdfPageAnchor.topLeft : PdfPageAnchor.topRight) + : (visibleCenter < pageCenter ? PdfPageAnchor.centerLeft : PdfPageAnchor.centerRight); + } + } else { + // Advancing to different page - snap to entry edge + if (isPrimaryVertical) { + // If cross overflows, anchor to top-left/bottom-left instead of top-center/bottom-center + return crossOverflows + ? (targetPage > currentPage ? PdfPageAnchor.topLeft : PdfPageAnchor.bottomLeft) + : (targetPage > currentPage ? PdfPageAnchor.topCenter : PdfPageAnchor.bottomCenter); + } else { + // If cross overflows, anchor to top-left/top-right instead of center-left/center-right + return crossOverflows + ? (targetPage > currentPage ? PdfPageAnchor.topLeft : PdfPageAnchor.topRight) + : (targetPage > currentPage ? PdfPageAnchor.centerLeft : PdfPageAnchor.centerRight); + } + } + } + + /// Determines target page based on visible area of pages. + /// + /// Transitions to whichever page has the most visible area on the primary axis. + /// This works correctly at any zoom level, including when zoomed in. + int _getTargetPageBasedOnThreshold(int currentPage, double scrollDelta, bool isPrimaryVertical) { + final layout = _layout!; + final visibleRect = _txController.value.calcVisibleRect(_viewSize!); + + // Calculate visible area for current page and adjacent pages + final currentPageBounds = layout is PdfSpreadLayout + ? layout.getSpreadBounds(currentPage) + : layout.pageLayouts[currentPage - 1]; + + final currentPageVisibleArea = _calcPageIntersectionArea(visibleRect, currentPageBounds, isPrimaryVertical); + + // Check previous page (if exists) + double prevPageVisibleArea = 0; + if (currentPage > 1) { + final prevPageBounds = layout.pageLayouts[currentPage - 2]; + prevPageVisibleArea = _calcPageIntersectionArea(visibleRect, prevPageBounds, isPrimaryVertical); + } + + // Check next page (if exists) + double nextPageVisibleArea = 0; + if (currentPage < layout.pageLayouts.length) { + final nextPageBounds = layout.pageLayouts[currentPage]; + nextPageVisibleArea = _calcPageIntersectionArea(visibleRect, nextPageBounds, isPrimaryVertical); + } + + // Transition to the page with the most visible area + if (prevPageVisibleArea > currentPageVisibleArea && prevPageVisibleArea > nextPageVisibleArea) { + return _getAdjacentPage(currentPage, layout, -1); + } else if (nextPageVisibleArea > currentPageVisibleArea && nextPageVisibleArea > prevPageVisibleArea) { + return _getAdjacentPage(currentPage, layout, 1); + } + + // Current page has most visible area - snap back to current page + return currentPage; + } + + /// Calculate the visible intersection area on the primary axis between visible rect and page bounds. + double _calcPageIntersectionArea(Rect visibleRect, Rect pageBounds, bool isPrimaryVertical) { + final intersection = visibleRect.intersect(pageBounds); + if (intersection.isEmpty) { + return 0; + } + // Return the primary axis length of intersection (not full area) + // This gives us how much of the page is visible on the scroll axis + return isPrimaryVertical ? intersection.height : intersection.width; + } + + /// Snaps to the target page/spread with animation. + Future _snapToPage(int targetPage, {required PdfPageAnchor anchor, int? currentPage}) async { + final duration = const Duration(milliseconds: 400); + + // Only reset scale when advancing to a different page, not when snapping back to current page + final isAdvancingToNewPage = currentPage != null && targetPage != currentPage; + + if (!isAdvancingToNewPage) { + // let InteractiveViewer's scroll physics handle snap back to current page + return; + } + + final shouldResetScale = widget.params.resetScaleOnDiscreteTransition && isAdvancingToNewPage; + + if (shouldResetScale && _viewSize != null) { + // Calculate fit scale for the target page + _calcFitScale(targetPage); + _adjustBoundaryMargins(_viewSize!, _fitScale); + } + + // Check if the target page fits in viewport and use centered anchor if so + var effectiveAnchor = anchor; + final layout = _layout; + if (layout != null && _viewSize != null && anchor != PdfPageAnchor.center) { + // Calculate what the scale will be for this page + final targetScale = shouldResetScale ? _fitScale : _currentZoom; + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + final pageRect = layout is PdfSpreadLayout + ? layout.getSpreadBounds(targetPage) + : layout.pageLayouts[targetPage - 1]; + + final pagePrimarySize = isPrimaryVertical ? pageRect.height : pageRect.width; + final pageCrossSize = isPrimaryVertical ? pageRect.width : pageRect.height; + final viewportPrimarySize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; + final viewportCrossSize = isPrimaryVertical ? _viewSize!.width : _viewSize!.height; + + final primaryFits = pagePrimarySize * targetScale <= viewportPrimarySize; + final crossFits = pageCrossSize * targetScale <= viewportCrossSize; + + // Only use center anchor if page fits on BOTH axes + if (primaryFits && crossFits) { + effectiveAnchor = PdfPageAnchor.center; + } + } + + final targetZoom = shouldResetScale ? _fitScale : _currentZoom; + + // Mark that we're transitioning pages to prevent page number updates during animation + if (shouldResetScale) { + _isTransitioningPages = true; + } + + _setCurrentPageNumber(targetPage, targetZoom: targetZoom, doSetState: true); + + final targetMatrix = shouldResetScale + ? _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, forceScale: targetZoom) + : _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, maintainCurrentZoom: true); + + await _goTo(targetMatrix, duration: duration, curve: Curves.easeInOutCubic); + + _isTransitioningPages = false; + _onAnimationEnd(); + } + Rect get _visibleRect => _txController.value.calcVisibleRect(_viewSize!); /// Set the current page number. /// /// Please note that the function does not scroll/zoom to the specified page but changes the current page number. - void _setCurrentPageNumber(int? pageNumber, {bool doSetState = false}) { + void _setCurrentPageNumber(int? pageNumber, {bool doSetState = false, double? targetZoom}) { _gotoTargetPageNumber = pageNumber; if (pageNumber != null && _pageNumber != pageNumber) { _pageNumber = pageNumber; + // Update boundary margins for the new page (for discrete mode with overflow) + // Use targetZoom if provided (for transitions), otherwise use current zoom + if (_viewSize != null) { + _adjustBoundaryMargins(_viewSize!, targetZoom ?? _currentZoom); + } if (doSetState) { _invalidate(); } @@ -874,14 +1306,24 @@ class _PdfViewerState extends State } int? _guessCurrentPageNumber() { - if (_layout == null || _viewSize == null) return null; + if (_layout == null || _viewSize == null) { + _visiblePageRange = null; + return null; + } if (widget.params.calculateCurrentPageNumber != null) { - return widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); + final pageNumber = widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); + _updateVisiblePageRange(); + return pageNumber; } final visibleRect = _visibleRect; - double calcIntersectionArea(int pageNumber) { - final rect = _layout!.pageLayouts[pageNumber - 1]; + final layout = _layout!; + + // Calculate visible page range (any page with any intersection) + _updateVisiblePageRange(); + + double calcPageIntersectionArea(int pageNumber) { + final rect = layout.pageLayouts[pageNumber - 1]; final intersection = rect.intersect(visibleRect); if (intersection.isEmpty) return 0; final area = intersection.width * intersection.height; @@ -891,7 +1333,7 @@ class _PdfViewerState extends State if (_gotoTargetPageNumber != null && _gotoTargetPageNumber! > 0 && _gotoTargetPageNumber! <= _document!.pages.length) { - final ratio = calcIntersectionArea(_gotoTargetPageNumber!); + final ratio = calcPageIntersectionArea(_gotoTargetPageNumber!); if (ratio > .2) return _gotoTargetPageNumber; } _gotoTargetPageNumber = null; @@ -899,7 +1341,7 @@ class _PdfViewerState extends State int? pageNumber; double maxRatio = 0; for (var i = 1; i <= _document!.pages.length; i++) { - final ratio = calcIntersectionArea(i); + final ratio = calcPageIntersectionArea(i); if (ratio == 0) continue; if (ratio > maxRatio) { maxRatio = ratio; @@ -909,13 +1351,54 @@ class _PdfViewerState extends State return pageNumber; } + /// Calculate the range of all pages that have any intersection with the visible viewport. + void _updateVisiblePageRange() { + if (_layout == null || _document == null) { + _visiblePageRange = null; + return; + } + + final visibleRect = _visibleRect; + final layout = _layout!; + int? firstVisible; + int? lastVisible; + + for (var i = 1; i <= _document!.pages.length; i++) { + final rect = layout.pageLayouts[i - 1]; + final intersection = rect.intersect(visibleRect); + if (!intersection.isEmpty) { + firstVisible ??= i; + lastVisible = i; + } + } + + if (firstVisible != null && lastVisible != null) { + _visiblePageRange = (first: firstVisible, last: lastVisible); + } else { + _visiblePageRange = null; + } + } + /// Returns true if page layouts are changed. bool _relayoutPages() { if (_document == null) { _layout = null; return false; } - final newLayout = (widget.params.layoutPages ?? _layoutPages)(_document!.pages, widget.params); + + final helper = PdfLayoutHelper( + viewportSize: _viewSize ?? Size.zero, + boundaryMargin: widget.params.boundaryMargin, + margin: widget.params.margin, + ); + var newLayout = (widget.params.layoutPages ?? _layoutPages)(_document!.pages, widget.params, helper); + + // In discrete mode, add spacing between pages to fill viewport and prevent neighboring pages from showing + if (widget.params.pageTransition == PageTransition.discrete) { + newLayout = _addDiscreteSpacing(newLayout, helper); + } + + // Only update if layout actually changed if (_layout == newLayout) { return false; } @@ -924,55 +1407,102 @@ class _PdfViewerState extends State return true; } - void _calcCoverFitScale() { - final params = widget.params; - final bmh = params.boundaryMargin?.horizontal == double.infinity ? 0 : params.boundaryMargin?.horizontal ?? 0; - final bmv = params.boundaryMargin?.vertical == double.infinity ? 0 : params.boundaryMargin?.vertical ?? 0; - - if (_viewSize != null) { - final s1 = _viewSize!.width / (_layout!.documentSize.width + bmh); - final s2 = _viewSize!.height / (_layout!.documentSize.height + bmv); - _coverScale = max(s1, s2); - } - final pageNumber = _pageNumber ?? _gotoTargetPageNumber; - if (pageNumber != null) { - final rect = _layout!.pageLayouts[pageNumber - 1]; - final m2 = params.margin * 2; - _alternativeFitScale = min( - (_viewSize!.width) / (rect.width + bmh + m2), - (_viewSize!.height) / (rect.height + bmv + m2), - ); - if (_alternativeFitScale! <= 0) { - _alternativeFitScale = null; + double _getInitialZoom() { + if (_viewSize != null && _layout != null) { + final params = widget.params; + + // In discrete mode, pages are already scaled in the layout, so zoom should be 1.0 + if (params.pageTransition == PageTransition.discrete) { + return 1.0; } - } else { - _alternativeFitScale = null; + + // For continuous mode, calculate fit scale for the current fitMode + return _calculateScaleForMode(params.fitMode); } - if (_coverScale == null) { - _minScale = _defaultMinScale; + // Fall back to fitScale + return _fitScale; + } + + /// Calculate scale for a specific fit mode on-demand. + /// Used for backward compatibility with deprecated APIs. + double _calculateScaleForMode(FitMode mode) { + if (_viewSize == null || _layout == null) { + return _defaultMinScale; + } + + final params = widget.params; + final helper = PdfLayoutHelper( + viewportSize: _viewSize!, + boundaryMargin: params.boundaryMargin, + margin: params.margin, + ); + + return _layout!.calculateFitScale(helper, mode); + } + + void _calcFitScale([int? pageNumber]) { + final params = widget.params; + + if (_viewSize == null || _layout == null) { + _fitScale = _defaultMinScale; return; } - _minScale = !widget.params.useAlternativeFitScaleAsMinScale - ? widget.params.minScale - : _alternativeFitScale == null - ? _coverScale! - : min(_coverScale!, _alternativeFitScale!); + + final helper = PdfLayoutHelper( + viewportSize: _viewSize!, + boundaryMargin: params.boundaryMargin, + margin: params.margin, + ); + + final effectivePageNumber = pageNumber ?? _pageNumber ?? _gotoTargetPageNumber; + + if (widget.params.useAlternativeFitScaleAsMinScale) { + // Legacy useAlternativeFitScaleAsMinScale behavior (deprecated) + // This maps to FitMode.fit (show whole page) + _fitScale = _layout!.calculateFitScale( + helper, + FitMode.fit, + pageTransition: widget.params.pageTransition, + pageNumber: effectivePageNumber, + ); + } else { + // Calculate fit scale based on fitMode for page positioning + // In discrete mode, calculate fit scale for the current/target page + // In continuous mode, calculate for the entire document + _fitScale = _layout!.calculateFitScale( + helper, + params.fitMode, + pageTransition: widget.params.pageTransition, + pageNumber: effectivePageNumber, + ); + } } void _calcZoomStopTable() { _zoomStops.clear(); double z; - if (_alternativeFitScale != null && !_areZoomsAlmostIdentical(_alternativeFitScale!, _coverScale!)) { - if (_alternativeFitScale! < _coverScale!) { - _zoomStops.add(_alternativeFitScale!); - z = _coverScale!; + + // Calculate both fit and cover scales to provide good zoom stops + // even when only one fitMode is selected + final fitScale = _calculateScaleForMode(FitMode.fit); + final coverScale = _calculateScaleForMode(FitMode.cover); + + // Use the primary scale based on current fitMode (or legacy behavior) + final primaryScale = _fitScale; + + // Add both scales to zoom stops if they're significantly different + if (!_areZoomsAlmostIdentical(fitScale, coverScale)) { + if (fitScale < coverScale) { + _zoomStops.add(fitScale); + z = coverScale; } else { - _zoomStops.add(_coverScale!); - z = _alternativeFitScale!; + _zoomStops.add(coverScale); + z = fitScale; } } else { - z = _coverScale!; + z = primaryScale; } + // in some case, z may be 0 and it causes infinite loop. if (z < 1 / 128) { _zoomStops.add(1.0); @@ -986,14 +1516,16 @@ class _PdfViewerState extends State _zoomStops.add(widget.params.maxScale); } - if (!widget.params.useAlternativeFitScaleAsMinScale) { + // Add smaller zoom stops down to the minimum scale + if (widget.params.minScale != null || !widget.params.useAlternativeFitScaleAsMinScale) { z = _zoomStops.first; - while (z > widget.params.minScale) { + + while (z > minScale) { z /= 2; _zoomStops.insert(0, z); } - if (!_areZoomsAlmostIdentical(z, widget.params.minScale)) { - _zoomStops.insert(0, widget.params.minScale); + if (!_areZoomsAlmostIdentical(z, minScale)) { + _zoomStops.insert(0, minScale); } } } @@ -1023,11 +1555,360 @@ class _PdfViewerState extends State static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; + /// Adds spacing between pages in discrete mode to fill viewport and prevent neighboring pages from showing. + /// This modifies the page positions to add viewport-sized gaps on the primary scroll axis. + /// For spread layouts, positions spreads as units rather than individual pages. + PdfPageLayout _addDiscreteSpacing(PdfPageLayout layout, PdfLayoutHelper helper) { + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + final viewportSize = isPrimaryVertical ? helper.viewportHeight : helper.viewportWidth; + final margin = widget.params.margin; + + final newPageLayouts = []; + var offset = 0.0; + + // For spread layouts, we need to iterate through spreads, not individual pages + if (layout is PdfSpreadLayout) { + // Track which pages we've already positioned + final processedPages = {}; + + for (var pageNum = 1; pageNum <= layout.pageLayouts.length; pageNum++) { + if (processedPages.contains(pageNum)) continue; + + // Get all pages in this spread + final (:first, :last) = layout.getPageRange(pageNum); + final firstPage = first; + final lastPage = last; + final spreadBounds = layout.getSpreadBounds(pageNum); + + // Calculate scale for this spread + final spreadScale = layout.calculateFitScale( + helper, + widget.params.fitMode, + pageTransition: PageTransition.discrete, + pageNumber: pageNum, + ); + + // Get spread size on primary axis, scaled to final rendered size + final scaledSpreadWidthWithMargins = (spreadBounds.width + margin * 2) * spreadScale; + final scaledSpreadHeightWithMargins = (spreadBounds.height + margin * 2) * spreadScale; + final spreadSizeWithMargins = isPrimaryVertical ? scaledSpreadHeightWithMargins : scaledSpreadWidthWithMargins; + + // Calculate slot size: if spread+margins fits in viewport, use viewport to add centering padding + final slotSize = spreadSizeWithMargins < viewportSize ? viewportSize : spreadSizeWithMargins; + + // Calculate viewport padding to center the spread in its slot on primary axis + final viewportPaddingPrimary = (slotSize - spreadSizeWithMargins) / 2; + + // The spread content (without margins) starts at: offset + padding + scaled margin + final spreadContentStart = offset + viewportPaddingPrimary + (margin * spreadScale); + + // Center the spread on cross-axis + final crossAxisSpreadSizeWithMargins = isPrimaryVertical + ? scaledSpreadWidthWithMargins + : scaledSpreadHeightWithMargins; + final crossAxisViewport = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + + // Calculate viewport padding on cross-axis + final viewportPaddingCross = max(0.0, (crossAxisViewport - crossAxisSpreadSizeWithMargins) / 2); + + // Spread content starts at: padding + scaled margin + final crossAxisSpreadContentStart = viewportPaddingCross + (margin * spreadScale); + + // Position all pages in this spread + for (var p = firstPage; p <= lastPage; p++) { + final pageRect = layout.pageLayouts[p - 1]; + final scaledPageWidth = pageRect.width * spreadScale; + final scaledPageHeight = pageRect.height * spreadScale; + + // Calculate page position relative to spread bounds on both axes + final pageOffsetInSpreadPrimary = isPrimaryVertical + ? pageRect.top - spreadBounds.top + : pageRect.left - spreadBounds.left; + final pageOffsetInSpreadCross = isPrimaryVertical + ? pageRect.left - spreadBounds.left + : pageRect.top - spreadBounds.top; + + final scaledPageOffsetInSpreadPrimary = pageOffsetInSpreadPrimary * spreadScale; + final scaledPageOffsetInSpreadCross = pageOffsetInSpreadCross * spreadScale; + + // Position pages relative to the centered spread content position on both axes + final newRect = isPrimaryVertical + ? Rect.fromLTWH( + crossAxisSpreadContentStart + + scaledPageOffsetInSpreadCross, // Horizontal: relative to centered spread content + spreadContentStart + scaledPageOffsetInSpreadPrimary, // Vertical: relative to centered spread content + scaledPageWidth, + scaledPageHeight, + ) + : Rect.fromLTWH( + spreadContentStart + + scaledPageOffsetInSpreadPrimary, // Horizontal: relative to centered spread content + crossAxisSpreadContentStart + + scaledPageOffsetInSpreadCross, // Vertical: relative to centered spread content + scaledPageWidth, + scaledPageHeight, + ); + + newPageLayouts.add(newRect); + processedPages.add(p); + } + + // Move offset for next spread + offset += slotSize; + } + } else { + // Single page layout - original logic + for (var i = 0; i < layout.pageLayouts.length; i++) { + final pageRect = layout.pageLayouts[i]; + + // Calculate the scale that will be applied to this page in discrete mode + final pageScale = layout.calculateFitScale( + helper, + widget.params.fitMode, + pageTransition: PageTransition.discrete, + pageNumber: i + 1, + ); + + // Get page size on primary axis, scaled to final rendered size + final scaledPageWidthWithMargins = (helper.widthWithMargins(pageRect.width)) * pageScale; + final scaledPageHeightWithMargins = (helper.heightWithMargins(pageRect.height)) * pageScale; + final pageSizeWithMargins = isPrimaryVertical ? scaledPageHeightWithMargins : scaledPageWidthWithMargins; + + // Calculate slot size: if page+margins fits in viewport, use viewport to add centering padding + final slotSize = pageSizeWithMargins < viewportSize ? viewportSize : pageSizeWithMargins; + + // Calculate viewport padding to center the page in its slot on primary axis + final viewportPaddingPrimary = (slotSize - pageSizeWithMargins) / 2; + + // The page content (without margins) starts at: offset + padding + scaled margin + final scaledPageWidth = pageRect.width * pageScale; + final scaledPageHeight = pageRect.height * pageScale; + final pageContentStart = offset + viewportPaddingPrimary + (margin * pageScale); + + // Position page on cross-axis + final crossAxisPageSizeWithMargins = isPrimaryVertical + ? scaledPageWidthWithMargins + : scaledPageHeightWithMargins; + final crossAxisViewport = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + + // Calculate viewport padding on cross-axis + final viewportPaddingCross = max(0.0, (crossAxisViewport - crossAxisPageSizeWithMargins) / 2); + + // Page content starts at: padding + scaled margin + final crossAxisPageContentStart = viewportPaddingCross + (margin * pageScale); + + final newRect = isPrimaryVertical + ? Rect.fromLTWH(crossAxisPageContentStart, pageContentStart, scaledPageWidth, scaledPageHeight) + : Rect.fromLTWH(pageContentStart, crossAxisPageContentStart, scaledPageWidth, scaledPageHeight); + + newPageLayouts.add(newRect); + + offset += slotSize; + } + } + + // offset now represents the total document size on primary axis (including all padding and margins) + + // Calculate new document size + // On cross-axis, use viewport size to accommodate centered pages with different aspect ratios + final crossAxisViewportSize = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + + // Also consider the maximum page extent on cross-axis to ensure no clipping + final maxCrossAxisExtent = newPageLayouts.fold(0.0, (max, rect) { + final extent = isPrimaryVertical ? rect.right : rect.bottom; + return extent > max ? extent : max; + }); + + final crossAxisSize = max(crossAxisViewportSize, maxCrossAxisExtent); + + final newDocSize = isPrimaryVertical ? Size(crossAxisSize, offset) : Size(offset, crossAxisSize); + + // Preserve spread layout information if present + if (layout is PdfSpreadLayout) { + // Recalculate spread bounds based on new page positions + final newSpreadLayouts = []; + for (var spreadIndex = 0; spreadIndex < layout.spreadLayouts.length; spreadIndex++) { + var minLeft = double.infinity; + var minTop = double.infinity; + var maxRight = 0.0; + var maxBottom = 0.0; + + for (var pageIndex = 0; pageIndex < layout.pageToSpreadIndex.length; pageIndex++) { + if (layout.pageToSpreadIndex[pageIndex] == spreadIndex) { + final pageRect = newPageLayouts[pageIndex]; + minLeft = min(minLeft, pageRect.left); + minTop = min(minTop, pageRect.top); + maxRight = max(maxRight, pageRect.right); + maxBottom = max(maxBottom, pageRect.bottom); + } + } + + newSpreadLayouts.add(Rect.fromLTRB(minLeft, minTop, maxRight, maxBottom)); + } + + return _DiscretePdfSpreadLayout( + pageLayouts: newPageLayouts, + documentSize: newDocSize, + spreadLayouts: newSpreadLayouts, + pageToSpreadIndex: layout.pageToSpreadIndex, + primaryAxis: layout.primaryAxis, + ); + } + + return _DiscretePdfPageLayout( + pageLayouts: newPageLayouts, + documentSize: newDocSize, + primaryAxis: layout.primaryAxis, + ); + } + + /// Returns the boundary rect for discrete mode (current page/spread bounds). + /// Used by boundaryProvider to restrict scrolling to current page. + Rect? _getDiscreteBoundaryRect(Rect visibleRect, Size childSize) { + final layout = _layout; + final currentPage = _pageNumber; + + if (layout == null || currentPage == null || currentPage < 1 || currentPage > layout.pageLayouts.length) { + return null; + } + + // Get base page/spread bounds + final baseBounds = layout is PdfSpreadLayout + ? layout.getSpreadBounds(currentPage) + : layout.pageLayouts[currentPage - 1]; + + // Add margin + var result = baseBounds.inflate(widget.params.margin); + + // Add boundary margins for discrete mode + final userBoundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; + if (!userBoundaryMargin.containsInfinite && _viewSize != null) { + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + final currentZoom = _currentZoom; + + // Check if page content extends beyond viewport on each axis at current zoom + final primaryAxisSize = isPrimaryVertical ? result.height : result.width; + final crossAxisSize = isPrimaryVertical ? result.width : result.height; + final primaryAxisViewport = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; + final crossAxisViewport = isPrimaryVertical ? _viewSize!.width : _viewSize!.height; + final scaledPrimaryAxisSize = primaryAxisSize * currentZoom; + final scaledCrossAxisSize = crossAxisSize * currentZoom; + + // For positive margins: only apply when content exceeds viewport to prevent unwanted scrolling + // For negative margins: always apply (they're meant to restrict boundaries regardless of zoom) + final needsPrimaryAxisMargin = + scaledPrimaryAxisSize > primaryAxisViewport || + (isPrimaryVertical + ? (userBoundaryMargin.top < 0 || userBoundaryMargin.bottom < 0) + : (userBoundaryMargin.left < 0 || userBoundaryMargin.right < 0)); + final needsCrossAxisMargin = + scaledCrossAxisSize > crossAxisViewport || + (isPrimaryVertical + ? (userBoundaryMargin.left < 0 || userBoundaryMargin.right < 0) + : (userBoundaryMargin.top < 0 || userBoundaryMargin.bottom < 0)); + + if (isPrimaryVertical) { + // Vertical layout: add top/bottom margins only if content exceeds viewport height + // Add left/right margins only if content exceeds viewport width + result = Rect.fromLTRB( + needsCrossAxisMargin ? result.left - userBoundaryMargin.left : result.left, + needsPrimaryAxisMargin ? result.top - userBoundaryMargin.top : result.top, + needsCrossAxisMargin ? result.right + userBoundaryMargin.right : result.right, + needsPrimaryAxisMargin ? result.bottom + userBoundaryMargin.bottom : result.bottom, + ); + } else { + // Horizontal layout: add left/right margins only if content exceeds viewport width + // Add top/bottom margins only if content exceeds viewport height + result = Rect.fromLTRB( + needsPrimaryAxisMargin ? result.left - userBoundaryMargin.left : result.left, + needsCrossAxisMargin ? result.top - userBoundaryMargin.top : result.top, + needsPrimaryAxisMargin ? result.right + userBoundaryMargin.right : result.right, + needsCrossAxisMargin ? result.bottom + userBoundaryMargin.bottom : result.bottom, + ); + } + } + + // Extend boundaries into adjacent pages during pan gestures + // This allows smooth page transitions when panning on the primary axis + if (_isActiveGesture && !_hadScaleChangeInInteraction && !_isActivelyZooming && _viewSize != null) { + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + const extensionRatio = 0.5; // Extend 50% into adjacent pages + + // During active drag gestures, always extend boundaries to allow deliberate page transitions. + // Fling-based transitions are controlled separately in _handleDiscretePageTransition. + final shouldExtendToPrev = true; + final shouldExtendToNext = true; + + final viewportPrimarySize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; + + // Calculate extension distance based on viewport size (not page size) + // This ensures proper extension even when viewport is much larger than page (discrete mode with spacing) + final extensionDistance = viewportPrimarySize * extensionRatio; + + // Extend to previous page (if exists and should extend) + if (shouldExtendToPrev && currentPage > 1) { + final prevPageBounds = layout.pageLayouts[currentPage - 2]; + if (isPrimaryVertical) { + // Extend upward + final newTop = max(prevPageBounds.top, result.top - extensionDistance); + result = Rect.fromLTRB(result.left, newTop, result.right, result.bottom); + } else { + // Extend leftward + final newLeft = max(prevPageBounds.left, result.left - extensionDistance); + result = Rect.fromLTRB(newLeft, result.top, result.right, result.bottom); + } + } + + // Extend to next page (if exists and should extend) + if (shouldExtendToNext && currentPage < layout.pageLayouts.length) { + final nextPageBounds = layout.pageLayouts[currentPage]; + if (isPrimaryVertical) { + // Extend downward + final newBottom = min(nextPageBounds.bottom, result.bottom + extensionDistance); + result = Rect.fromLTRB(result.left, result.top, result.right, newBottom); + } else { + // Extend rightward + final newRight = min(nextPageBounds.right, result.right + extensionDistance); + result = Rect.fromLTRB(result.left, result.top, newRight, result.bottom); + } + } + } + return result; + } + // Auto-adjust boundaries when content is smaller than the view, centering // the content and ensuring InteractiveViewer's scrollPhysics works when specified void _adjustBoundaryMargins(Size viewSize, double zoom) { final boundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; + // Discrete mode: restrict scrolling to current page/spread only + if (widget.params.pageTransition == PageTransition.discrete) { + final layout = _layout; + final currentPage = _pageNumber; + + if (layout == null || currentPage == null || currentPage < 1 || currentPage > layout.pageLayouts.length) { + _adjustedBoundaryMargins = boundaryMargin; + return; + } + + // Get current page or spread bounds + Rect pageBounds; + if (layout is PdfSpreadLayout) { + pageBounds = layout.getSpreadBounds(currentPage); + } else { + pageBounds = layout.getPageRectWithMargins(currentPage); + } + + var left = -pageBounds.left; + var top = -pageBounds.top; + var right = pageBounds.right - layout.documentSize.width; + var bottom = pageBounds.bottom - layout.documentSize.height; + + _adjustedBoundaryMargins = EdgeInsets.fromLTRB(left, top, right, bottom); + return; + } + + // Continuous mode: add extra boundary margin to center content when zoomed out if (boundaryMargin.containsInfinite) { _adjustedBoundaryMargins = boundaryMargin; return; @@ -1037,8 +1918,9 @@ class _PdfViewerState extends State final effectiveWidth = currentDocumentSize.width * zoom; final effectiveHeight = currentDocumentSize.height * zoom; final extraWidth = effectiveWidth - viewSize.width; - final extraBoundaryHorizontal = extraWidth < 0 ? (-extraWidth / 2) / zoom : 0.0; final extraHeight = effectiveHeight - viewSize.height; + + final extraBoundaryHorizontal = extraWidth < 0 ? (-extraWidth / 2) / zoom : 0.0; final extraBoundaryVertical = extraHeight < 0 ? (-extraHeight / 2) / zoom : 0.0; _adjustedBoundaryMargins = @@ -1162,7 +2044,23 @@ class _PdfViewerState extends State for (var i = 0; i < _document!.pages.length; i++) { final rect = _layout!.pageLayouts[i]; final intersection = rect.intersect(cacheTargetRect); - if (intersection.isEmpty) { + + // In discrete mode, only render current page(s)/spread during zoom and its animations + // This ensures that pinch zooming out doesn't show neighboring pages + var shouldSkipForDiscrete = false; + if (widget.params.pageTransition == PageTransition.discrete && + _pageNumber != null && + (_isActivelyZooming || _hasActiveAnimations)) { + final layout = _layout; + if (layout is PdfSpreadLayout) { + final (:first, :last) = layout.getPageRange(_pageNumber!); + shouldSkipForDiscrete = i + 1 < first || i + 1 > last; + } else { + shouldSkipForDiscrete = i + 1 != _pageNumber; + } + } + + if (intersection.isEmpty || shouldSkipForDiscrete) { final page = _document!.pages[i]; cache.cancelPendingRenderings(page.pageNumber); if (cache.pageImages.containsKey(i + 1)) { @@ -1260,20 +2158,20 @@ class _PdfViewerState extends State callback(canvas, rect, page); } } + } - if (unusedPageList.isNotEmpty) { - final currentPageNumber = _pageNumber; - if (currentPageNumber != null && currentPageNumber > 0) { - final currentPage = _document!.pages[currentPageNumber - 1]; - cache.removeCacheImagesIfCacheBytesExceedsLimit( - unusedPageList, - maxImageCacheBytes, - currentPage, - dist: (pageNumber) => - (_layout!.pageLayouts[pageNumber - 1].center - _layout!.pageLayouts[currentPage.pageNumber - 1].center) - .distanceSquared, - ); - } + if (unusedPageList.isNotEmpty) { + final currentPageNumber = _pageNumber; + if (currentPageNumber != null && currentPageNumber > 0) { + final currentPage = _document!.pages[currentPageNumber - 1]; + cache.removeCacheImagesIfCacheBytesExceedsLimit( + unusedPageList, + maxImageCacheBytes, + currentPage, + dist: (pageNumber) => + (_layout!.pageLayouts[pageNumber - 1].center - _layout!.pageLayouts[currentPage.pageNumber - 1].center) + .distanceSquared, + ); } } } @@ -1333,19 +2231,8 @@ class _PdfViewerState extends State return false; } - PdfPageLayout _layoutPages(List pages, PdfViewerParams params) { - final width = pages.fold(0.0, (w, p) => max(w, p.width)) + params.margin * 2; - - final pageLayout = []; - var y = params.margin; - for (var i = 0; i < pages.length; i++) { - final page = pages[i]; - final rect = Rect.fromLTWH((width - page.width) / 2, y, page.width, page.height); - pageLayout.add(rect); - y += page.height + params.margin; - } - - return PdfPageLayout(pageLayouts: pageLayout, documentSize: Size(width, y)); + PdfPageLayout _layoutPages(List pages, PdfViewerParams params, PdfLayoutHelper helper) { + return SinglePagesLayout.fromPages(pages, params, helper: helper); } void _invalidate() => _updateStream.add(_txController.value); @@ -1488,7 +2375,7 @@ class _PdfViewerState extends State // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. // So, I just add both values. var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; - final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); + final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(minScale, widget.params.maxScale); if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. _controller!.zoomOnLocalPosition( @@ -1532,33 +2419,80 @@ class _PdfViewerState extends State vec.Vector3( zoom, zoom, - zoom, // setting zoom of 1 on z caused a call to Matrix4.getMaxScaleOnAxis() to return 1 even when x and y are < 1 - ), + zoom, + ), // setting zoom of 1 on z caused a call to matrix.maxScaleOnAxis() to return 1 even when x and y are < 1 ); } /// The minimum zoom ratio allowed. - double get minScale => _minScale; + double get minScale { + // Always honor explicit minScale first + if (widget.params.minScale != null) { + return widget.params.minScale!; + } + // Legacy behavior: use fitScale as minimum when flag is true + if (widget.params.useAlternativeFitScaleAsMinScale) { + return _fitScale; + } + // Modern behavior: use fitScale as minimum (matches fitMode) + // This ensures that when fitMode is set, the minimum scale respects it + return _fitScale; + } - Matrix4 _calcMatrixForRect(Rect rect, {double? zoomMax, double? margin}) { + Matrix4 _calcMatrixForRect( + Rect rect, { + double? zoomMax, + double? margin, + bool maintainCurrentZoom = false, + bool forceZoomMax = false, + }) { margin ??= 0; - var zoom = min((_viewSize!.width - margin * 2) / rect.width, (_viewSize!.height - margin * 2) / rect.height); - if (zoomMax != null && zoom > zoomMax) zoom = zoomMax; + + final double zoom; + if (maintainCurrentZoom && _currentZoom > 0) { + // Use existing zoom for positioning only + zoom = _currentZoom; + } else if (forceZoomMax && zoomMax != null) { + // Force the zoomMax value (used in discrete mode with resetScale) + zoom = zoomMax; + } else { + // Calculate zoom to fit rect + var calculatedZoom = min( + (_viewSize!.width - margin * 2) / rect.width, + (_viewSize!.height - margin * 2) / rect.height, + ); + if (zoomMax != null && calculatedZoom > zoomMax) calculatedZoom = zoomMax; + zoom = calculatedZoom; + } + return _calcMatrixFor(rect.center, zoom: zoom, viewSize: _viewSize!); } - Matrix4 _calcMatrixForArea({required Rect rect, double? zoomMax, double? margin, PdfPageAnchor? anchor}) => - _calcMatrixForRect( - _calcRectForArea(rect: rect, anchor: anchor ?? widget.params.pageAnchor), - zoomMax: zoomMax, - margin: margin, - ); + Matrix4 _calcMatrixForArea({ + required Rect rect, + double? zoomMax, + double? margin, + PdfPageAnchor? anchor, + bool maintainCurrentZoom = false, + bool forceZoomMax = false, + }) => _calcMatrixForRect( + _calcRectForArea(rect: rect, anchor: anchor ?? widget.params.pageAnchor), + zoomMax: zoomMax, + margin: margin, + maintainCurrentZoom: maintainCurrentZoom, + forceZoomMax: forceZoomMax, + ); /// The function calculate the rectangle which should be shown in the view. + /// + /// If the rect is smaller than the view size, it will Rect _calcRectForArea({required Rect rect, required PdfPageAnchor anchor}) { - final viewSize = _visibleRect.size; + // Use physical viewport size, not current visible rect + // _visibleRect.size varies with zoom, but we want consistent anchor behavior + final viewSize = _viewSize!; final w = min(rect.width, viewSize.width); final h = min(rect.height, viewSize.height); + switch (anchor) { case PdfPageAnchor.top: return Rect.fromLTWH(rect.left, rect.top, rect.width, h); @@ -1577,7 +2511,7 @@ class _PdfViewerState extends State case PdfPageAnchor.centerLeft: return Rect.fromLTWH(rect.left, rect.center.dy - h / 2, viewSize.width, viewSize.height); case PdfPageAnchor.center: - return Rect.fromLTWH(rect.center.dx - w / 2, rect.center.dy - h / 2, viewSize.width, viewSize.height); + return Rect.fromLTWH(rect.center.dx - w / 2, rect.center.dy - h / 2, w, h); case PdfPageAnchor.centerRight: return Rect.fromLTWH(rect.right - w, rect.center.dy - h / 2, viewSize.width, viewSize.height); case PdfPageAnchor.bottomLeft: @@ -1591,14 +2525,48 @@ class _PdfViewerState extends State } } - Matrix4 _calcMatrixForPage({required int pageNumber, PdfPageAnchor? anchor}) { - final boundaryMargin = _adjustedBoundaryMargins; - final pageRect = _layout!.pageLayouts[pageNumber - 1].inflate(widget.params.margin); + /// Gets the effective page bounds for a given page, including margins. + /// Optionally includes boundary margins for positioning purposes. + Rect _getEffectivePageBounds(int pageNumber, PdfPageLayout layout, {PdfPageAnchor? anchor}) { + final baseRect = layout is PdfSpreadLayout && widget.params.pageTransition == PageTransition.discrete + ? layout.getSpreadBounds(pageNumber) + : layout.pageLayouts[pageNumber - 1]; + + var result = baseRect.inflate(widget.params.margin); + + // Add boundary margins for positioning when appropriate + // Continuous mode: use adjusted boundary margins (with centering adjustments) + // Discrete mode: always include user's boundary margins for proper positioning + if (anchor != null) { + if (widget.params.pageTransition == PageTransition.continuous) { + result = _adjustedBoundaryMargins.inflateRectIfFinite(result); + } else { + // In discrete mode, always use the user's boundary margins for positioning + final userBoundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; + result = userBoundaryMargin.inflateRectIfFinite(result); + } + } - // If boundaryMargin is infinite, don't inflate the rect - final targetRect = boundaryMargin.inflateRectIfFinite(pageRect); + return result; + } + + Matrix4 _calcMatrixForPage({ + required int pageNumber, + PdfPageAnchor? anchor, + double? zoomMax, + bool maintainCurrentZoom = false, + double? forceScale, + }) { + final layout = _layout!; + final targetRect = _getEffectivePageBounds(pageNumber, layout, anchor: anchor); - return _calcMatrixForArea(rect: targetRect, anchor: anchor, zoomMax: _currentZoom); + return _calcMatrixForArea( + rect: targetRect, + anchor: anchor, + zoomMax: forceScale ?? zoomMax ?? _currentZoom, + maintainCurrentZoom: forceScale == null && maintainCurrentZoom, + forceZoomMax: forceScale != null, + ); } Rect _calcRectForRectInsidePage({required int pageNumber, required PdfRect rect}) { @@ -1685,24 +2653,30 @@ class _PdfViewerState extends State return null; } - Future _goTo(Matrix4? destination, {Duration duration = const Duration(milliseconds: 200)}) async { + Future _goTo( + Matrix4? destination, { + Duration duration = const Duration(milliseconds: 200), + Curve curve = Curves.easeInOut, + }) async { void update() { if (_animationResettingGuard != 0) return; _txController.value = _animGoTo!.value; } try { - if (destination == null) return; // do nothing + if (destination == null) { + return; // do nothing + } + + final safeDestination = _makeMatrixInSafeRange(destination, forceClamp: true); + _stopInteractiveViewerAnimation(); _animationResettingGuard++; _animController.reset(); _animationResettingGuard--; - _animGoTo = Matrix4Tween( - begin: _txController.value, - end: _makeMatrixInSafeRange(destination, forceClamp: true), - ).animate(_animController); + _animGoTo = Matrix4Tween(begin: _txController.value, end: safeDestination).animate(_animController); _animGoTo!.addListener(update); - await _animController.animateTo(1.0, duration: duration, curve: Curves.easeInOut); + await _animController.animateTo(1.0, duration: duration, curve: curve); } finally { _animGoTo?.removeListener(update); } @@ -1750,6 +2724,8 @@ class _PdfViewerState extends State required int pageNumber, PdfPageAnchor? anchor, Duration duration = const Duration(milliseconds: 200), + bool maintainCurrentZoom = false, + double? forceScale, }) async { final pageCount = _document!.pages.length; final int targetPageNumber; @@ -1765,7 +2741,12 @@ class _PdfViewerState extends State await _goTo( _calcMatrixForClampedToNearestBoundary( - _calcMatrixForPage(pageNumber: targetPageNumber, anchor: anchor), + _calcMatrixForPage( + pageNumber: targetPageNumber, + anchor: anchor, + maintainCurrentZoom: maintainCurrentZoom, + forceScale: forceScale, + ), viewSize: _viewSize!, ), duration: duration, @@ -1865,14 +2846,30 @@ class _PdfViewerState extends State bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200), - }) => _setZoom(zoomCenter ?? _centerPosition, _getNextZoom(loop: loop), duration: duration); + }) async { + final newZoom = _getNextZoom(loop: loop); + + // In discrete mode, zoom to the current page to avoid jumping to other pages + if (widget.params.pageTransition == PageTransition.discrete && zoomCenter == null && _pageNumber != null) { + await _goToPage(pageNumber: _pageNumber!, duration: duration, anchor: PdfPageAnchor.center, forceScale: newZoom); + } else { + await _setZoom(zoomCenter ?? _centerPosition, newZoom, duration: duration); + } + } Future _zoomDown({ bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200), }) async { - await _setZoom(zoomCenter ?? _centerPosition, _getPreviousZoom(loop: loop), duration: duration); + final newZoom = _getPreviousZoom(loop: loop); + + // In discrete mode, zoom to the current page to avoid jumping to other pages + if (widget.params.pageTransition == PageTransition.discrete && zoomCenter == null && _pageNumber != null) { + await _goToPage(pageNumber: _pageNumber!, duration: duration, anchor: PdfPageAnchor.center, forceScale: newZoom); + } else { + await _setZoom(zoomCenter ?? _centerPosition, newZoom, duration: duration); + } } RenderBox? get _renderBox { @@ -2387,31 +3384,12 @@ class _PdfViewerState extends State : null; Offset? a, b; - if (isMobile) { - a = localOffset; - switch (_textSelA?.direction) { - case PdfTextDirection.ltr: - a ??= _anchorARect?.topLeft; - b = localOffset == null ? _anchorBRect?.bottomLeft : null; - case PdfTextDirection.rtl: - case PdfTextDirection.vrtl: - a ??= _anchorARect?.topRight; - b = localOffset == null ? _anchorBRect?.bottomRight : null; - default: - } - } else { - // NOTE: - // On Desktop, AdaptiveTextSelectionToolbar determines the context menu position by only the first anchor (a). - // So, we need to be careful about where to place the anchor (a). - if (!_isSelectingAWord && localOffset != null) { - a = localOffset; - } else { - // NOTE: it's still a little strange behavior when selecting a word by long-pressing on it on Desktop - if (_isSelectingAWord) { - _isSelectingAWord = false; - a = _anchorBRect?.bottomRight ?? _anchorARect?.center; - } - a ??= _pointerOffset; + switch (Theme.of(context).platform) { + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.macOS: + a = _pointerOffset; if (_anchorARect != null && _anchorBRect != null) { switch (_textSelA?.direction) { case PdfTextDirection.ltr: @@ -2427,8 +3405,8 @@ class _PdfViewerState extends State } case PdfTextDirection.rtl: case PdfTextDirection.vrtl: - final distA = (a - _anchorARect!.center).distanceSquared; - final distB = (a - _anchorBRect!.center).distanceSquared; + final distA = (_pointerOffset - _anchorARect!.center).distanceSquared; + final distB = (_pointerOffset - _anchorBRect!.center).distanceSquared; if (distA < distB) { a = _anchorARect!.bottomLeft.translate(8, 8); } else { @@ -2436,9 +3414,19 @@ class _PdfViewerState extends State } default: } - _contextMenuDocumentPosition = offsetToDocument(context, a); } - } + default: + a = localOffset; + switch (_textSelA?.direction) { + case PdfTextDirection.ltr: + a ??= _anchorARect?.topLeft; + b = localOffset == null ? _anchorBRect?.bottomLeft : null; + case PdfTextDirection.rtl: + case PdfTextDirection.vrtl: + a ??= _anchorARect?.topRight; + b = localOffset == null ? _anchorBRect?.bottomRight : null; + default: + } } contextMenu = createContextMenu(a, b, _contextMenuFor); @@ -2975,11 +3963,9 @@ class _PdfViewerState extends State } _selPartMoving = _TextSelectionPart.none; - _selPartLastMoved = _TextSelectionPart.b; + _selPartLastMoved = _TextSelectionPart.a; _isSelectingAllText = false; _selectionPointerDeviceKind = deviceKind; - _contextMenuDocumentPosition = null; - _isSelectingAWord = true; _notifyTextSelectionChange(); } @@ -3040,7 +4026,7 @@ class _PdfViewerState extends State } @override - PdfViewerCoordinateConverter get doc2local => this; + DocumentCoordinateConverter get doc2local => this; void forceRepaintAllPageImages() { _imageCache.cancelAllPendingRenderings(); @@ -3341,21 +4327,38 @@ class PdfTextSelectionAnchor { /// It can be either [a] or [b], which represents the start and end of the selection respectively. enum PdfTextSelectionAnchorType { a, b } -/// Defines page layout. -class PdfPageLayout { - PdfPageLayout({required this.pageLayouts, required this.documentSize}); - final List pageLayouts; - final Size documentSize; +/// Defines how the PDF pages should fit within the viewport. +enum FitMode { + /// Entire page/spread visible (may have letterboxing). + fit, - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is! PdfPageLayout) return false; - return listEquals(pageLayouts, other.pageLayouts) && documentSize == other.documentSize; - } + /// Fill viewport along the cross axis (may crop content). + fill, - @override - int get hashCode => pageLayouts.hashCode ^ documentSize.hashCode; + /// Legacy cover mode - ensures the whole document fills the viewport (may crop content). + cover, + + /// No scaling applied. + none, +} + +/// Defines how pages transition when navigating through the document. +enum PageTransition { + /// Pages flow continuously in an uninterrupted scrollable view. + /// Similar to browsing a webpage - all pages are laid out sequentially. + continuous, + + /// Pages transition discretely, one page (or spread for facing pages) at a time. + /// + /// When a pan gesture ends at fit zoom, the viewer snaps to either: + /// - The current page/spread (if insufficient movement) + /// - The next/previous page/spread (if swipe velocity > 300 px/s or dragged > 50% threshold) + /// + /// Important behaviors: + /// - Only applies to pan-only gestures (zoom/pinch gestures work normally) + /// - Only active when at or near fit zoom level (free panning when zoomed in) + /// - Provides a controlled, book-like reading experience + discrete, } /// Represents the result of the hit test on the page. @@ -3422,11 +4425,19 @@ class PdfViewerController extends ValueListenable { /// The view port size (The widget's client area's size) Size get viewSize => _state._viewSize!; - /// The zoom ratio that fits the page's smaller side (either horizontal or vertical) to the view port. - double get coverScale => _state._coverScale!; + /// **DEPRECATED:** The zoom ratio for FitMode.cover (fills viewport, may crop content). + /// + /// This getter calculates the cover scale on-demand. Consider using `PdfViewerParams(fitMode: FitMode.cover)` instead. + /// This API will be removed in a future version. + @Deprecated('Use PdfViewerParams(fitMode: FitMode.cover) to set cover mode. This getter will be removed.') + double get coverScale => _state._calculateScaleForMode(FitMode.cover); - /// The zoom ratio that fits whole the page to the view port. - double? get alternativeFitScale => _state._alternativeFitScale; + /// **DEPRECATED:** The zoom ratio for FitMode.fit (shows whole page in viewport). + /// + /// This getter calculates the fit scale on-demand. Consider using `PdfViewerParams(fitMode: FitMode.fit)` instead. + /// This API will be removed in a future version. + @Deprecated('Use PdfViewerParams(fitMode: FitMode.fit) to set fit mode. This getter will be removed.') + double? get alternativeFitScale => _state._calculateScaleForMode(FitMode.fit); /// The minimum zoom ratio allowed. double get minScale => _state.minScale; @@ -3455,6 +4466,12 @@ class PdfViewerController extends ValueListenable { /// The current page number if available. int? get pageNumber => _state._pageNumber; + /// The range of all visible pages (any page with any intersection with the viewport). + /// Returns null if no pages are visible. + /// This is useful for displaying page ranges in UI elements like scroll thumbs, + /// especially when zoomed out or using spread layouts where multiple pages are visible. + ({int first, int last})? get visiblePageRange => _state._visiblePageRange; + /// The document reference associated to the [PdfViewer]. PdfDocumentRef get documentRef => _state.widget.documentRef; @@ -3786,7 +4803,7 @@ class PdfViewerController extends ValueListenable { Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); /// Converts document coordinates to local coordinates. - PdfViewerCoordinateConverter get doc2local => _state; + DocumentCoordinateConverter get doc2local => _state; /// Provided to workaround certain widgets eating wheel events. Use with [Listener.onPointerSignal]. void handlePointerSignalEvent(PointerSignalEvent event) { @@ -4055,3 +5072,25 @@ class _CanvasLinkPainter { } } } + +/// Wrapper layout for discrete mode single page layouts with viewport spacing. +class _DiscretePdfPageLayout extends PdfPageLayout { + _DiscretePdfPageLayout({required super.pageLayouts, required super.documentSize, required this.primaryAxis}); + + @override + final Axis primaryAxis; +} + +/// Wrapper layout for discrete mode spread layouts with viewport spacing. +class _DiscretePdfSpreadLayout extends PdfSpreadLayout { + _DiscretePdfSpreadLayout({ + required super.pageLayouts, + required super.documentSize, + required super.spreadLayouts, + required super.pageToSpreadIndex, + required this.primaryAxis, + }); + + @override + final Axis primaryAxis; +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 8a9b9c8f..5af66b34 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../pdfrx.dart'; -import '../utils/platform.dart'; +import '../utils/native/native.dart'; /// Viewer customization parameters. /// @@ -18,9 +18,12 @@ class PdfViewerParams { this.backgroundColor = Colors.grey, this.layoutPages, this.normalizeMatrix, + this.fitMode = FitMode.fit, + this.pageTransition = PageTransition.continuous, + this.resetScaleOnDiscreteTransition = false, this.maxScale = 8.0, - this.minScale = 0.1, - this.useAlternativeFitScaleAsMinScale = true, + this.minScale, + this.useAlternativeFitScaleAsMinScale = false, this.panAxis = PanAxis.free, this.boundaryMargin, this.annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, @@ -71,9 +74,16 @@ class PdfViewerParams { this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.behaviorControlParams = const PdfViewerBehaviorControlParams(), this.forceReload = false, - this.scrollPhysics, + ScrollPhysics? scrollPhysics, this.scrollPhysicsScale, - }); + }) : scrollPhysics = + scrollPhysics ?? (pageTransition == PageTransition.discrete ? const ClampingScrollPhysics() : null), + assert( + !useAlternativeFitScaleAsMinScale || fitMode == FitMode.fit, + 'useAlternativeFitScaleAsMinScale is deprecated and forces FitMode.fit behavior, ' + 'making the fitMode parameter ($fitMode) ineffective. ' + 'Remove the useAlternativeFitScaleAsMinScale parameter to use fitMode as intended.', + ); /// Margin around the page. final double margin; @@ -139,32 +149,109 @@ class PdfViewerParams { /// ``` final PdfMatrixNormalizeFunction? normalizeMatrix; + /// How pages should be fitted within the viewport. + /// + /// - [FitMode.fit]: Entire page/spread visible (may have letterboxing) + /// - [FitMode.fill]: Fill viewport (may crop content perpendicular to scroll direction) + /// + /// The default is [FitMode.fit]. + final FitMode fitMode; + + /// Defines how pages transition when navigating through the document. + /// + /// - [PageTransition.continuous]: Pages flow continuously in an uninterrupted scrollable view + /// - [PageTransition.discrete]: Pages transition discretely, one page (or spread) at a time + /// + /// When using [PageTransition.discrete]: + /// - Swipe gestures (velocity > 300 px/s) advance to next/previous page + /// - Drag gestures snap based on 50% threshold + /// - Only applies to pan-only gestures (zoom/pinch work normally) + /// - Only active at fit zoom level (free panning when zoomed in) + /// - Works with all layout types (single pages and facing pages) + /// - Provides a book-like reading experience + /// + /// Example: + /// ```dart + /// PdfViewer.asset( + /// 'assets/sample.pdf', + /// params: PdfViewerParams( + /// pageTransition: PageTransition.discrete, + /// ), + /// ) + /// ``` + /// + /// The default is [PageTransition.continuous]. + final PageTransition pageTransition; + + /// Whether to reset zoom to fit scale on discrete page transitions. + /// + /// When enabled in discrete mode, page transitions will reset the zoom level + /// back to fit scale (minScale). This ensures that when navigating between pages, + /// the viewer always returns to the default fit scale regardless of the current zoom level. + /// + /// The default is false. + final bool resetScaleOnDiscreteTransition; + /// The maximum allowed scale. /// /// The default is 8.0. final double maxScale; - /// The minimum allowed scale. + /// The minimum allowed scale for zooming. + /// + /// - If `null` (default): The minimum scale is automatically calculated using the layout's + /// `calculateFitScale()` method with the current [fitMode], ensuring content fits appropriately. + /// - If a value is provided: That value is used as the explicit minimum scale. + /// + /// **Note:** When [useAlternativeFitScaleAsMinScale] is `true` (deprecated), it overrides this setting. /// - /// The default is 0.1. + /// **Examples:** + /// ```dart + /// // Automatic calculation (recommended): + /// PdfViewerParams(minScale: null) // or omit entirely /// - /// Please note that the value is not used if [useAlternativeFitScaleAsMinScale] is true. - /// See [useAlternativeFitScaleAsMinScale] for the details. - final double minScale; + /// // Explicit minimum scale: + /// PdfViewerParams(minScale: 0.5) + /// ``` + final double? minScale; - /// If true, the minimum scale is set to the calculated [PdfViewerController.alternativeFitScale]. + /// **DEPRECATED:** Use `fitMode` and `minScale` parameters instead. + /// + /// This legacy parameter controlled whether to force `FitMode.fit` behavior for fit scale calculation. + /// When `true`, the fit scale is always calculated as `FitMode.fit` regardless of the `fitMode` parameter. + /// When `false` (now default as of v2.3.0), the fit scale respects the `fitMode` parameter. + /// + /// **Breaking change in v2.3.0:** Default changed from `true` to `false`. + /// If you were relying on the old default, explicitly set this to `true`. + /// + /// **Important:** Explicit `minScale` values are now always honored regardless of this flag (fixed in v2.3.0). + /// Previously, when this flag was `true`, explicit `minScale` values were ignored. + /// + /// **Migration:** + /// - If you want the old behavior: Set `useAlternativeFitScaleAsMinScale: true` explicitly. + /// - If you want to allow zooming out beyond the fit scale: Set `minScale: 0.1` (or desired value). + /// - If you want different fit modes to work correctly: Remove this parameter or set to `false` (default). + /// + /// **Example:** + /// ```dart + /// // Old code (pre-v2.3.0): + /// PdfViewerParams(fitMode: FitMode.fill) // Didn't work, behaved like FitMode.fit + /// + /// // New code (v2.3.0+): + /// PdfViewerParams(fitMode: FitMode.fill) // Works correctly now /// - /// If the minimum scale is small value, it makes many pages visible inside the view and it finally - /// renders many pages at once. It may make the viewer to be slow or even crash due to high memory consumption. - /// So, it is recommended to set this to false if you want to show PDF documents with many pages. + /// // To keep old behavior: + /// PdfViewerParams(fitMode: FitMode.fill, useAlternativeFitScaleAsMinScale: true) + /// ``` + @Deprecated('Use fitMode parameter instead. See documentation for migration guide.') final bool useAlternativeFitScaleAsMinScale; - /// See [InteractiveViewer.panAxis] for details. + /// See [InteractiveViewer.panAxis] f or details. final PanAxis panAxis; /// See [InteractiveViewer.boundaryMargin] for details. /// - /// The default is `EdgeInsets.zero`. + /// The default is `EdgeInsets.all(double.infinity)`. final EdgeInsets? boundaryMargin; /// Annotation rendering mode. @@ -545,6 +632,10 @@ class PdfViewerParams { /// does not allow zooming beyond the min/max scale, and flings on panning come to rest quickly relative to /// Scrollables in Flutter (such as [SingleChildScrollView]). /// + /// **Important for discrete mode:** When [pageTransition] is [PageTransition.discrete], scroll physics + /// are required for proper boundary snapping and settling behavior. If null in discrete mode, + /// [ClampingScrollPhysics] is automatically used as a fallback. + /// /// A convenience function [getScrollPhysics] is provided to get platform-specific default scroll physics. /// If you want no overscroll, but still want the physics for panning to be similar to other Scrollables, /// you can use [ClampingScrollPhysics]. @@ -577,6 +668,7 @@ class PdfViewerParams { forceReload || other.margin != margin || other.backgroundColor != backgroundColor || + other.fitMode != fitMode || other.maxScale != maxScale || other.minScale != minScale || other.useAlternativeFitScaleAsMinScale != useAlternativeFitScaleAsMinScale || @@ -611,6 +703,7 @@ class PdfViewerParams { return other.margin == margin && other.backgroundColor == backgroundColor && + other.fitMode == fitMode && other.maxScale == maxScale && other.minScale == minScale && other.useAlternativeFitScaleAsMinScale == useAlternativeFitScaleAsMinScale && @@ -670,6 +763,7 @@ class PdfViewerParams { int get hashCode { return margin.hashCode ^ backgroundColor.hashCode ^ + fitMode.hashCode ^ maxScale.hashCode ^ minScale.hashCode ^ useAlternativeFitScaleAsMinScale.hashCode ^ @@ -962,11 +1056,11 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { Future selectWord(Offset position); /// Convert document coordinates to local coordinates and vice versa. - PdfViewerCoordinateConverter get doc2local; + DocumentCoordinateConverter get doc2local; } /// Utility class to convert document coordinates to local coordinates and vice versa. -abstract class PdfViewerCoordinateConverter { +abstract class DocumentCoordinateConverter { /// Convert a document position to a local position in the specified [context]. Offset? offsetToLocal(BuildContext context, Offset? position); @@ -1175,10 +1269,26 @@ typedef PdfViewerGetPageRenderingScale = /// Function to customize the layout of the pages. /// -/// - [pages] is the list of pages. -/// This is just a copy of the first loaded page of the document. -/// - [params] is the viewer parameters. -typedef PdfPageLayoutFunction = PdfPageLayout Function(List pages, PdfViewerParams params); +/// **Parameters:** +/// - [pages] - List of pages from the PDF document +/// - [params] - Viewer parameters +/// - [helper] - Layout helper with viewport and margin information +/// +/// **Example:** +/// ```dart +/// layoutPages: (pages, params, helper) { +/// // Use helper for viewport-aware layouts +/// return SinglePagesLayout.fromPages(pages, params, helper: helper); +/// } +/// ``` +/// +/// If you have custom layout functions, add `helper` parameter: +/// - Old: `(pages, params) => ...` +/// - New: `(pages, params, helper) => ...` +/// +/// The helper provides viewport size and margins for dynamic layouts. +typedef PdfPageLayoutFunction = + PdfPageLayout Function(List pages, PdfViewerParams params, PdfLayoutHelper helper); /// Function to normalize the matrix. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index 274d28dd..4ef4461f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'pdf_page_layout.dart'; import 'pdf_viewer.dart'; import 'pdf_viewer_params.dart'; @@ -13,6 +14,8 @@ class PdfViewerScrollThumb extends StatefulWidget { this.thumbSize, this.margin = 2, this.thumbBuilder, + this.visiblePageRangeBuilder, + this.useVisiblePageRange, super.key, }); @@ -28,10 +31,24 @@ class PdfViewerScrollThumb extends StatefulWidget { /// Margin from the viewer's edge. final double margin; - /// Function to customize the thumb widget. + /// Function to customize the thumb widget for single-page display. + /// This is called when visiblePageRangeBuilder is not provided or returns null. final Widget? Function(BuildContext context, Size thumbSize, int? pageNumber, PdfViewerController controller)? thumbBuilder; + /// Function to customize the thumb widget using the visible page range. + /// If provided and a visible page range is available, this takes precedence over thumbBuilder. + /// The first and last parameters indicate the range of all visible pages. + /// For single visible pages, first equals last. + final Widget? Function(BuildContext context, Size thumbSize, int first, int last, PdfViewerController controller)? + visiblePageRangeBuilder; + + /// Whether to use the visible page range (all pages with any intersection with the viewport). + /// If null, automatically set to true for spread layouts and false otherwise. + /// When true, the default thumb shows the range of all visible pages (e.g., "1-4"). + /// When false, only shows the current page number. + final bool? useVisiblePageRange; + /// Determine whether the orientation is vertical or not. bool get isVertical => orientation == ScrollbarOrientation.left || orientation == ScrollbarOrientation.right; @@ -49,6 +66,66 @@ class _PdfViewerScrollThumbState extends State { return widget.isVertical ? _buildVertical(context) : _buildHorizontal(context); } + /// Build the thumb widget with visible page range awareness. + Widget _buildThumbWidget(BuildContext context, Size thumbSize) { + final pageNumber = widget.controller.pageNumber; + + // If visiblePageRangeBuilder is provided and we have a visible range, use it + if (widget.visiblePageRangeBuilder != null) { + final visibleRange = widget.controller.visiblePageRange; + if (visibleRange != null) { + final (:first, :last) = visibleRange; + final result = widget.visiblePageRangeBuilder!(context, thumbSize, first, last, widget.controller); + if (result != null) return result; + } + } + + // Otherwise, if thumbBuilder is provided, use it + if (widget.thumbBuilder != null) { + return widget.thumbBuilder!(context, thumbSize, pageNumber, widget.controller) ?? + _buildDefaultThumb(thumbSize, pageNumber); + } + + // Use default builder + return _buildDefaultThumb(thumbSize, pageNumber); + } + + /// Build default thumb widget with visible page range awareness. + Widget _buildDefaultThumb(Size thumbSize, int? pageNumber) { + String label; + if (pageNumber == null) { + label = ''; + } else { + final layout = widget.controller.layout; + // Auto-determine useVisiblePageRange if not explicitly set + final shouldUseVisibleRange = widget.useVisiblePageRange ?? (layout is PdfSpreadLayout); + + // Use visible page range if enabled and available + if (shouldUseVisibleRange) { + final visibleRange = widget.controller.visiblePageRange; + if (visibleRange != null) { + final (:first, :last) = visibleRange; + label = first == last ? '$first' : '$first-$last'; + } else { + label = '$pageNumber'; + } + } else { + label = '$pageNumber'; + } + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow(color: Colors.black.withAlpha(127), spreadRadius: 1, blurRadius: 1, offset: const Offset(1, 1)), + ], + ), + child: Center(child: Text(label)), + ); + } + Widget _buildVertical(BuildContext context) { final thumbSize = widget.thumbSize ?? const Size(25, 40); final view = widget.controller.visibleRect; @@ -74,23 +151,7 @@ class _PdfViewerScrollThumbState extends State { width: thumbSize.width, height: thumbSize.height, child: GestureDetector( - child: - widget.thumbBuilder?.call(context, thumbSize, widget.controller.pageNumber, widget.controller) ?? - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(127), - spreadRadius: 1, - blurRadius: 1, - offset: const Offset(1, 1), - ), - ], - ), - child: Center(child: Text(widget.controller.pageNumber.toString())), - ), + child: _buildThumbWidget(context, thumbSize), onPanStart: (details) { _panStartOffset = top - details.localPosition.dy; }, @@ -129,23 +190,7 @@ class _PdfViewerScrollThumbState extends State { width: thumbSize.width, height: thumbSize.height, child: GestureDetector( - child: - widget.thumbBuilder?.call(context, thumbSize, widget.controller.pageNumber, widget.controller) ?? - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(127), - spreadRadius: 1, - blurRadius: 1, - offset: const Offset(1, 1), - ), - ], - ), - child: Center(child: Text(widget.controller.pageNumber.toString())), - ), + child: _buildThumbWidget(context, thumbSize), onPanStart: (details) { _panStartOffset = left - details.localPosition.dx; }, From 734d839f532e3122c2c3b20640c3d689d66595d0 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 18 Oct 2025 10:35:43 +1030 Subject: [PATCH 433/663] Podfile.lock --- packages/pdfrx/example/viewer/ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index b31260c1..b18a867f 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 5d590cfc73b66a19231c9bff0367a5875aea32e8 + pdfrx: 8b94a416168f5da3cdff56390f6412edf92ec915 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 From 387eea6486b86e66ffd237982dc83c1d623359e5 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 18 Oct 2025 14:40:42 +1030 Subject: [PATCH 434/663] remove resetScaleOnDiscreteTransition --- packages/pdfrx/example/viewer/lib/main.dart | 1 - packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 16 +++++----------- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 10 ---------- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 4907574d..3d427024 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -315,7 +315,6 @@ class _MainPageState extends State with WidgetsBindingObserver { margin: 10, boundaryMargin: const EdgeInsets.all(10), pageTransition: PageTransition.discrete, - resetScaleOnDiscreteTransition: true, scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index db581ae1..3351ef35 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1230,9 +1230,7 @@ class _PdfViewerState extends State return; } - final shouldResetScale = widget.params.resetScaleOnDiscreteTransition && isAdvancingToNewPage; - - if (shouldResetScale && _viewSize != null) { + if (isAdvancingToNewPage && _viewSize != null) { // Calculate fit scale for the target page _calcFitScale(targetPage); _adjustBoundaryMargins(_viewSize!, _fitScale); @@ -1243,7 +1241,7 @@ class _PdfViewerState extends State final layout = _layout; if (layout != null && _viewSize != null && anchor != PdfPageAnchor.center) { // Calculate what the scale will be for this page - final targetScale = shouldResetScale ? _fitScale : _currentZoom; + final targetScale = _fitScale; final isPrimaryVertical = layout.primaryAxis == Axis.vertical; final pageRect = layout is PdfSpreadLayout ? layout.getSpreadBounds(targetPage) @@ -1263,18 +1261,14 @@ class _PdfViewerState extends State } } - final targetZoom = shouldResetScale ? _fitScale : _currentZoom; + final targetZoom = _fitScale; // Mark that we're transitioning pages to prevent page number updates during animation - if (shouldResetScale) { - _isTransitioningPages = true; - } + _isTransitioningPages = true; _setCurrentPageNumber(targetPage, targetZoom: targetZoom, doSetState: true); - final targetMatrix = shouldResetScale - ? _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, forceScale: targetZoom) - : _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, maintainCurrentZoom: true); + final targetMatrix = _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, forceScale: targetZoom); await _goTo(targetMatrix, duration: duration, curve: Curves.easeInOutCubic); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 5af66b34..51c2e161 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -20,7 +20,6 @@ class PdfViewerParams { this.normalizeMatrix, this.fitMode = FitMode.fit, this.pageTransition = PageTransition.continuous, - this.resetScaleOnDiscreteTransition = false, this.maxScale = 8.0, this.minScale, this.useAlternativeFitScaleAsMinScale = false, @@ -183,15 +182,6 @@ class PdfViewerParams { /// The default is [PageTransition.continuous]. final PageTransition pageTransition; - /// Whether to reset zoom to fit scale on discrete page transitions. - /// - /// When enabled in discrete mode, page transitions will reset the zoom level - /// back to fit scale (minScale). This ensures that when navigating between pages, - /// the viewer always returns to the default fit scale regardless of the current zoom level. - /// - /// The default is false. - final bool resetScaleOnDiscreteTransition; - /// The maximum allowed scale. /// /// The default is 8.0. From d31494303c7602a7176ca6d5c030562b324ddb56 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 18 Oct 2025 14:51:23 +1030 Subject: [PATCH 435/663] update main.dart - pageTransition: PageTransition.discrete set for testing --- packages/pdfrx/example/viewer/lib/main.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 3d427024..9bd4f708 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -310,10 +310,7 @@ class _MainPageState extends State with WidgetsBindingObserver { ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), maxScale: 8, - fitMode: FitMode.fill, - // pageDropShadow: null, - margin: 10, - boundaryMargin: const EdgeInsets.all(10), + //fitMode: FitMode.fill, pageTransition: PageTransition.discrete, scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ From 2b5c0a6ac67173c705723e9cb77202e00a63bd9c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 20 Oct 2025 01:17:03 +0900 Subject: [PATCH 436/663] WIP - Should we need to gitignore Podfile.lock/project.pbxproj changes? - Revert some of facial changes that has nothing to do with the current PR - At least, the wording should be backward-compatible just now. In your wording, FitMode.cover seems no meaning. We should need just FitMode.fill (but it should be renamed to FitMode.cover). --- .gitignore | 2 +- .../pdfrx/example/viewer/ios/Podfile.lock | 8 ++-- .../ios/Runner.xcodeproj/project.pbxproj | 42 +++++++++---------- packages/pdfrx/example/viewer/lib/main.dart | 14 +++---- .../pdfrx/example/viewer/macos/Podfile.lock | 8 ++-- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index a8d8da46..d42b052f 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,7 @@ pubspec_overrides.yaml .serena/ -# iOS/macOS build artifacts that change frequently +# SHOULD WE NEED THIS??: iOS/macOS build artifacts that change frequently **/ios/Podfile.lock **/macos/Podfile.lock **/ios/**/*.xcodeproj/project.pbxproj diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index b18a867f..b7d68ca6 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c + file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 8b94a416168f5da3cdff56390f6412edf92ec915 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 19623d87..2dd02ad7 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 5F441919C7BCC240DA875443 /* [CP] Embed Pods Frameworks */, + B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -317,23 +317,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 5F441919C7BCC240DA875443 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 938050D037D35BB79276ADD0 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -371,6 +354,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -480,7 +480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ERC2H3WBFG; + DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -663,7 +663,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ERC2H3WBFG; + DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -690,7 +690,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ERC2H3WBFG; + DEVELOPMENT_TEAM = XRDM278W3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 9bd4f708..8438bdcf 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -103,7 +103,7 @@ class _MainPageState extends State with WidgetsBindingObserver { return Row( children: [ if (!isMobileDevice) ...[ - Expanded(child: Text(_fileName(documentRef?.sourceName) ?? 'No document loaded')), + Expanded(child: Text(_fileName(documentRef?.key.sourceName) ?? 'No document loaded')), SizedBox(width: 10), FilledButton(onPressed: () => openFile(), child: Text('Open File')), SizedBox(width: 20), @@ -198,7 +198,7 @@ class _MainPageState extends State with WidgetsBindingObserver { valueListenable: documentRef, builder: (context, documentRef, child) => Expanded( child: Text( - _fileName(documentRef?.sourceName) ?? 'No document loaded', + _fileName(documentRef?.key.sourceName) ?? 'No document loaded', softWrap: false, ), ), @@ -312,7 +312,7 @@ class _MainPageState extends State with WidgetsBindingObserver { maxScale: 8, //fitMode: FitMode.fill, pageTransition: PageTransition.discrete, - scrollPhysics: PdfViewerParams.getScrollPhysics(context), + // scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures @@ -342,28 +342,28 @@ class _MainPageState extends State with WidgetsBindingObserver { controller: controller, orientation: ScrollbarOrientation.right, thumbSize: const Size(40, 25), - /* thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( color: Colors.black, child: isHorizontalLayout ? null : Center( child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), ), - ), */ + ), ), // Just a simple horizontal scroll thumb on the bottom PdfViewerScrollThumb( controller: controller, orientation: ScrollbarOrientation.bottom, thumbSize: const Size(40, 25), - /*thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( color: Colors.black, child: !isHorizontalLayout ? null : Center( child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), ), - ), */ + ), ), ], // diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index f6e0ea09..fc322724 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d + file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - pdfrx: 5d590cfc73b66a19231c9bff0367a5875aea32e8 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 3351ef35..773a7179 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -664,7 +664,7 @@ class _PdfViewerState extends State _document!, _controller!, _calculateScaleForMode(FitMode.fit), // fitZoom (was _alternativeFitScale) - _calculateScaleForMode(FitMode.cover), // coverZoom (was _coverScale) + _calculateScaleForMode(FitMode.fill), // coverZoom (was _coverScale)** ) ?? _getInitialZoom(); await _setZoom(Offset.zero, zoom, duration: Duration.zero); From cd3e4ae7931aa73346c77ed0236f81dc3dfb6899 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 20 Oct 2025 03:03:39 +0900 Subject: [PATCH 437/663] WIP: just remove pana's warnings on comments... The comments are not good anyway. Consider better explanation on it. --- packages/pdfrx/lib/src/widgets/pdf_page_layout.dart | 6 ++++-- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index a205ac82..c4a259b6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -2,8 +2,10 @@ // This file is part of pdfrx. import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; + import '../../pdfrx.dart'; /// Helper class to hold layout calculation results. @@ -570,8 +572,8 @@ class PdfPageLayout { /// - Page 1 (cover): spread 0 /// - Pages 2-3: spread 1 /// - Pages 4-5: spread 2 -/// - pageToSpreadIndex = [0, 1, 1, 2, 2, ...] (0-based: index 0 = page 1) -/// - spreadLayouts = [Rect(cover bounds), Rect(pages 2-3 bounds), Rect(pages 4-5 bounds), ...] +/// - `pageToSpreadIndex = [0, 1, 1, 2, 2, ...]` (0-based: index 0 = page 1) +/// - `spreadLayouts = [Rect(cover bounds), Rect(pages 2-3 bounds), Rect(pages 4-5 bounds), ...]` abstract class PdfSpreadLayout extends PdfPageLayout { PdfSpreadLayout({ required super.pageLayouts, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 51c2e161..8d48c728 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -236,7 +236,7 @@ class PdfViewerParams { @Deprecated('Use fitMode parameter instead. See documentation for migration guide.') final bool useAlternativeFitScaleAsMinScale; - /// See [InteractiveViewer.panAxis] f or details. + /// See [InteractiveViewer.panAxis] for details. final PanAxis panAxis; /// See [InteractiveViewer.boundaryMargin] for details. @@ -854,6 +854,7 @@ class PdfTextSelectionParams { bool operator ==(Object other) { if (identical(this, other)) return true; return other is PdfTextSelectionParams && + other.enabled == enabled && other.buildSelectionHandle == buildSelectionHandle && other.onTextSelectionChange == onTextSelectionChange && other.enableSelectionHandles == enableSelectionHandles && @@ -863,6 +864,7 @@ class PdfTextSelectionParams { @override int get hashCode => + enabled.hashCode ^ buildSelectionHandle.hashCode ^ onTextSelectionChange.hashCode ^ enableSelectionHandles.hashCode ^ @@ -983,7 +985,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, From 73f002a67083610e12f2638edb3dd9c5eb622dda Mon Sep 17 00:00:00 2001 From: james Date: Mon, 20 Oct 2025 07:15:12 +1030 Subject: [PATCH 438/663] _DiscretePdfPageLayout remove primaryAxis override --- packages/pdfrx/example/viewer/macos/Podfile.lock | 8 ++++---- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 16 ++-------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index fc322724..91e6aa8d 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 + file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab - url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 8b94a416168f5da3cdff56390f6412edf92ec915 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 773a7179..563119a8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1745,15 +1745,10 @@ class _PdfViewerState extends State documentSize: newDocSize, spreadLayouts: newSpreadLayouts, pageToSpreadIndex: layout.pageToSpreadIndex, - primaryAxis: layout.primaryAxis, ); } - return _DiscretePdfPageLayout( - pageLayouts: newPageLayouts, - documentSize: newDocSize, - primaryAxis: layout.primaryAxis, - ); + return _DiscretePdfPageLayout(pageLayouts: newPageLayouts, documentSize: newDocSize); } /// Returns the boundary rect for discrete mode (current page/spread bounds). @@ -5069,10 +5064,7 @@ class _CanvasLinkPainter { /// Wrapper layout for discrete mode single page layouts with viewport spacing. class _DiscretePdfPageLayout extends PdfPageLayout { - _DiscretePdfPageLayout({required super.pageLayouts, required super.documentSize, required this.primaryAxis}); - - @override - final Axis primaryAxis; + _DiscretePdfPageLayout({required super.pageLayouts, required super.documentSize}); } /// Wrapper layout for discrete mode spread layouts with viewport spacing. @@ -5082,9 +5074,5 @@ class _DiscretePdfSpreadLayout extends PdfSpreadLayout { required super.documentSize, required super.spreadLayouts, required super.pageToSpreadIndex, - required this.primaryAxis, }); - - @override - final Axis primaryAxis; } From 0c16a4cd7b482ab91cae02204d8522f5345aaff3 Mon Sep 17 00:00:00 2001 From: james Date: Mon, 20 Oct 2025 07:16:41 +1030 Subject: [PATCH 439/663] remove _isTransitioningpages --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 563119a8..8a397242 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -265,7 +265,6 @@ class _PdfViewerState extends State bool _isActiveGesture = false; // True during pan/scale gestures bool _isActivelyZooming = false; // True only during active pinch-zoom gesture bool _hasActiveAnimations = false; // True when InteractiveViewer has active animations - bool _isTransitioningPages = false; // True during discrete page transition animation BuildContext? _contextForFocusNode; Offset _pointerOffset = Offset.zero; @@ -1263,16 +1262,12 @@ class _PdfViewerState extends State final targetZoom = _fitScale; - // Mark that we're transitioning pages to prevent page number updates during animation - _isTransitioningPages = true; - _setCurrentPageNumber(targetPage, targetZoom: targetZoom, doSetState: true); final targetMatrix = _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, forceScale: targetZoom); await _goTo(targetMatrix, duration: duration, curve: Curves.easeInOutCubic); - _isTransitioningPages = false; _onAnimationEnd(); } From dbc6046e5942c6116f446b57f695bd14de97d0c1 Mon Sep 17 00:00:00 2001 From: james Date: Mon, 20 Oct 2025 11:22:19 +1030 Subject: [PATCH 440/663] updates to FacingPagesLayout to provide parity with legacy layout --- .../lib/src/widgets/pdf_page_layout.dart | 179 +++++++++++++++--- 1 file changed, 157 insertions(+), 22 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index c4a259b6..d0e3aed1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -766,20 +766,21 @@ class FacingPagesLayout extends PdfSpreadLayout { PdfLayoutHelper? helper, bool firstPageIsCoverPage = false, bool isRightToLeftReadingOrder = false, - double gutter = 4.0, // gap between left/right pages + double? gutter, // gap between left/right pages + bool singlePagesFillAvailableWidth = true, + bool independentPageScaling = true, }) { + final effectiveGutter = gutter ?? params.margin; + if (pages.isEmpty) { return FacingPagesLayout(pageLayouts: [], documentSize: Size.zero, spreadLayouts: [], pageToSpreadIndex: []); } - // For fit/fill/cover modes with viewport scaling, we need helper - final scaleToViewport = - params.fitMode == FitMode.fit || params.fitMode == FitMode.fill || params.fitMode == FitMode.cover; assert( - !scaleToViewport || helper != null, + !independentPageScaling || helper != null, 'FitMode.${params.fitMode.name} requires PdfLayoutHelper for FacingPagesLayout.', ); - if (scaleToViewport && helper == null) { + if (independentPageScaling && helper == null) { return FacingPagesLayout(pageLayouts: [], documentSize: Size.zero, spreadLayouts: [], pageToSpreadIndex: []); } @@ -791,22 +792,71 @@ class FacingPagesLayout extends PdfSpreadLayout { var pageIndex = 0; // Available space for content - final availableWidth = scaleToViewport ? helper!.availableWidth : 0.0; - final availableHeight = scaleToViewport ? helper!.availableHeight : 0.0; + final double availableWidth; + final double availableHeight; + final double maxPageWidth; // Max width of any page (for non-independent scaling) + + if (independentPageScaling) { + availableWidth = helper!.availableWidth; + availableHeight = helper.availableHeight; + maxPageWidth = 0.0; // Not used when scaling independently + } else { + // For non-independent scaling, find the maximum page width + maxPageWidth = pages.fold(0.0, (prev, page) => max(prev, page.width)); + availableWidth = 0.0; // Not used when not scaling independently + availableHeight = 0.0; // Not used when not scaling independently + } // Handle cover page if needed if (firstPageIsCoverPage && pages.isNotEmpty) { final coverPage = pages[0]; - final coverSize = scaleToViewport + + // Determine target width based on singlePagesFillAvailableWidth setting + final coverTargetWidth = singlePagesFillAvailableWidth ? availableWidth : (availableWidth - effectiveGutter) / 2; + + final coverSize = independentPageScaling ? _calculatePageSize( page: coverPage, - targetWidth: availableWidth, + targetWidth: coverTargetWidth, availableHeight: availableHeight, fitMode: params.fitMode, ) : Size(coverPage.width, coverPage.height); - final coverX = scaleToViewport ? params.margin + (availableWidth - coverSize.width) / 2 : params.margin; + // Position cover page based on RTL and fill setting + // RTL: cover on left, LTR: cover on right (when not filling full width) + final double coverX; + if (independentPageScaling) { + if (singlePagesFillAvailableWidth) { + // Center the page in available width + coverX = params.margin + (availableWidth - coverSize.width) / 2; + } else { + // Position on left (RTL) or right (LTR) + if (isRightToLeftReadingOrder) { + // RTL: cover page on left + coverX = params.margin; + } else { + // LTR: cover page on right + coverX = params.margin + (availableWidth - coverSize.width); + } + } + } else { + // Legacy mode: position cover page using maxPageWidth centering + if (singlePagesFillAvailableWidth) { + // Center in document width + coverX = params.margin + (maxPageWidth * 2 + params.margin - coverSize.width) / 2; + } else { + // Position in one half based on RTL + if (isRightToLeftReadingOrder) { + // RTL: cover page on left side (right-aligned within left half) + coverX = params.margin + (maxPageWidth - coverSize.width); + } else { + // LTR: cover page on right side (left-aligned within right half) + coverX = params.margin * 2 + maxPageWidth; + } + } + } + pageLayouts.add(Rect.fromLTWH(coverX, y, coverSize.width, coverSize.height)); pageToSpreadIndex[0] = spreadIndex; // Page 1 is at index 0 spreadLayouts.add(pageLayouts.last); @@ -822,35 +872,114 @@ class FacingPagesLayout extends PdfSpreadLayout { final rightPageIndex = pageIndex + 1; final rightPage = rightPageIndex < pages.length ? pages[rightPageIndex] : null; + // Determine if this is the last page and it's a single page + final isLastPageSingle = rightPage == null; + // Calculate page dimensions - final leftSize = scaleToViewport + // For last single page, use singlePagesFillAvailableWidth to determine width + final leftTargetWidth = independentPageScaling + ? (isLastPageSingle && singlePagesFillAvailableWidth + ? availableWidth + : (availableWidth - effectiveGutter) / 2) + : 0.0; + + final leftSize = independentPageScaling ? _calculatePageSize( page: leftPage, - targetWidth: rightPage != null ? (availableWidth - gutter) / 2 : availableWidth, + targetWidth: leftTargetWidth, availableHeight: availableHeight, fitMode: params.fitMode, ) : Size(leftPage.width, leftPage.height); - final rightSize = rightPage != null && scaleToViewport + final rightSize = rightPage != null && independentPageScaling ? _calculatePageSize( page: rightPage, - targetWidth: (availableWidth - gutter) / 2, + targetWidth: (availableWidth - effectiveGutter) / 2, availableHeight: availableHeight, fitMode: params.fitMode, ) : Size(rightPage?.width ?? 0.0, rightPage?.height ?? 0.0); - // Calculate spread dimensions - final spreadWidth = rightPage != null ? leftSize.width + gutter + rightSize.width : leftSize.width; + // Calculate spread dimensions and positioning + final double spreadWidth; final spreadHeight = max(leftSize.height, rightSize.height); - final spreadX = scaleToViewport ? params.margin + (availableWidth - spreadWidth) / 2 : params.margin; + + // Determine spread X position + final double spreadX; + if (independentPageScaling) { + spreadWidth = rightPage != null ? leftSize.width + effectiveGutter + rightSize.width : leftSize.width; + if (rightPage != null) { + // Two-page spread: center it + spreadX = params.margin + (availableWidth - spreadWidth) / 2; + } else { + // Single last page + if (singlePagesFillAvailableWidth) { + // Fill full width: center the page + spreadX = params.margin + (availableWidth - spreadWidth) / 2; + } else { + // Don't fill full width: position based on RTL + if (isRightToLeftReadingOrder) { + // RTL: last page on right + spreadX = params.margin + (availableWidth - spreadWidth); + } else { + // LTR: last page on left + spreadX = params.margin; + } + } + } + } else { + // Legacy mode: spread width is based on two max-width slots + spreadWidth = rightPage != null ? maxPageWidth * 2 + params.margin : leftSize.width; + // Legacy mode: spread starts at left margin + // The individual page positions will be calculated relative to maxPageWidth + spreadX = params.margin; + } spreadLayouts.add(Rect.fromLTWH(spreadX, y, spreadWidth, spreadHeight)); // Layout pages within spread (RTL vs LTR) - final leftX = isRightToLeftReadingOrder && rightPage != null ? spreadX + rightSize.width + gutter : spreadX; - final rightX = isRightToLeftReadingOrder ? spreadX : spreadX + leftSize.width + gutter; + final double leftX; + final double rightX; + + if (!independentPageScaling) { + // Legacy facing pages layout: pages are centered relative to maxPageWidth + // Document width = (margin + maxPageWidth) * 2 + margin + if (rightPage != null) { + // Two-page spread + // Left pages: maxPageWidth + margin - page.width (right-aligned within left half) + // Right pages: margin * 2 + maxPageWidth (left-aligned within right half) + if (isRightToLeftReadingOrder) { + // RTL: right page on left (right-aligned), left page on right (left-aligned) + rightX = params.margin + (maxPageWidth - rightSize.width); + leftX = params.margin * 2 + maxPageWidth; + } else { + // LTR: left page on left (right-aligned), right page on right (left-aligned) + leftX = params.margin + (maxPageWidth - leftSize.width); + rightX = params.margin * 2 + maxPageWidth; + } + } else { + // Single last page + if (singlePagesFillAvailableWidth) { + // Center in document width + leftX = params.margin + (maxPageWidth * 2 + params.margin - leftSize.width) / 2; + } else { + // Position in one half based on RTL + if (isRightToLeftReadingOrder) { + // RTL: last page on right side (left-aligned within right half) + leftX = params.margin * 2 + maxPageWidth; + } else { + // LTR: last page on left side (right-aligned within left half) + leftX = params.margin + (maxPageWidth - leftSize.width); + } + } + rightX = 0; // Not used for single pages + } + } else { + // Original behavior for independentPageScaling = true + leftX = isRightToLeftReadingOrder && rightPage != null ? spreadX + rightSize.width + effectiveGutter : spreadX; + rightX = isRightToLeftReadingOrder ? spreadX : spreadX + leftSize.width + effectiveGutter; + } pageLayouts.add(Rect.fromLTWH(leftX, y + (spreadHeight - leftSize.height) / 2, leftSize.width, leftSize.height)); pageToSpreadIndex[pageIndex] = spreadIndex; // 0-based indexing @@ -868,8 +997,14 @@ class FacingPagesLayout extends PdfSpreadLayout { } // Calculate document width based on content - final maxSpreadRight = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.right)); - final documentWidth = maxSpreadRight + params.margin; + final double documentWidth; + if (independentPageScaling) { + final maxSpreadRight = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.right)); + documentWidth = maxSpreadRight + params.margin; + } else { + // Legacy mode: document width = (margin + maxPageWidth) * 2 + margin + documentWidth = (params.margin + maxPageWidth) * 2 + params.margin; + } return FacingPagesLayout( pageLayouts: pageLayouts, From 10ae7b385c71d4161908f439f169ff60b240fb56 Mon Sep 17 00:00:00 2001 From: james Date: Mon, 20 Oct 2025 11:40:01 +1030 Subject: [PATCH 441/663] rename SinglePagesLayout to SequentialPagesLayout --- .../lib/src/widgets/pdf_page_layout.dart | 24 +++++++++---------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- .../lib/src/widgets/pdf_viewer_params.dart | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index d0e3aed1..935b2eb6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -132,7 +132,7 @@ class PdfPageLayout { /// height >= width, otherwise [Axis.horizontal]. /// /// Subclasses can override this to use explicit scroll direction instead. - /// For example, [SinglePagesLayout] overrides to use its scroll direction parameter. + /// For example, [SequentialPagesLayout] overrides to use its scroll direction parameter. /// /// Determines the direction of scrolling and page layout. final Axis primaryAxis; @@ -671,7 +671,7 @@ abstract class PdfSpreadLayout extends PdfPageLayout { int get hashCode => Object.hash(super.hashCode, Object.hashAll(spreadLayouts), Object.hashAll(pageToSpreadIndex)); } -/// Single pages layout implementation supporting both vertical and horizontal scrolling. +/// Sequential pages layout implementation supporting both vertical and horizontal scrolling. /// /// This layout displays pages one after another in either a vertical or horizontal /// scrolling direction. The scroll direction is specified when creating the layout. @@ -680,33 +680,33 @@ abstract class PdfSpreadLayout extends PdfPageLayout { /// ```dart /// // Vertical scrolling (default) /// layoutPages: (pages, params, {viewport}) => -/// SinglePagesLayout.fromPages(pages, params, helper: helper), +/// SequentialPagesLayout.fromPages(pages, params, helper: helper), /// /// // Horizontal scrolling /// layoutPages: (pages, params, {viewport}) => -/// SinglePagesLayout.fromPages( +/// SequentialPagesLayout.fromPages( /// pages, /// params, /// helper: helper, /// scrollDirection: Axis.horizontal, /// ), /// ``` -class SinglePagesLayout extends PdfPageLayout { - SinglePagesLayout({required super.pageLayouts, required super.documentSize, required this.scrollDirection}); +class SequentialPagesLayout extends PdfPageLayout { + SequentialPagesLayout({required super.pageLayouts, required super.documentSize, required this.scrollDirection}); - /// Create a single pages layout from pages and parameters. + /// Create a sequential pages layout from pages and parameters. /// /// The [scrollDirection] parameter determines whether pages scroll vertically (default) /// or horizontally. - factory SinglePagesLayout.fromPages( + factory SequentialPagesLayout.fromPages( List pages, PdfViewerParams params, { PdfLayoutHelper? helper, Axis scrollDirection = Axis.vertical, }) { - final layout = SinglePagesLayout(pageLayouts: [], documentSize: Size.zero, scrollDirection: scrollDirection); + final layout = SequentialPagesLayout(pageLayouts: [], documentSize: Size.zero, scrollDirection: scrollDirection); final result = layout.layoutBuilder(pages, params, helper: helper); - return SinglePagesLayout( + return SequentialPagesLayout( pageLayouts: result.pageLayouts, documentSize: result.documentSize, scrollDirection: scrollDirection, @@ -721,7 +721,7 @@ class SinglePagesLayout extends PdfPageLayout { @override bool operator ==(Object other) { if (identical(this, other)) return true; - if (other is! SinglePagesLayout) return false; + if (other is! SequentialPagesLayout) return false; return super == other && scrollDirection == other.scrollDirection; } @@ -730,7 +730,7 @@ class SinglePagesLayout extends PdfPageLayout { @override LayoutResult layoutBuilder(List pages, PdfViewerParams params, {PdfLayoutHelper? helper}) { - assert(helper != null, 'SinglePagesLayout requires PdfLayoutHelper for fit modes other than none or cover'); + assert(helper != null, 'SequentialPagesLayout requires PdfLayoutHelper for fit modes other than none or cover'); final pageSizes = PdfPageLayout.calculatePageSizes( pages: pages, fitMode: params.fitMode, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8a397242..51e967d9 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2216,7 +2216,7 @@ class _PdfViewerState extends State } PdfPageLayout _layoutPages(List pages, PdfViewerParams params, PdfLayoutHelper helper) { - return SinglePagesLayout.fromPages(pages, params, helper: helper); + return SequentialPagesLayout.fromPages(pages, params, helper: helper); } void _invalidate() => _updateStream.add(_txController.value); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 8d48c728..4fef1447 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1270,7 +1270,7 @@ typedef PdfViewerGetPageRenderingScale = /// ```dart /// layoutPages: (pages, params, helper) { /// // Use helper for viewport-aware layouts -/// return SinglePagesLayout.fromPages(pages, params, helper: helper); +/// return SequentialPagesLayout.fromPages(pages, params, helper: helper); /// } /// ``` /// From 2de03d6142852baa613f7f36070984435b2b7652 Mon Sep 17 00:00:00 2001 From: james Date: Mon, 20 Oct 2025 11:42:15 +1030 Subject: [PATCH 442/663] SinglePagesLayout to SequentialPagesLayout in main.dart --- packages/pdfrx/example/viewer/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 8438bdcf..0e37f960 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -475,7 +475,7 @@ class _MainPageState extends State with WidgetsBindingObserver { null, // Horizontal layout (using built-in layout class) (pages, params, helper) => - SinglePagesLayout.fromPages(pages, params, helper: helper, scrollDirection: Axis.horizontal), + SequentialPagesLayout.fromPages(pages, params, helper: helper, scrollDirection: Axis.horizontal), // Facing pages layout (using built-in layout class) (pages, params, helper) => FacingPagesLayout.fromPages( From 97347f315026c12834ae6ac2253eb5635396cc74 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 20 Oct 2025 17:06:20 +0900 Subject: [PATCH 443/663] _calcZoomStopTable fix --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 51e967d9..f31ef1f8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1474,7 +1474,7 @@ class _PdfViewerState extends State // Calculate both fit and cover scales to provide good zoom stops // even when only one fitMode is selected final fitScale = _calculateScaleForMode(FitMode.fit); - final coverScale = _calculateScaleForMode(FitMode.cover); + final coverScale = _calculateScaleForMode(FitMode.fill); // Use the primary scale based on current fitMode (or legacy behavior) final primaryScale = _fitScale; From 261af3682a8d03aedf1bffb32ceb831d11ebf438 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 20 Oct 2025 18:17:06 +0900 Subject: [PATCH 444/663] FIXED: #501 SwiftPM/pod package update --- packages/pdfrx/darwin/pdfrx.podspec | 2 +- packages/pdfrx/darwin/pdfrx/Package.swift | 14 +++++++--- .../{pdfrx => interop}/include/.gitkeep | 0 .../Sources/{pdfrx => interop}/pdfrx.cpp | 0 .../Sources/{ => main}/PdfrxPlugin.swift | 0 .../pdfrx/example/viewer/ios/Podfile.lock | 6 ++--- .../ios/Runner.xcodeproj/project.pbxproj | 4 +-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../pdfrx/example/viewer/macos/Podfile.lock | 26 ------------------- .../macos/Runner.xcodeproj/project.pbxproj | 18 ------------- 10 files changed, 17 insertions(+), 55 deletions(-) rename packages/pdfrx/darwin/pdfrx/Sources/{pdfrx => interop}/include/.gitkeep (100%) rename packages/pdfrx/darwin/pdfrx/Sources/{pdfrx => interop}/pdfrx.cpp (100%) rename packages/pdfrx/darwin/pdfrx/Sources/{ => main}/PdfrxPlugin.swift (100%) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index 86363c75..a1446fc5 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -6,7 +6,7 @@ lib_tag = 'pdfium-apple-v11' Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.11' + s.version = '0.0.12' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift index 85b49d62..8328be15 100644 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ b/packages/pdfrx/darwin/pdfrx/Package.swift @@ -17,14 +17,20 @@ let package = Package( .target( name: "pdfrx", dependencies: [ - .target(name: "pdfium", condition: .when(platforms: [.iOS])), + .target(name: "pdfium"), + ], + path: "Sources/main" + ), + .target( + name: "pdfium", + dependencies: [ + .target(name: "pdfium-ios", condition: .when(platforms: [.iOS])), .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])), ], - path: ".", - sources: ["Sources"] + path: "Sources/interop" ), .binaryTarget( - name: "pdfium", + name: "pdfium-ios", url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-ios.zip", checksum: "968e270318f9a52697f42b677ff5b46bde4da0702fb3930384d0a7f7e62c3073" ), diff --git a/packages/pdfrx/darwin/pdfrx/Sources/pdfrx/include/.gitkeep b/packages/pdfrx/darwin/pdfrx/Sources/interop/include/.gitkeep similarity index 100% rename from packages/pdfrx/darwin/pdfrx/Sources/pdfrx/include/.gitkeep rename to packages/pdfrx/darwin/pdfrx/Sources/interop/include/.gitkeep diff --git a/packages/pdfrx/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp similarity index 100% rename from packages/pdfrx/darwin/pdfrx/Sources/pdfrx/pdfrx.cpp rename to packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp diff --git a/packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift b/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift similarity index 100% rename from packages/pdfrx/darwin/pdfrx/Sources/PdfrxPlugin.swift rename to packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index b7d68ca6..8c86de35 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 + file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 2dd02ad7..d91ad2c9 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */, + E3321798E1D30E12796E6D41 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -354,7 +354,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - B053A3ACEB01271C75170FBB /* [CP] Embed Pods Frameworks */ = { + E3321798E1D30E12796E6D41 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c3fedb29..95d6e55f 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 54ab7043..9d359e12 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -1,41 +1,15 @@ PODS: - - file_selector_macos (0.0.1): - - FlutterMacOS - FlutterMacOS (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.0.11): - - Flutter - - FlutterMacOS - - url_launcher_macos (0.0.1): - - FlutterMacOS DEPENDENCIES: - - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) - - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: - file_selector_macos: - :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral - path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - pdfrx: - :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin - url_launcher_macos: - :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 4d84cd0897c1a01d974258c0f2fe211b79954fa0 - url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index f5ce6f1e..6d418bf7 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -244,7 +244,6 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - B85BC795505CC27018AFCA24 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -414,23 +413,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B85BC795505CC27018AFCA24 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ From 48f8079fa49f0d4a18e517053e3c26ed31652868 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 20 Oct 2025 18:55:49 +0900 Subject: [PATCH 445/663] Release pdfrx_engine v0.2.1 and pdfrx v2.2.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pdfrx_engine v0.2.1: Handle servers returning 200 instead of 206 - pdfrx v2.2.4: SwiftPM/pod package structure updates for iOS/macOS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bef4ba47..cf3fda9f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.3 + pdfrx: ^2.2.4 ``` ### For Pure Dart Applications @@ -54,7 +54,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.1.21 + pdfrx_engine: ^0.2.1 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 90f74307..abbdf668 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.2.4 + +- FIXED: SwiftPM/pod package structure updates for iOS/macOS ([#501](https://github.com/espresso3389/pdfrx/issues/501)) +- Updated to pdfrx_engine 0.2.1 + # 2.2.3 - POSSIBLE FIX: Error on `openFile()` or `openAsset()` on iOS production builds installed from AppStore/TestFlight ([#501](https://github.com/espresso3389/pdfrx/issues/501)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 16a7712c..ad3e645d 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.3 + pdfrx: ^2.2.4 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 36b65b39..dccf528d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.3 +version: 2.2.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.0 + pdfrx_engine: ^0.2.1 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index f52e1eaf..a276c5fe 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1 + +- FIXED: Handle servers that return 200 instead of 206 for content-range requests ([#468](https://github.com/espresso3389/pdfrx/issues/468)) + ## 0.2.0 - **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index b2ab5eeb..5b6fda34 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.2.0 +version: 0.2.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 2926b1d6d848e55df1f1a5e6fbf2362c6af9c0e5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 21 Oct 2025 00:13:04 +0900 Subject: [PATCH 446/663] pdfrxFlutterInitialize internally calls WidgetsFlutterBinding.ensureInitialized --- doc/pdfrx-Initialization.md | 1 + packages/pdfrx/lib/src/pdfrx_flutter.dart | 3 +++ 2 files changed, 4 insertions(+) diff --git a/doc/pdfrx-Initialization.md b/doc/pdfrx-Initialization.md index a09d0bbc..983414c6 100644 --- a/doc/pdfrx-Initialization.md +++ b/doc/pdfrx-Initialization.md @@ -16,6 +16,7 @@ For pure Dart apps (or even some of Flutter apps), you can use [pdfrxInitialize] Basically, these initialization functions do the following things: +- Call [WidgetsFlutterBinding.ensureInitialized](https://api.flutter.dev/flutter/widgets/WidgetsFlutterBinding/ensureInitialized.html) (Flutter only) - Set [Pdfrx.getCacheDirectory](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/getCacheDirectory.html) - Map PdfDocument [factory/interop functions](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions-class.html) to actual platform ones - Set [Pdfrx.loadAsset](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/loadAsset.html) (Flutter only) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 5c533ecb..4ad6fcda 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' show WidgetsFlutterBinding; import 'package:flutter/services.dart'; import '../pdfrx.dart'; @@ -23,6 +24,8 @@ bool _isInitialized = false; void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { if (_isInitialized) return; + WidgetsFlutterBinding.ensureInitialized(); + if (pdfrxEntryFunctionsOverride != null) { PdfrxEntryFunctions.instance = pdfrxEntryFunctionsOverride!; } From 2fbf40fb2863d9b82b0b2888a76c6dab1a3203ac Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 21 Oct 2025 00:13:35 +0900 Subject: [PATCH 447/663] build-test for iOS/macOS now tests SwiftPM and CocoaPods --- .github/workflows/build-test.yml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a86bc2fe..e1889917 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -90,6 +90,10 @@ jobs: ios: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_ios }} runs-on: macos-latest + strategy: + matrix: + package_manager: [cocoapods, swiftpm] + name: ios-${{ matrix.package_manager }} steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -101,11 +105,19 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Configure package manager + run: | + if [ "${{ matrix.package_manager }}" = "swiftpm" ]; then + ~/flutter/bin/flutter config --enable-swift-package-manager + else + ~/flutter/bin/flutter config --no-enable-swift-package-manager + fi + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - - name: Build iOS (no signing) + - name: Build iOS (no signing) - ${{ matrix.package_manager }} working-directory: packages/pdfrx/example/viewer run: flutter build ios --debug --no-codesign --verbose @@ -113,6 +125,10 @@ jobs: macos: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_macos }} runs-on: macos-latest + strategy: + matrix: + package_manager: [cocoapods, swiftpm] + name: macos-${{ matrix.package_manager }} steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -124,11 +140,19 @@ jobs: ~/flutter/bin/flutter channel stable ~/flutter/bin/flutter doctor -v + - name: Configure package manager + run: | + if [ "${{ matrix.package_manager }}" = "swiftpm" ]; then + ~/flutter/bin/flutter config --enable-swift-package-manager + else + ~/flutter/bin/flutter config --no-enable-swift-package-manager + fi + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: flutter pub get - - name: Build macOS + - name: Build macOS - ${{ matrix.package_manager }} working-directory: packages/pdfrx/example/viewer run: flutter build macos --debug --verbose From 375b0db5cf51681ff4934132c0a096f39d866ba3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 21 Oct 2025 06:00:04 +0900 Subject: [PATCH 448/663] Introduces experimental setupAppleDirectLookupIfApplicable for #501 --- .vscode/c_cpp_properties.json | 12 +- .../bin/remove_darwin_pdfium_modules.dart | 24 +- .../darwin/pdfrx/Sources/interop/pdfrx.cpp | 337 ++++++++++++++++++ .../pdfrx/Sources/main/PdfrxPlugin.swift | 56 ++- .../pdfrx/example/viewer/ios/Podfile.lock | 26 -- .../ios/Runner.xcodeproj/project.pbxproj | 18 - packages/pdfrx/lib/src/pdf_document_ref.dart | 97 ++--- packages/pdfrx/lib/src/pdfrx_flutter.dart | 6 +- .../pdfrx/lib/src/utils/native/native.dart | 17 +- packages/pdfrx/lib/src/utils/web/web.dart | 3 +- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 3 + packages/pdfrx/src/pdfium_interop.cpp | 336 ----------------- packages/pdfrx_coregraphics/README.md | 1 - .../lib/pdfrx_coregraphics.dart | 3 + .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 + .../lib/src/native/apple_direct_lookup.dart | 27 ++ .../pdfrx_engine/lib/src/native/pdfium.dart | 35 +- .../lib/src/native/pdfium_interop.dart | 69 ++-- .../lib/src/native/pdfrx_pdfium.dart | 16 +- .../pdfrx_engine/lib/src/native/worker.dart | 5 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 19 + 21 files changed, 631 insertions(+), 482 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 55e1beca..7671433d 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -8,8 +8,8 @@ "includePath": [ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", "${workspaceFolder}/packages/pdfrx/example/viewer/build/windows/x64/.lib/latest/include", - "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx", - "${env:LOCALAPPDATA}/Android/Sdk/ndk/${ndkVersion}/sysroot/usr/include/*" + "${workspaceFolder}/packages/pdfrx/src", + "${env.LOCALAPPDATA}/Android/Sdk/ndk/${ndkVersion}/sysroot/usr/include/*" ], "defines": [], "cStandard": "c17", @@ -21,7 +21,7 @@ "includePath": [ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", "${workspaceFolder}/packages/pdfrx/example/viewer/build/linux/x64/.lib/latest/include", - "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx" + "${workspaceFolder}/packages/pdfrx/src" ], "defines": [], "cStandard": "c17", @@ -32,8 +32,10 @@ "name": "Mac", "includePath": [ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include", - "${workspaceFolder}/packages/pdfrx/darwin/pdfium/.lib/pdfium-apple-*/macos/pdfium.xcframework/macos-arm64_x86_64/Headers", - "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/pdfrx" + "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/interop", + "${workspaceFolder}/packages/pdfrx/src", + "${workspaceFolder}/packages/pdfrx/darwin/pdfium/.lib/**", + "${workspaceFolder}/packages/pdfrx/example/viewer/build/**" ], "defines": [], "cStandard": "c17", diff --git a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart index 40f0e2d2..e6944ef1 100644 --- a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart +++ b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart @@ -62,13 +62,19 @@ Future main(List args) async { String _commentPlatforms(String yaml) { // Comment out iOS platform configuration yaml = yaml.replaceAllMapped( - RegExp(r'^(\s*ios:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), + RegExp( + r'^(\s*ios:\s*\n\s*pluginClass:\s*PdfrxPlugin\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', + multiLine: true, + ), (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', ); // Comment out macOS platform configuration yaml = yaml.replaceAllMapped( - RegExp(r'^(\s*macos:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), + RegExp( + r'^(\s*macos:\s*\n\s*pluginClass:\s*PdfrxPlugin\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', + multiLine: true, + ), (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', ); @@ -78,14 +84,20 @@ String _commentPlatforms(String yaml) { String _uncommentPlatforms(String yaml) { // Uncomment iOS platform configuration yaml = yaml.replaceAllMapped( - RegExp(r'^# (\s*ios:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), - (match) => '${match[1]}${match[2]}${match[3]}', + RegExp( + r'^# (\s*ios:\s*\n)# (\s*pluginClass:\s*PdfrxPlugin\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', + multiLine: true, + ), + (match) => '${match[1]}${match[2]}${match[3]}${match[4]}', ); // Uncomment macOS platform configuration yaml = yaml.replaceAllMapped( - RegExp(r'^# (\s*macos:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), - (match) => '${match[1]}${match[2]}${match[3]}', + RegExp( + r'^# (\s*macos:\s*\n)# (\s*pluginClass:\s*PdfrxPlugin\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', + multiLine: true, + ), + (match) => '${match[1]}${match[2]}${match[3]}${match[4]}', ); return yaml; diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp index 815fc1a3..7b3b032d 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp +++ b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp @@ -1,3 +1,340 @@ // Relative import to be able to reuse the C sources. // See the comment in ../{projectName}}.podspec for more information. #include "../../../../src/pdfium_interop.cpp" + +#include +#include +#include +#include +#include + +// This function is used to keep the linker from stripping out the PDFium +// functions that are not directly referenced in this file. This is necessary +// because we are dynamically loading the functions at runtime. +extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() +{ +#define KEEP_FUNC(func) reinterpret_cast(#func), reinterpret_cast(func) + + static void const * const bindings[] = { + // File access functions + KEEP_FUNC(pdfrx_file_access_create), + KEEP_FUNC(pdfrx_file_access_destroy), + KEEP_FUNC(pdfrx_file_access_set_value), + + // PDFium functions + KEEP_FUNC(FPDF_InitLibraryWithConfig), + KEEP_FUNC(FPDF_InitLibrary), + KEEP_FUNC(FPDF_DestroyLibrary), + KEEP_FUNC(FPDF_SetSandBoxPolicy), + KEEP_FUNC(FPDF_LoadDocument), + KEEP_FUNC(FPDF_LoadMemDocument), + KEEP_FUNC(FPDF_LoadMemDocument64), + KEEP_FUNC(FPDF_LoadCustomDocument), + KEEP_FUNC(FPDF_GetFileVersion), + KEEP_FUNC(FPDF_GetLastError), + KEEP_FUNC(FPDF_DocumentHasValidCrossReferenceTable), + KEEP_FUNC(FPDF_GetTrailerEnds), + KEEP_FUNC(FPDF_GetDocPermissions), + KEEP_FUNC(FPDF_GetDocUserPermissions), + KEEP_FUNC(FPDF_GetSecurityHandlerRevision), + KEEP_FUNC(FPDF_GetPageCount), + KEEP_FUNC(FPDF_LoadPage), + KEEP_FUNC(FPDF_GetPageWidthF), + KEEP_FUNC(FPDF_GetPageWidth), + KEEP_FUNC(FPDF_GetPageHeightF), + KEEP_FUNC(FPDF_GetPageHeight), + KEEP_FUNC(FPDF_GetPageBoundingBox), + KEEP_FUNC(FPDF_GetPageSizeByIndexF), + KEEP_FUNC(FPDF_GetPageSizeByIndex), + KEEP_FUNC(FPDF_RenderPageBitmap), + KEEP_FUNC(FPDF_RenderPageBitmapWithMatrix), + KEEP_FUNC(FPDF_ClosePage), + KEEP_FUNC(FPDF_CloseDocument), + KEEP_FUNC(FPDF_DeviceToPage), + KEEP_FUNC(FPDF_PageToDevice), + KEEP_FUNC(FPDFBitmap_Create), + KEEP_FUNC(FPDFBitmap_CreateEx), + KEEP_FUNC(FPDFBitmap_GetFormat), + KEEP_FUNC(FPDFBitmap_FillRect), + KEEP_FUNC(FPDFBitmap_GetBuffer), + KEEP_FUNC(FPDFBitmap_GetWidth), + KEEP_FUNC(FPDFBitmap_GetHeight), + KEEP_FUNC(FPDFBitmap_GetStride), + KEEP_FUNC(FPDFBitmap_Destroy), + KEEP_FUNC(FPDF_VIEWERREF_GetPrintScaling), + KEEP_FUNC(FPDF_VIEWERREF_GetNumCopies), + KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRange), + KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRangeCount), + KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRangeElement), + KEEP_FUNC(FPDF_VIEWERREF_GetDuplex), + KEEP_FUNC(FPDF_VIEWERREF_GetName), + KEEP_FUNC(FPDF_CountNamedDests), + KEEP_FUNC(FPDF_GetNamedDestByName), + KEEP_FUNC(FPDF_GetNamedDest), + KEEP_FUNC(FPDF_GetXFAPacketCount), + KEEP_FUNC(FPDF_GetXFAPacketName), + KEEP_FUNC(FPDF_GetXFAPacketContent), + KEEP_FUNC(FPDFDOC_InitFormFillEnvironment), + KEEP_FUNC(FPDFDOC_ExitFormFillEnvironment), + KEEP_FUNC(FPDFPage_HasFormFieldAtPoint), + KEEP_FUNC(FPDFPage_FormFieldZOrderAtPoint), + KEEP_FUNC(FPDF_SetFormFieldHighlightColor), + KEEP_FUNC(FPDF_SetFormFieldHighlightAlpha), + KEEP_FUNC(FPDF_RemoveFormFieldHighlight), + KEEP_FUNC(FPDF_FFLDraw), + KEEP_FUNC(FPDF_GetFormType), + KEEP_FUNC(FPDF_LoadXFA), + KEEP_FUNC(FPDFAnnot_IsSupportedSubtype), + KEEP_FUNC(FPDFPage_CreateAnnot), + KEEP_FUNC(FPDFPage_GetAnnotCount), + KEEP_FUNC(FPDFPage_GetAnnot), + KEEP_FUNC(FPDFPage_GetAnnotIndex), + KEEP_FUNC(FPDFPage_CloseAnnot), + KEEP_FUNC(FPDFPage_RemoveAnnot), + KEEP_FUNC(FPDFAnnot_GetSubtype), + KEEP_FUNC(FPDFAnnot_IsObjectSupportedSubtype), + KEEP_FUNC(FPDFAnnot_UpdateObject), + KEEP_FUNC(FPDFAnnot_AddInkStroke), + KEEP_FUNC(FPDFAnnot_RemoveInkList), + KEEP_FUNC(FPDFAnnot_AppendObject), + KEEP_FUNC(FPDFAnnot_GetObjectCount), + KEEP_FUNC(FPDFAnnot_GetObject), + KEEP_FUNC(FPDFAnnot_RemoveObject), + KEEP_FUNC(FPDFAnnot_SetColor), + KEEP_FUNC(FPDFAnnot_GetColor), + KEEP_FUNC(FPDFAnnot_HasAttachmentPoints), + KEEP_FUNC(FPDFAnnot_SetAttachmentPoints), + KEEP_FUNC(FPDFAnnot_AppendAttachmentPoints), + KEEP_FUNC(FPDFAnnot_CountAttachmentPoints), + KEEP_FUNC(FPDFAnnot_GetAttachmentPoints), + KEEP_FUNC(FPDFAnnot_SetRect), + KEEP_FUNC(FPDFAnnot_GetRect), + KEEP_FUNC(FPDFAnnot_GetVertices), + KEEP_FUNC(FPDFAnnot_GetInkListCount), + KEEP_FUNC(FPDFAnnot_GetInkListPath), + KEEP_FUNC(FPDFAnnot_GetLine), + KEEP_FUNC(FPDFAnnot_SetBorder), + KEEP_FUNC(FPDFAnnot_GetBorder), + KEEP_FUNC(FPDFAnnot_GetFormAdditionalActionJavaScript), + KEEP_FUNC(FPDFAnnot_HasKey), + KEEP_FUNC(FPDFAnnot_GetValueType), + KEEP_FUNC(FPDFAnnot_SetStringValue), + KEEP_FUNC(FPDFAnnot_GetStringValue), + KEEP_FUNC(FPDFAnnot_GetNumberValue), + KEEP_FUNC(FPDFAnnot_SetAP), + KEEP_FUNC(FPDFAnnot_GetAP), + KEEP_FUNC(FPDFAnnot_GetLinkedAnnot), + KEEP_FUNC(FPDFAnnot_GetFlags), + KEEP_FUNC(FPDFAnnot_SetFlags), + KEEP_FUNC(FPDFAnnot_GetFormFieldFlags), + KEEP_FUNC(FPDFAnnot_SetFormFieldFlags), + KEEP_FUNC(FPDFAnnot_GetFormFieldAtPoint), + KEEP_FUNC(FPDFAnnot_GetFormFieldName), + KEEP_FUNC(FPDFAnnot_GetFormFieldAlternateName), + KEEP_FUNC(FPDFAnnot_GetFormFieldType), + KEEP_FUNC(FPDFAnnot_GetFormFieldValue), + KEEP_FUNC(FPDFAnnot_GetOptionCount), + KEEP_FUNC(FPDFAnnot_GetOptionLabel), + KEEP_FUNC(FPDFAnnot_IsOptionSelected), + KEEP_FUNC(FPDFAnnot_GetFontSize), + KEEP_FUNC(FPDFAnnot_SetFontColor), + KEEP_FUNC(FPDFAnnot_GetFontColor), + KEEP_FUNC(FPDFAnnot_IsChecked), + KEEP_FUNC(FPDFAnnot_SetFocusableSubtypes), + KEEP_FUNC(FPDFAnnot_GetFocusableSubtypesCount), + KEEP_FUNC(FPDFAnnot_GetFocusableSubtypes), + KEEP_FUNC(FPDFAnnot_GetLink), + KEEP_FUNC(FPDFAnnot_GetFormControlCount), + KEEP_FUNC(FPDFAnnot_GetFormControlIndex), + KEEP_FUNC(FPDFAnnot_GetFormFieldExportValue), + KEEP_FUNC(FPDFAnnot_SetURI), + KEEP_FUNC(FPDFAnnot_GetFileAttachment), + KEEP_FUNC(FPDFAnnot_AddFileAttachment), + KEEP_FUNC(FPDFText_LoadPage), + KEEP_FUNC(FPDFText_ClosePage), + KEEP_FUNC(FPDFText_CountChars), + KEEP_FUNC(FPDFText_GetUnicode), + KEEP_FUNC(FPDFText_GetTextObject), + KEEP_FUNC(FPDFText_IsGenerated), + KEEP_FUNC(FPDFText_IsHyphen), + KEEP_FUNC(FPDFText_HasUnicodeMapError), + KEEP_FUNC(FPDFText_GetFontSize), + KEEP_FUNC(FPDFText_GetFontInfo), + KEEP_FUNC(FPDFText_GetFontWeight), + KEEP_FUNC(FPDFText_GetFillColor), + KEEP_FUNC(FPDFText_GetStrokeColor), + KEEP_FUNC(FPDFText_GetCharAngle), + KEEP_FUNC(FPDFText_GetCharBox), + KEEP_FUNC(FPDFText_GetLooseCharBox), + KEEP_FUNC(FPDFText_GetMatrix), + KEEP_FUNC(FPDFText_GetCharOrigin), + KEEP_FUNC(FPDFText_GetCharIndexAtPos), + KEEP_FUNC(FPDFText_GetText), + KEEP_FUNC(FPDFText_CountRects), + KEEP_FUNC(FPDFText_GetRect), + KEEP_FUNC(FPDFText_GetBoundedText), + KEEP_FUNC(FPDFText_FindStart), + KEEP_FUNC(FPDFText_FindNext), + KEEP_FUNC(FPDFText_FindPrev), + KEEP_FUNC(FPDFText_GetSchResultIndex), + KEEP_FUNC(FPDFText_GetSchCount), + KEEP_FUNC(FPDFText_FindClose), + KEEP_FUNC(FPDFLink_LoadWebLinks), + KEEP_FUNC(FPDFLink_CountWebLinks), + KEEP_FUNC(FPDFLink_GetURL), + KEEP_FUNC(FPDFLink_CountRects), + KEEP_FUNC(FPDFLink_GetRect), + KEEP_FUNC(FPDFLink_GetTextRange), + KEEP_FUNC(FPDFLink_CloseWebLinks), + KEEP_FUNC(FPDFBookmark_GetFirstChild), + KEEP_FUNC(FPDFBookmark_GetNextSibling), + KEEP_FUNC(FPDFBookmark_GetTitle), + KEEP_FUNC(FPDFBookmark_GetCount), + KEEP_FUNC(FPDFBookmark_Find), + KEEP_FUNC(FPDFBookmark_GetDest), + KEEP_FUNC(FPDFBookmark_GetAction), + KEEP_FUNC(FPDFAction_GetType), + KEEP_FUNC(FPDFAction_GetDest), + KEEP_FUNC(FPDFAction_GetFilePath), + KEEP_FUNC(FPDFAction_GetURIPath), + KEEP_FUNC(FPDFDest_GetDestPageIndex), + KEEP_FUNC(FPDFDest_GetView), + KEEP_FUNC(FPDFDest_GetLocationInPage), + KEEP_FUNC(FPDFLink_GetLinkAtPoint), + KEEP_FUNC(FPDFLink_GetLinkZOrderAtPoint), + KEEP_FUNC(FPDFLink_GetDest), + KEEP_FUNC(FPDFLink_GetAction), + KEEP_FUNC(FPDFLink_Enumerate), + KEEP_FUNC(FPDFLink_GetAnnot), + KEEP_FUNC(FPDFLink_GetAnnotRect), + KEEP_FUNC(FPDFLink_CountQuadPoints), + KEEP_FUNC(FPDFLink_GetQuadPoints), + KEEP_FUNC(FPDF_GetPageAAction), + KEEP_FUNC(FPDF_GetFileIdentifier), + KEEP_FUNC(FPDF_GetMetaText), + KEEP_FUNC(FPDF_GetPageLabel), + KEEP_FUNC(FPDF_CreateNewDocument), + KEEP_FUNC(FPDFPage_New), + KEEP_FUNC(FPDFPage_Delete), + KEEP_FUNC(FPDF_MovePages), + KEEP_FUNC(FPDFPage_GetRotation), + KEEP_FUNC(FPDFPage_SetRotation), + KEEP_FUNC(FPDFPage_InsertObject), + KEEP_FUNC(FPDFPage_RemoveObject), + KEEP_FUNC(FPDFPage_CountObjects), + KEEP_FUNC(FPDFPage_GetObject), + KEEP_FUNC(FPDFPage_HasTransparency), + KEEP_FUNC(FPDFPage_GenerateContent), + KEEP_FUNC(FPDFPageObj_Destroy), + KEEP_FUNC(FPDFPageObj_HasTransparency), + KEEP_FUNC(FPDFPageObj_GetType), + KEEP_FUNC(FPDFPageObj_GetIsActive), + KEEP_FUNC(FPDFPageObj_SetIsActive), + KEEP_FUNC(FPDFPageObj_Transform), + KEEP_FUNC(FPDFPageObj_TransformF), + KEEP_FUNC(FPDFPageObj_GetMatrix), + KEEP_FUNC(FPDFPageObj_SetMatrix), + KEEP_FUNC(FPDFPage_TransformAnnots), + KEEP_FUNC(FPDFPageObj_NewImageObj), + KEEP_FUNC(FPDFPageObj_GetMarkedContentID), + KEEP_FUNC(FPDFPageObj_CountMarks), + KEEP_FUNC(FPDFPageObj_GetMark), + KEEP_FUNC(FPDFPageObj_AddMark), + KEEP_FUNC(FPDFPageObj_RemoveMark), + KEEP_FUNC(FPDFPageObjMark_GetName), + KEEP_FUNC(FPDFPageObjMark_CountParams), + KEEP_FUNC(FPDFPageObjMark_GetParamKey), + KEEP_FUNC(FPDFPageObjMark_GetParamValueType), + KEEP_FUNC(FPDFPageObjMark_GetParamIntValue), + KEEP_FUNC(FPDFPageObjMark_GetParamStringValue), + KEEP_FUNC(FPDFPageObjMark_GetParamBlobValue), + KEEP_FUNC(FPDFPageObjMark_SetIntParam), + KEEP_FUNC(FPDFPageObjMark_SetStringParam), + KEEP_FUNC(FPDFPageObjMark_SetBlobParam), + KEEP_FUNC(FPDFPageObjMark_RemoveParam), + KEEP_FUNC(FPDFImageObj_LoadJpegFile), + KEEP_FUNC(FPDFImageObj_LoadJpegFileInline), + KEEP_FUNC(FPDFImageObj_SetMatrix), + KEEP_FUNC(FPDFImageObj_SetBitmap), + KEEP_FUNC(FPDFImageObj_GetBitmap), + KEEP_FUNC(FPDFImageObj_GetRenderedBitmap), + KEEP_FUNC(FPDFImageObj_GetImageDataDecoded), + KEEP_FUNC(FPDFImageObj_GetImageDataRaw), + KEEP_FUNC(FPDFImageObj_GetImageFilterCount), + KEEP_FUNC(FPDFImageObj_GetImageFilter), + KEEP_FUNC(FPDFImageObj_GetImageMetadata), + KEEP_FUNC(FPDFImageObj_GetImagePixelSize), + KEEP_FUNC(FPDFImageObj_GetIccProfileDataDecoded), + KEEP_FUNC(FPDFPageObj_CreateNewPath), + KEEP_FUNC(FPDFPageObj_CreateNewRect), + KEEP_FUNC(FPDFPageObj_GetBounds), + KEEP_FUNC(FPDFPageObj_GetRotatedBounds), + KEEP_FUNC(FPDFPageObj_SetBlendMode), + KEEP_FUNC(FPDFPageObj_SetStrokeColor), + KEEP_FUNC(FPDFPageObj_GetStrokeColor), + KEEP_FUNC(FPDFPageObj_SetStrokeWidth), + KEEP_FUNC(FPDFPageObj_GetStrokeWidth), + KEEP_FUNC(FPDFPageObj_GetLineJoin), + KEEP_FUNC(FPDFPageObj_SetLineJoin), + KEEP_FUNC(FPDFPageObj_GetLineCap), + KEEP_FUNC(FPDFPageObj_SetLineCap), + KEEP_FUNC(FPDFPageObj_SetFillColor), + KEEP_FUNC(FPDFPageObj_GetFillColor), + KEEP_FUNC(FPDFPageObj_GetDashPhase), + KEEP_FUNC(FPDFPageObj_SetDashPhase), + KEEP_FUNC(FPDFPageObj_GetDashCount), + KEEP_FUNC(FPDFPageObj_GetDashArray), + KEEP_FUNC(FPDFPageObj_SetDashArray), + KEEP_FUNC(FPDFPath_CountSegments), + KEEP_FUNC(FPDFPath_GetPathSegment), + KEEP_FUNC(FPDFPathSegment_GetPoint), + KEEP_FUNC(FPDFPathSegment_GetType), + KEEP_FUNC(FPDFPathSegment_GetClose), + KEEP_FUNC(FPDFPath_MoveTo), + KEEP_FUNC(FPDFPath_LineTo), + KEEP_FUNC(FPDFPath_BezierTo), + KEEP_FUNC(FPDFPath_Close), + KEEP_FUNC(FPDFPath_SetDrawMode), + KEEP_FUNC(FPDFPath_GetDrawMode), + KEEP_FUNC(FPDFPageObj_NewTextObj), + KEEP_FUNC(FPDFText_SetText), + KEEP_FUNC(FPDFText_SetCharcodes), + KEEP_FUNC(FPDFText_LoadFont), + KEEP_FUNC(FPDFText_LoadStandardFont), + KEEP_FUNC(FPDFText_LoadCidType2Font), + KEEP_FUNC(FPDFTextObj_GetFontSize), + KEEP_FUNC(FPDFFont_Close), + KEEP_FUNC(FPDFPageObj_CreateTextObj), + KEEP_FUNC(FPDFTextObj_GetTextRenderMode), + KEEP_FUNC(FPDFTextObj_SetTextRenderMode), + KEEP_FUNC(FPDFTextObj_GetText), + KEEP_FUNC(FPDFTextObj_GetRenderedBitmap), + KEEP_FUNC(FPDFTextObj_GetFont), + KEEP_FUNC(FPDFFont_GetBaseFontName), + KEEP_FUNC(FPDFFont_GetFamilyName), + KEEP_FUNC(FPDFFont_GetFontData), + KEEP_FUNC(FPDFFont_GetIsEmbedded), + KEEP_FUNC(FPDFFont_GetFlags), + KEEP_FUNC(FPDFFont_GetWeight), + KEEP_FUNC(FPDFFont_GetItalicAngle), + KEEP_FUNC(FPDFFont_GetAscent), + KEEP_FUNC(FPDFFont_GetDescent), + KEEP_FUNC(FPDFFont_GetGlyphWidth), + KEEP_FUNC(FPDFFont_GetGlyphPath), + KEEP_FUNC(FPDFGlyphPath_CountGlyphSegments), + KEEP_FUNC(FPDFGlyphPath_GetGlyphPathSegment), + KEEP_FUNC(FPDFFormObj_CountObjects), + KEEP_FUNC(FPDFFormObj_GetObject), + KEEP_FUNC(FPDFFormObj_RemoveObject), + KEEP_FUNC(FPDF_GetDefaultTTFMap), + KEEP_FUNC(FPDF_GetDefaultTTFMapCount), + KEEP_FUNC(FPDF_GetDefaultTTFMapEntry), + KEEP_FUNC(FPDF_AddInstalledFont), + KEEP_FUNC(FPDF_SetSystemFontInfo), + KEEP_FUNC(FPDF_GetDefaultSystemFontInfo), + KEEP_FUNC(FPDF_FreeDefaultSystemFontInfo), + + nullptr, nullptr // End marker + }; + return bindings; +} diff --git a/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift b/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift index 1831d86f..848e6873 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift +++ b/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift @@ -13,8 +13,58 @@ func pdfrx_binding() -> UnsafePointer /// The PdfrxPlugin class that is used to keep PDFium exports alive. public class PdfrxPlugin: NSObject, FlutterPlugin { - public static func register(with _: FlutterPluginRegistrar) { - // NOTE: Call the function to ensure symbols are kept alive - _ = pdfrx_binding() + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "pdfrx", + binaryMessenger: registrar.pdfrxMessenger + ) + let instance = PdfrxPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "loadBindings": + loadBindings(arguments: call.arguments, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + private func loadBindings(arguments _: Any?, result: @escaping FlutterResult) { + let unsafeBindings = pdfrx_binding() + var bindings: [String: Int64] = [:] + var index = 0 + + while true { + let namePtr = unsafeBindings[index] + let funcPtr = unsafeBindings[index + 1] + + // Check for end marker (nullptr, nullptr) + if namePtr == nil || funcPtr == nil { + break + } + + let functionName = String(cString: namePtr!.assumingMemoryBound(to: CChar.self)) + let functionAddress = Int64(Int(bitPattern: funcPtr!)) + bindings[functionName] = functionAddress + + index += 2 + } + + result(bindings) + } +} + +private extension FlutterPluginRegistrar { + #if os(iOS) + var pdfrxMessenger: FlutterBinaryMessenger { + messenger() + } + + #elseif os(macOS) + var pdfrxMessenger: FlutterBinaryMessenger { + messenger + } + #endif } diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 8c86de35..c3dcb254 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,41 +1,15 @@ PODS: - - file_selector_ios (0.0.1): - - Flutter - Flutter (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.0.11): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter DEPENDENCIES: - - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: - file_selector_ios: - :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - pdfrx: - :path: ".symlinks/plugins/pdfrx/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index d91ad2c9..96fca8b5 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - E3321798E1D30E12796E6D41 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -354,23 +353,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - E3321798E1D30E12796E6D41 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index 1cbc1c57..ba8561bc 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -133,12 +133,15 @@ class PdfDocumentRefAsset extends PdfDocumentRef { final bool useProgressiveLoading; @override - Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openAsset( - name, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) async { + await pdfrxFlutterInitialize(); + return await PdfDocument.openAsset( + name, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + ); + } } /// A [PdfDocumentRef] that loads the document from network. @@ -175,16 +178,19 @@ class PdfDocumentRefUri extends PdfDocumentRef { final bool useProgressiveLoading; @override - Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openUri( - uri, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - progressCallback: progressCallback, - preferRangeAccess: preferRangeAccess, - headers: headers, - withCredentials: withCredentials, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) async { + await pdfrxFlutterInitialize(); + return await PdfDocument.openUri( + uri, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + progressCallback: progressCallback, + preferRangeAccess: preferRangeAccess, + headers: headers, + withCredentials: withCredentials, + ); + } } /// A [PdfDocumentRef] that loads the document from file. @@ -208,12 +214,15 @@ class PdfDocumentRefFile extends PdfDocumentRef { final bool useProgressiveLoading; @override - Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openFile( - file, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) async { + await pdfrxFlutterInitialize(); + return await PdfDocument.openFile( + file, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + ); + } } /// A [PdfDocumentRef] that loads the document from data. @@ -244,15 +253,18 @@ class PdfDocumentRefData extends PdfDocumentRef { final bool useProgressiveLoading; @override - Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openData( - data, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - sourceName: key.sourceName, - allowDataOwnershipTransfer: allowDataOwnershipTransfer, - onDispose: onDispose, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) async { + await pdfrxFlutterInitialize(); + return await PdfDocument.openData( + data, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + sourceName: key.sourceName, + allowDataOwnershipTransfer: allowDataOwnershipTransfer, + onDispose: onDispose, + ); + } } /// A [PdfDocumentRef] that loads the document from custom source. @@ -283,16 +295,19 @@ class PdfDocumentRefCustom extends PdfDocumentRef { final bool useProgressiveLoading; @override - Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => PdfDocument.openCustom( - read: read, - fileSize: fileSize, - sourceName: key.sourceName, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, - onDispose: onDispose, - ); + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) async { + await pdfrxFlutterInitialize(); + return await PdfDocument.openCustom( + read: read, + fileSize: fileSize, + sourceName: key.sourceName, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, + onDispose: onDispose, + ); + } } /// A [PdfDocumentRef] that directly contains [PdfDocument]. diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 4ad6fcda..52a75517 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -21,7 +21,7 @@ bool _isInitialized = false; /// /// The function shows PDFium WASM module warnings in debug mode by default. /// You can disable these warnings by setting [dismissPdfiumWasmWarnings] to true. -void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { +Future pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) async { if (_isInitialized) return; WidgetsFlutterBinding.ensureInitialized(); @@ -36,8 +36,6 @@ void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { }; Pdfrx.getCacheDirectory ??= getCacheDirectory; - platformInitialize(); - // Checking pdfium.wasm availability for Web and debug builds. if (kDebugMode && !dismissPdfiumWasmWarnings) { () async { @@ -64,7 +62,7 @@ void pdfrxFlutterInitialize({bool dismissPdfiumWasmWarnings = false}) { } /// NOTE: it's actually async, but hopefully, it finishes quickly... - PdfrxEntryFunctions.instance.init(); + await platformInitialize(); _isInitialized = true; } diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index 33d25b50..af6c5853 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,7 +1,10 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; +// ignore: implementation_imports +import 'package:pdfrx_engine/src/native/apple_direct_lookup.dart'; import '../../../pdfrx.dart'; @@ -32,7 +35,19 @@ PdfrxEntryFunctions? get pdfrxEntryFunctionsOverride => null; /// Initializes the Pdfrx library for native platforms. /// /// This function is here to maintain a consistent API with web and other platforms. -void platformInitialize() {} +Future platformInitialize() async { + if (PdfrxEntryFunctions.instance.backend == PdfrxBackend.pdfium && isApple) { + await _enableAppleDirectBindings(); + } + await PdfrxEntryFunctions.instance.init(); +} + +Future _enableAppleDirectBindings() async { + debugPrint('pdfrx: Enabling direct bindings for iOS/macOS platforms...'); + final channel = MethodChannel('pdfrx'); + Pdfrx.pdfiumNativeBindings = (await channel.invokeMethod('loadBindings') as Map).cast(); + setupAppleDirectLookupIfApplicable(); +} /// Reports focus changes for the Web platform to handle right-click context menus. /// diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index fed78f2d..66b96fb3 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -38,7 +38,7 @@ final _focusObject = {}; /// /// For Web, this function currently setup "contextmenu" event listener to prevent the default context menu from /// appearing on right-click. -void platformInitialize() { +Future platformInitialize() async { web.document.addEventListener( 'contextmenu', ((web.Event event) { @@ -51,6 +51,7 @@ void platformInitialize() { } }).toJS, ); + await PdfrxEntryFunctions.instance.init(); } /// Reports focus changes for the Web platform to handle right-click context menus. diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 139a060a..b45cbff8 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -317,6 +317,9 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { await init(); await _sendCommand('clearAllFontData', parameters: {'dummy': true}); } + + @override + PdfrxBackend get backend => PdfrxBackend.pdfiumWasm; } class _PdfDocumentWasm extends PdfDocument { diff --git a/packages/pdfrx/src/pdfium_interop.cpp b/packages/pdfrx/src/pdfium_interop.cpp index e57fbffc..57cbdbae 100644 --- a/packages/pdfrx/src/pdfium_interop.cpp +++ b/packages/pdfrx/src/pdfium_interop.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -66,338 +65,3 @@ extern "C" PDFRX_EXPORT void PDFRX_INTEROP_API pdfrx_file_access_set_value(pdfrx fileAccess->retValue = retValue; fileAccess->cond.notify_one(); } - -#if defined(__APPLE__) -#include -#include -#include -#include -#include - -// This function is used to keep the linker from stripping out the PDFium -// functions that are not directly referenced in this file. This is necessary -// because we are dynamically loading the functions at runtime. -extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() -{ - static const void *bindings[] = { - // File access functions - reinterpret_cast(pdfrx_file_access_create), - reinterpret_cast(pdfrx_file_access_destroy), - reinterpret_cast(pdfrx_file_access_set_value), - - // PDFium functions - reinterpret_cast(FPDF_InitLibraryWithConfig), - reinterpret_cast(FPDF_InitLibrary), - reinterpret_cast(FPDF_DestroyLibrary), - reinterpret_cast(FPDF_SetSandBoxPolicy), - reinterpret_cast(FPDF_LoadDocument), - reinterpret_cast(FPDF_LoadMemDocument), - reinterpret_cast(FPDF_LoadMemDocument64), - reinterpret_cast(FPDF_LoadCustomDocument), - reinterpret_cast(FPDF_GetFileVersion), - reinterpret_cast(FPDF_GetLastError), - reinterpret_cast(FPDF_DocumentHasValidCrossReferenceTable), - reinterpret_cast(FPDF_GetTrailerEnds), - reinterpret_cast(FPDF_GetDocPermissions), - reinterpret_cast(FPDF_GetDocUserPermissions), - reinterpret_cast(FPDF_GetSecurityHandlerRevision), - reinterpret_cast(FPDF_GetPageCount), - reinterpret_cast(FPDF_LoadPage), - reinterpret_cast(FPDF_GetPageWidthF), - reinterpret_cast(FPDF_GetPageWidth), - reinterpret_cast(FPDF_GetPageHeightF), - reinterpret_cast(FPDF_GetPageHeight), - reinterpret_cast(FPDF_GetPageBoundingBox), - reinterpret_cast(FPDF_GetPageSizeByIndexF), - reinterpret_cast(FPDF_GetPageSizeByIndex), - reinterpret_cast(FPDF_RenderPageBitmap), - reinterpret_cast(FPDF_RenderPageBitmapWithMatrix), - reinterpret_cast(FPDF_ClosePage), - reinterpret_cast(FPDF_CloseDocument), - reinterpret_cast(FPDF_DeviceToPage), - reinterpret_cast(FPDF_PageToDevice), - reinterpret_cast(FPDFBitmap_Create), - reinterpret_cast(FPDFBitmap_CreateEx), - reinterpret_cast(FPDFBitmap_GetFormat), - reinterpret_cast(FPDFBitmap_FillRect), - reinterpret_cast(FPDFBitmap_GetBuffer), - reinterpret_cast(FPDFBitmap_GetWidth), - reinterpret_cast(FPDFBitmap_GetHeight), - reinterpret_cast(FPDFBitmap_GetStride), - reinterpret_cast(FPDFBitmap_Destroy), - reinterpret_cast(FPDF_VIEWERREF_GetPrintScaling), - reinterpret_cast(FPDF_VIEWERREF_GetNumCopies), - reinterpret_cast(FPDF_VIEWERREF_GetPrintPageRange), - reinterpret_cast(FPDF_VIEWERREF_GetPrintPageRangeCount), - reinterpret_cast(FPDF_VIEWERREF_GetPrintPageRangeElement), - reinterpret_cast(FPDF_VIEWERREF_GetDuplex), - reinterpret_cast(FPDF_VIEWERREF_GetName), - reinterpret_cast(FPDF_CountNamedDests), - reinterpret_cast(FPDF_GetNamedDestByName), - reinterpret_cast(FPDF_GetNamedDest), - reinterpret_cast(FPDF_GetXFAPacketCount), - reinterpret_cast(FPDF_GetXFAPacketName), - reinterpret_cast(FPDF_GetXFAPacketContent), - reinterpret_cast(FPDFDOC_InitFormFillEnvironment), - reinterpret_cast(FPDFDOC_ExitFormFillEnvironment), - reinterpret_cast(FPDFPage_HasFormFieldAtPoint), - reinterpret_cast(FPDFPage_FormFieldZOrderAtPoint), - reinterpret_cast(FPDF_SetFormFieldHighlightColor), - reinterpret_cast(FPDF_SetFormFieldHighlightAlpha), - reinterpret_cast(FPDF_RemoveFormFieldHighlight), - reinterpret_cast(FPDF_FFLDraw), - reinterpret_cast(FPDF_GetFormType), - reinterpret_cast(FPDF_LoadXFA), - reinterpret_cast(FPDFAnnot_IsSupportedSubtype), - reinterpret_cast(FPDFPage_CreateAnnot), - reinterpret_cast(FPDFPage_GetAnnotCount), - reinterpret_cast(FPDFPage_GetAnnot), - reinterpret_cast(FPDFPage_GetAnnotIndex), - reinterpret_cast(FPDFPage_CloseAnnot), - reinterpret_cast(FPDFPage_RemoveAnnot), - reinterpret_cast(FPDFAnnot_GetSubtype), - reinterpret_cast(FPDFAnnot_IsObjectSupportedSubtype), - reinterpret_cast(FPDFAnnot_UpdateObject), - reinterpret_cast(FPDFAnnot_AddInkStroke), - reinterpret_cast(FPDFAnnot_RemoveInkList), - reinterpret_cast(FPDFAnnot_AppendObject), - reinterpret_cast(FPDFAnnot_GetObjectCount), - reinterpret_cast(FPDFAnnot_GetObject), - reinterpret_cast(FPDFAnnot_RemoveObject), - reinterpret_cast(FPDFAnnot_SetColor), - reinterpret_cast(FPDFAnnot_GetColor), - reinterpret_cast(FPDFAnnot_HasAttachmentPoints), - reinterpret_cast(FPDFAnnot_SetAttachmentPoints), - reinterpret_cast(FPDFAnnot_AppendAttachmentPoints), - reinterpret_cast(FPDFAnnot_CountAttachmentPoints), - reinterpret_cast(FPDFAnnot_GetAttachmentPoints), - reinterpret_cast(FPDFAnnot_SetRect), - reinterpret_cast(FPDFAnnot_GetRect), - reinterpret_cast(FPDFAnnot_GetVertices), - reinterpret_cast(FPDFAnnot_GetInkListCount), - reinterpret_cast(FPDFAnnot_GetInkListPath), - reinterpret_cast(FPDFAnnot_GetLine), - reinterpret_cast(FPDFAnnot_SetBorder), - reinterpret_cast(FPDFAnnot_GetBorder), - reinterpret_cast(FPDFAnnot_GetFormAdditionalActionJavaScript), - reinterpret_cast(FPDFAnnot_HasKey), - reinterpret_cast(FPDFAnnot_GetValueType), - reinterpret_cast(FPDFAnnot_SetStringValue), - reinterpret_cast(FPDFAnnot_GetStringValue), - reinterpret_cast(FPDFAnnot_GetNumberValue), - reinterpret_cast(FPDFAnnot_SetAP), - reinterpret_cast(FPDFAnnot_GetAP), - reinterpret_cast(FPDFAnnot_GetLinkedAnnot), - reinterpret_cast(FPDFAnnot_GetFlags), - reinterpret_cast(FPDFAnnot_SetFlags), - reinterpret_cast(FPDFAnnot_GetFormFieldFlags), - reinterpret_cast(FPDFAnnot_SetFormFieldFlags), - reinterpret_cast(FPDFAnnot_GetFormFieldAtPoint), - reinterpret_cast(FPDFAnnot_GetFormFieldName), - reinterpret_cast(FPDFAnnot_GetFormFieldAlternateName), - reinterpret_cast(FPDFAnnot_GetFormFieldType), - reinterpret_cast(FPDFAnnot_GetFormFieldValue), - reinterpret_cast(FPDFAnnot_GetOptionCount), - reinterpret_cast(FPDFAnnot_GetOptionLabel), - reinterpret_cast(FPDFAnnot_IsOptionSelected), - reinterpret_cast(FPDFAnnot_GetFontSize), - reinterpret_cast(FPDFAnnot_SetFontColor), - reinterpret_cast(FPDFAnnot_GetFontColor), - reinterpret_cast(FPDFAnnot_IsChecked), - reinterpret_cast(FPDFAnnot_SetFocusableSubtypes), - reinterpret_cast(FPDFAnnot_GetFocusableSubtypesCount), - reinterpret_cast(FPDFAnnot_GetFocusableSubtypes), - reinterpret_cast(FPDFAnnot_GetLink), - reinterpret_cast(FPDFAnnot_GetFormControlCount), - reinterpret_cast(FPDFAnnot_GetFormControlIndex), - reinterpret_cast(FPDFAnnot_GetFormFieldExportValue), - reinterpret_cast(FPDFAnnot_SetURI), - reinterpret_cast(FPDFAnnot_GetFileAttachment), - reinterpret_cast(FPDFAnnot_AddFileAttachment), - reinterpret_cast(FPDFText_LoadPage), - reinterpret_cast(FPDFText_ClosePage), - reinterpret_cast(FPDFText_CountChars), - reinterpret_cast(FPDFText_GetUnicode), - reinterpret_cast(FPDFText_GetTextObject), - reinterpret_cast(FPDFText_IsGenerated), - reinterpret_cast(FPDFText_IsHyphen), - reinterpret_cast(FPDFText_HasUnicodeMapError), - reinterpret_cast(FPDFText_GetFontSize), - reinterpret_cast(FPDFText_GetFontInfo), - reinterpret_cast(FPDFText_GetFontWeight), - reinterpret_cast(FPDFText_GetFillColor), - reinterpret_cast(FPDFText_GetStrokeColor), - reinterpret_cast(FPDFText_GetCharAngle), - reinterpret_cast(FPDFText_GetCharBox), - reinterpret_cast(FPDFText_GetLooseCharBox), - reinterpret_cast(FPDFText_GetMatrix), - reinterpret_cast(FPDFText_GetCharOrigin), - reinterpret_cast(FPDFText_GetCharIndexAtPos), - reinterpret_cast(FPDFText_GetText), - reinterpret_cast(FPDFText_CountRects), - reinterpret_cast(FPDFText_GetRect), - reinterpret_cast(FPDFText_GetBoundedText), - reinterpret_cast(FPDFText_FindStart), - reinterpret_cast(FPDFText_FindNext), - reinterpret_cast(FPDFText_FindPrev), - reinterpret_cast(FPDFText_GetSchResultIndex), - reinterpret_cast(FPDFText_GetSchCount), - reinterpret_cast(FPDFText_FindClose), - reinterpret_cast(FPDFLink_LoadWebLinks), - reinterpret_cast(FPDFLink_CountWebLinks), - reinterpret_cast(FPDFLink_GetURL), - reinterpret_cast(FPDFLink_CountRects), - reinterpret_cast(FPDFLink_GetRect), - reinterpret_cast(FPDFLink_GetTextRange), - reinterpret_cast(FPDFLink_CloseWebLinks), - reinterpret_cast(FPDFBookmark_GetFirstChild), - reinterpret_cast(FPDFBookmark_GetNextSibling), - reinterpret_cast(FPDFBookmark_GetTitle), - reinterpret_cast(FPDFBookmark_GetCount), - reinterpret_cast(FPDFBookmark_Find), - reinterpret_cast(FPDFBookmark_GetDest), - reinterpret_cast(FPDFBookmark_GetAction), - reinterpret_cast(FPDFAction_GetType), - reinterpret_cast(FPDFAction_GetDest), - reinterpret_cast(FPDFAction_GetFilePath), - reinterpret_cast(FPDFAction_GetURIPath), - reinterpret_cast(FPDFDest_GetDestPageIndex), - reinterpret_cast(FPDFDest_GetView), - reinterpret_cast(FPDFDest_GetLocationInPage), - reinterpret_cast(FPDFLink_GetLinkAtPoint), - reinterpret_cast(FPDFLink_GetLinkZOrderAtPoint), - reinterpret_cast(FPDFLink_GetDest), - reinterpret_cast(FPDFLink_GetAction), - reinterpret_cast(FPDFLink_Enumerate), - reinterpret_cast(FPDFLink_GetAnnot), - reinterpret_cast(FPDFLink_GetAnnotRect), - reinterpret_cast(FPDFLink_CountQuadPoints), - reinterpret_cast(FPDFLink_GetQuadPoints), - reinterpret_cast(FPDF_GetPageAAction), - reinterpret_cast(FPDF_GetFileIdentifier), - reinterpret_cast(FPDF_GetMetaText), - reinterpret_cast(FPDF_GetPageLabel), - reinterpret_cast(FPDF_CreateNewDocument), - reinterpret_cast(FPDFPage_New), - reinterpret_cast(FPDFPage_Delete), - reinterpret_cast(FPDF_MovePages), - reinterpret_cast(FPDFPage_GetRotation), - reinterpret_cast(FPDFPage_SetRotation), - reinterpret_cast(FPDFPage_InsertObject), - reinterpret_cast(FPDFPage_RemoveObject), - reinterpret_cast(FPDFPage_CountObjects), - reinterpret_cast(FPDFPage_GetObject), - reinterpret_cast(FPDFPage_HasTransparency), - reinterpret_cast(FPDFPage_GenerateContent), - reinterpret_cast(FPDFPageObj_Destroy), - reinterpret_cast(FPDFPageObj_HasTransparency), - reinterpret_cast(FPDFPageObj_GetType), - reinterpret_cast(FPDFPageObj_GetIsActive), - reinterpret_cast(FPDFPageObj_SetIsActive), - reinterpret_cast(FPDFPageObj_Transform), - reinterpret_cast(FPDFPageObj_TransformF), - reinterpret_cast(FPDFPageObj_GetMatrix), - reinterpret_cast(FPDFPageObj_SetMatrix), - reinterpret_cast(FPDFPage_TransformAnnots), - reinterpret_cast(FPDFPageObj_NewImageObj), - reinterpret_cast(FPDFPageObj_GetMarkedContentID), - reinterpret_cast(FPDFPageObj_CountMarks), - reinterpret_cast(FPDFPageObj_GetMark), - reinterpret_cast(FPDFPageObj_AddMark), - reinterpret_cast(FPDFPageObj_RemoveMark), - reinterpret_cast(FPDFPageObjMark_GetName), - reinterpret_cast(FPDFPageObjMark_CountParams), - reinterpret_cast(FPDFPageObjMark_GetParamKey), - reinterpret_cast(FPDFPageObjMark_GetParamValueType), - reinterpret_cast(FPDFPageObjMark_GetParamIntValue), - reinterpret_cast(FPDFPageObjMark_GetParamStringValue), - reinterpret_cast(FPDFPageObjMark_GetParamBlobValue), - reinterpret_cast(FPDFPageObjMark_SetIntParam), - reinterpret_cast(FPDFPageObjMark_SetStringParam), - reinterpret_cast(FPDFPageObjMark_SetBlobParam), - reinterpret_cast(FPDFPageObjMark_RemoveParam), - reinterpret_cast(FPDFImageObj_LoadJpegFile), - reinterpret_cast(FPDFImageObj_LoadJpegFileInline), - reinterpret_cast(FPDFImageObj_SetMatrix), - reinterpret_cast(FPDFImageObj_SetBitmap), - reinterpret_cast(FPDFImageObj_GetBitmap), - reinterpret_cast(FPDFImageObj_GetRenderedBitmap), - reinterpret_cast(FPDFImageObj_GetImageDataDecoded), - reinterpret_cast(FPDFImageObj_GetImageDataRaw), - reinterpret_cast(FPDFImageObj_GetImageFilterCount), - reinterpret_cast(FPDFImageObj_GetImageFilter), - reinterpret_cast(FPDFImageObj_GetImageMetadata), - reinterpret_cast(FPDFImageObj_GetImagePixelSize), - reinterpret_cast(FPDFImageObj_GetIccProfileDataDecoded), - reinterpret_cast(FPDFPageObj_CreateNewPath), - reinterpret_cast(FPDFPageObj_CreateNewRect), - reinterpret_cast(FPDFPageObj_GetBounds), - reinterpret_cast(FPDFPageObj_GetRotatedBounds), - reinterpret_cast(FPDFPageObj_SetBlendMode), - reinterpret_cast(FPDFPageObj_SetStrokeColor), - reinterpret_cast(FPDFPageObj_GetStrokeColor), - reinterpret_cast(FPDFPageObj_SetStrokeWidth), - reinterpret_cast(FPDFPageObj_GetStrokeWidth), - reinterpret_cast(FPDFPageObj_GetLineJoin), - reinterpret_cast(FPDFPageObj_SetLineJoin), - reinterpret_cast(FPDFPageObj_GetLineCap), - reinterpret_cast(FPDFPageObj_SetLineCap), - reinterpret_cast(FPDFPageObj_SetFillColor), - reinterpret_cast(FPDFPageObj_GetFillColor), - reinterpret_cast(FPDFPageObj_GetDashPhase), - reinterpret_cast(FPDFPageObj_SetDashPhase), - reinterpret_cast(FPDFPageObj_GetDashCount), - reinterpret_cast(FPDFPageObj_GetDashArray), - reinterpret_cast(FPDFPageObj_SetDashArray), - reinterpret_cast(FPDFPath_CountSegments), - reinterpret_cast(FPDFPath_GetPathSegment), - reinterpret_cast(FPDFPathSegment_GetPoint), - reinterpret_cast(FPDFPathSegment_GetType), - reinterpret_cast(FPDFPathSegment_GetClose), - reinterpret_cast(FPDFPath_MoveTo), - reinterpret_cast(FPDFPath_LineTo), - reinterpret_cast(FPDFPath_BezierTo), - reinterpret_cast(FPDFPath_Close), - reinterpret_cast(FPDFPath_SetDrawMode), - reinterpret_cast(FPDFPath_GetDrawMode), - reinterpret_cast(FPDFPageObj_NewTextObj), - reinterpret_cast(FPDFText_SetText), - reinterpret_cast(FPDFText_SetCharcodes), - reinterpret_cast(FPDFText_LoadFont), - reinterpret_cast(FPDFText_LoadStandardFont), - reinterpret_cast(FPDFText_LoadCidType2Font), - reinterpret_cast(FPDFTextObj_GetFontSize), - reinterpret_cast(FPDFFont_Close), - reinterpret_cast(FPDFPageObj_CreateTextObj), - reinterpret_cast(FPDFTextObj_GetTextRenderMode), - reinterpret_cast(FPDFTextObj_SetTextRenderMode), - reinterpret_cast(FPDFTextObj_GetText), - reinterpret_cast(FPDFTextObj_GetRenderedBitmap), - reinterpret_cast(FPDFTextObj_GetFont), - reinterpret_cast(FPDFFont_GetBaseFontName), - reinterpret_cast(FPDFFont_GetFamilyName), - reinterpret_cast(FPDFFont_GetFontData), - reinterpret_cast(FPDFFont_GetIsEmbedded), - reinterpret_cast(FPDFFont_GetFlags), - reinterpret_cast(FPDFFont_GetWeight), - reinterpret_cast(FPDFFont_GetItalicAngle), - reinterpret_cast(FPDFFont_GetAscent), - reinterpret_cast(FPDFFont_GetDescent), - reinterpret_cast(FPDFFont_GetGlyphWidth), - reinterpret_cast(FPDFFont_GetGlyphPath), - reinterpret_cast(FPDFGlyphPath_CountGlyphSegments), - reinterpret_cast(FPDFGlyphPath_GetGlyphPathSegment), - reinterpret_cast(FPDFFormObj_CountObjects), - reinterpret_cast(FPDFFormObj_GetObject), - reinterpret_cast(FPDFFormObj_RemoveObject), - reinterpret_cast(FPDF_GetDefaultTTFMap), - reinterpret_cast(FPDF_GetDefaultTTFMapCount), - reinterpret_cast(FPDF_GetDefaultTTFMapEntry), - reinterpret_cast(FPDF_AddInstalledFont), - reinterpret_cast(FPDF_SetSystemFontInfo), - reinterpret_cast(FPDF_GetDefaultSystemFontInfo), - reinterpret_cast(FPDF_FreeDefaultSystemFontInfo), - }; - return bindings; -} -#endif diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 57efcf63..9812380e 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -26,7 +26,6 @@ import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx_coregraphics/pdfrx_coregraphics.dart'; void main() { - WidgetsFlutterBinding.ensureInitialized(); PdfrxEntryFunctions.instance = PdfrxCoreGraphicsEntryFunctions(); pdfrxFlutterInitialize(); runApp(const MyApp()); diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 39a31381..38cb7cd3 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -220,6 +220,9 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { // Custom font registration is not currently supported by the CoreGraphics bridge. } + @override + PdfrxBackend get backend => PdfrxBackend.pdfKit; + Future _openWithPassword({ required PdfPasswordProvider? passwordProvider, required bool firstAttemptByEmptyPassword, diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 1862f5a7..226e480d 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -84,4 +84,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future clearAllFontData() => unimplemented(); + + @override + PdfrxBackend get backend => PdfrxBackend.mock; } diff --git a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart new file mode 100644 index 00000000..894de828 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart @@ -0,0 +1,27 @@ +import 'dart:ffi' as ffi; + +import '../pdfrx_api.dart'; +import 'pdfium.dart' as pdfium_native; +import 'pdfium_bindings.dart' as pdfium_bindings; +import 'pdfium_interop.dart' as file_access_helpers; + +/// Sets up direct lookup for Apple platforms if applicable. +/// +/// Instead of using dynamic library loading, this function sets up +/// direct symbol lookups for iOS and macOS platforms to workaround link-time function +/// stripping issues. +void setupAppleDirectLookupIfApplicable() { + if (Pdfrx.pdfiumNativeBindings != null) { + final bindings = Pdfrx.pdfiumNativeBindings!; + ffi.Pointer lookup(String symbolName) { + final ptr = bindings[symbolName]; + //print('Lookup symbol: $symbolName -> $ptr'); + if (ptr == null) throw Exception('Failed to find binding for $symbolName'); + return ffi.Pointer.fromAddress(ptr); + } + + //print('Loading PDFium bindings via direct interop...'); + pdfium_native.pdfium = pdfium_bindings.pdfium.fromLookup(lookup); + file_access_helpers.interop = file_access_helpers.PdfrxFileAccessHelpers.fromLookup(lookup); + } +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index 25d3e1c0..6389b1c6 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -1,4 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:ffi' as ffi; import 'dart:ffi'; import 'dart:io'; @@ -18,13 +19,39 @@ String _getModuleFileName() { } DynamicLibrary _getModule() { - try { - return DynamicLibrary.open(_getModuleFileName()); - } catch (e) { + if (Platform.isIOS || Platform.isMacOS) { // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) return DynamicLibrary.process(); } + return DynamicLibrary.open(_getModuleFileName()); } +pdfium_bindings.pdfium? _pdfium; + /// Loaded PDFium module. -final pdfium = pdfium_bindings.pdfium(_getModule()); +pdfium_bindings.pdfium get pdfium { + _pdfium ??= pdfium_bindings.pdfium(_getModule()); + return _pdfium!; +} + +set pdfium(pdfium_bindings.pdfium value) { + //print('Debug: Overriding pdfium bindings'); + _pdfium = value; +} + +typedef PdfrxNativeFunctionLookup = ffi.Pointer Function(String symbolName); + +PdfrxNativeFunctionLookup? createPdfrxNativeFunctionLookup() { + if (Pdfrx.pdfiumNativeBindings != null) { + final bindings = Pdfrx.pdfiumNativeBindings!; + ffi.Pointer lookup(String symbolName) { + final ptr = bindings[symbolName]; + //print('Lookup symbol: $symbolName -> $ptr'); + if (ptr == null) throw Exception('Failed to find binding for $symbolName'); + return ffi.Pointer.fromAddress(ptr); + } + + return lookup; + } + return null; +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart b/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart index 4252fe91..45245ff3 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart @@ -7,39 +7,50 @@ import 'dart:typed_data'; import 'pdfium_bindings.dart'; -String _getModuleFileName() { - if (Platform.isAndroid) return 'libpdfrx.so'; - if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; - if (Platform.isWindows) return 'pdfrx.dll'; - if (Platform.isLinux) { - return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfrx.so'; +typedef InteropLookupFunction = Pointer Function(String symbolName); + +final class PdfrxFileAccessHelpers { + PdfrxFileAccessHelpers() : lookup = PdfrxFileAccessHelpers._lookupDefault; + PdfrxFileAccessHelpers.fromLookup(this.lookup); + + final InteropLookupFunction lookup; + + static String _getModuleFileName() { + if (Platform.isAndroid) return 'libpdfrx.so'; + if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; + if (Platform.isWindows) return 'pdfrx.dll'; + if (Platform.isLinux) { + return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfrx.so'; + } + throw UnsupportedError('Unsupported platform'); } - throw UnsupportedError('Unsupported platform'); -} -DynamicLibrary _getModule() { - try { + static DynamicLibrary _getModule() { + if (Platform.isIOS || Platform.isMacOS) { + return DynamicLibrary.process(); + } return DynamicLibrary.open(_getModuleFileName()); - } catch (e) { - // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) - return DynamicLibrary.process(); } -} -final interopLib = _getModule(); + static final _interopLib = _getModule(); + + static Pointer _lookupDefault(String symbolName) => _interopLib.lookup(symbolName); -final _pdfrx_file_access_create = interopLib - .lookupFunction( - 'pdfrx_file_access_create', - ); + late final _pdfrx_file_access_create = + Pointer>.fromAddress( + lookup>('pdfrx_file_access_create').address, + ).asFunction(); -final _pdfrx_file_access_destroy = interopLib.lookupFunction( - 'pdfrx_file_access_destroy', -); + late final _pdfrx_file_access_destroy = Pointer>.fromAddress( + lookup>('pdfrx_file_access_destroy').address, + ).asFunction(); + + late final _pdfrx_file_access_set_value = Pointer>.fromAddress( + lookup>('pdfrx_file_access_set_value').address, + ).asFunction(); +} -final _pdfrx_file_access_set_value = interopLib.lookupFunction( - 'pdfrx_file_access_set_value', -); +PdfrxFileAccessHelpers interop = PdfrxFileAccessHelpers(); typedef _NativeFileReadCallable = NativeCallable, IntPtr)>; @@ -48,18 +59,18 @@ class FileAccess { void readNative(int param, int position, Pointer buffer, int size) async { try { final readSize = await read(buffer.asTypedList(size), position, size); - _pdfrx_file_access_set_value(_fileAccess, readSize); + interop._pdfrx_file_access_set_value(_fileAccess, readSize); } catch (e) { - _pdfrx_file_access_set_value(_fileAccess, -1); + interop._pdfrx_file_access_set_value(_fileAccess, -1); } } _nativeCallable = _NativeFileReadCallable.listener(readNative); - _fileAccess = _pdfrx_file_access_create(fileSize, _nativeCallable.nativeFunction.address, 0); + _fileAccess = interop._pdfrx_file_access_create(fileSize, _nativeCallable.nativeFunction.address, 0); } void dispose() { - _pdfrx_file_access_destroy(_fileAccess); + interop._pdfrx_file_access_destroy(_fileAccess); _nativeCallable.close(); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index d2be445d..b7519dcc 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -16,26 +16,27 @@ import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; -import 'pdfium_interop.dart'; +import 'pdfium_interop.dart' as file_access_helpers; import 'worker.dart'; Directory? _appLocalFontPath; bool _initialized = false; +final _initSync = Object(); /// Initializes PDFium library. Future _init() async { if (_initialized) return; - await pdfium.synchronized(() async { + await _initSync.synchronized(() async { if (_initialized) return; _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); - await using((arena) { + (await backgroundWorker).computeWithArena((arena, params) { final config = arena.allocate(sizeOf()); config.ref.version = 2; - final fontPaths = [?_appLocalFontPath?.path, ...Pdfrx.fontPaths]; + final fontPaths = [?params.appLocalFontPath?.path, ...params.fontPaths]; if (fontPaths.isNotEmpty) { // NOTE: m_pUserFontPaths must not be freed until FPDF_DestroyLibrary is called; on pdfrx, it's never freed. final fontPathArray = malloc>(sizeOf>() * (fontPaths.length + 1)); @@ -54,7 +55,7 @@ Future _init() async { config.ref.m_v8EmbedderSlot = 0; pdfium.FPDF_InitLibraryWithConfig(config); _initialized = true; - }); + }, (appLocalFontPath: _appLocalFontPath, fontPaths: Pdfrx.fontPaths)); }); await _initializeFontEnvironment(); @@ -327,7 +328,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } // Otherwise, load the file on demand - final fa = FileAccess(fileSize, read); + final fa = file_access_helpers.FileAccess(fileSize, read); try { return _openByFunc( (password) async => (await backgroundWorker).computeWithArena( @@ -454,6 +455,9 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { // ignored } } + + @override + PdfrxBackend get backend => PdfrxBackend.pdfium; } extension _FpdfUtf8StringExt on String { diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index c0e062d5..4278d28d 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -6,6 +6,7 @@ import 'dart:isolate'; import 'package:ffi/ffi.dart'; import '../pdfrx_api.dart'; +import 'apple_direct_lookup.dart'; typedef PdfrxComputeCallback = FutureOr Function(M message); @@ -24,7 +25,9 @@ class BackgroundWorker { // propagate the pdfium module path to the worker worker.compute((params) { Pdfrx.pdfiumModulePath = params.modulePath; - }, (modulePath: Pdfrx.pdfiumModulePath)); + Pdfrx.pdfiumNativeBindings = params.bindings; + setupAppleDirectLookupIfApplicable(); + }, (modulePath: Pdfrx.pdfiumModulePath, bindings: Pdfrx.pdfiumNativeBindings)); return worker; } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 395e2f50..1678229e 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -68,6 +68,8 @@ class Pdfrx { /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. /// For Dart only, you can set this function to obtain the cache directory from your own file system. static FutureOr Function()? getCacheDirectory; + + static Map? pdfiumNativeBindings; } abstract class PdfrxEntryFunctions { @@ -145,6 +147,23 @@ abstract class PdfrxEntryFunctions { /// Clear all font data added by [addFontData]. Future clearAllFontData(); + + /// Backend in use. + PdfrxBackend get backend; +} + +enum PdfrxBackend { + /// PDFium backend. + pdfium, + + /// PDFium WebAssembly backend for Web platform. + pdfiumWasm, + + /// pdfKit (CoreGraphics) backend for Apple platforms. + pdfKit, + + /// Mock backend for testing. + mock, } /// Callback function to notify download progress. From 8714854df4178b3bf95157a5f05814243c893ef8 Mon Sep 17 00:00:00 2001 From: james Date: Tue, 21 Oct 2025 12:22:30 +1030 Subject: [PATCH 449/663] _guessCurrentPageNumber() enhancement --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 127 +++++++++++++++--- 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 3cc0a8df..d05815d5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -224,6 +224,8 @@ class _PdfViewerState extends State double _minScale = _defaultMinScale; int? _pageNumber; bool _initialized = false; + bool _usingScrollPercentageMode = false; + StreamSubscription? _documentSubscription; final _interactiveViewerKey = GlobalKey(); @@ -861,7 +863,6 @@ class _PdfViewerState extends State /// /// Please note that the function does not scroll/zoom to the specified page but changes the current page number. void _setCurrentPageNumber(int? pageNumber, {bool doSetState = false}) { - _gotoTargetPageNumber = pageNumber; if (pageNumber != null && _pageNumber != pageNumber) { _pageNumber = pageNumber; if (doSetState) { @@ -873,6 +874,20 @@ class _PdfViewerState extends State } } + double _calcPrimaryAxisVisibility(Rect pageRect, Rect viewportRect, bool isHorizontal) { + if (isHorizontal) { + if (pageRect.right <= viewportRect.left || pageRect.left >= viewportRect.right) return 0.0; + final visibleLeft = pageRect.left < viewportRect.left ? viewportRect.left : pageRect.left; + final visibleRight = pageRect.right > viewportRect.right ? viewportRect.right : pageRect.right; + return ((visibleRight - visibleLeft) / pageRect.width).clamp(0.0, 1.0); + } else { + if (pageRect.bottom <= viewportRect.top || pageRect.top >= viewportRect.bottom) return 0.0; + final visibleTop = pageRect.top < viewportRect.top ? viewportRect.top : pageRect.top; + final visibleBottom = pageRect.bottom > viewportRect.bottom ? viewportRect.bottom : pageRect.bottom; + return ((visibleBottom - visibleTop) / pageRect.height).clamp(0.0, 1.0); + } + } + int? _guessCurrentPageNumber() { if (_layout == null || _viewSize == null) return null; if (widget.params.calculateCurrentPageNumber != null) { @@ -880,33 +895,101 @@ class _PdfViewerState extends State } final visibleRect = _visibleRect; - double calcIntersectionArea(int pageNumber) { - final rect = _layout!.pageLayouts[pageNumber - 1]; - final intersection = rect.intersect(visibleRect); - if (intersection.isEmpty) return 0; - final area = intersection.width * intersection.height; - return area / (rect.width * rect.height); + final layout = _layout!; + final isHorizontal = layout.documentSize.width > layout.documentSize.height; + final isSimple = _isSimpleLayout(layout.pageLayouts, isHorizontal); + + // Calculate primary axis visibility for all pages (map: page number -> visibility %) + final visible = {}; + for (var i = 0; i < layout.pageLayouts.length; i++) { + final pct = _calcPrimaryAxisVisibility(layout.pageLayouts[i], visibleRect, isHorizontal); + if (pct > 0) visible[i + 1] = pct; } - if (_gotoTargetPageNumber != null && - _gotoTargetPageNumber! > 0 && - _gotoTargetPageNumber! <= _document!.pages.length) { - final ratio = calcIntersectionArea(_gotoTargetPageNumber!); - if (ratio > .2) return _gotoTargetPageNumber; + if (visible.isEmpty) return _pageNumber ?? 1; + + final current = _pageNumber; + final fullyVisiblePages = visible.entries.where((e) => e.value >= 1.0).map((e) => e.key).toList(); + + // 3+ fully visible pages to enter scroll percentage mode, <2 to exit + // to stop flapping between modes + if (_usingScrollPercentageMode) { + if (fullyVisiblePages.length < 2 && isSimple) _usingScrollPercentageMode = false; + } else { + if (fullyVisiblePages.length >= 3 || !isSimple) _usingScrollPercentageMode = true; } - _gotoTargetPageNumber = null; - int? pageNumber; - double maxRatio = 0; - for (var i = 1; i <= _document!.pages.length; i++) { - final ratio = calcIntersectionArea(i); - if (ratio == 0) continue; - if (ratio > maxRatio) { - maxRatio = ratio; - pageNumber = i; + // Check goto target + final gotoVisibility = visible[_gotoTargetPageNumber] ?? 0.0; + if (gotoVisibility >= 0.5) return _gotoTargetPageNumber; + if (_gotoTargetPageNumber != null && !_animController.isAnimating) _gotoTargetPageNumber = null; + + // Scroll percentage mode + if (_usingScrollPercentageMode) { + final scrollPosition = isHorizontal ? visibleRect.left : visibleRect.top; + final scrollLength = + (isHorizontal ? layout.documentSize.width : layout.documentSize.height) - + (isHorizontal ? visibleRect.width : visibleRect.height); + final scrollPercentage = scrollLength > 0 ? (scrollPosition / scrollLength).clamp(0.0, 1.0) : 0.0; + return ((scrollPercentage * layout.pageLayouts.length).floor() + 1).clamp(1, layout.pageLayouts.length); + } + + // Sticky mode - prefer current page if it is fully visible as long + // as the first or last page is not fully visible also + if (current != null) { + final currentPercentage = visible[current] ?? 0.0; + if (currentPercentage >= 1.0) { + // Edge detection: prefer first/last page if also fully visible + if (fullyVisiblePages.contains(1) && current != 1) return 1; + if (fullyVisiblePages.contains(layout.pageLayouts.length) && current != layout.pageLayouts.length) { + return layout.pageLayouts.length; + } + return current; + } + // Most visible with proximity tie-break + var maxPercentage = 0.0, maxPage = visible.keys.first; + for (final entry in visible.entries) { + if (entry.value > maxPercentage || + (entry.value == maxPercentage && (current - entry.key).abs() < (current - maxPage).abs())) { + maxPercentage = entry.value; + maxPage = entry.key; + } + } + return maxPage; + } + + // Should not reach here, but fallback to most visible + return visible.entries.reduce((a, b) => a.value > b.value ? a : b).key; + } + + /// Detect if layout is "simple" (sequential pages) vs "complex" (facing pages) + /// In facing pages, multiple pages can share the same PRIMARY SCROLL axis coordinate + bool _isSimpleLayout(List pageLayouts, bool isHorizontalLayout) { + if (pageLayouts.length <= 1) return true; + + // Check if any two consecutive pages overlap along the PRIMARY SCROLL axis + // For horizontal layouts (scroll left-right), check if pages overlap horizontally (same x-range = facing) + // For vertical layouts (scroll up-down), check if pages overlap vertically (same y-range = facing) + for (var i = 0; i < pageLayouts.length - 1; i++) { + final page1 = pageLayouts[i]; + final page2 = pageLayouts[i + 1]; + + if (isHorizontalLayout) { + // Horizontal scroll: pages are "complex" if they overlap horizontally (facing pages side-by-side) + // In sequential horizontal, page2.left should be >= page1.right (no overlap) + if (page2.left < page1.right - 1.0) { + return false; // Overlap detected = facing pages + } + } else { + // Vertical scroll: pages are "complex" if they overlap vertically (facing pages top-bottom) + // In sequential vertical, page2.top should be >= page1.bottom (no overlap) + if (page2.top < page1.bottom - 1.0) { + return false; // Overlap detected = facing pages + } } } - return pageNumber; + + return true; } /// Returns true if page layouts are changed. From a6cf717e0eec9a5c538af05c90d6351efb20e6ad Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 03:30:12 +0900 Subject: [PATCH 450/663] Release pdfrx_engine v0.2.2, pdfrx_coregraphics v0.1.4, and pdfrx v2.2.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Experimental iOS/macOS direct symbol lookup for #501 - pdfrxFlutterInitialize now calls WidgetsFlutterBinding.ensureInitialized() internally - Added PdfrxBackend enum support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_coregraphics/CHANGELOG.md | 6 ++++++ packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 6 ++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 8 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cf3fda9f..a56f50a8 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.4 + pdfrx: ^2.2.5 ``` ### For Pure Dart Applications @@ -54,7 +54,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.2.1 + pdfrx_engine: ^0.2.2 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index abbdf668..aa000dbd 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.5 + +- Experimental iOS/macOS direct symbol lookup to address SwiftPM TestFlight/App Store symbol lookup issues ([#501](https://github.com/espresso3389/pdfrx/issues/501)) +- `pdfrxFlutterInitialize()` now internally calls `WidgetsFlutterBinding.ensureInitialized()` - no need to call it explicitly +- Updated to pdfrx_engine 0.2.2 + # 2.2.4 - FIXED: SwiftPM/pod package structure updates for iOS/macOS ([#501](https://github.com/espresso3389/pdfrx/issues/501)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index ad3e645d..5b65f1c5 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.4 + pdfrx: ^2.2.5 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index dccf528d..4ac4168e 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.4 +version: 2.2.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.1 + pdfrx_engine: ^0.2.2 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 57fa72b9..605f10e0 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.4 + +- Updated to pdfrx_engine 0.2.2 +- Updated README example to remove explicit `WidgetsFlutterBinding.ensureInitialized()` call (now handled internally by `pdfrxFlutterInitialize()`) +- Implemented `PdfrxBackend` enum support + ## 0.1.3 - **BREAKING**: Renamed `PdfrxEntryFunctions.initPdfium()` to `PdfrxEntryFunctions.init()` for consistency diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index a67feed5..0c286bdc 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.3 +version: 0.1.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.2.0 + pdfrx_engine: ^0.2.2 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index a276c5fe..3d9cf450 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.2 + +- Experimental support for Apple platforms direct symbol lookup to address TestFlight/App Store symbol lookup issues ([#501](https://github.com/espresso3389/pdfrx/issues/501)) +- Added `PdfrxBackend` enum to identify which PDF backend is being used +- Internal refactoring to support lookup-based function loading on iOS/macOS + ## 0.2.1 - FIXED: Handle servers that return 200 instead of 206 for content-range requests ([#468](https://github.com/espresso3389/pdfrx/issues/468)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 5b6fda34..6a1dfb44 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.2.1 +version: 0.2.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7ab4852f0036e763de5825d786436c832f383e44 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 15:56:19 +0900 Subject: [PATCH 451/663] pdfrx_coregraphics: dest handling update --- .../Sources/PdfrxCoregraphicsPlugin.swift | 474 +++++++++--------- .../lib/pdfrx_coregraphics.dart | 55 +- 2 files changed, 273 insertions(+), 256 deletions(-) diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index aa7be293..8d2f1293 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -235,7 +235,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } - /// Builds the document outline. + /// Builds the document outline using CoreGraphics for accurate zoom values. private func loadOutline(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -286,8 +286,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result(parseCGOutlineNodes(first, document: document)) } - /// Loads page links by merging PDFKit annotation data with CoreGraphics parsing and optional - /// text-based auto-detection. Duplicate links are filtered via a stable hash. + /// Loads page links using CoreGraphics PDF parsing and optional text-based auto-detection. + /// Duplicate links are filtered via a stable hash. private func loadPageLinks(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any], @@ -316,15 +316,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var occupiedRects: [CGRect] = [] var seenKeys = Set() - let (pdfKitLinks, pdfKitRects) = annotationLinks(on: page, document: document) - for link in pdfKitLinks { - let key = linkKey(link) - if seenKeys.insert(key).inserted { - links.append(link) - } - } - occupiedRects.append(contentsOf: pdfKitRects) - + // Parse annotations using CoreGraphics for reliable, spec-compliant extraction if let cgDocument = document.documentRef { let (cgLinks, cgRects) = cgAnnotationLinks( cgDocument: cgDocument, @@ -340,6 +332,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { occupiedRects.append(contentsOf: cgRects) } + // Optionally detect links from text content (URLs in plain text) let enableAutoLinkDetection = args["enableAutoLinkDetection"] as? Bool ?? true if enableAutoLinkDetection { for link in autodetectedLinks(on: page, excluding: occupiedRects) { @@ -352,92 +345,6 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result(links) } - private func annotationLinks(on page: PDFPage, document: PDFDocument) -> ([[String: Any]], [CGRect]) { - var links: [[String: Any]] = [] - var rects: [CGRect] = [] - for annotation in page.annotations { - guard isLinkAnnotation(annotation) else { continue } - let annotationRects = annotationRectangles(annotation) - guard !annotationRects.isEmpty else { continue } - let content = annotation.contents - let dest = annotationDestinationMap(annotation, document: document) - var urlString: String? - if let url = annotation.url { - urlString = url.absoluteString - } - else if let actionURL = annotation.action as? PDFActionURL, let url = actionURL.url { - urlString = url.absoluteString - } - - if dest == nil, urlString == nil, content == nil { - continue - } - - var linkEntry: [String: Any] = [ - "rects": annotationRects.map(rectDictionary) - ] - if let dest = dest { - linkEntry["dest"] = dest - } - if let urlString { - linkEntry["url"] = urlString - } - if let content { - linkEntry["annotationContent"] = content - } - links.append(linkEntry) - rects.append(contentsOf: annotationRects) - } - return (links, rects) - } - - private func annotationDestinationMap(_ annotation: PDFAnnotation, document: PDFDocument) -> [String: Any]? { - if let destination = annotation.destination { - return destinationMap(destination, document: document) - } - if let action = annotation.action as? PDFActionGoTo { - return destinationMap(action.destination, document: document) - } - return nil - } - - private func destinationMap(_ destination: PDFDestination?, document: PDFDocument) -> [String: Any]? { - guard let destination = destination, let page = destination.page else { - return nil - } - let pageIndex = document.index(for: page) - if pageIndex == NSNotFound || pageIndex < 0 { - return nil - } - var params: [Double] = [ - Double(destination.point.x), - Double(destination.point.y) - ] - let zoom = destination.zoom - // Some PDFs store invalid zoom values such as NaN or Infinity; clamp to 0 to indicate "use current". - params.append(zoom.isFinite && zoom > 0 && zoom < 10.0 ? Double(zoom) : 0.0) - return [ - "page": pageIndex + 1, - "command": "xyz", - "params": params - ] - } - - private func isLinkAnnotation(_ annotation: PDFAnnotation) -> Bool { - if let subtypeValue = annotation.value(forAnnotationKey: PDFAnnotationKey.subtype) as? String { - let normalized = subtypeValue.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() - let linkRaw = PDFAnnotationSubtype.link.rawValue - let linkNormalized = linkRaw.trimmingCharacters(in: CharacterSet(charactersIn: "/")).lowercased() - if normalized == linkNormalized || normalized == linkRaw.lowercased() || normalized == "link" { - return true - } - } - if annotation.url != nil { return true } - if annotation.action is PDFActionURL { return true } - if annotation.action is PDFActionGoTo { return true } - return false - } - private func autodetectedLinks(on page: PDFPage, excluding occupiedRects: [CGRect]) -> [[String: Any]] { guard let text = page.string, !text.isEmpty else { return [] } guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { @@ -557,6 +464,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } + /// Parses outline nodes using CoreGraphics for accurate destination data including zoom values. private func parseCGOutlineNodes(_ outlineDict: CGPDFDictionaryRef, document: PDFDocument) -> [[String: Any]] { var result: [[String: Any]] = [] var currentDict: CGPDFDictionaryRef? = outlineDict @@ -578,6 +486,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return result } + /// Parses a single outline node from CoreGraphics dictionary. private func parseCGOutlineNode(_ outlineDict: CGPDFDictionaryRef, document: PDFDocument) -> [String: Any]? { // Extract title var titleString: CGPDFStringRef? @@ -596,7 +505,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var node: [String: Any] = ["title": title] - // Extract destination + // Extract destination using CoreGraphics to get accurate zoom values if let dest = parseCGDestination(outlineDict, document: document) { node["dest"] = dest } @@ -623,11 +532,33 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { // Check for "A" key (action dictionary) var actionDict: CGPDFDictionaryRef? if CGPDFDictionaryGetDictionary(dict, "A", &actionDict), let action = actionDict { - if let actionType = actionType(from: action), - actionType.caseInsensitiveCompare("GoTo") == .orderedSame, - let dest = parseCGDestinationFromAction(action, document: document) - { - return dest + if let actionType = actionType(from: action) { + let normalizedType = actionType.lowercased() + + // Handle GoTo action (internal navigation) + if normalizedType == "goto" { + if let dest = parseCGDestinationFromAction(action, document: document) { + return dest + } + } + + // Handle GoToR action (remote goto) - extract destination from current document + // Note: Remote file reference is ignored as we only handle current document + else if normalizedType == "gotor" { + if let dest = parseCGDestinationFromAction(action, document: document) { + return dest + } + } + + // Handle GoToE action (embedded goto) + else if normalizedType == "gotoe" { + if let dest = parseCGDestinationFromAction(action, document: document) { + return dest + } + } + + // Thread actions are not supported for destination extraction + // URI, Launch, Named, and other actions don't have destinations } } @@ -668,7 +599,13 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { if CGPDFObjectGetValue(destObject, .string, &destString), let string = destString { if let cfString = CGPDFStringCopyTextString(string) { let destName = cfString as String - return lookupNamedDestination(destName, document: document) + let result = lookupNamedDestination(destName, document: document) + #if DEBUG + if result == nil { + print("Warning: Named destination '\(destName)' not found in document") + } + #endif + return result } } @@ -676,9 +613,18 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var destName: UnsafePointer? if CGPDFObjectGetValue(destObject, .name, &destName), let name = destName { let destNameStr = String(cString: name).trimmingCharacters(in: CharacterSet(charactersIn: "/")) - return lookupNamedDestination(destNameStr, document: document) + let result = lookupNamedDestination(destNameStr, document: document) + #if DEBUG + if result == nil { + print("Warning: Named destination '\(destNameStr)' not found in document") + } + #endif + return result } + #if DEBUG + print("Warning: Unable to parse destination object - unrecognized type") + #endif return nil } @@ -688,19 +634,32 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { // First element should be a page reference (dictionary or integer) var pageIndex: Int? + + // Try to get the page reference as a dictionary (most common case - indirect reference to page) var pageRef: CGPDFDictionaryRef? if CGPDFArrayGetDictionary(array, 0, &pageRef), let pageDict = pageRef { - // Find the page index from the page dictionary - pageIndex = findPageIndex(pageDict, document: document) + // Use improved page index finding with object number comparison + pageIndex = findPageIndexByObjectNumber(pageDict, document: document) } - else { + + // Fallback: try direct integer (rare, but some PDFs use 0-based page numbers directly) + if pageIndex == nil { var rawIndex: CGPDFInteger = 0 if CGPDFArrayGetInteger(array, 0, &rawIndex) { - pageIndex = Int(rawIndex) + // Treat as 0-based page index + let idx = Int(rawIndex) + if idx >= 0, idx < document.pageCount { + pageIndex = idx + } } } - guard let pageIndex, pageIndex >= 0, pageIndex < document.pageCount else { return nil } + guard let pageIndex, pageIndex >= 0, pageIndex < document.pageCount else { + #if DEBUG + print("Warning: Failed to resolve page reference in destination array") + #endif + return nil + } // Extract command type (XYZ, Fit, FitH, etc.) var commandRaw = "xyz" @@ -800,12 +759,45 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { while i + 1 < count { var nameString: CGPDFStringRef? if CGPDFArrayGetString(names, i, &nameString), let str = nameString { + // Try CGPDFStringCopyTextString first (for text strings with BOM) if let cfString = CGPDFStringCopyTextString(str), (cfString as String) == name { var valueObj: CGPDFObjectRef? if CGPDFArrayGetObject(names, i + 1, &valueObj) { return valueObj } } + // Also try raw byte string comparison with multiple encodings + else if let bytePtr = CGPDFStringGetBytePtr(str) { + let length = CGPDFStringGetLength(str) + // Try UTF-8 + if let byteString = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .utf8, + freeWhenDone: false + ), + byteString == name + { + var valueObj: CGPDFObjectRef? + if CGPDFArrayGetObject(names, i + 1, &valueObj) { + return valueObj + } + } + // Try ISO-Latin-1 as fallback (common in older PDFs) + else if let latin1String = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .isoLatin1, + freeWhenDone: false + ), + latin1String == name + { + var valueObj: CGPDFObjectRef? + if CGPDFArrayGetObject(names, i + 1, &valueObj) { + return valueObj + } + } + } } i += 2 } @@ -815,11 +807,16 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var kidsArray: CGPDFArrayRef? if CGPDFDictionaryGetArray(nameTree, "Kids", &kidsArray), let kids = kidsArray { let count = CGPDFArrayGetCount(kids) + + // Use Limits to optimize search if available for i in 0 ..< count { var kidDict: CGPDFDictionaryRef? if CGPDFArrayGetDictionary(kids, i, &kidDict), let kid = kidDict { - if let result = lookupInNameTree(name, nameTree: kid) { - return result + // Check if name falls within this kid's limits + if nameInLimits(name, limits: kid) { + if let result = lookupInNameTree(name, nameTree: kid) { + return result + } } } } @@ -828,99 +825,175 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return nil } - private func findPageIndex(_ pageDict: CGPDFDictionaryRef, document: PDFDocument) -> Int? { - // Try to match the page dictionary reference with actual pages + /// Checks if a name falls within the Limits of a name tree node + private func nameInLimits(_ name: String, limits dict: CGPDFDictionaryRef) -> Bool { + var limitsArray: CGPDFArrayRef? + guard CGPDFDictionaryGetArray(dict, "Limits", &limitsArray), let limits = limitsArray else { + // No limits means we should search this node + return true + } + + guard CGPDFArrayGetCount(limits) >= 2 else { + return true + } + + // Extract lower limit - try text strings and byte strings with multiple encodings + var lowerString: CGPDFStringRef? + if CGPDFArrayGetString(limits, 0, &lowerString), let lower = lowerString { + var lowerName: String? + // Try text string first (UTF-16 with BOM) + if let cfString = CGPDFStringCopyTextString(lower) as String? { + lowerName = cfString + } + // Try raw byte strings with multiple encodings + else if let bytePtr = CGPDFStringGetBytePtr(lower) { + let length = CGPDFStringGetLength(lower) + // Try UTF-8 + if let utf8String = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .utf8, + freeWhenDone: false + ) { + lowerName = utf8String + } + // Try ISO-Latin-1 as fallback + else if let latin1String = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .isoLatin1, + freeWhenDone: false + ) { + lowerName = latin1String + } + } + + if let lowerName, name < lowerName { + return false + } + } + + // Extract upper limit - try text strings and byte strings with multiple encodings + var upperString: CGPDFStringRef? + if CGPDFArrayGetString(limits, 1, &upperString), let upper = upperString { + var upperName: String? + // Try text string first (UTF-16 with BOM) + if let cfString = CGPDFStringCopyTextString(upper) as String? { + upperName = cfString + } + // Try raw byte strings with multiple encodings + else if let bytePtr = CGPDFStringGetBytePtr(upper) { + let length = CGPDFStringGetLength(upper) + // Try UTF-8 + if let utf8String = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .utf8, + freeWhenDone: false + ) { + upperName = utf8String + } + // Try ISO-Latin-1 as fallback + else if let latin1String = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: bytePtr), + length: length, + encoding: .isoLatin1, + freeWhenDone: false + ) { + upperName = latin1String + } + } + + if let upperName, name > upperName { + return false + } + } + + return true + } + + /// Finds page index by comparing object identifiers in the PDF structure. + /// This is more robust than pointer comparison for indirect references. + private func findPageIndexByObjectNumber(_ pageDict: CGPDFDictionaryRef, document: PDFDocument) -> Int? { guard let cgDocument = document.documentRef else { return nil } + // First try: Direct pointer comparison (works for direct references) for i in 0 ..< document.pageCount { guard let cgPage = cgDocument.page(at: i + 1) else { continue } guard let currentPageDict = cgPage.dictionary else { continue } - // Compare dictionary pointers - they should be the same object if it's the same page if currentPageDict == pageDict { return i } } - // If exact match fails, try comparing by object reference indirectly - // Some PDFs use indirect references, so we need to check the page type - var pageType: UnsafePointer? - if CGPDFDictionaryGetName(pageDict, "Type", &pageType), - let type = pageType, - String(cString: type) == "Page" || String(cString: type) == "/Page" - { - // This is a valid page dictionary but we couldn't find exact match - // Fall back to checking page by content comparison (checking some unique properties) - for i in 0 ..< document.pageCount { - guard let cgPage = cgDocument.page(at: i + 1) else { continue } - guard let currentPageDict = cgPage.dictionary else { continue } - - // Compare page properties (MediaBox, Resources, etc.) - if comparePagDictionaries(pageDict, currentPageDict) { - return i - } + // Second try: Compare by extracting unique page properties + // This works when indirect references point to the same logical page + // We create a fingerprint based on MediaBox, CropBox, Rotate, and Resources reference + let targetFingerprint = createPageFingerprint(pageDict) + + for i in 0 ..< document.pageCount { + guard let cgPage = cgDocument.page(at: i + 1) else { continue } + guard let currentPageDict = cgPage.dictionary else { continue } + + let currentFingerprint = createPageFingerprint(currentPageDict) + if targetFingerprint == currentFingerprint { + return i } } + #if DEBUG + print("Warning: Could not match page dictionary to any page in document") + #endif return nil } - private func comparePagDictionaries(_ dict1: CGPDFDictionaryRef, _ dict2: CGPDFDictionaryRef) -> Bool { - // Compare MediaBox - var mediaBox1: CGPDFArrayRef? - var mediaBox2: CGPDFArrayRef? - let hasMediaBox1 = CGPDFDictionaryGetArray(dict1, "MediaBox", &mediaBox1) - let hasMediaBox2 = CGPDFDictionaryGetArray(dict2, "MediaBox", &mediaBox2) + /// Creates a fingerprint for a page dictionary based on its key properties + private func createPageFingerprint(_ pageDict: CGPDFDictionaryRef) -> String { + var components: [String] = [] - if hasMediaBox1 != hasMediaBox2 { - return false + // MediaBox + var mediaBox: CGPDFArrayRef? + if CGPDFDictionaryGetArray(pageDict, "MediaBox", &mediaBox), let mb = mediaBox { + components.append("MB:\(arrayToString(mb))") } - if hasMediaBox1, let mb1 = mediaBox1, let mb2 = mediaBox2 { - if !compareArrays(mb1, mb2) { - return false - } + // CropBox (if present) + var cropBox: CGPDFArrayRef? + if CGPDFDictionaryGetArray(pageDict, "CropBox", &cropBox), let cb = cropBox { + components.append("CB:\(arrayToString(cb))") } - // Compare Rotate - var rotate1: CGPDFInteger = 0 - var rotate2: CGPDFInteger = 0 - _ = CGPDFDictionaryGetInteger(dict1, "Rotate", &rotate1) - _ = CGPDFDictionaryGetInteger(dict2, "Rotate", &rotate2) - - if rotate1 != rotate2 { - return false + // Rotate + var rotate: CGPDFInteger = 0 + if CGPDFDictionaryGetInteger(pageDict, "Rotate", &rotate) { + components.append("R:\(rotate)") } - return true - } - - private func compareArrays(_ array1: CGPDFArrayRef, _ array2: CGPDFArrayRef) -> Bool { - let count1 = CGPDFArrayGetCount(array1) - let count2 = CGPDFArrayGetCount(array2) - - if count1 != count2 { - return false + // Contents reference (if available) - helps distinguish pages + var contents: CGPDFObjectRef? + if CGPDFDictionaryGetObject(pageDict, "Contents", &contents) { + components.append("C:exists") } - for i in 0 ..< count1 { - var num1: CGPDFReal = 0 - var num2: CGPDFReal = 0 - let hasNum1 = CGPDFArrayGetNumber(array1, i, &num1) - let hasNum2 = CGPDFArrayGetNumber(array2, i, &num2) + return components.joined(separator: "|") + } - if hasNum1 != hasNum2 { - return false - } + /// Converts a CGPDFArray to a string representation + private func arrayToString(_ array: CGPDFArrayRef) -> String { + let count = CGPDFArrayGetCount(array) + var values: [String] = [] - if hasNum1, abs(num1 - num2) > 0.01 { - return false + for i in 0 ..< count { + var num: CGPDFReal = 0 + if CGPDFArrayGetNumber(array, i, &num) { + values.append(String(format: "%.2f", num)) } } - return true + return values.joined(separator: ",") } private func cgAnnotationLinks( @@ -1142,61 +1215,6 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ) } - private func annotationRectangles(_ annotation: PDFAnnotation) -> [CGRect] { - let quadPoints = annotation.quadrilateralPoints - if let quadPoints, quadPoints.count >= 4 { - var rects: [CGRect] = [] - rects.reserveCapacity(quadPoints.count / 4) - var index = 0 - while index + 3 < quadPoints.count { - #if os(iOS) - let points = [ - quadPoints[index].cgPointValue, - quadPoints[index + 1].cgPointValue, - quadPoints[index + 2].cgPointValue, - quadPoints[index + 3].cgPointValue - ] - #else - let points = [ - quadPoints[index].pointValue, - quadPoints[index + 1].pointValue, - quadPoints[index + 2].pointValue, - quadPoints[index + 3].pointValue - ] - #endif - index += 4 - if let rect = rectangle(from: points) { - rects.append(rect) - } - } - if !rects.isEmpty { - return rects - } - } - let bounds = annotation.bounds - return bounds.isNull || bounds.isEmpty ? [] : [bounds] - } - - private func rectangle(from points: [CGPoint]) -> CGRect? { - guard !points.isEmpty else { return nil } - var minX = CGFloat.greatestFiniteMagnitude - var minY = CGFloat.greatestFiniteMagnitude - var maxX = -CGFloat.greatestFiniteMagnitude - var maxY = -CGFloat.greatestFiniteMagnitude - for point in points { - if point.x < minX { minX = point.x } - if point.y < minY { minY = point.y } - if point.x > maxX { maxX = point.x } - if point.y > maxY { maxY = point.y } - } - let width = maxX - minX - let height = maxY - minY - if width <= 0 || height <= 0 { - return nil - } - return CGRect(x: minX, y: minY, width: width, height: height) - } - private func intersects(_ rect: CGRect, with others: [CGRect]) -> Bool { for other in others where rect.intersects(other) { return true diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 38cb7cd3..14c5251e 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -47,6 +47,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { 'Pdfrx.loadAsset is not set. Please set it before calling openAsset.', ); } + await init(); final data = await Pdfrx.loadAsset!(name); return openData( data, @@ -131,6 +132,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { int? maxSizeToCacheOnMemory, void Function()? onDispose, }) async { + await init(); final buffer = Uint8List(fileSize); final chunk = Uint8List(min(fileSize, 128 * 1024)); var offset = 0; @@ -166,6 +168,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { Map? headers, bool withCredentials = false, }) async { + await init(); final clientFactory = Pdfrx.createHttpClient ?? () => http.Client(); final client = clientFactory(); try { @@ -413,22 +416,6 @@ class _CoreGraphicsPdfDocument extends PdfDocument { ); } - PdfDest? _parseDest(Map? dest) { - if (dest == null) { - return null; - } - final map = dest.cast(); - final page = map['page'] as int? ?? 1; - final params = (map['params'] as List?) - ?.map((value) => (value as num?)?.toDouble()) - .toList(growable: false); - final commandName = map['command'] as String?; - final command = commandName == null - ? PdfDestCommand.unknown - : PdfDestCommand.parse(commandName); - return PdfDest(page, command, params); - } - @override bool isIdenticalDocumentHandle(Object? other) { return other is _CoreGraphicsPdfDocument && other.handle == handle; @@ -617,21 +604,10 @@ class _CoreGraphicsPdfPage extends PdfPage { const []; final url = map['url'] as String?; final destMap = map['dest'] as Map?; - final dest = destMap == null - ? null - : PdfDest( - (destMap['page'] as int?) ?? pageNumber, - destMap['command'] is String - ? PdfDestCommand.parse(destMap['command'] as String) - : PdfDestCommand.unknown, - (destMap['params'] as List?) - ?.map((value) => (value as num?)?.toDouble()) - .toList(growable: false), - ); final link = PdfLink( rects, url: url == null ? null : Uri.tryParse(url), - dest: dest, + dest: _parseDest(destMap, defaultPageNumber: pageNumber), annotationContent: map['annotationContent'] as String?, ); return compact ? link.compact() : link; @@ -692,3 +668,26 @@ class _CoreGraphicsCancellationToken implements PdfPageRenderCancellationToken { @override bool get isCanceled => _isCanceled; } + +PdfDest? _parseDest(Map? dest, {int defaultPageNumber = 1}) { + if (dest == null) return null; + final map = dest.cast(); + return PdfDest( + map['page'] as int? ?? defaultPageNumber, + _tryParseDestCommand(map['command'] as String?), + (map['params'] as List?) + ?.map((value) => (value as num?)?.toDouble()) + .toList(growable: false), + ); +} + +PdfDestCommand _tryParseDestCommand(String? commandName) { + try { + if (commandName == null) { + return PdfDestCommand.unknown; + } + return PdfDestCommand.parse(commandName); + } catch (e) { + return PdfDestCommand.unknown; + } +} From 4c86697292729ca07b3a985f17041f33c98d2e50 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 15:56:44 +0900 Subject: [PATCH 452/663] Add PdfDocumentRefByLoader --- packages/pdfrx/lib/src/pdf_document_ref.dart | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index ba8561bc..3f9754ba 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -311,6 +311,10 @@ class PdfDocumentRefCustom extends PdfDocumentRef { } /// A [PdfDocumentRef] that directly contains [PdfDocument]. +/// +/// It's useful when you already have a [PdfDocument] instance and want to use it as a [PdfDocumentRef] +/// but sometimes it breaks the lifecycle management of [PdfDocument] on [PdfDocumentRef] and you had better +/// use [PdfDocumentRefByLoader] if possible. class PdfDocumentRefDirect extends PdfDocumentRef { PdfDocumentRefDirect(this.document, {super.autoDispose = true, PdfDocumentRefKey? key}) : super(key: key ?? PdfDocumentRefKey(document.sourceName)); @@ -327,6 +331,26 @@ class PdfDocumentRefDirect extends PdfDocumentRef { PdfPasswordProvider? get passwordProvider => throw UnimplementedError('Not applicable for PdfDocumentRefDirect'); } +/// A [PdfDocumentRef] that loads the document using a custom loader function. +/// +/// The loader function is called when the document is really needed to be loaded and the [PdfDocument] will be closed +/// automatically when the reference is disposed if [autoDispose] is true. +class PdfDocumentRefByLoader extends PdfDocumentRef { + PdfDocumentRefByLoader(this.loader, {required super.key, super.autoDispose = true}); + + /// The loader function to load the document. + final Future Function(PdfDocumentLoaderProgressCallback progressCallback) loader; + + @override + Future loadDocument(PdfDocumentLoaderProgressCallback progressCallback) => loader(progressCallback); + + @override + bool get firstAttemptByEmptyPassword => throw UnimplementedError('Not applicable for PdfDocumentRefByLoader'); + + @override + PdfPasswordProvider? get passwordProvider => throw UnimplementedError('Not applicable for PdfDocumentRefByLoader'); +} + /// The class is used to load the referenced document and notify the listeners. class PdfDocumentListenable extends Listenable { PdfDocumentListenable._(this.ref); From 66b6cd08b80c2b518273536d6ff5cb9cd581715d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 20:07:26 +0900 Subject: [PATCH 453/663] FIXED: pdfrx_coregraphics could not handle some dests correctly --- .../Sources/PdfrxCoregraphicsPlugin.swift | 10 ++++++++++ .../pdfrx_coregraphics/lib/pdfrx_coregraphics.dart | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index 8d2f1293..148a4255 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -594,6 +594,16 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return parseDestinationArray(array, document: document) } + // Try dictionary-type destination (indirect destination reference with /D key) + var destDict: CGPDFDictionaryRef? + if CGPDFObjectGetValue(destObject, .dictionary, &destDict), let dict = destDict { + // The dictionary should contain a /D key with the actual destination array + var actualDestObject: CGPDFObjectRef? + if CGPDFDictionaryGetObject(dict, "D", &actualDestObject), let actualDest = actualDestObject { + return parseCGDestinationObject(actualDest, document: document) + } + } + // Try string-type destination (named destination) var destString: CGPDFStringRef? if CGPDFObjectGetValue(destObject, .string, &destString), let string = destString { diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 14c5251e..f3524687 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -676,7 +676,11 @@ PdfDest? _parseDest(Map? dest, {int defaultPageNumber = 1}) { map['page'] as int? ?? defaultPageNumber, _tryParseDestCommand(map['command'] as String?), (map['params'] as List?) - ?.map((value) => (value as num?)?.toDouble()) + ?.map((value) { + if (value == null) return null; + if (value is num) return value.toDouble(); + return null; + }) .toList(growable: false), ); } From 07d9592d8f6391a189345a3ce15cb9e481555628 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 20:09:35 +0900 Subject: [PATCH 454/663] WIP: reverting example's project files --- .../pdfrx/example/viewer/macos/Podfile.lock | 26 +++++++++++++++++++ .../macos/Runner.xcodeproj/project.pbxproj | 18 +++++++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 9d359e12..432da463 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_macos (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfrx (0.0.12): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + pdfrx: + :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + pdfrx: af3a9815e0f9b28ad40c6bdc8ee56f3d77dc5574 + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index 6d418bf7..b64200e1 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -244,6 +244,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 9E685165F03DA85483B0D5E9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -391,6 +392,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 9E685165F03DA85483B0D5E9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; AD9238A20634DDAB9D2BD9BF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 7c7e59cfc28fc87bc9e5b5a3e3f2d4715570ad90 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 24 Oct 2025 20:15:52 +0900 Subject: [PATCH 455/663] Release pdfrx_coregraphics v0.1.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx_coregraphics/CHANGELOG.md | 4 ++++ packages/pdfrx_coregraphics/README.md | 2 +- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 605f10e0..4b29f4a9 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.5 + +- Fixed destination handling issues where some PDF destinations could not be processed correctly + ## 0.1.4 - Updated to pdfrx_engine 0.2.2 diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 9812380e..18114c4f 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -15,7 +15,7 @@ Add the package to your Flutter app: ```yaml dependencies: pdfrx: ^2.2.0 - pdfrx_coregraphics: ^0.1.3 + pdfrx_coregraphics: ^0.1.5 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 0c286bdc..2ed7d225 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.4 +version: 0.1.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From a0afe317d8d504727a56a46cb90544ba5e5a7803 Mon Sep 17 00:00:00 2001 From: Gunnar Magholder Date: Fri, 24 Oct 2025 15:31:57 +0200 Subject: [PATCH 456/663] Add configurable timeout parameter to openUri/pdfDocumentFromUri - Add optional timeout parameter to PdfDocument.openUri API - Add timeout parameter to pdfDocumentFromUri function - Default timeout remains 5 seconds for backward compatibility - Allows users to configure HTTP request timeout for PDF downloads --- packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 1 + packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart | 8 ++++++-- packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 2 ++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 226e480d..17d80a74 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -74,6 +74,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }) => unimplemented(); @override diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index b02d30e4..7b1d985d 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -295,6 +295,7 @@ Future pdfDocumentFromUri( PdfDownloadProgressCallback? progressCallback, bool useRangeAccess = true, Map? headers, + Duration? timeout, }) async { progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); @@ -311,6 +312,7 @@ Future pdfDocumentFromUri( 0, useRangeAccess: useRangeAccess, headers: headers, + timeout: timeout, ); if (result.isFullDownload) { return await PdfDocument.openFile( @@ -333,6 +335,7 @@ Future pdfDocumentFromUri( addCacheControlHeaders: true, useRangeAccess: useRangeAccess, headers: headers, + timeout: timeout, ); // cached file has expired // if the file has fully downloaded again or has not been modified @@ -358,7 +361,7 @@ Future pdfDocumentFromUri( final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { - await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers); + await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers, timeout: timeout); } final readEnd = min(p + size, (blockId + 1) * cache.blockSize); final sizeToRead = readEnd - p; @@ -419,6 +422,7 @@ Future<_DownloadResult> _downloadBlock( bool addCacheControlHeaders = false, bool useRangeAccess = true, Map? headers, + Duration? timeout, }) => httpClientWrapper.synchronized(() async { int? fileSize; final blockOffset = blockId * cache.blockSize; @@ -431,7 +435,7 @@ Future<_DownloadResult> _downloadBlock( }); late final http.StreamedResponse response; try { - response = await httpClientWrapper.client.send(request).timeout(Duration(seconds: 5)); + response = await httpClientWrapper.client.send(request).timeout(timeout ?? const Duration(seconds: 5)); } on TimeoutException { httpClientWrapper.reset(); rethrow; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index b7519dcc..389289ce 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -366,6 +366,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }) => pdfDocumentFromUri( uri, passwordProvider: passwordProvider, @@ -374,6 +375,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { progressCallback: progressCallback, useRangeAccess: preferRangeAccess, headers: headers, + timeout: timeout, ); static Future _openByFunc( diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 1678229e..bd07e5bf 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -131,6 +131,7 @@ abstract class PdfrxEntryFunctions { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }); /// Reload the fonts. From 740d984ae60ee609bd17c3900f17425f033961ec Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 00:02:21 +0900 Subject: [PATCH 457/663] Add missing implementations. --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 1 + .../lib/pdfrx_coregraphics.dart | 53 +++++-------------- .../lib/src/native/pdf_file_cache.dart | 19 +++++-- .../lib/src/native/pdfrx_pdfium.dart | 1 + packages/pdfrx_engine/lib/src/pdfrx_api.dart | 4 ++ 5 files changed, 35 insertions(+), 43 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index b45cbff8..2fd48a2c 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -228,6 +228,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }) async { _PdfiumWasmCallback? progressCallbackReg; void cleanupCallbacks() => progressCallbackReg?.unregister(); diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index f3524687..fee2be9e 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; import 'package:pdfrx_engine/pdfrx_engine.dart'; +// ignore: implementation_imports +import 'package:pdfrx_engine/src/native/pdf_file_cache.dart'; const _kPasswordErrorCode = 'wrong-password'; @@ -167,43 +167,18 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, - }) async { - await init(); - final clientFactory = Pdfrx.createHttpClient ?? () => http.Client(); - final client = clientFactory(); - try { - final request = http.Request('GET', uri); - if (headers != null && headers.isNotEmpty) { - request.headers.addAll(headers); - } - final response = await client.send(request); - if (response.statusCode < 200 || response.statusCode >= 300) { - throw PdfException( - 'Failed to download PDF from $uri (HTTP ${response.statusCode}).', - ); - } - final total = response.contentLength; - progressCallback?.call(0, total); - - final builder = BytesBuilder(copy: false); - var downloaded = 0; - await for (final chunk in response.stream) { - builder.add(chunk); - downloaded += chunk.length; - progressCallback?.call(downloaded, total); - } - final data = builder.takeBytes(); - return openData( - data, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - sourceName: uri.toString(), - ); - } finally { - client.close(); - } - } + Duration? timeout, + }) => pdfDocumentFromUri( + uri, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + progressCallback: progressCallback, + useRangeAccess: preferRangeAccess, + headers: headers, + timeout: timeout, + entryFunctions: this, + ); @override Future reloadFonts() async { diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 7b1d985d..84c365a0 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -296,7 +296,9 @@ Future pdfDocumentFromUri( bool useRangeAccess = true, Map? headers, Duration? timeout, + PdfrxEntryFunctions? entryFunctions, }) async { + entryFunctions ??= PdfrxEntryFunctions.instance; progressCallback?.call(0); cache ??= await PdfFileCache.fromUri(uri); final httpClientWrapper = _HttpClientWrapper(Pdfrx.createHttpClient ?? () => http.Client()); @@ -315,10 +317,11 @@ Future pdfDocumentFromUri( timeout: timeout, ); if (result.isFullDownload) { - return await PdfDocument.openFile( + return await entryFunctions.openFile( cache.filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, ); } } else { @@ -342,7 +345,7 @@ Future pdfDocumentFromUri( if (result.isFullDownload || result.notModified) { cache.close(); // close the cache file before opening it. httpClientWrapper.reset(); - return await PdfDocument.openFile( + return await entryFunctions.openFile( cache.filePath, passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, @@ -352,7 +355,7 @@ Future pdfDocumentFromUri( } } - return await PdfDocument.openCustom( + return await entryFunctions.openCustom( read: (buffer, position, size) async { final totalSize = size; final end = position + size; @@ -361,7 +364,15 @@ Future pdfDocumentFromUri( final blockId = p ~/ cache!.blockSize; final isAvailable = cache.isCached(blockId); if (!isAvailable) { - await _downloadBlock(httpClientWrapper, uri, cache, progressCallback, blockId, headers: headers, timeout: timeout); + await _downloadBlock( + httpClientWrapper, + uri, + cache, + progressCallback, + blockId, + headers: headers, + timeout: timeout, + ); } final readEnd = min(p + size, (blockId + 1) * cache.blockSize); final sizeToRead = readEnd - p; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 389289ce..b9d957c4 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -376,6 +376,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { useRangeAccess: preferRangeAccess, headers: headers, timeout: timeout, + entryFunctions: this, ); static Future _openByFunc( diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index bd07e5bf..ec567f98 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -351,6 +351,8 @@ abstract class PdfDocument { /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. /// /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). + /// + /// [timeout] is used to specify the timeout duration for each HTTP request (Only supported on non-Web platforms). static Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -360,6 +362,7 @@ abstract class PdfDocument { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }) => PdfrxEntryFunctions.instance.openUri( uri, passwordProvider: passwordProvider, @@ -369,6 +372,7 @@ abstract class PdfDocument { preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, + timeout: timeout, ); /// Load pages progressively. From a3e8519128e85e411db848c4dbd703c99a15675c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 00:35:36 +0900 Subject: [PATCH 458/663] Release pdfrx_engine v0.2.3, pdfrx v2.2.6, and pdfrx_coregraphics v0.1.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_coregraphics/CHANGELOG.md | 4 ++++ packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 8 files changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a56f50a8..94449e37 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.5 + pdfrx: ^2.2.6 ``` ### For Pure Dart Applications @@ -54,7 +54,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.2.2 + pdfrx_engine: ^0.2.3 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index aa000dbd..a0263e74 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.2.6 + +- Added configurable timeout parameter to `openUri` and `pdfDocumentFromUri` functions ([#509](https://github.com/espresso3389/pdfrx/pull/509)) +- Updated to pdfrx_engine 0.2.3 + # 2.2.5 - Experimental iOS/macOS direct symbol lookup to address SwiftPM TestFlight/App Store symbol lookup issues ([#501](https://github.com/espresso3389/pdfrx/issues/501)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 5b65f1c5..dc882c8a 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.5 + pdfrx: ^2.2.6 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4ac4168e..372743a4 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.5 +version: 2.2.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.2 + pdfrx_engine: ^0.2.3 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 4b29f4a9..bbf237c7 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.6 + +- Updated to pdfrx_engine 0.2.3 + ## 0.1.5 - Fixed destination handling issues where some PDF destinations could not be processed correctly diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 2ed7d225..110081e5 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.5 +version: 0.1.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.2.2 + pdfrx_engine: ^0.2.3 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 3d9cf450..d09a4fe2 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.3 + +- Added configurable timeout parameter to `openUri` and `pdfDocumentFromUri` functions ([#509](https://github.com/espresso3389/pdfrx/pull/509)) + ## 0.2.2 - Experimental support for Apple platforms direct symbol lookup to address TestFlight/App Store symbol lookup issues ([#501](https://github.com/espresso3389/pdfrx/issues/501)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 6a1dfb44..5254ea38 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.2.2 +version: 0.2.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From ad2b403e2c80061f0b85dbdef95111a0fbc030ae Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 01:06:12 +0900 Subject: [PATCH 459/663] #508: PdfViewer.uri supports timeout --- packages/pdfrx/lib/src/pdf_document_ref.dart | 5 ++ .../pdfrx/lib/src/widgets/pdf_viewer.dart | 83 ++++++++++--------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index 3f9754ba..148cc791 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -154,6 +154,7 @@ class PdfDocumentRefUri extends PdfDocumentRef { this.preferRangeAccess = false, this.headers, this.withCredentials = false, + this.timeout, this.useProgressiveLoading = true, PdfDocumentRefKey? key, }) : super(key: key ?? PdfDocumentRefKey(uri.toString())); @@ -174,6 +175,9 @@ class PdfDocumentRefUri extends PdfDocumentRef { /// Whether to include credentials in the request (Only supported on Web). final bool withCredentials; + /// Timeout duration for loading the document. (Only supported on non-Web platforms). + final Duration? timeout; + /// Whether to use progressive loading or not. final bool useProgressiveLoading; @@ -189,6 +193,7 @@ class PdfDocumentRefUri extends PdfDocumentRef { preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, + timeout: timeout, ); } } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 3cc0a8df..92412443 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -33,10 +33,10 @@ import 'pdf_page_links_overlay.dart'; class PdfViewer extends StatefulWidget { /// Create [PdfViewer] from a [PdfDocumentRef]. /// - /// [documentRef] is the [PdfDocumentRef]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. + /// - [documentRef] is the [PdfDocumentRef]. + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. const PdfViewer( this.documentRef, { super.key, @@ -47,13 +47,13 @@ class PdfViewer extends StatefulWidget { /// Create [PdfViewer] from an asset. /// - /// [assetName] is the asset name. - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// - [assetName] is the asset name. + /// - [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// - [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. PdfViewer.asset( String assetName, { PdfPasswordProvider? passwordProvider, @@ -72,13 +72,13 @@ class PdfViewer extends StatefulWidget { /// Create [PdfViewer] from a file. /// - /// [path] is the file path. - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// - [path] is the file path. + /// - [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// - [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. PdfViewer.file( String path, { PdfPasswordProvider? passwordProvider, @@ -97,16 +97,17 @@ class PdfViewer extends StatefulWidget { /// Create [PdfViewer] from a URI. /// - /// [uri] is the URI. - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// - [uri] is the URI. + /// - [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// - [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. - /// [preferRangeAccess] to prefer range access to download the PDF. The default is false. (Not supported on Web). - /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. - /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. + /// - [preferRangeAccess] to prefer range access to download the PDF. The default is false. (Not supported on Web). + /// - [headers] is used to specify additional HTTP headers especially for authentication/authorization. + /// - [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). + /// - [timeout] is the timeout duration for loading the document. (Only supported on non-Web platforms). PdfViewer.uri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -119,6 +120,7 @@ class PdfViewer extends StatefulWidget { bool preferRangeAccess = false, Map? headers, bool withCredentials = false, + Duration? timeout, }) : documentRef = PdfDocumentRefUri( uri, passwordProvider: passwordProvider, @@ -127,19 +129,20 @@ class PdfViewer extends StatefulWidget { preferRangeAccess: preferRangeAccess, headers: headers, withCredentials: withCredentials, + timeout: timeout, ); /// Create [PdfViewer] from a byte data. /// - /// [data] is the byte data. - /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// - [data] is the byte data. + /// - [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// - [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// - [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. PdfViewer.data( Uint8List data, { required String sourceName, @@ -160,16 +163,16 @@ class PdfViewer extends StatefulWidget { /// Create [PdfViewer] from a custom source. /// - /// [fileSize] is the size of the PDF file. - /// [read] is the function to read the PDF file. - /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// - [fileSize] is the size of the PDF file. + /// - [read] is the function to read the PDF file. + /// - [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// - [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// - [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password /// or not. For more info, see [PdfPasswordProvider]. - /// [controller] is the controller to control the viewer. - /// [params] is the parameters to customize the viewer. - /// [initialPageNumber] is the page number to show initially. + /// - [controller] is the controller to control the viewer. + /// - [params] is the parameters to customize the viewer. + /// - [initialPageNumber] is the page number to show initially. PdfViewer.custom({ required int fileSize, required FutureOr Function(Uint8List buffer, int position, int size) read, From 8d010ac10b3156534402445f69c82031fe80adfe Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 01:28:57 +0900 Subject: [PATCH 460/663] Release pdfrx v2.2.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94449e37..0c2ecc6d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.6 + pdfrx: ^2.2.7 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index a0263e74..ba0945ef 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.7 + +- `PdfViewer.uri` now supports timeout parameter ([#508](https://github.com/espresso3389/pdfrx/issues/508)) + # 2.2.6 - Added configurable timeout parameter to `openUri` and `pdfDocumentFromUri` functions ([#509](https://github.com/espresso3389/pdfrx/pull/509)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index dc882c8a..b29e6689 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.6 + pdfrx: ^2.2.7 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 372743a4..b9bc93ff 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.6 +version: 2.2.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 7f38ba1b5aa5dc11e24d7eb9277c27c0060f35ba Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 01:58:38 +0900 Subject: [PATCH 461/663] FIXED: The type parameter 'T' shadows a type parameter from the enclosing function. lib/src/native/pdfium.dart:47:27 --- packages/pdfrx_engine/lib/src/native/pdfium.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index 6389b1c6..c380a592 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -35,7 +35,6 @@ pdfium_bindings.pdfium get pdfium { } set pdfium(pdfium_bindings.pdfium value) { - //print('Debug: Overriding pdfium bindings'); _pdfium = value; } @@ -44,9 +43,8 @@ typedef PdfrxNativeFunctionLookup = ffi.Pointer Fun PdfrxNativeFunctionLookup? createPdfrxNativeFunctionLookup() { if (Pdfrx.pdfiumNativeBindings != null) { final bindings = Pdfrx.pdfiumNativeBindings!; - ffi.Pointer lookup(String symbolName) { + ffi.Pointer lookup(String symbolName) { final ptr = bindings[symbolName]; - //print('Lookup symbol: $symbolName -> $ptr'); if (ptr == null) throw Exception('Failed to find binding for $symbolName'); return ffi.Pointer.fromAddress(ptr); } From cb66b1df0d62f371f157b9c227fa78340cd3dceb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 02:09:05 +0900 Subject: [PATCH 462/663] Document updates --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index ec567f98..0d97f068 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -72,7 +72,16 @@ class Pdfrx { static Map? pdfiumNativeBindings; } +/// The class is used to implement Pdfrx's backend functions. +/// +/// In normal usage, you should use [PdfDocument]'s static functions to open PDF files instead of using this class directly. +/// +/// [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) provide an alternative implementation of this +/// class for Apple platforms. abstract class PdfrxEntryFunctions { + /// Singleton instance of [PdfrxEntryFunctions]. + /// + /// [PdfDocument] internally calls this instance to open PDF files. static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. @@ -87,6 +96,7 @@ abstract class PdfrxEntryFunctions { /// To avoid such problems, you can wrap the code that calls those libraries with this function. Future suspendPdfiumWorkerDuringAction(FutureOr Function() action); + /// See [PdfDocument.openAsset]. Future openAsset( String name, { PdfPasswordProvider? passwordProvider, @@ -94,6 +104,7 @@ abstract class PdfrxEntryFunctions { bool useProgressiveLoading = false, }); + /// See [PdfDocument.openData]. Future openData( Uint8List data, { PdfPasswordProvider? passwordProvider, @@ -104,6 +115,7 @@ abstract class PdfrxEntryFunctions { void Function()? onDispose, }); + /// See [PdfDocument.openFile]. Future openFile( String filePath, { PdfPasswordProvider? passwordProvider, @@ -111,6 +123,7 @@ abstract class PdfrxEntryFunctions { bool useProgressiveLoading = false, }); + /// See [PdfDocument.openCustom]. Future openCustom({ required FutureOr Function(Uint8List buffer, int position, int size) read, required int fileSize, @@ -122,6 +135,7 @@ abstract class PdfrxEntryFunctions { void Function()? onDispose, }); + /// See [PdfDocument.openUri]. Future openUri( Uri uri, { PdfPasswordProvider? passwordProvider, @@ -153,18 +167,26 @@ abstract class PdfrxEntryFunctions { PdfrxBackend get backend; } +/// Pdfrx backend types. enum PdfrxBackend { /// PDFium backend. pdfium, /// PDFium WebAssembly backend for Web platform. + /// + /// The implementation for this is provided by [pdfrx](https://pub.dev/packages/pdfrx) package. pdfiumWasm, /// pdfKit (CoreGraphics) backend for Apple platforms. + /// + /// The implementation for this is provided by [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) package. pdfKit, - /// Mock backend for testing. + /// Mock backend for internal consistency. mock, + + /// Unknown backend. + unknown, } /// Callback function to notify download progress. From 4ecc0ce030bb7e5042a46bd27b56eed1c2f2f99f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 25 Oct 2025 02:39:37 +0900 Subject: [PATCH 463/663] WIP --- .vscode/c_cpp_properties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 7671433d..7e52833e 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,6 +1,6 @@ { "env": { - "ndkVersion": "21.1.6352462" + "ndkVersion": "27.0.12077973" }, "configurations": [ { From 24b463ff325e64007dd8badfe1877b46a136b552 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 27 Oct 2025 18:04:15 +0900 Subject: [PATCH 464/663] Enable Windows ARM64 build test (just for testing) Rel: #388 --- .github/workflows/build-test.yml | 56 +++++++++++++-------------- packages/pdfrx/windows/CMakeLists.txt | 14 +++---- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e1889917..f93496fb 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -44,11 +44,11 @@ on: required: false type: boolean default: true - # build_windows_arm64: - # description: 'Build Windows ARM64' - # required: false - # type: boolean - # default: true + build_windows_arm64: + description: 'Build Windows ARM64' + required: false + type: boolean + default: true build_web: description: 'Build Web' required: false @@ -237,30 +237,28 @@ jobs: run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Windows ARM64 build (requires ARM64 runner or cross-compilation) - # windows-arm64: - # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} - # runs-on: windows-11-arm - # continue-on-error: true # ARM64 build might fail on x64 runners - # steps: - # - uses: actions/checkout@v4 - - # - name: Setup Flutter - # shell: pwsh - # run: | - # git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter - # echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - # C:\flutter\bin\flutter.bat config --no-enable-android - # C:\flutter\bin\flutter.bat channel stable - # C:\flutter\bin\flutter.bat doctor -v - # - name: Monorepo setup - # run: ~/flutter/bin/dart pub get - # - name: Install dependencies - # working-directory: packages/pdfrx/example/viewer - # run: C:\flutter\bin\flutter.bat pub get - - # - name: Build Windows ARM64 - # working-directory: packages/pdfrx/example/viewer - # run: C:\flutter\bin\flutter.bat build windows --debug --verbose + windows-arm64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} + runs-on: windows-11-arm + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup Flutter (master branch for ARM64 support) + shell: pwsh + run: | + git clone https://github.com/flutter/flutter.git --depth 1 C:\flutter + echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + C:\flutter\bin\flutter.bat config --no-enable-android + C:\flutter\bin\flutter.bat doctor -v + - name: Monorepo setup + run: ~/flutter/bin/dart pub get + - name: Install dependencies + working-directory: packages/pdfrx/example/viewer + run: C:\flutter\bin\flutter.bat pub get + + - name: Build Windows ARM64 + working-directory: packages/pdfrx/example/viewer + run: C:\flutter\bin\flutter.bat build windows --debug --verbose # Web build web: diff --git a/packages/pdfrx/windows/CMakeLists.txt b/packages/pdfrx/windows/CMakeLists.txt index 03b7083e..b526183a 100644 --- a/packages/pdfrx/windows/CMakeLists.txt +++ b/packages/pdfrx/windows/CMakeLists.txt @@ -23,14 +23,14 @@ set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) file(MAKE_DIRECTORY ${PDFIUM_RELEASE_DIR}) # Determine target processor name for Windows -# IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") SET(CPU_NAME "x64") -# ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") -# SET(CPU_NAME "arm64") -# ELSE() -# MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only AMD64 and ARM64 are supported.") -# ENDIF() -# message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) +ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") + SET(CPU_NAME "arm64") +ELSE() + MESSAGE(FATAL_ERROR "Unsupported Windows architecture \"${CMAKE_SYSTEM_PROCESSOR}\". Only AMD64 and ARM64 are supported.") +ENDIF() +message( STATUS "Target Windows CPU Name: ${CPU_NAME}" ) set(PDFIUM_PLATFORM "win") set(PDFIUM_LIB_FILENAME "pdfium.dll") From 82085447743a86f8fed1af1f573439457a32bca7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 27 Oct 2025 18:24:38 +0900 Subject: [PATCH 465/663] WIP: arm64 windows config. --- .github/workflows/build-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f93496fb..c942e225 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -250,8 +250,7 @@ jobs: echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append C:\flutter\bin\flutter.bat config --no-enable-android C:\flutter\bin\flutter.bat doctor -v - - name: Monorepo setup - run: ~/flutter/bin/dart pub get + - name: Install dependencies working-directory: packages/pdfrx/example/viewer run: C:\flutter\bin\flutter.bat pub get From 1489afbd7246462ffb311b637f21c9eb69cc74a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:58:44 +0000 Subject: [PATCH 466/663] Bump actions/upload-artifact from 4.6.2 to 5.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...330a01c490aca151604b8cf639adc76d48f6c5d4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 1be45c3b..febbb26c 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -15,7 +15,7 @@ jobs: - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: pdfium-${{ matrix.target }} path: ./packages/pdfrx/darwin/pdfium/pdfium-${{ matrix.target }}.zip From 1e20020dad5c6fb8f2d38b5c7f371d33c22d4402 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:44:50 +0000 Subject: [PATCH 467/663] Bump actions/download-artifact from 5.0.0 to 6.0.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/634f93cb2916e3fdff6788551b99b062d0335ce0...018cc2cf5baa6db3ef3c5f8a56943fffe632ef53) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 1be45c3b..1550e4cc 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -25,12 +25,12 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: pdfium-ios path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ From e4af566775abc60388cba1360bdec09d57e865f5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 28 Oct 2025 02:23:31 +0900 Subject: [PATCH 468/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 6 +++--- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f31ef1f8..2e40b478 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -208,7 +208,7 @@ class PdfViewer extends StatefulWidget { class _PdfViewerState extends State with SingleTickerProviderStateMixin - implements PdfTextSelectionDelegate, DocumentCoordinateConverter { + implements PdfTextSelectionDelegate, PdfViewerCoordinateConverter { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); late final AnimationController _animController; @@ -4010,7 +4010,7 @@ class _PdfViewerState extends State } @override - DocumentCoordinateConverter get doc2local => this; + PdfViewerCoordinateConverter get doc2local => this; void forceRepaintAllPageImages() { _imageCache.cancelAllPendingRenderings(); @@ -4787,7 +4787,7 @@ class PdfViewerController extends ValueListenable { Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); /// Converts document coordinates to local coordinates. - DocumentCoordinateConverter get doc2local => _state; + PdfViewerCoordinateConverter get doc2local => _state; /// Provided to workaround certain widgets eating wheel events. Use with [Listener.onPointerSignal]. void handlePointerSignalEvent(PointerSignalEvent event) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 4fef1447..2aa924c5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1048,11 +1048,11 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { Future selectWord(Offset position); /// Convert document coordinates to local coordinates and vice versa. - DocumentCoordinateConverter get doc2local; + PdfViewerCoordinateConverter get doc2local; } /// Utility class to convert document coordinates to local coordinates and vice versa. -abstract class DocumentCoordinateConverter { +abstract class PdfViewerCoordinateConverter { /// Convert a document position to a local position in the specified [context]. Offset? offsetToLocal(BuildContext context, Offset? position); From 7b72e64018c229229adfc85fc6604371d00228c5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 28 Oct 2025 02:38:09 +0900 Subject: [PATCH 469/663] Don't import native implementation. --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2aa924c5..35a3bbef 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../pdfrx.dart'; -import '../utils/native/native.dart'; +import '../utils/platform.dart'; /// Viewer customization parameters. /// From 6f3949efe0951c2098b68860aafcafda4812bab2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 28 Oct 2025 02:58:06 +0900 Subject: [PATCH 470/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 2e40b478..e62bfdc3 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -914,9 +914,7 @@ class _PdfViewerState extends State void _onAnimationEnd() { // Check if all animations have completed - final ivState = _interactiveViewerKey.currentState; - final hasAnimations = - (ivState != null && (ivState as dynamic).hasActiveAnimations == true) || _animController.isAnimating; + final hasAnimations = (_interactiveViewerState?.hasActiveAnimations == true) || _animController.isAnimating; if (!hasAnimations) { // Animations complete, clear flags From 7ea0d5a797c8e41dcef7b3567d0bbb6bc7b2fdc6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 28 Oct 2025 04:18:34 +0900 Subject: [PATCH 471/663] WIP: introducing PdfPageRange (not finished) --- packages/pdfrx/example/viewer/lib/main.dart | 10 +-- .../lib/src/widgets/pdf_page_layout.dart | 51 ++++++++++-- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 79 +++++++----------- .../src/widgets/pdf_viewer_scroll_thumb.dart | 83 ++++++------------- 4 files changed, 103 insertions(+), 120 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 0e37f960..febe6c63 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -311,7 +311,7 @@ class _MainPageState extends State with WidgetsBindingObserver { keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), maxScale: 8, //fitMode: FitMode.fill, - pageTransition: PageTransition.discrete, + //pageTransition: PageTransition.discrete, // scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // @@ -342,12 +342,12 @@ class _MainPageState extends State with WidgetsBindingObserver { controller: controller, orientation: ScrollbarOrientation.right, thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + thumbBuilder: (context, thumbSize, showRange, pageRange, controller) => Container( color: Colors.black, child: isHorizontalLayout ? null : Center( - child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), + child: Text(pageRange?.label ?? '', style: const TextStyle(color: Colors.white)), ), ), ), @@ -356,12 +356,12 @@ class _MainPageState extends State with WidgetsBindingObserver { controller: controller, orientation: ScrollbarOrientation.bottom, thumbSize: const Size(40, 25), - thumbBuilder: (context, thumbSize, pageNumber, controller) => Container( + thumbBuilder: (context, thumbSize, showRange, pageRange, controller) => Container( color: Colors.black, child: !isHorizontalLayout ? null : Center( - child: Text(pageNumber.toString(), style: const TextStyle(color: Colors.white)), + child: Text(pageRange?.label ?? '', style: const TextStyle(color: Colors.white)), ), ), ), diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index 935b2eb6..84b7894a 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -574,7 +574,7 @@ class PdfPageLayout { /// - Pages 4-5: spread 2 /// - `pageToSpreadIndex = [0, 1, 1, 2, 2, ...]` (0-based: index 0 = page 1) /// - `spreadLayouts = [Rect(cover bounds), Rect(pages 2-3 bounds), Rect(pages 4-5 bounds), ...]` -abstract class PdfSpreadLayout extends PdfPageLayout { +class PdfSpreadLayout extends PdfPageLayout { PdfSpreadLayout({ required super.pageLayouts, required super.documentSize, @@ -601,8 +601,8 @@ abstract class PdfSpreadLayout extends PdfPageLayout { return spreadLayouts[pageToSpreadIndex[pageNumber - 1]]; } - /// Get the page range (first, last) for the spread containing pageNumber. - ({int first, int last}) getPageRange(int pageNumber) { + /// Get the page range for the spread containing pageNumber. + PdfPageRange getPageRange(int pageNumber) { final spreadIndex = pageToSpreadIndex[pageNumber - 1]; var first = -1; var last = -1; @@ -612,14 +612,14 @@ abstract class PdfSpreadLayout extends PdfPageLayout { last = i + 1; // Convert to 1-based } } - return (first: first, last: last); + return PdfPageRange(first, last); } /// Get the first page number of the spread containing pageNumber. - int getSpreadFirstPage(int pageNumber) => getPageRange(pageNumber).first; + int getSpreadFirstPage(int pageNumber) => getPageRange(pageNumber).firstPageNumber; /// Get the last page number of the spread containing pageNumber. - int getSpreadLastPage(int pageNumber) => getPageRange(pageNumber).last; + int getSpreadLastPage(int pageNumber) => getPageRange(pageNumber).lastPageNumber; /// Get the first page number of a spread by its index. int? getFirstPageOfSpread(int spreadIndex) { @@ -1047,3 +1047,42 @@ Size _calculatePageSize({ return Size(targetWidth, page.height * scale); } } + +/// Represents a range of pages in the PDF document. +@immutable +class PdfPageRange { + /// Creates a page range from [firstPageNumber] to [lastPageNumber], inclusive. + const PdfPageRange(this.firstPageNumber, this.lastPageNumber); + + /// Creates a page range representing a single page. + const PdfPageRange.single(int pageNumber) : firstPageNumber = pageNumber, lastPageNumber = pageNumber; + + /// 1-based page number of first page in range. + /// + /// [firstPageNumber] is always <= [lastPageNumber]. They can be equal for a single-page range. + final int firstPageNumber; + + /// 1-based page number of last page in range. + /// + /// [lastPageNumber] is always >= [firstPageNumber]. They can be equal for a single-page range. + /// As "last" implies, this is inclusive. + final int lastPageNumber; + + /// Returns a string representation of the page range. + /// + /// If the range is a single page, returns just that page number (e.g., "5"). + /// If the range spans multiple pages, returns in "start-last" format (e.g., "3-7"). + String get label => firstPageNumber == lastPageNumber ? '$firstPageNumber' : '$firstPageNumber-$lastPageNumber'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfPageRange && firstPageNumber == other.firstPageNumber && lastPageNumber == other.lastPageNumber; + } + + @override + int get hashCode => Object.hash(firstPageNumber, lastPageNumber); + + @override + String toString() => 'PdfPageRange($label)'; +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e62bfdc3..df4f55cb 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -221,7 +221,7 @@ class _PdfViewerState extends State static const _defaultMinScale = 0.1; double _fitScale = _defaultMinScale; // Scale calculated based on fitMode for page positioning int? _pageNumber; - ({int first, int last})? _visiblePageRange; + PdfPageRange? _visiblePageRange; bool _initialized = false; StreamSubscription? _documentSubscription; final _interactiveViewerKey = GlobalKey(); @@ -1292,43 +1292,43 @@ class _PdfViewerState extends State } } + double _calcPageIntersectionPercentage(int pageNumber, Rect visibleRect) { + final rect = _layout!.pageLayouts[pageNumber - 1]; + final intersection = rect.intersect(visibleRect); + if (intersection.isEmpty) return 0; + final area = intersection.width * intersection.height; + return area / (rect.width * rect.height); + } + + static const double _kPageIntersectionThreshold = 0.2; + int? _guessCurrentPageNumber() { if (_layout == null || _viewSize == null) { _visiblePageRange = null; return null; } + final visibleRect = _visibleRect; if (widget.params.calculateCurrentPageNumber != null) { - final pageNumber = widget.params.calculateCurrentPageNumber!(_visibleRect, _layout!.pageLayouts, _controller!); - _updateVisiblePageRange(); + final pageNumber = widget.params.calculateCurrentPageNumber!(visibleRect, _layout!.pageLayouts, _controller!); + _updateVisiblePageRange(visibleRect); return pageNumber; } - final visibleRect = _visibleRect; - final layout = _layout!; - // Calculate visible page range (any page with any intersection) - _updateVisiblePageRange(); - - double calcPageIntersectionArea(int pageNumber) { - final rect = layout.pageLayouts[pageNumber - 1]; - final intersection = rect.intersect(visibleRect); - if (intersection.isEmpty) return 0; - final area = intersection.width * intersection.height; - return area / (rect.width * rect.height); - } + _updateVisiblePageRange(visibleRect); if (_gotoTargetPageNumber != null && _gotoTargetPageNumber! > 0 && _gotoTargetPageNumber! <= _document!.pages.length) { - final ratio = calcPageIntersectionArea(_gotoTargetPageNumber!); - if (ratio > .2) return _gotoTargetPageNumber; + final ratio = _calcPageIntersectionPercentage(_gotoTargetPageNumber!, visibleRect); + if (ratio > _kPageIntersectionThreshold) return _gotoTargetPageNumber; } _gotoTargetPageNumber = null; int? pageNumber; double maxRatio = 0; for (var i = 1; i <= _document!.pages.length; i++) { - final ratio = calcPageIntersectionArea(i); + final ratio = _calcPageIntersectionPercentage(i, visibleRect); if (ratio == 0) continue; if (ratio > maxRatio) { maxRatio = ratio; @@ -1339,28 +1339,24 @@ class _PdfViewerState extends State } /// Calculate the range of all pages that have any intersection with the visible viewport. - void _updateVisiblePageRange() { + void _updateVisiblePageRange(Rect visibleRect) { if (_layout == null || _document == null) { _visiblePageRange = null; return; } - final visibleRect = _visibleRect; - final layout = _layout!; int? firstVisible; int? lastVisible; - for (var i = 1; i <= _document!.pages.length; i++) { - final rect = layout.pageLayouts[i - 1]; - final intersection = rect.intersect(visibleRect); - if (!intersection.isEmpty) { + final ratio = _calcPageIntersectionPercentage(i, visibleRect); + if (ratio > _kPageIntersectionThreshold) { firstVisible ??= i; lastVisible = i; } } if (firstVisible != null && lastVisible != null) { - _visiblePageRange = (first: firstVisible, last: lastVisible); + _visiblePageRange = PdfPageRange(firstVisible, lastVisible); } else { _visiblePageRange = null; } @@ -1562,9 +1558,7 @@ class _PdfViewerState extends State if (processedPages.contains(pageNum)) continue; // Get all pages in this spread - final (:first, :last) = layout.getPageRange(pageNum); - final firstPage = first; - final lastPage = last; + final range = layout.getPageRange(pageNum); final spreadBounds = layout.getSpreadBounds(pageNum); // Calculate scale for this spread @@ -1602,7 +1596,7 @@ class _PdfViewerState extends State final crossAxisSpreadContentStart = viewportPaddingCross + (margin * spreadScale); // Position all pages in this spread - for (var p = firstPage; p <= lastPage; p++) { + for (var p = range.firstPageNumber; p <= range.lastPageNumber; p++) { final pageRect = layout.pageLayouts[p - 1]; final scaledPageWidth = pageRect.width * spreadScale; final scaledPageHeight = pageRect.height * spreadScale; @@ -1733,7 +1727,7 @@ class _PdfViewerState extends State newSpreadLayouts.add(Rect.fromLTRB(minLeft, minTop, maxRight, maxBottom)); } - return _DiscretePdfSpreadLayout( + return PdfSpreadLayout( pageLayouts: newPageLayouts, documentSize: newDocSize, spreadLayouts: newSpreadLayouts, @@ -1741,7 +1735,7 @@ class _PdfViewerState extends State ); } - return _DiscretePdfPageLayout(pageLayouts: newPageLayouts, documentSize: newDocSize); + return PdfPageLayout(pageLayouts: newPageLayouts, documentSize: newDocSize); } /// Returns the boundary rect for discrete mode (current page/spread bounds). @@ -2035,8 +2029,8 @@ class _PdfViewerState extends State (_isActivelyZooming || _hasActiveAnimations)) { final layout = _layout; if (layout is PdfSpreadLayout) { - final (:first, :last) = layout.getPageRange(_pageNumber!); - shouldSkipForDiscrete = i + 1 < first || i + 1 > last; + final range = layout.getPageRange(_pageNumber!); + shouldSkipForDiscrete = i + 1 < range.firstPageNumber || i + 1 > range.lastPageNumber; } else { shouldSkipForDiscrete = i + 1 != _pageNumber; } @@ -4452,7 +4446,7 @@ class PdfViewerController extends ValueListenable { /// Returns null if no pages are visible. /// This is useful for displaying page ranges in UI elements like scroll thumbs, /// especially when zoomed out or using spread layouts where multiple pages are visible. - ({int first, int last})? get visiblePageRange => _state._visiblePageRange; + PdfPageRange? get visiblePageRange => _state._visiblePageRange; /// The document reference associated to the [PdfViewer]. PdfDocumentRef get documentRef => _state.widget.documentRef; @@ -5054,18 +5048,3 @@ class _CanvasLinkPainter { } } } - -/// Wrapper layout for discrete mode single page layouts with viewport spacing. -class _DiscretePdfPageLayout extends PdfPageLayout { - _DiscretePdfPageLayout({required super.pageLayouts, required super.documentSize}); -} - -/// Wrapper layout for discrete mode spread layouts with viewport spacing. -class _DiscretePdfSpreadLayout extends PdfSpreadLayout { - _DiscretePdfSpreadLayout({ - required super.pageLayouts, - required super.documentSize, - required super.spreadLayouts, - required super.pageToSpreadIndex, - }); -} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index 4ef4461f..c7c3d271 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -14,8 +14,7 @@ class PdfViewerScrollThumb extends StatefulWidget { this.thumbSize, this.margin = 2, this.thumbBuilder, - this.visiblePageRangeBuilder, - this.useVisiblePageRange, + this.showVisiblePageRange, super.key, }); @@ -31,23 +30,21 @@ class PdfViewerScrollThumb extends StatefulWidget { /// Margin from the viewer's edge. final double margin; - /// Function to customize the thumb widget for single-page display. - /// This is called when visiblePageRangeBuilder is not provided or returns null. - final Widget? Function(BuildContext context, Size thumbSize, int? pageNumber, PdfViewerController controller)? - thumbBuilder; - - /// Function to customize the thumb widget using the visible page range. - /// If provided and a visible page range is available, this takes precedence over thumbBuilder. - /// The first and last parameters indicate the range of all visible pages. - /// For single visible pages, first equals last. - final Widget? Function(BuildContext context, Size thumbSize, int first, int last, PdfViewerController controller)? - visiblePageRangeBuilder; - - /// Whether to use the visible page range (all pages with any intersection with the viewport). + /// Whether to show the visible page range (all pages with any intersection with the viewport). /// If null, automatically set to true for spread layouts and false otherwise. /// When true, the default thumb shows the range of all visible pages (e.g., "1-4"). /// When false, only shows the current page number. - final bool? useVisiblePageRange; + final bool? showVisiblePageRange; + + /// Function to customize the thumb widget. + final Widget? Function( + BuildContext context, + Size thumbSize, + bool showVisiblePageRange, + PdfPageRange? visiblePageRange, + PdfViewerController controller, + )? + thumbBuilder; /// Determine whether the orientation is vertical or not. bool get isVertical => orientation == ScrollbarOrientation.left || orientation == ScrollbarOrientation.right; @@ -68,52 +65,20 @@ class _PdfViewerScrollThumbState extends State { /// Build the thumb widget with visible page range awareness. Widget _buildThumbWidget(BuildContext context, Size thumbSize) { + final showVisiblePageRange = widget.showVisiblePageRange ?? widget.controller.layout is PdfSpreadLayout; final pageNumber = widget.controller.pageNumber; - - // If visiblePageRangeBuilder is provided and we have a visible range, use it - if (widget.visiblePageRangeBuilder != null) { - final visibleRange = widget.controller.visiblePageRange; - if (visibleRange != null) { - final (:first, :last) = visibleRange; - final result = widget.visiblePageRangeBuilder!(context, thumbSize, first, last, widget.controller); - if (result != null) return result; - } - } - - // Otherwise, if thumbBuilder is provided, use it - if (widget.thumbBuilder != null) { - return widget.thumbBuilder!(context, thumbSize, pageNumber, widget.controller) ?? - _buildDefaultThumb(thumbSize, pageNumber); - } - - // Use default builder - return _buildDefaultThumb(thumbSize, pageNumber); + final range = showVisiblePageRange + ? widget.controller.visiblePageRange + : pageNumber != null + ? PdfPageRange.single(pageNumber) + : null; + return widget.thumbBuilder?.call(context, thumbSize, showVisiblePageRange, range, widget.controller) ?? + _buildDefaultThumb(thumbSize, showVisiblePageRange, range); } /// Build default thumb widget with visible page range awareness. - Widget _buildDefaultThumb(Size thumbSize, int? pageNumber) { - String label; - if (pageNumber == null) { - label = ''; - } else { - final layout = widget.controller.layout; - // Auto-determine useVisiblePageRange if not explicitly set - final shouldUseVisibleRange = widget.useVisiblePageRange ?? (layout is PdfSpreadLayout); - - // Use visible page range if enabled and available - if (shouldUseVisibleRange) { - final visibleRange = widget.controller.visiblePageRange; - if (visibleRange != null) { - final (:first, :last) = visibleRange; - label = first == last ? '$first' : '$first-$last'; - } else { - label = '$pageNumber'; - } - } else { - label = '$pageNumber'; - } - } - + Widget _buildDefaultThumb(Size thumbSize, bool showVisiblePageRange, PdfPageRange? visibleRange) { + final label = visibleRange?.label; return Container( decoration: BoxDecoration( color: Colors.white, @@ -122,7 +87,7 @@ class _PdfViewerScrollThumbState extends State { BoxShadow(color: Colors.black.withAlpha(127), spreadRadius: 1, blurRadius: 1, offset: const Offset(1, 1)), ], ), - child: Center(child: Text(label)), + child: label != null ? Center(child: Text(label)) : const SizedBox(), ); } From 2c4ca34679f9f81e3690e3c9cd314e18d63c9086 Mon Sep 17 00:00:00 2001 From: james Date: Tue, 28 Oct 2025 09:48:58 +1030 Subject: [PATCH 472/663] fix for _goToXXX is initiated by non-user action not working for pageTransition.discrete --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index df4f55cb..26b75aa5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1742,7 +1742,7 @@ class _PdfViewerState extends State /// Used by boundaryProvider to restrict scrolling to current page. Rect? _getDiscreteBoundaryRect(Rect visibleRect, Size childSize) { final layout = _layout; - final currentPage = _pageNumber; + final currentPage = _gotoTargetPageNumber ?? _pageNumber; if (layout == null || currentPage == null || currentPage < 1 || currentPage > layout.pageLayouts.length) { return null; From 151de17f63aacba1cf760fb6f4fd0ddac0571d9d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 03:53:00 +0900 Subject: [PATCH 473/663] WIP: reorganize PdfPageLayout classes --- .../lib/src/utils/edge_insets_extensions.dart | 2 +- .../lib/src/widgets/pdf_page_layout.dart | 46 +++---- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 113 +++++++++--------- .../src/widgets/pdf_viewer_scroll_thumb.dart | 1 + 4 files changed, 84 insertions(+), 78 deletions(-) diff --git a/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart b/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart index a003c517..50efdbb4 100644 --- a/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart +++ b/packages/pdfrx/lib/src/utils/edge_insets_extensions.dart @@ -5,7 +5,7 @@ extension EdgeInsetsExtensions on EdgeInsets { /// Returns true if any of the EdgeInsets values (left, top, right, bottom) are infinite. bool get containsInfinite => left.isInfinite || right.isInfinite || top.isInfinite || bottom.isInfinite; - /// Inflates a given Rect by the EdgeInsets values if all values are finite, otherwise retruns the rect + /// Inflates a given Rect by the EdgeInsets values if all values are finite, otherwise returns the rect Rect inflateRectIfFinite(Rect rect) { if (containsInfinite) return rect; return inflateRect(rect); diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index 84b7894a..a00047fa 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -106,7 +106,7 @@ class PdfLayoutHelper { class PdfPageLayout { PdfPageLayout({required this.pageLayouts, required this.documentSize}) : primaryAxis = documentSize.height >= documentSize.width ? Axis.vertical : Axis.horizontal { - margin = impliedMargin(); + _impliedMargin = _calcImpliedMargin(); } /// Page rectangles positioned within the document coordinate space. @@ -120,7 +120,7 @@ class PdfPageLayout { /// Total document size including content margins. /// - /// This is the size of the scrollable content area and includes [margin] spacing + /// This is the size of the scrollable content area and includes [_impliedMargin] spacing /// on all sides. Does NOT include boundary margins - those are handled separately /// at the viewport level. /// @@ -139,11 +139,18 @@ class PdfPageLayout { /// Content margin around the document edges. /// - /// Calculated automatically via [impliedMargin] based on the spacing + /// Calculated automatically via [_calcImpliedMargin] based on the spacing /// between page dimensions and document size. /// /// See [getPageRectWithMargins] to get page bounds including margins. - late double margin; + late double _impliedMargin; + + /// Get the spread bounds for a given page number. + /// + /// For single page layouts, this simply returns the page bounds. + Rect getSpreadBounds(int pageNumber) { + return pageLayouts[pageNumber - 1]; + } /// Each layout implements its own calculation logic. /// Optional [helper] can be used for fit mode calculations. @@ -158,18 +165,25 @@ class PdfPageLayout { /// For single page layouts, this is the maximum page width. /// For spread layouts, this can be overridden to return the maximum spread width. double getMaxWidth({bool withMargins = false}) => - pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.width)) + (withMargins ? margin * 2 : 0); + pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.width)) + (withMargins ? _impliedMargin * 2 : 0); + + /// Gets the maximum height across all layout units. + /// For single page layouts, this is the maximum page height. + /// For spread layouts, this can be overridden to return the maximum spread height. + double getMaxHeight({bool withMargins = false}) => + pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.height)) + (withMargins ? _impliedMargin * 2 : 0); /// Calculates the implied margin based on document size and max page dimensions. /// This assumes pages are centered within the document size. - double impliedMargin() => primaryAxis == Axis.vertical + double _calcImpliedMargin() => primaryAxis == Axis.vertical ? (documentSize.width - getMaxWidth()) / 2 : (documentSize.height - getMaxHeight()) / 2; - Rect getPageRectWithMargins(int pageNumber) { + Rect getPageRectWithMargins(int pageNumber, {double? margin}) { if (pageNumber < 1 || pageNumber > pageLayouts.length) { throw RangeError('Invalid page number $pageNumber'); } + margin ??= _impliedMargin; final pageRect = pageLayouts[pageNumber - 1]; return Rect.fromLTWH( pageRect.left - margin, @@ -179,12 +193,6 @@ class PdfPageLayout { ); } - /// Gets the maximum height across all layout units. - /// For single page layouts, this is the maximum page height. - /// For spread layouts, this can be overridden to return the maximum spread height. - double getMaxHeight({bool withMargins = false}) => - pageLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.height)) + (withMargins ? margin * 2 : 0); - /// Gets the dimensions for a specific page number. /// For single page layouts, returns the page dimensions. /// For spread layouts, should return the spread dimensions containing the page. @@ -194,7 +202,7 @@ class PdfPageLayout { return null; } final pageRect = pageLayouts[pageNumber - 1]; - return pageRect.size + (withMargins ? Offset(margin * 2, margin * 2) : Offset.zero); + return pageRect.size + (withMargins ? Offset(_impliedMargin * 2, _impliedMargin * 2) : Offset.zero); } /// Calculates page sizes based on fit mode and scroll direction, to enable independent @@ -593,10 +601,8 @@ class PdfSpreadLayout extends PdfPageLayout { /// - Example: `pageToSpreadIndex[0]` = spread index for page 1 final List pageToSpreadIndex; - /// Get the spread index for a given page number. - int getSpreadIndex(int pageNumber) => pageToSpreadIndex[pageNumber - 1]; - /// Get the spread bounds for a given page number. + @override Rect getSpreadBounds(int pageNumber) { return spreadLayouts[pageToSpreadIndex[pageNumber - 1]]; } @@ -641,21 +647,21 @@ class PdfSpreadLayout extends PdfPageLayout { return null; } final spreadBounds = getSpreadBounds(pageNumber); - return spreadBounds.size + (withMargins ? Offset(margin * 2, margin * 2) : Offset.zero); + return spreadBounds.size + (withMargins ? Offset(_impliedMargin * 2, _impliedMargin * 2) : Offset.zero); } /// Gets the maximum spread width across all spreads. @override double getMaxWidth({bool withMargins = false}) { final maxWidthNoMargins = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.width)); - return maxWidthNoMargins + (withMargins ? margin * 2 : 0); + return maxWidthNoMargins + (withMargins ? _impliedMargin * 2 : 0); } /// Gets the maximum spread height across all spreads. @override double getMaxHeight({bool withMargins = false}) { final maxHeightNoMargins = spreadLayouts.fold(0.0, (maximum, rect) => max(maximum, rect.height)); - return maxHeightNoMargins + (withMargins ? margin * 2 : 0); + return maxHeightNoMargins + (withMargins ? _impliedMargin * 2 : 0); } @override diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 26b75aa5..66a805c8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -676,9 +676,7 @@ class _PdfViewerState extends State // Check if page fits in viewport on primary axis final layout = _layout!; final isPrimaryVertical = layout.primaryAxis == Axis.vertical; - final pageRect = layout is PdfSpreadLayout - ? layout.getSpreadBounds(_pageNumber!) - : layout.pageLayouts[_pageNumber! - 1]; + final pageRect = layout.getSpreadBounds(_pageNumber!); final pageSize = isPrimaryVertical ? pageRect.height : pageRect.width; final viewportSize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; final pageFitsInViewport = pageSize * zoom <= viewportSize; @@ -1053,14 +1051,14 @@ class _PdfViewerState extends State } final snapAnchor = _getSnapAnchor( - targetPage: targetPage, - currentPage: currentPage, + targetPageNumber: targetPage, + currentPageNumber: currentPage, layout: layout, isPrimaryVertical: isPrimaryVertical, endRect: endRect, ); - await _snapToPage(targetPage, anchor: snapAnchor, currentPage: currentPage); + await _snapToPage(targetPage, anchor: snapAnchor, currentPageNumber: currentPage); } /// Checks if viewport is at a page boundary in the given direction. @@ -1105,16 +1103,14 @@ class _PdfViewerState extends State /// Determines the snap anchor based on target page and current position. PdfPageAnchor _getSnapAnchor({ - required int targetPage, - required int currentPage, + required int targetPageNumber, + required int currentPageNumber, required PdfPageLayout layout, required bool isPrimaryVertical, required Rect endRect, }) { // Check if page/spread overflows on primary and cross axes at current zoom - final pageRect = layout is PdfSpreadLayout - ? layout.getSpreadBounds(targetPage) - : layout.pageLayouts[targetPage - 1]; + final pageRect = layout.getSpreadBounds(targetPageNumber); final pagePrimarySize = isPrimaryVertical ? pageRect.height : pageRect.width; final pageCrossSize = isPrimaryVertical ? pageRect.width : pageRect.height; @@ -1130,7 +1126,7 @@ class _PdfViewerState extends State } // Page overflows on at least one axis - if (targetPage == currentPage) { + if (targetPageNumber == currentPageNumber) { // Staying on current page - snap to nearest edge final pageCenter = isPrimaryVertical ? (pageRect.top + pageRect.bottom) / 2 @@ -1153,13 +1149,13 @@ class _PdfViewerState extends State if (isPrimaryVertical) { // If cross overflows, anchor to top-left/bottom-left instead of top-center/bottom-center return crossOverflows - ? (targetPage > currentPage ? PdfPageAnchor.topLeft : PdfPageAnchor.bottomLeft) - : (targetPage > currentPage ? PdfPageAnchor.topCenter : PdfPageAnchor.bottomCenter); + ? (targetPageNumber > currentPageNumber ? PdfPageAnchor.topLeft : PdfPageAnchor.bottomLeft) + : (targetPageNumber > currentPageNumber ? PdfPageAnchor.topCenter : PdfPageAnchor.bottomCenter); } else { // If cross overflows, anchor to top-left/top-right instead of center-left/center-right return crossOverflows - ? (targetPage > currentPage ? PdfPageAnchor.topLeft : PdfPageAnchor.topRight) - : (targetPage > currentPage ? PdfPageAnchor.centerLeft : PdfPageAnchor.centerRight); + ? (targetPageNumber > currentPageNumber ? PdfPageAnchor.topLeft : PdfPageAnchor.topRight) + : (targetPageNumber > currentPageNumber ? PdfPageAnchor.centerLeft : PdfPageAnchor.centerRight); } } } @@ -1168,40 +1164,37 @@ class _PdfViewerState extends State /// /// Transitions to whichever page has the most visible area on the primary axis. /// This works correctly at any zoom level, including when zoomed in. - int _getTargetPageBasedOnThreshold(int currentPage, double scrollDelta, bool isPrimaryVertical) { + int _getTargetPageBasedOnThreshold(int currentPageNumber, double scrollDelta, bool isPrimaryVertical) { final layout = _layout!; final visibleRect = _txController.value.calcVisibleRect(_viewSize!); // Calculate visible area for current page and adjacent pages - final currentPageBounds = layout is PdfSpreadLayout - ? layout.getSpreadBounds(currentPage) - : layout.pageLayouts[currentPage - 1]; - + final currentPageBounds = layout.getSpreadBounds(currentPageNumber); final currentPageVisibleArea = _calcPageIntersectionArea(visibleRect, currentPageBounds, isPrimaryVertical); // Check previous page (if exists) double prevPageVisibleArea = 0; - if (currentPage > 1) { - final prevPageBounds = layout.pageLayouts[currentPage - 2]; + if (currentPageNumber >= 2) { + final prevPageBounds = layout.pageLayouts[currentPageNumber - 2]; prevPageVisibleArea = _calcPageIntersectionArea(visibleRect, prevPageBounds, isPrimaryVertical); } // Check next page (if exists) double nextPageVisibleArea = 0; - if (currentPage < layout.pageLayouts.length) { - final nextPageBounds = layout.pageLayouts[currentPage]; + if (currentPageNumber < layout.pageLayouts.length) { + final nextPageBounds = layout.pageLayouts[currentPageNumber]; nextPageVisibleArea = _calcPageIntersectionArea(visibleRect, nextPageBounds, isPrimaryVertical); } // Transition to the page with the most visible area if (prevPageVisibleArea > currentPageVisibleArea && prevPageVisibleArea > nextPageVisibleArea) { - return _getAdjacentPage(currentPage, layout, -1); + return _getAdjacentPage(currentPageNumber, layout, -1); } else if (nextPageVisibleArea > currentPageVisibleArea && nextPageVisibleArea > prevPageVisibleArea) { - return _getAdjacentPage(currentPage, layout, 1); + return _getAdjacentPage(currentPageNumber, layout, 1); } // Current page has most visible area - snap back to current page - return currentPage; + return currentPageNumber; } /// Calculate the visible intersection area on the primary axis between visible rect and page bounds. @@ -1216,11 +1209,11 @@ class _PdfViewerState extends State } /// Snaps to the target page/spread with animation. - Future _snapToPage(int targetPage, {required PdfPageAnchor anchor, int? currentPage}) async { + Future _snapToPage(int targetPageNumber, {required PdfPageAnchor anchor, int? currentPageNumber}) async { final duration = const Duration(milliseconds: 400); // Only reset scale when advancing to a different page, not when snapping back to current page - final isAdvancingToNewPage = currentPage != null && targetPage != currentPage; + final isAdvancingToNewPage = currentPageNumber != null && targetPageNumber != currentPageNumber; if (!isAdvancingToNewPage) { // let InteractiveViewer's scroll physics handle snap back to current page @@ -1229,7 +1222,7 @@ class _PdfViewerState extends State if (isAdvancingToNewPage && _viewSize != null) { // Calculate fit scale for the target page - _calcFitScale(targetPage); + _calcFitScale(targetPageNumber); _adjustBoundaryMargins(_viewSize!, _fitScale); } @@ -1240,9 +1233,7 @@ class _PdfViewerState extends State // Calculate what the scale will be for this page final targetScale = _fitScale; final isPrimaryVertical = layout.primaryAxis == Axis.vertical; - final pageRect = layout is PdfSpreadLayout - ? layout.getSpreadBounds(targetPage) - : layout.pageLayouts[targetPage - 1]; + final pageRect = layout.getSpreadBounds(targetPageNumber); final pagePrimarySize = isPrimaryVertical ? pageRect.height : pageRect.width; final pageCrossSize = isPrimaryVertical ? pageRect.width : pageRect.height; @@ -1260,9 +1251,13 @@ class _PdfViewerState extends State final targetZoom = _fitScale; - _setCurrentPageNumber(targetPage, targetZoom: targetZoom, doSetState: true); + _setCurrentPageNumber(targetPageNumber, targetZoom: targetZoom, doSetState: true); - final targetMatrix = _calcMatrixForPage(pageNumber: targetPage, anchor: effectiveAnchor, forceScale: targetZoom); + final targetMatrix = _calcMatrixForPage( + pageNumber: targetPageNumber, + anchor: effectiveAnchor, + forceScale: targetZoom, + ); await _goTo(targetMatrix, duration: duration, curve: Curves.easeInOutCubic); @@ -1742,16 +1737,17 @@ class _PdfViewerState extends State /// Used by boundaryProvider to restrict scrolling to current page. Rect? _getDiscreteBoundaryRect(Rect visibleRect, Size childSize) { final layout = _layout; - final currentPage = _gotoTargetPageNumber ?? _pageNumber; + final currentPageNumber = _gotoTargetPageNumber ?? _pageNumber; - if (layout == null || currentPage == null || currentPage < 1 || currentPage > layout.pageLayouts.length) { + if (layout == null || + currentPageNumber == null || + currentPageNumber < 1 || + currentPageNumber > layout.pageLayouts.length) { return null; } // Get base page/spread bounds - final baseBounds = layout is PdfSpreadLayout - ? layout.getSpreadBounds(currentPage) - : layout.pageLayouts[currentPage - 1]; + final baseBounds = layout.getSpreadBounds(currentPageNumber); // Add margin var result = baseBounds.inflate(widget.params.margin); @@ -1822,8 +1818,8 @@ class _PdfViewerState extends State final extensionDistance = viewportPrimarySize * extensionRatio; // Extend to previous page (if exists and should extend) - if (shouldExtendToPrev && currentPage > 1) { - final prevPageBounds = layout.pageLayouts[currentPage - 2]; + if (shouldExtendToPrev && currentPageNumber > 1) { + final prevPageBounds = layout.pageLayouts[currentPageNumber - 2]; if (isPrimaryVertical) { // Extend upward final newTop = max(prevPageBounds.top, result.top - extensionDistance); @@ -1836,8 +1832,8 @@ class _PdfViewerState extends State } // Extend to next page (if exists and should extend) - if (shouldExtendToNext && currentPage < layout.pageLayouts.length) { - final nextPageBounds = layout.pageLayouts[currentPage]; + if (shouldExtendToNext && currentPageNumber < layout.pageLayouts.length) { + final nextPageBounds = layout.pageLayouts[currentPageNumber]; if (isPrimaryVertical) { // Extend downward final newBottom = min(nextPageBounds.bottom, result.bottom + extensionDistance); @@ -1860,20 +1856,25 @@ class _PdfViewerState extends State // Discrete mode: restrict scrolling to current page/spread only if (widget.params.pageTransition == PageTransition.discrete) { final layout = _layout; - final currentPage = _pageNumber; + final currentPageNumber = _pageNumber; - if (layout == null || currentPage == null || currentPage < 1 || currentPage > layout.pageLayouts.length) { + if (layout == null || + currentPageNumber == null || + currentPageNumber < 1 || + currentPageNumber > layout.pageLayouts.length) { _adjustedBoundaryMargins = boundaryMargin; return; } - // Get current page or spread bounds - Rect pageBounds; - if (layout is PdfSpreadLayout) { - pageBounds = layout.getSpreadBounds(currentPage); - } else { - pageBounds = layout.getPageRectWithMargins(currentPage); - } + // TODO: should we need _implicit margins if not SpreadLayout? + // If we don't need it, I once remove getPageRectWithMargins function. + // Rect pageBounds; + // if (layout is PdfSpreadLayout) { + // pageBounds = layout.getSpreadBounds(currentPageNumber); + // } else { + // pageBounds = layout.getPageRectWithMargins(currentPageNumber); + // } + final pageBounds = layout.getSpreadBounds(currentPageNumber); var left = -pageBounds.left; var top = -pageBounds.top; @@ -2504,9 +2505,7 @@ class _PdfViewerState extends State /// Gets the effective page bounds for a given page, including margins. /// Optionally includes boundary margins for positioning purposes. Rect _getEffectivePageBounds(int pageNumber, PdfPageLayout layout, {PdfPageAnchor? anchor}) { - final baseRect = layout is PdfSpreadLayout && widget.params.pageTransition == PageTransition.discrete - ? layout.getSpreadBounds(pageNumber) - : layout.pageLayouts[pageNumber - 1]; + final baseRect = layout.getSpreadBounds(pageNumber); var result = baseRect.inflate(widget.params.margin); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index c7c3d271..7fc8f04f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -147,6 +147,7 @@ class _PdfViewerScrollThumbState extends State { final x = (-widget.controller.value.x - minScrollX) / scrollRange; final vw = view.width * widget.controller.currentZoom - thumbSize.width; + final left = x * vw; return Positioned( top: widget.orientation == ScrollbarOrientation.top ? widget.margin : null, From cba3a6bf3b00667b3f4b41378cae9c0de3566dd5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 04:20:44 +0900 Subject: [PATCH 474/663] WIP --- .../lib/src/widgets/pdf_page_layout.dart | 66 +++++-------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index a00047fa..91269a02 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -114,8 +114,6 @@ class PdfPageLayout { /// Each rect represents a page's position and size. The rects include positioning /// with spacing between pages, but the rect dimensions themselves are /// the page sizes WITHOUT margins added to width/height. - /// - /// Use [getPageRectWithMargins] to get a page rect with margins included. final List pageLayouts; /// Total document size including content margins. @@ -141,15 +139,16 @@ class PdfPageLayout { /// /// Calculated automatically via [_calcImpliedMargin] based on the spacing /// between page dimensions and document size. - /// - /// See [getPageRectWithMargins] to get page bounds including margins. late double _impliedMargin; /// Get the spread bounds for a given page number. /// /// For single page layouts, this simply returns the page bounds. - Rect getSpreadBounds(int pageNumber) { - return pageLayouts[pageNumber - 1]; + Rect getSpreadBounds(int pageNumber, {bool withMargins = false}) { + if (pageNumber < 1 || pageNumber > pageLayouts.length) { + throw RangeError('Invalid page number $pageNumber'); + } + return pageLayouts[pageNumber - 1].inflate(withMargins ? _impliedMargin : 0); } /// Each layout implements its own calculation logic. @@ -179,30 +178,8 @@ class PdfPageLayout { ? (documentSize.width - getMaxWidth()) / 2 : (documentSize.height - getMaxHeight()) / 2; - Rect getPageRectWithMargins(int pageNumber, {double? margin}) { - if (pageNumber < 1 || pageNumber > pageLayouts.length) { - throw RangeError('Invalid page number $pageNumber'); - } - margin ??= _impliedMargin; - final pageRect = pageLayouts[pageNumber - 1]; - return Rect.fromLTWH( - pageRect.left - margin, - pageRect.top - margin, - pageRect.width + margin * 2, - pageRect.height + margin * 2, - ); - } - - /// Gets the dimensions for a specific page number. - /// For single page layouts, returns the page dimensions. - /// For spread layouts, should return the spread dimensions containing the page. - /// Returns null if pageNumber is invalid. - Size? getPageDimensions(int pageNumber, {bool withMargins = false}) { - if (pageNumber < 1 || pageNumber > pageLayouts.length) { - return null; - } - final pageRect = pageLayouts[pageNumber - 1]; - return pageRect.size + (withMargins ? Offset(_impliedMargin * 2, _impliedMargin * 2) : Offset.zero); + bool _isPageNumberValid(int? pageNumber) { + return pageNumber != null && pageNumber >= 1 && pageNumber <= pageLayouts.length; } /// Calculates page sizes based on fit mode and scroll direction, to enable independent @@ -509,9 +486,9 @@ class PdfPageLayout { final double width; final double height; - if (pageTransition == PageTransition.discrete && pageNumber != null && getPageDimensions(pageNumber) != null) { - final dimensions = getPageDimensions(pageNumber); - width = dimensions!.width; + if (pageTransition == PageTransition.discrete && _isPageNumberValid(pageNumber)) { + final dimensions = getSpreadBounds(pageNumber!).size; + width = dimensions.width; height = dimensions.height; // For discrete, add margins since page dimensions don't include them final widthWithMargins = width + helper.margin * 2 + helper.boundaryMarginHorizontal; @@ -533,9 +510,9 @@ class PdfPageLayout { final double width; final double height; - if (pageTransition == PageTransition.discrete && pageNumber != null && getPageDimensions(pageNumber) != null) { - final dimensions = getPageDimensions(pageNumber); - width = dimensions!.width; + if (pageTransition == PageTransition.discrete && _isPageNumberValid(pageNumber)) { + final dimensions = getSpreadBounds(pageNumber!).size; + width = dimensions.width; height = dimensions.height; } else { width = getMaxWidth(); @@ -603,8 +580,11 @@ class PdfSpreadLayout extends PdfPageLayout { /// Get the spread bounds for a given page number. @override - Rect getSpreadBounds(int pageNumber) { - return spreadLayouts[pageToSpreadIndex[pageNumber - 1]]; + Rect getSpreadBounds(int pageNumber, {bool withMargins = false}) { + if (pageNumber < 1 || pageNumber > pageLayouts.length) { + throw RangeError('Invalid page number $pageNumber'); + } + return spreadLayouts[pageToSpreadIndex[pageNumber - 1]].inflate(withMargins ? _impliedMargin : 0); } /// Get the page range for the spread containing pageNumber. @@ -640,16 +620,6 @@ class PdfSpreadLayout extends PdfPageLayout { return null; } - /// Gets the spread dimensions for a page number - @override - Size? getPageDimensions(int pageNumber, {bool withMargins = false}) { - if (pageNumber < 1 || pageNumber > pageToSpreadIndex.length) { - return null; - } - final spreadBounds = getSpreadBounds(pageNumber); - return spreadBounds.size + (withMargins ? Offset(_impliedMargin * 2, _impliedMargin * 2) : Offset.zero); - } - /// Gets the maximum spread width across all spreads. @override double getMaxWidth({bool withMargins = false}) { From f4b79e6991e8a7b7b02c107673b4d899e439e0ba Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 04:31:57 +0900 Subject: [PATCH 475/663] WIP --- .../lib/src/widgets/pdf_page_layout.dart | 23 +++++++++---------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 18 +++------------ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index 91269a02..2582cc63 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -20,13 +20,8 @@ class LayoutResult { /// Bundles viewport size with boundary and content margins, providing /// convenient getters for calculating available space and inflating dimensions. /// -/// **Example usage:** /// ```dart -/// final helper = PdfLayoutHelper( -/// viewportSize: viewportSize, -/// boundaryMargin: params.boundaryMargin, -/// margin: params.margin, -/// ); +/// final helper = PdfLayoutHelper.fromParams(params, viewSize: viewSize); /// /// // Get available space for content /// final width = helper.availableWidth; @@ -35,10 +30,14 @@ class LayoutResult { /// // Add margins to content dimensions /// final totalWidth = helper.widthWithMargins(contentWidth); /// ``` +@immutable class PdfLayoutHelper { - const PdfLayoutHelper({required this.viewportSize, this.boundaryMargin, this.margin = 0.0}); + const PdfLayoutHelper({required this.viewSize, this.boundaryMargin, this.margin = 0.0}); + + PdfLayoutHelper.fromParams(PdfViewerParams params, {required Size viewSize}) + : this(viewSize: viewSize, boundaryMargin: params.boundaryMargin, margin: params.margin); - final Size viewportSize; + final Size viewSize; final EdgeInsets? boundaryMargin; final double margin; @@ -54,16 +53,16 @@ class PdfLayoutHelper { /// Available width after subtracting boundary margins and content margins (margin * 2). double get availableWidth { - return viewportSize.width - boundaryMarginHorizontal - margin * 2; + return viewSize.width - boundaryMarginHorizontal - margin * 2; } /// Available height after subtracting boundary margins and content margins (margin * 2). double get availableHeight { - return viewportSize.height - boundaryMarginVertical - margin * 2; + return viewSize.height - boundaryMarginVertical - margin * 2; } - double get viewportWidth => viewportSize.width; - double get viewportHeight => viewportSize.height; + double get viewportWidth => viewSize.width; + double get viewportHeight => viewSize.height; /// Add horizontal boundary margin and content margins to a content width. double widthWithMargins(double contentWidth) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 66a805c8..096d30ed 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1364,11 +1364,7 @@ class _PdfViewerState extends State return false; } - final helper = PdfLayoutHelper( - viewportSize: _viewSize ?? Size.zero, - boundaryMargin: widget.params.boundaryMargin, - margin: widget.params.margin, - ); + final helper = PdfLayoutHelper.fromParams(widget.params, viewSize: _viewSize ?? Size.zero); var newLayout = (widget.params.layoutPages ?? _layoutPages)(_document!.pages, widget.params, helper); // In discrete mode, add spacing between pages to fill viewport and prevent neighboring pages from showing @@ -1409,11 +1405,7 @@ class _PdfViewerState extends State } final params = widget.params; - final helper = PdfLayoutHelper( - viewportSize: _viewSize!, - boundaryMargin: params.boundaryMargin, - margin: params.margin, - ); + final helper = PdfLayoutHelper.fromParams(params, viewSize: _viewSize!); return _layout!.calculateFitScale(helper, mode); } @@ -1426,11 +1418,7 @@ class _PdfViewerState extends State return; } - final helper = PdfLayoutHelper( - viewportSize: _viewSize!, - boundaryMargin: params.boundaryMargin, - margin: params.margin, - ); + final helper = PdfLayoutHelper.fromParams(params, viewSize: _viewSize!); final effectivePageNumber = pageNumber ?? _pageNumber ?? _gotoTargetPageNumber; From 55090e33c3276565f9f9a1edfbf220c2182bc571 Mon Sep 17 00:00:00 2001 From: james Date: Wed, 29 Oct 2025 11:59:03 +1030 Subject: [PATCH 476/663] fix page snap issue + improve discrete boundary calculation --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 66a805c8..bebdfe5a 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -863,6 +863,7 @@ class _PdfViewerState extends State double? _interactionStartScale; int? _interactionStartPage; bool _hadScaleChangeInInteraction = false; + Offset _lastPanDelta = Offset.zero; Future _onInteractionEnd(ScaleEndDetails details) async { _isActiveGesture = false; @@ -892,11 +893,15 @@ class _PdfViewerState extends State _interactionStartScale = _currentZoom; _interactionStartPage = _pageNumber; _hadScaleChangeInInteraction = false; // Reset for new interaction + _lastPanDelta = Offset.zero; // Reset pan delta for new interaction } widget.params.onInteractionStart?.call(details); } void _onInteractionUpdate(ScaleUpdateDetails details) { + // Track pan delta for boundary extension logic + _lastPanDelta = details.focalPointDelta; + // Track if scale changed during the interaction if (widget.params.pageTransition == PageTransition.discrete && _interactionStartScale != null) { final currentScale = _currentZoom; @@ -1036,14 +1041,26 @@ class _PdfViewerState extends State // Determine target page based on fling velocity or drag threshold int targetPage; if (hasSignificantVelocity) { - // Flutter velocity: positive = finger down/right → content up/left → previous page - final velocityDirection = scrollVelocity > 0 ? -1 : 1; - - // Only advance if at boundary when fling started - if (_isAtBoundary(startRect, currentPage, layout, isPrimaryVertical, velocityDirection)) { - targetPage = _getAdjacentPage(currentPage, layout, velocityDirection); + // Check if velocity direction matches drag direction to detect snapback + // scrollDelta: positive = viewport moved down/right, negative = viewport moved up/left + // scrollVelocity: positive = finger moving down/right → viewport moves opposite direction + // So we need to INVERT velocity to get viewport movement direction + final dragDirection = scrollDelta > 0 ? 1 : (scrollDelta < 0 ? -1 : 0); + final velocityDirection = scrollVelocity > 0 + ? -1 + : 1; // INVERTED: positive velocity = viewport moves up (previous page) + + // If velocity contradicts drag direction, this is likely a snapback - ignore velocity + if (dragDirection != 0 && dragDirection != velocityDirection) { + targetPage = _getTargetPageBasedOnThreshold(currentPage, scrollDelta, isPrimaryVertical); } else { - return; // Not at boundary - let InteractiveViewer handle fling + // Velocity matches drag direction - use velocity for page transition + // Only advance if at boundary when fling started + if (_isAtBoundary(startRect, currentPage, layout, isPrimaryVertical, velocityDirection)) { + targetPage = _getAdjacentPage(currentPage, layout, velocityDirection); + } else { + return; // Not at boundary - let InteractiveViewer handle fling + } } } else { // No fling - use visible page area threshold @@ -1802,45 +1819,54 @@ class _PdfViewerState extends State // Extend boundaries into adjacent pages during pan gestures // This allows smooth page transitions when panning on the primary axis - if (_isActiveGesture && !_hadScaleChangeInInteraction && !_isActivelyZooming && _viewSize != null) { + final shouldExtendBoundaries = + _isActiveGesture && !_hadScaleChangeInInteraction && !_isActivelyZooming && _viewSize != null; + + if (shouldExtendBoundaries) { final isPrimaryVertical = layout.primaryAxis == Axis.vertical; const extensionRatio = 0.5; // Extend 50% into adjacent pages - // During active drag gestures, always extend boundaries to allow deliberate page transitions. - // Fling-based transitions are controlled separately in _handleDiscretePageTransition. - final shouldExtendToPrev = true; - final shouldExtendToNext = true; - final viewportPrimarySize = isPrimaryVertical ? _viewSize!.height : _viewSize!.width; // Calculate extension distance based on viewport size (not page size) // This ensures proper extension even when viewport is much larger than page (discrete mode with spacing) final extensionDistance = viewportPrimarySize * extensionRatio; + // Determine swipe direction to only extend boundaries in the direction being swiped + // This prevents unwanted scrolling beyond document boundaries on first/last pages + var shouldExtendToPrev = true; + var shouldExtendToNext = true; + + final panDelta = isPrimaryVertical ? _lastPanDelta.dy : _lastPanDelta.dx; + + if (currentPageNumber == 1) { + // First page: only extend upward/leftward if user is panning down/right + // This allows smooth transition toward page 2 but prevents scrolling before page 1 + shouldExtendToPrev = panDelta < 0; + } else if (currentPageNumber == layout.pageLayouts.length) { + // Last page: only extend downward/rightward if user is panning up/left + // This allows smooth transition toward previous page but prevents scrolling after last page + shouldExtendToNext = panDelta > 0; + } + // Extend to previous page (if exists and should extend) - if (shouldExtendToPrev && currentPageNumber > 1) { - final prevPageBounds = layout.pageLayouts[currentPageNumber - 2]; + if (shouldExtendToPrev) { if (isPrimaryVertical) { - // Extend upward - final newTop = max(prevPageBounds.top, result.top - extensionDistance); + final newTop = result.top - extensionDistance; result = Rect.fromLTRB(result.left, newTop, result.right, result.bottom); } else { - // Extend leftward - final newLeft = max(prevPageBounds.left, result.left - extensionDistance); + final newLeft = result.left - extensionDistance; result = Rect.fromLTRB(newLeft, result.top, result.right, result.bottom); } } // Extend to next page (if exists and should extend) - if (shouldExtendToNext && currentPageNumber < layout.pageLayouts.length) { - final nextPageBounds = layout.pageLayouts[currentPageNumber]; + if (shouldExtendToNext) { if (isPrimaryVertical) { - // Extend downward - final newBottom = min(nextPageBounds.bottom, result.bottom + extensionDistance); + final newBottom = result.bottom + extensionDistance; result = Rect.fromLTRB(result.left, result.top, result.right, newBottom); } else { - // Extend rightward - final newRight = min(nextPageBounds.right, result.right + extensionDistance); + final newRight = result.right + extensionDistance; result = Rect.fromLTRB(result.left, result.top, newRight, result.bottom); } } From 8efde5827edb5b8445e8ade9e09b0113f631590c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 16:44:02 +0900 Subject: [PATCH 477/663] WIP --- .../lib/src/widgets/pdf_page_layout.dart | 61 +++++++++++-------- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 12 ++-- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index 2582cc63..c1cf8201 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -32,50 +32,64 @@ class LayoutResult { /// ``` @immutable class PdfLayoutHelper { + /// Creates a layout helper with specified parameters. const PdfLayoutHelper({required this.viewSize, this.boundaryMargin, this.margin = 0.0}); + /// Creates a layout helper from viewer parameters. PdfLayoutHelper.fromParams(PdfViewerParams params, {required Size viewSize}) : this(viewSize: viewSize, boundaryMargin: params.boundaryMargin, margin: params.margin); + /// Viewport size. final Size viewSize; + + /// Boundary margin around the viewport. final EdgeInsets? boundaryMargin; + + /// Content margin around the document edges. final double margin; - /// Horizontal boundary margin (0 if infinite or null). + /// Horizontal boundary margin + /// + /// 0 if infinite or null. double get boundaryMarginHorizontal { return boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; } - /// Vertical boundary margin (0 if infinite or null). + /// Vertical boundary margin. + /// + /// 0 if infinite or null. double get boundaryMarginVertical { return boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; } - /// Available width after subtracting boundary margins and content margins (margin * 2). + /// Available width after subtracting boundary margins and content margins (`margin * 2`). double get availableWidth { return viewSize.width - boundaryMarginHorizontal - margin * 2; } - /// Available height after subtracting boundary margins and content margins (margin * 2). + /// Available height after subtracting boundary margins and content margins (`margin * 2`). double get availableHeight { return viewSize.height - boundaryMarginVertical - margin * 2; } - double get viewportWidth => viewSize.width; - double get viewportHeight => viewSize.height; + /// Viewport width. + double get viewWidth => viewSize.width; + + /// Viewport height. + double get viewHeight => viewSize.height; /// Add horizontal boundary margin and content margins to a content width. - double widthWithMargins(double contentWidth) { + double getWidthWithMargins(double contentWidth) { return contentWidth + boundaryMarginHorizontal + margin * 2; } /// Add vertical boundary margin and content margins to a content height. - double heightWithMargins(double contentHeight) { + double getHeightWithMargins(double contentHeight) { return contentHeight + boundaryMarginVertical + margin * 2; } } -/// Defines page layout. +/// Base class for PDF page layouts. /// /// **Simple usage (backward compatible):** /// Create instances directly with pre-computed page layouts: @@ -93,7 +107,7 @@ class PdfLayoutHelper { /// 3. Override [calculateFitScale] for custom scaling logic /// /// **Document size and margin handling:** -/// Use [PdfLayoutHelper.widthWithMargins] and [PdfLayoutHelper.heightWithMargins] to properly +/// Use [PdfLayoutHelper.getWidthWithMargins] and [PdfLayoutHelper.getHeightWithMargins] to properly /// include both boundary margins and content margins in your document size calculations. /// The helper handles the complexity of margin application so you don't have to. /// @@ -120,7 +134,6 @@ class PdfPageLayout { /// This is the size of the scrollable content area and includes [_impliedMargin] spacing /// on all sides. Does NOT include boundary margins - those are handled separately /// at the viewport level. - /// final Size documentSize; /// The primary scroll axis for this layout. @@ -269,15 +282,15 @@ class PdfPageLayout { for (var i = 0; i < pages.length; i++) { final page = pages[i]; - final widthWithMargins = helper.widthWithMargins(page.width); - final heightWithMargins = helper.heightWithMargins(page.height); + final widthWithMargins = helper.getWidthWithMargins(page.width); + final heightWithMargins = helper.getHeightWithMargins(page.height); final crossAxisScale = scrollAxis == Axis.vertical - ? helper.viewportWidth / widthWithMargins - : helper.viewportHeight / heightWithMargins; + ? helper.viewWidth / widthWithMargins + : helper.viewHeight / heightWithMargins; final primaryAxisScale = scrollAxis == Axis.vertical - ? helper.viewportHeight / heightWithMargins - : helper.viewportWidth / widthWithMargins; + ? helper.viewHeight / heightWithMargins + : helper.viewWidth / widthWithMargins; // For fill mode: always constrained by cross-axis // For fit mode: only if cross-axis is the limiting factor @@ -355,19 +368,19 @@ class PdfPageLayout { // Both margins and boundaryMargins are in PDF points and scale with the content // So we need: scale = viewport / (pageSize + margin*2 + boundaryMargin) // This ensures: (pageSize + margin*2 + boundaryMargin) * scale = viewport - final widthWithMargins = helper.widthWithMargins(width); - final heightWithMargins = helper.heightWithMargins(height); + final widthWithMargins = helper.getWidthWithMargins(width); + final heightWithMargins = helper.getHeightWithMargins(height); switch (mode) { case FitMode.fit: // Scale to fit viewport (letterbox) - content + all margins must fit - return min(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + return min(helper.viewWidth / widthWithMargins, helper.viewHeight / heightWithMargins); case FitMode.fill: // Scale to fill cross-axis - content + all margins on cross-axis must fit return scrollAxis == Axis.vertical - ? helper.viewportWidth / widthWithMargins - : helper.viewportHeight / heightWithMargins; + ? helper.viewWidth / widthWithMargins + : helper.viewHeight / heightWithMargins; case FitMode.cover: // Cover mode not supported at page level - requires full document dimensions @@ -492,7 +505,7 @@ class PdfPageLayout { // For discrete, add margins since page dimensions don't include them final widthWithMargins = width + helper.margin * 2 + helper.boundaryMarginHorizontal; final heightWithMargins = height + helper.margin * 2 + helper.boundaryMarginVertical; - return max(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + return max(helper.viewWidth / widthWithMargins, helper.viewHeight / heightWithMargins); } else { // Continuous mode uses document dimensions (which already include margin * 2) // This matches legacy _coverScale calculation: viewport / (documentSize + boundaryMargin) @@ -500,7 +513,7 @@ class PdfPageLayout { height = documentSize.height; final widthWithMargins = width + helper.boundaryMarginHorizontal; final heightWithMargins = height + helper.boundaryMarginVertical; - return max(helper.viewportWidth / widthWithMargins, helper.viewportHeight / heightWithMargins); + return max(helper.viewWidth / widthWithMargins, helper.viewHeight / heightWithMargins); } } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 30da2fde..6e536bb4 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1543,7 +1543,7 @@ class _PdfViewerState extends State /// For spread layouts, positions spreads as units rather than individual pages. PdfPageLayout _addDiscreteSpacing(PdfPageLayout layout, PdfLayoutHelper helper) { final isPrimaryVertical = layout.primaryAxis == Axis.vertical; - final viewportSize = isPrimaryVertical ? helper.viewportHeight : helper.viewportWidth; + final viewportSize = isPrimaryVertical ? helper.viewHeight : helper.viewWidth; final margin = widget.params.margin; final newPageLayouts = []; @@ -1587,7 +1587,7 @@ class _PdfViewerState extends State final crossAxisSpreadSizeWithMargins = isPrimaryVertical ? scaledSpreadWidthWithMargins : scaledSpreadHeightWithMargins; - final crossAxisViewport = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + final crossAxisViewport = isPrimaryVertical ? helper.viewWidth : helper.viewHeight; // Calculate viewport padding on cross-axis final viewportPaddingCross = max(0.0, (crossAxisViewport - crossAxisSpreadSizeWithMargins) / 2); @@ -1651,8 +1651,8 @@ class _PdfViewerState extends State ); // Get page size on primary axis, scaled to final rendered size - final scaledPageWidthWithMargins = (helper.widthWithMargins(pageRect.width)) * pageScale; - final scaledPageHeightWithMargins = (helper.heightWithMargins(pageRect.height)) * pageScale; + final scaledPageWidthWithMargins = (helper.getWidthWithMargins(pageRect.width)) * pageScale; + final scaledPageHeightWithMargins = (helper.getHeightWithMargins(pageRect.height)) * pageScale; final pageSizeWithMargins = isPrimaryVertical ? scaledPageHeightWithMargins : scaledPageWidthWithMargins; // Calculate slot size: if page+margins fits in viewport, use viewport to add centering padding @@ -1670,7 +1670,7 @@ class _PdfViewerState extends State final crossAxisPageSizeWithMargins = isPrimaryVertical ? scaledPageWidthWithMargins : scaledPageHeightWithMargins; - final crossAxisViewport = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + final crossAxisViewport = isPrimaryVertical ? helper.viewWidth : helper.viewHeight; // Calculate viewport padding on cross-axis final viewportPaddingCross = max(0.0, (crossAxisViewport - crossAxisPageSizeWithMargins) / 2); @@ -1692,7 +1692,7 @@ class _PdfViewerState extends State // Calculate new document size // On cross-axis, use viewport size to accommodate centered pages with different aspect ratios - final crossAxisViewportSize = isPrimaryVertical ? helper.viewportWidth : helper.viewportHeight; + final crossAxisViewportSize = isPrimaryVertical ? helper.viewWidth : helper.viewHeight; // Also consider the maximum page extent on cross-axis to ensure no clipping final maxCrossAxisExtent = newPageLayouts.fold(0.0, (max, rect) { From 02437e67bbb28ed25ab58ee4f097aa392e864cbd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 20:51:29 +0900 Subject: [PATCH 478/663] Add more PDFium functions to pdfium_bindings.dart --- .../darwin/pdfrx/Sources/interop/pdfrx.cpp | 43 +++ .../lib/src/native/pdfium_bindings.dart | 284 +++++++++++++++++- packages/pdfrx_engine/pubspec.yaml | 2 + 3 files changed, 323 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp index 7b3b032d..bc9003df 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp +++ b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include // This function is used to keep the linker from stripping out the PDFium @@ -76,6 +78,35 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() KEEP_FUNC(FPDF_GetXFAPacketContent), KEEP_FUNC(FPDFDOC_InitFormFillEnvironment), KEEP_FUNC(FPDFDOC_ExitFormFillEnvironment), + KEEP_FUNC(FORM_OnAfterLoadPage), + KEEP_FUNC(FORM_OnBeforeClosePage), + KEEP_FUNC(FORM_DoDocumentJSAction), + KEEP_FUNC(FORM_DoDocumentOpenAction), + KEEP_FUNC(FORM_DoDocumentAAction), + KEEP_FUNC(FORM_DoPageAAction), + KEEP_FUNC(FORM_OnMouseMove), + KEEP_FUNC(FORM_OnMouseWheel), + KEEP_FUNC(FORM_OnFocus), + KEEP_FUNC(FORM_OnLButtonDown), + KEEP_FUNC(FORM_OnRButtonDown), + KEEP_FUNC(FORM_OnLButtonUp), + KEEP_FUNC(FORM_OnRButtonUp), + KEEP_FUNC(FORM_OnLButtonDoubleClick), + KEEP_FUNC(FORM_OnKeyDown), + KEEP_FUNC(FORM_OnKeyUp), + KEEP_FUNC(FORM_OnChar), + KEEP_FUNC(FORM_GetFocusedText), + KEEP_FUNC(FORM_GetSelectedText), + KEEP_FUNC(FORM_ReplaceAndKeepSelection), + KEEP_FUNC(FORM_ReplaceSelection), + KEEP_FUNC(FORM_SelectAllText), + KEEP_FUNC(FORM_CanUndo), + KEEP_FUNC(FORM_CanRedo), + KEEP_FUNC(FORM_Undo), + KEEP_FUNC(FORM_Redo), + KEEP_FUNC(FORM_ForceToKillFocus), + KEEP_FUNC(FORM_GetFocusedAnnot), + KEEP_FUNC(FORM_SetFocusedAnnot), KEEP_FUNC(FPDFPage_HasFormFieldAtPoint), KEEP_FUNC(FPDFPage_FormFieldZOrderAtPoint), KEEP_FUNC(FPDF_SetFormFieldHighlightColor), @@ -83,6 +114,8 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() KEEP_FUNC(FPDF_RemoveFormFieldHighlight), KEEP_FUNC(FPDF_FFLDraw), KEEP_FUNC(FPDF_GetFormType), + KEEP_FUNC(FORM_SetIndexSelected), + KEEP_FUNC(FORM_IsIndexSelected), KEEP_FUNC(FPDF_LoadXFA), KEEP_FUNC(FPDFAnnot_IsSupportedSubtype), KEEP_FUNC(FPDFPage_CreateAnnot), @@ -220,6 +253,7 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() KEEP_FUNC(FPDFPage_GetRotation), KEEP_FUNC(FPDFPage_SetRotation), KEEP_FUNC(FPDFPage_InsertObject), + KEEP_FUNC(FPDFPage_InsertObjectAtIndex), KEEP_FUNC(FPDFPage_RemoveObject), KEEP_FUNC(FPDFPage_CountObjects), KEEP_FUNC(FPDFPage_GetObject), @@ -326,6 +360,15 @@ extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() KEEP_FUNC(FPDFFormObj_CountObjects), KEEP_FUNC(FPDFFormObj_GetObject), KEEP_FUNC(FPDFFormObj_RemoveObject), + KEEP_FUNC(FPDF_ImportPagesByIndex), + KEEP_FUNC(FPDF_ImportPages), + KEEP_FUNC(FPDF_ImportNPagesToOne), + KEEP_FUNC(FPDF_NewXObjectFromPage), + KEEP_FUNC(FPDF_CloseXObject), + KEEP_FUNC(FPDF_NewFormObjectFromXObject), + KEEP_FUNC(FPDF_CopyViewerPreferences), + KEEP_FUNC(FPDF_SaveAsCopy), + KEEP_FUNC(FPDF_SaveWithVersion), KEEP_FUNC(FPDF_GetDefaultTTFMap), KEEP_FUNC(FPDF_GetDefaultTTFMapCount), KEEP_FUNC(FPDF_GetDefaultTTFMapEntry), diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart index ae9b48f1..d9218c80 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart @@ -5780,6 +5780,33 @@ late final _FPDFPage_InsertObjectPtr = _lookup< ffi.NativeFunction>('FPDFPage_InsertObject'); late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); +/// Insert |page_object| into |page| at the specified |index|. +/// +/// page - handle to a page +/// page_object - handle to a page object as previously obtained by +/// FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). Ownership of the object +/// is transferred back to PDFium. +/// index - the index position to insert the object at. If index equals +/// the current object count, the object will be appended to the +/// end. If index is greater than the object count, the function +/// will fail and return false. +/// +/// Returns true if successful. +int FPDFPage_InsertObjectAtIndex(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +int index, +) { + return _FPDFPage_InsertObjectAtIndex(page, +page_object, +index, +); +} + +late final _FPDFPage_InsertObjectAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObjectAtIndex'); +late final _FPDFPage_InsertObjectAtIndex = _FPDFPage_InsertObjectAtIndexPtr.asFunction(); + /// Experimental API. /// Remove |page_object| from |page|. /// @@ -8207,6 +8234,216 @@ late final _FPDFFormObj_RemoveObjectPtr = _lookup< ffi.NativeFunction>('FPDFFormObj_RemoveObject'); late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); +/// Experimental API. +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// page_indices - An array of page indices to be imported. The first page is +/// zero. If |page_indices| is NULL, all pages from |src_doc| +/// are imported. +/// length - The length of the |page_indices| array. +/// index - The page index at which to insert the first imported page +/// into |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is +/// invalid. +int FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +ffi.Pointer page_indices, +int length, +int index, +) { + return _FPDF_ImportPagesByIndex(dest_doc, +src_doc, +page_indices, +length, +index, +); +} + +late final _FPDF_ImportPagesByIndexPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_ImportPagesByIndex'); +late final _FPDF_ImportPagesByIndex = _FPDF_ImportPagesByIndexPtr.asFunction , int , int )>(); + +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// pagerange - A page range string, Such as "1,3,5-7". The first page is one. +/// If |pagerange| is NULL, all pages from |src_doc| are imported. +/// index - The page index at which to insert the first imported page into +/// |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is +/// invalid or if |pagerange| cannot be read. +int FPDF_ImportPages(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +FPDF_BYTESTRING pagerange, +int index, +) { + return _FPDF_ImportPages(dest_doc, +src_doc, +pagerange, +index, +); +} + +late final _FPDF_ImportPagesPtr = _lookup< + ffi.NativeFunction>('FPDF_ImportPages'); +late final _FPDF_ImportPages = _FPDF_ImportPagesPtr.asFunction(); + +/// Experimental API. +/// Create a new document from |src_doc|. The pages of |src_doc| will be +/// combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per +/// |output_doc| page. +/// +/// src_doc - The document to be imported. +/// output_width - The output page width in PDF "user space" units. +/// output_height - The output page height in PDF "user space" units. +/// num_pages_on_x_axis - The number of pages on X Axis. +/// num_pages_on_y_axis - The number of pages on Y Axis. +/// +/// Return value: +/// A handle to the created document, or NULL on failure. +/// +/// Comments: +/// number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis +FPDF_DOCUMENT FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, +double output_width, +double output_height, +int num_pages_on_x_axis, +int num_pages_on_y_axis, +) { + return _FPDF_ImportNPagesToOne(src_doc, +output_width, +output_height, +num_pages_on_x_axis, +num_pages_on_y_axis, +); +} + +late final _FPDF_ImportNPagesToOnePtr = _lookup< + ffi.NativeFunction>('FPDF_ImportNPagesToOne'); +late final _FPDF_ImportNPagesToOne = _FPDF_ImportNPagesToOnePtr.asFunction(); + +/// Experimental API. +/// Create a template to generate form xobjects from |src_doc|'s page at +/// |src_page_index|, for use in |dest_doc|. +/// +/// Returns a handle on success, or NULL on failure. Caller owns the newly +/// created object. +FPDF_XOBJECT FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +int src_page_index, +) { + return _FPDF_NewXObjectFromPage(dest_doc, +src_doc, +src_page_index, +); +} + +late final _FPDF_NewXObjectFromPagePtr = _lookup< + ffi.NativeFunction>('FPDF_NewXObjectFromPage'); +late final _FPDF_NewXObjectFromPage = _FPDF_NewXObjectFromPagePtr.asFunction(); + +/// Experimental API. +/// Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage(). +/// FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected. +void FPDF_CloseXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_CloseXObject(xobject, +); +} + +late final _FPDF_CloseXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_CloseXObject'); +late final _FPDF_CloseXObject = _FPDF_CloseXObjectPtr.asFunction(); + +/// Experimental API. +/// Create a new form object from an FPDF_XOBJECT object. +/// +/// Returns a new form object on success, or NULL on failure. Caller owns the +/// newly created object. +FPDF_PAGEOBJECT FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_NewFormObjectFromXObject(xobject, +); +} + +late final _FPDF_NewFormObjectFromXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_NewFormObjectFromXObject'); +late final _FPDF_NewFormObjectFromXObject = _FPDF_NewFormObjectFromXObjectPtr.asFunction(); + +/// Copy the viewer preferences from |src_doc| into |dest_doc|. +/// +/// dest_doc - Document to write the viewer preferences into. +/// src_doc - Document to read the viewer preferences from. +/// +/// Returns TRUE on success. +int FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +) { + return _FPDF_CopyViewerPreferences(dest_doc, +src_doc, +); +} + +late final _FPDF_CopyViewerPreferencesPtr = _lookup< + ffi.NativeFunction>('FPDF_CopyViewerPreferences'); +late final _FPDF_CopyViewerPreferences = _FPDF_CopyViewerPreferencesPtr.asFunction(); + +/// Function: FPDF_SaveAsCopy +/// Saves the copy of specified document in custom way. +/// Parameters: +/// document - Handle to document, as returned by +/// FPDF_LoadDocument() or FPDF_CreateNewDocument(). +/// pFileWrite - A pointer to a custom file write structure. +/// flags - Flags above that affect how the PDF gets saved. +/// Pass in 0 when there are no flags. +/// Return value: +/// TRUE for succeed, FALSE for failed. +int FPDF_SaveAsCopy(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +) { + return _FPDF_SaveAsCopy(document, +pFileWrite, +flags, +); +} + +late final _FPDF_SaveAsCopyPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD )>>('FPDF_SaveAsCopy'); +late final _FPDF_SaveAsCopy = _FPDF_SaveAsCopyPtr.asFunction , int )>(); + +/// Function: FPDF_SaveWithVersion +/// Same as FPDF_SaveAsCopy(), except the file version of the +/// saved document can be specified by the caller. +/// Parameters: +/// document - Handle to document. +/// pFileWrite - A pointer to a custom file write structure. +/// flags - The creating flags. +/// fileVersion - The PDF file version. File version: 14 for 1.4, +/// 15 for 1.5, ... +/// Return value: +/// TRUE if succeed, FALSE if failed. +int FPDF_SaveWithVersion(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +int fileVersion, +) { + return _FPDF_SaveWithVersion(document, +pFileWrite, +flags, +fileVersion, +); +} + +late final _FPDF_SaveWithVersionPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD , ffi.Int )>>('FPDF_SaveWithVersion'); +late final _FPDF_SaveWithVersion = _FPDF_SaveWithVersionPtr.asFunction , int , int )>(); + /// Function: FPDF_GetDefaultTTFMap /// Returns a pointer to the default character set to TT Font name map. The /// map is an array of FPDF_CharsetFontMap structs, with its end indicated @@ -8291,7 +8528,7 @@ late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction pFontInfo, +void FPDF_SetSystemFontInfo(ffi.Pointer font_info, ) { - return _FPDF_SetSystemFontInfo(pFontInfo, + return _FPDF_SetSystemFontInfo(font_info, ); } @@ -8335,15 +8572,15 @@ late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.as /// Function: FPDF_FreeDefaultSystemFontInfo /// Free a default system font info interface /// Parameters: -/// pFontInfo - Pointer to a FPDF_SYSFONTINFO structure +/// font_info - Pointer to a FPDF_SYSFONTINFO structure /// Return Value: /// None /// Comments: /// This function should be called on the output from /// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. -void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer pFontInfo, +void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer font_info, ) { - return _FPDF_FreeDefaultSystemFontInfo(pFontInfo, + return _FPDF_FreeDefaultSystemFontInfo(font_info, ); } @@ -9832,6 +10069,32 @@ final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ } +/// Structure for custom file write +final class FPDF_FILEWRITE_ extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: WriteBlock + /// Output a block of data in your custom way. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Comments: + /// Called by function FPDF_SaveDocument + /// Parameters: + /// pThis - Pointer to the structure itself + /// pData - Pointer to a buffer to output + /// size - The size of the buffer. + /// Return value: + /// Should be non-zero if successful, zero for error. + external ffi.Pointer pThis, ffi.Pointer pData, ffi.UnsignedLong size)>> WriteBlock; + +} + +/// Structure for custom file write +typedef FPDF_FILEWRITE = FPDF_FILEWRITE_; /// Interface: FPDF_SYSFONTINFO /// Interface for getting system font information and font mapping final class _FPDF_SYSFONTINFO extends ffi.Struct{ @@ -10608,6 +10871,15 @@ const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; +const int FPDF_INCREMENTAL = 1; + + +const int FPDF_NO_INCREMENTAL = 2; + + +const int FPDF_REMOVE_SECURITY = 3; + + const int FXFONT_ANSI_CHARSET = 0; diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 5254ea38..e65543db 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -42,6 +42,8 @@ ffigen: - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_text.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_edit.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_ppo.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_save.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_formfill.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_sysfontinfo.h" include-directives: From 0b3fa8f3da554434a8201339a95d6016d505adf4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 29 Oct 2025 20:53:51 +0900 Subject: [PATCH 479/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6e536bb4..e5ee24d7 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1428,17 +1428,13 @@ class _PdfViewerState extends State } void _calcFitScale([int? pageNumber]) { - final params = widget.params; - if (_viewSize == null || _layout == null) { _fitScale = _defaultMinScale; return; } - final helper = PdfLayoutHelper.fromParams(params, viewSize: _viewSize!); - + final helper = PdfLayoutHelper.fromParams(widget.params, viewSize: _viewSize!); final effectivePageNumber = pageNumber ?? _pageNumber ?? _gotoTargetPageNumber; - if (widget.params.useAlternativeFitScaleAsMinScale) { // Legacy useAlternativeFitScaleAsMinScale behavior (deprecated) // This maps to FitMode.fit (show whole page) @@ -1454,7 +1450,7 @@ class _PdfViewerState extends State // In continuous mode, calculate for the entire document _fitScale = _layout!.calculateFitScale( helper, - params.fitMode, + widget.params.fitMode, pageTransition: widget.params.pageTransition, pageNumber: effectivePageNumber, ); From 4acc79a69948965654e0ae4fd4a2b9396a398033 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:02:35 +0900 Subject: [PATCH 480/663] PdfDocument now supports page re-arragement and even accepts PdfPage instances from other documents. This enables PDF combine tools. --- packages/pdfrx/assets/pdfium_worker.js | 318 +++++++++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 162 +++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 7 +- .../lib/pdfrx_coregraphics.dart | 24 + packages/pdfrx_engine/example/README.md | 117 ++++ packages/pdfrx_engine/example/pdfcombine.dart | 260 +++++++++ .../lib/src/native/pdfrx_pdfium.dart | 503 +++++++++++++----- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 75 ++- .../lib/src/utils/shuffle_in_place.dart | 147 +++++ 9 files changed, 1458 insertions(+), 155 deletions(-) create mode 100644 packages/pdfrx_engine/example/README.md create mode 100644 packages/pdfrx_engine/example/pdfcombine.dart create mode 100644 packages/pdfrx_engine/lib/src/utils/shuffle_in_place.dart diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index c0397c62..d33148c4 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1507,6 +1507,322 @@ function clearAllFontData() { return { message: 'All font data cleared' }; } +/** + * Assemble the document (apply page manipulations if any) + * @param {{docHandle: number, pageIndices: number[]|undefined, importedPages: Object.|undefined}} params + * @returns {{modified: boolean}} + */ +function assemble(params) { + const { docHandle, pageIndices, importedPages } = params; + + // If no page indices specified, no modifications needed + if (!pageIndices || pageIndices.length === 0) { + return { modified: false }; + } + + const originalLength = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); + + // Check if there are any changes + let hasChanges = pageIndices.length !== originalLength; + if (!hasChanges) { + for (let i = 0; i < pageIndices.length; i++) { + if (pageIndices[i] !== i) { + hasChanges = true; + break; + } + } + } + + if (!hasChanges) { + return { modified: false }; + } + + // Perform the shuffle using the PDFium page manipulation functions + _shuffleInPlaceAccordingToIndices(docHandle, pageIndices, originalLength, importedPages); + + return { modified: true }; +} + +/** + * Internal class to track page tokens during shuffling + */ +class _ArrayOfItemsToken { + /** + * @param {number|null} originalIndex + * @param {boolean} isOriginal + */ + constructor(originalIndex, isOriginal) { + this.originalIndex = originalIndex; + this.isOriginal = isOriginal; + } +} + +/** + * Shuffle pages in place according to the given list of resulting item indices + * @param {number} docHandle Document handle + * @param {number[]} resultingItemIndices Array of page indices representing the desired order + * @param {number} originalLength Original number of pages + * @param {Object.|undefined} importedPages Map of negative indices to import info + */ +function _shuffleInPlaceAccordingToIndices(docHandle, resultingItemIndices, originalLength, importedPages) { + if (resultingItemIndices.length === 0) { + if (originalLength > 0) { + _removePages(docHandle, 0, originalLength); + } + return; + } + + const tokens = []; + for (let i = 0; i < originalLength; i++) { + tokens.push(new _ArrayOfItemsToken(i, true)); + } + + // Count usage of each original page + const usageCounts = new Array(originalLength).fill(0); + for (let i = 0; i < resultingItemIndices.length; i++) { + const index = resultingItemIndices[i]; + if (index >= 0) { + if (index >= originalLength) { + throw new Error(`resultingItemIndices[${i}] = ${index} is out of range for current length ${originalLength}`); + } + usageCounts[index]++; + } + } + + // Remove unused pages (from end to beginning to maintain indices) + for (let i = originalLength - 1; i >= 0; i--) { + if (usageCounts[i] === 0) { + _removePages(docHandle, i, 1); + tokens.splice(i, 1); + } + } + + const placedCounts = new Array(originalLength).fill(0); + let currentIndex = 0; + + while (currentIndex < resultingItemIndices.length) { + if (currentIndex > tokens.length) { + throw new Error(`Destination index ${currentIndex} is out of range for current length ${tokens.length}.`); + } + + const target = resultingItemIndices[currentIndex]; + if (target >= 0) { + const isFirst = placedCounts[target] === 0; + if (isFirst) { + // Find the original page + let fromIndex = -1; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].originalIndex === target && tokens[i].isOriginal) { + fromIndex = i; + break; + } + } + if (fromIndex === -1) { + throw new Error(`Item at index ${target} could not be found for initial placement.`); + } + + // Try to find consecutive pages to move as a chunk + let chunkLength = 1; + while (currentIndex + chunkLength < resultingItemIndices.length && fromIndex + chunkLength < tokens.length) { + const nextTarget = resultingItemIndices[currentIndex + chunkLength]; + if (nextTarget < 0 || placedCounts[nextTarget] > 0) break; + const nextToken = tokens[fromIndex + chunkLength]; + if (!nextToken.isOriginal || nextToken.originalIndex !== nextTarget) break; + chunkLength++; + } + + let placementIndex = currentIndex; + if (fromIndex !== currentIndex) { + const removalIndices = []; + for (let offset = 0; offset < chunkLength; offset++) { + removalIndices.push(fromIndex + offset); + } + + _movePages(docHandle, fromIndex, currentIndex, chunkLength); + + // Update tokens + const removedTokens = []; + for (let i = removalIndices.length - 1; i >= 0; i--) { + removedTokens.unshift(tokens.splice(removalIndices[i], 1)[0]); + } + + let insertIndex = currentIndex; + for (const index of removalIndices) { + if (index < currentIndex) { + insertIndex--; + } + } + if (insertIndex < 0) insertIndex = 0; + if (insertIndex > tokens.length) insertIndex = tokens.length; + tokens.splice(insertIndex, 0, ...removedTokens); + placementIndex = insertIndex; + } + + for (let offset = 0; offset < chunkLength; offset++) { + const token = tokens[placementIndex + offset]; + if (token.originalIndex !== null) { + placedCounts[token.originalIndex]++; + } + } + currentIndex += chunkLength; + continue; + } else { + // Duplicate page + let sourceIndex = -1; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].originalIndex === target) { + sourceIndex = i; + break; + } + } + if (sourceIndex === -1) { + throw new Error(`Item at index ${target} could not be found for duplication.`); + } + _duplicatePages(docHandle, sourceIndex, currentIndex, 1); + tokens.splice(currentIndex, 0, new _ArrayOfItemsToken(target, false)); + placedCounts[target]++; + } + } else { + // Negative index means importing from another document + if (!importedPages || !importedPages[target]) { + throw new Error(`Imported page info not found for negative index ${target}`); + } + const importInfo = importedPages[target]; + _insertImportedPage(docHandle, importInfo.docHandle, importInfo.pageNumber, currentIndex); + tokens.splice(currentIndex, 0, new _ArrayOfItemsToken(null, false)); + } + currentIndex++; + } + + const expectedLength = resultingItemIndices.length; + if (tokens.length > expectedLength) { + const extra = tokens.length - expectedLength; + _removePages(docHandle, expectedLength, extra); + tokens.splice(expectedLength, extra); + } else if (tokens.length < expectedLength) { + throw new Error(`Internal length mismatch after shuffling (expected ${expectedLength}, got ${tokens.length}).`); + } +} + +/** + * Move pages within a document + * @param {number} docHandle Document handle + * @param {number} fromIndex Starting index of pages to move + * @param {number} toIndex Destination index + * @param {number} count Number of pages to move + */ +function _movePages(docHandle, fromIndex, toIndex, count) { + const pageIndices = Pdfium.wasmExports.malloc(count * 4); // Int32 array + const pageIndicesView = new Int32Array(Pdfium.memory.buffer, pageIndices, count); + for (let i = 0; i < count; i++) { + pageIndicesView[i] = fromIndex + i; + } + Pdfium.wasmExports.FPDF_MovePages(docHandle, pageIndices, count, toIndex); + Pdfium.wasmExports.free(pageIndices); +} + +/** + * Remove pages from a document + * @param {number} docHandle Document handle + * @param {number} index Starting index + * @param {number} count Number of pages to remove + */ +function _removePages(docHandle, index, count) { + for (let i = count - 1; i >= 0; i--) { + Pdfium.wasmExports.FPDFPage_Delete(docHandle, index + i); + } +} + +/** + * Duplicate pages within a document + * @param {number} docHandle Document handle + * @param {number} fromIndex Index of page to duplicate + * @param {number} toIndex Destination index for the duplicate + * @param {number} count Number of pages to duplicate + */ +function _duplicatePages(docHandle, fromIndex, toIndex, count) { + const pageIndices = Pdfium.wasmExports.malloc(count * 4); // Int32 array + const pageIndicesView = new Int32Array(Pdfium.memory.buffer, pageIndices, count); + for (let i = 0; i < count; i++) { + pageIndicesView[i] = fromIndex + i; + } + Pdfium.wasmExports.FPDF_ImportPagesByIndex(docHandle, docHandle, pageIndices, count, toIndex); + Pdfium.wasmExports.free(pageIndices); +} + +/** + * Insert a page from another document + * @param {number} destDocHandle Destination document handle + * @param {number} srcDocHandle Source document handle + * @param {number} srcPageIndex Source page index (0-based) + * @param {number} destIndex Destination index + */ +function _insertImportedPage(destDocHandle, srcDocHandle, srcPageIndex, destIndex) { + const pageIndices = Pdfium.wasmExports.malloc(4); // Int32 for one page + const pageIndicesView = new Int32Array(Pdfium.memory.buffer, pageIndices, 1); + pageIndicesView[0] = srcPageIndex; + Pdfium.wasmExports.FPDF_ImportPagesByIndex(destDocHandle, srcDocHandle, pageIndices, 1, destIndex); + Pdfium.wasmExports.free(pageIndices); +} + +/** + * Encode PDF document to bytes + * @param {{docHandle: number, incremental: boolean, removeSecurity: boolean}} params + * @returns {{data: ArrayBuffer}} + */ +function encodePdf(params) { + const { docHandle, incremental = false, removeSecurity = false } = params; + + const chunks = []; + + // Create a callback function that will be called by PDFium to write data + const writeCallback = Pdfium.addFunction((pThis, pData, size) => { + void pThis; // Suppress unused parameter warning + const chunk = new Uint8Array(Pdfium.memory.buffer, pData, size); + chunks.push(new Uint8Array(chunk)); // Copy the data + return size; + }, 'iiii'); + + try { + const fileWriteSize = 8; // sizeof(FPDF_FILEWRITE): version(4) + WriteBlock(4) + const fileWrite = Pdfium.wasmExports.malloc(fileWriteSize); + const fileWriteView = new Int32Array(Pdfium.memory.buffer, fileWrite, 2); + fileWriteView[0] = 1; // version + fileWriteView[1] = writeCallback; // WriteBlock function pointer + + // Determine flags based on parameters + let flags; + if (removeSecurity) { + flags = 3; // FPDF_SAVE_NO_SECURITY(3) + } else { + flags = incremental ? 1 : 2; // FPDF_INCREMENTAL(1) or FPDF_NO_INCREMENTAL(2) + } + + const result = Pdfium.wasmExports.FPDF_SaveAsCopy(docHandle, fileWrite, flags); + Pdfium.wasmExports.free(fileWrite); + + if (!result) { + throw new Error('FPDF_SaveAsCopy failed'); + } + + // Combine all chunks into a single ArrayBuffer + const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const combined = new Uint8Array(totalSize); + let offset = 0; + for (const chunk of chunks) { + combined.set(chunk, offset); + offset += chunk.length; + } + + return { + result: { data: combined.buffer }, + transfer: [combined.buffer], + }; + } finally { + Pdfium.removeFunction(writeCallback); + } +} + /** * Functions that can be called from the main thread */ @@ -1524,6 +1840,8 @@ const functions = { reloadFonts, addFontData, clearAllFontData, + assemble, + encodePdf, }; /** diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 2fd48a2c..78c44596 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -326,7 +326,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { class _PdfDocumentWasm extends PdfDocument { _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) : permissions = parsePermissions(document) { - pages = parsePages(this, document['pages'] as List); + _pages = parsePages(this, document['pages'] as List); updateMissingFonts(document['missingFonts']); } @@ -402,7 +402,8 @@ class _PdfDocumentWasm extends PdfDocument { } if (!subject.isClosed) { - subject.add(PdfDocumentPageStatusChangedEvent(this, pagesLoaded)); + final changes = {for (var p in pagesLoaded) p.pageNumber: PdfPageStatusModified()}; + subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); } updateMissingFonts(result['missingFonts']); @@ -417,8 +418,49 @@ class _PdfDocumentWasm extends PdfDocument { }); } + late List _pages; + + @override + List get pages => _pages; + @override - late final List pages; + set pages(Iterable newPages) { + final pagesList = []; + final changes = {}; + + for (final newPage in newPages) { + if (pagesList.length < _pages.length) { + final old = _pages[pagesList.length]; + if (identical(newPage, old)) { + pagesList.add(newPage); + continue; + } + } + + final newPageNumber = pagesList.length + 1; + final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); + if (oldPageIndex != -1) { + pagesList.add(newPage); + changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); + continue; + } + + if (newPage is! _PdfPageWasm && newPage is! _PdfPageImported) { + throw ArgumentError('Unsupported PdfPage instances found at [${pagesList.length}]', 'newPages'); + } + + if (newPage.document != this || newPage.pageNumber != newPageNumber) { + final imported = _PdfPageImported._(imported: newPage, pageNumber: newPageNumber); + pagesList.add(imported); + changes[newPageNumber] = PdfPageStatusChange.modified; + } else { + pagesList.add(newPage); + } + } + + _pages = pagesList; + subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); + } void updateMissingFonts(Map? missingFonts) { if (missingFonts == null || missingFonts.isEmpty) { @@ -466,6 +508,59 @@ class _PdfDocumentWasm extends PdfDocument { ) .toList(); } + + @override + Future assemble() async { + // Build the indices and imported pages map + final indices = []; + final importedPages = >{}; + var modifiedCount = 0; + + for (var i = 0; i < pages.length; i++) { + final page = pages[i]; + if (page is _PdfPageImported) { + final wasmPage = page.imported as _PdfPageWasm; + indices.add(-i); + importedPages[-i] = { + 'docHandle': wasmPage.document.document['docHandle'], + 'pageNumber': wasmPage.pageNumber - 1, // 0-based + }; + modifiedCount++; + } else if (page is _PdfPageWasm) { + indices.add(page.pageNumber - 1); + if (page.pageNumber - 1 != i) { + modifiedCount++; + } + } + } + + if (modifiedCount == 0) { + // No changes + return false; + } + + final result = await _sendCommand( + 'assemble', + parameters: { + 'docHandle': document['docHandle'], + 'pageIndices': indices, + if (importedPages.isNotEmpty) 'importedPages': importedPages, + }, + ); + + return result['modified'] as bool; + } + + @override + Future encodePdf({bool incremental = false, bool removeSecurity = false}) async { + await assemble(); + final result = await _sendCommand( + 'encodePdf', + parameters: {'docHandle': document['docHandle'], 'incremental': incremental, 'removeSecurity': removeSecurity}, + ); + final bb = result['data'] as ByteBuffer; + return Uint8List.view(bb.asByteData().buffer, 0, bb.lengthInBytes); + } } class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { @@ -659,6 +754,67 @@ class PdfImageWeb extends PdfImage { void dispose() {} } +/// A PDF page that is imported from another document or position. +class _PdfPageImported extends PdfPage { + _PdfPageImported._({required PdfPage imported, required this.pageNumber}) + : imported = imported is _PdfPageImported ? imported.imported : imported; // Unwrap nested imports + + /// The imported page + final PdfPage imported; + @override + final int pageNumber; + + @override + PdfPageRenderCancellationToken createCancellationToken() => imported.createCancellationToken(); + + @override + PdfDocument get document => imported.document; + + @override + double get height => imported.height; + + @override + bool get isLoaded => imported.isLoaded; + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + imported.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + @override + Future loadText() => imported.loadText(); + + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) => imported.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + ); + + @override + PdfPageRotation get rotation => imported.rotation; + + @override + double get width => imported.width; +} + PdfDest? _pdfDestFromMap(dynamic dest) { if (dest == null) return null; final params = dest['params'] as List; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 92412443..e320d97e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -401,9 +401,10 @@ class _PdfViewerState extends State void _onDocumentEvent(PdfDocumentEvent event) { if (event is PdfDocumentPageStatusChangedEvent) { - for (final page in event.pages) { - _imageCache.removeCacheImagesForPage(page.pageNumber); - _magnifierImageCache.removeCacheImagesForPage(page.pageNumber); + // TODO: we can reuse images for moved pages + for (final change in event.changes.entries) { + _imageCache.removeCacheImagesForPage(change.key); + _magnifierImageCache.removeCacheImagesForPage(change.key); } _invalidate(); } diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index fee2be9e..f40d0455 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -395,6 +395,30 @@ class _CoreGraphicsPdfDocument extends PdfDocument { bool isIdenticalDocumentHandle(Object? other) { return other is _CoreGraphicsPdfDocument && other.handle == handle; } + + @override + set pages(List value) { + throw UnimplementedError( + 'Setting pages is not implemented for CoreGraphics backend.', + ); + } + + @override + Future assemble() async { + throw UnimplementedError( + 'assemble() is not implemented for CoreGraphics backend.', + ); + } + + @override + Future encodePdf({ + bool incremental = false, + bool removeSecurity = false, + }) async { + throw UnimplementedError( + 'encodePdf() is not implemented for CoreGraphics backend.', + ); + } } class _CoreGraphicsPdfPage extends PdfPage { diff --git a/packages/pdfrx_engine/example/README.md b/packages/pdfrx_engine/example/README.md new file mode 100644 index 00000000..e0b43c52 --- /dev/null +++ b/packages/pdfrx_engine/example/README.md @@ -0,0 +1,117 @@ +# pdfrx_engine Examples + +This directory contains example applications demonstrating the capabilities of `pdfrx_engine`. + +## Examples + +### pdf2image.dart + +Converts PDF pages to PNG images and extracts text from each page. + +**Usage:** + +```bash +dart run example/main.dart [output_dir] +``` + +**Example:** + +```bash +dart run example/main.dart document.pdf ./output +``` + +### pdfcombine.dart + +Combines multiple PDF files into a single PDF, with flexible page selection and ordering. + +**Usage:** + +```bash +dart run example/pdfcombine.dart [...] -o [...] -- ... +``` + +Input PDF files are automatically assigned IDs: a, b, c, etc. in order. +The `-o` flag can appear anywhere before the `--` separator. + +**Arguments:** + +- `-o ` - Output PDF file path (can appear anywhere before `--`) +- `...` - Input PDF file(s) (assigned IDs a, b, c, ... in order) +- `--` - Separator between input files and page specifications +- `...` - Page specification (e.g., `a`, `b[1-3]`, `c[1,2,3]`) + +**Page specification formats:** + +- `a` - All pages from file a +- `a[1-10]` - Pages 1-10 from file a +- `a[1,2,3,4]` - Pages 1,2,3,4 from file a +- `a[1-3,5,6,7,10]` - Pages 1,2,3,5,6,7,10 from file a (hybrid of ranges and individual pages) + +**Examples:** + +Combine all pages from three PDFs in order (files are assigned IDs a, b, c): + +```bash +dart run example/pdfcombine.dart -o output.pdf doc1.pdf doc2.pdf doc3.pdf -- a b c +``` + +Combine specific pages from multiple PDFs (-o at the beginning): + +```bash +dart run example/pdfcombine.dart -o output.pdf doc1.pdf doc2.pdf doc3.pdf -- a b[1-3] c b[4,5,6] +``` + +Same command with -o in the middle (files can be split around -o flag): + +```bash +dart run example/pdfcombine.dart doc1.pdf doc2.pdf -o output.pdf doc3.pdf -- a b[1-3] c b[4,5,6] +``` + +This will: +1. `doc1.pdf` is assigned ID `a`, `doc2.pdf` is assigned ID `b`, `doc3.pdf` is assigned ID `c` +2. Add all pages from `a` (doc1.pdf) +3. Add pages 1-3 from `b` (doc2.pdf) +4. Add all pages from `c` (doc3.pdf) +5. Add pages 4,5,6 from `b` (doc2.pdf) + +Split and reorder pages from a single file: + +```bash +dart run example/pdfcombine.dart -o output.pdf input.pdf -- a[1-10] a[20-30] a[11-19] +``` + +Merge two PDFs with custom ordering: + +```bash +dart run example/pdfcombine.dart -o merged.pdf input1.pdf input2.pdf -- a[1-10] b a[11-20] +``` + +Use hybrid page specifications (ranges and individual pages): + +```bash +dart run example/pdfcombine.dart -o output.pdf doc.pdf -- a[1-3,5,6,7,10] +``` + +This extracts pages 1,2,3,5,6,7,10 from `doc.pdf`. + +## Running Examples + +From the repository root: + +```bash +# Run pdf2image example +dart run packages/pdfrx_engine/example/main.dart [output_dir] + +# Run pdfcombine example +dart run packages/pdfrx_engine/example/pdfcombine.dart +``` + +From the `packages/pdfrx_engine` directory: + +```bash +# Run pdf2image example +dart run example/main.dart [output_dir] + +# Run pdfcombine example +dart run example/pdfcombine.dart +``` diff --git a/packages/pdfrx_engine/example/pdfcombine.dart b/packages/pdfrx_engine/example/pdfcombine.dart new file mode 100644 index 00000000..24005cdc --- /dev/null +++ b/packages/pdfrx_engine/example/pdfcombine.dart @@ -0,0 +1,260 @@ +import 'dart:io'; + +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +/// Represents a page specification for a PDF file. +/// +/// Examples: +/// - `a` - all pages from file 'a' +/// - `a[1-10]` - pages 1-10 from file 'a' +/// - `a[1,2,3,4]` - pages 1,2,3,4 from file 'a' +/// - `a[1-3,5,6,7,10]` - pages 1,2,3,5,6,7,10 from file 'a' (hybrid) +class PageSpec { + PageSpec(this.fileId, this.pages); + + final String fileId; + final List? pages; // null means all pages + + /// Parses a page specification string like 'a', 'a[1-10]', 'a[1,2,3,4]', or 'a[1-3,5,6,7,10]' + static PageSpec parse(String spec) { + final match = RegExp(r'^([a-zA-Z0-9_-]+)(?:\[([0-9,\-\s]+)\])?$').firstMatch(spec.trim()); + if (match == null) { + throw ArgumentError('Invalid page specification: $spec'); + } + + final fileId = match.group(1)!; + final pageRange = match.group(2); + + if (pageRange == null) { + return PageSpec(fileId, null); // All pages + } + + final pages = []; + for (final part in pageRange.split(',')) { + final rangePart = part.trim(); + if (rangePart.contains('-')) { + final rangeParts = rangePart.split('-').map((s) => s.trim()).toList(); + if (rangeParts.length != 2) { + throw ArgumentError('Invalid page range: $rangePart'); + } + final start = int.parse(rangeParts[0]); + final end = int.parse(rangeParts[1]); + if (start > end) { + throw ArgumentError('Invalid page range: $rangePart (start > end)'); + } + for (var i = start; i <= end; i++) { + pages.add(i); + } + } else { + pages.add(int.parse(rangePart)); + } + } + + return PageSpec(fileId, pages); + } + + @override + String toString() => pages == null ? fileId : '$fileId[${pages!.join(',')}]'; +} + +Future main(List args) async { + if (args.length < 4) { + print('Usage: dart pdfcombine.dart [...] -o [...] -- ...'); + print(''); + print('Input PDF files are automatically assigned IDs: a, b, c, etc.'); + print('The -o flag can appear anywhere before the -- separator.'); + print(''); + print('Examples:'); + print(' dart pdfcombine.dart -o output.pdf doc1.pdf doc2.pdf doc3.pdf -- a b[1-3] c b[4,5,6]'); + print(' dart pdfcombine.dart doc1.pdf doc2.pdf -o output.pdf doc3.pdf -- a b[1-3] c b[4,5,6]'); + print(' dart pdfcombine.dart input1.pdf input2.pdf -o merged.pdf -- a[1-10] b a[11-20]'); + print(''); + print('Arguments:'); + print(' -o - Output PDF file path (can appear anywhere before --)'); + print(' ... - Input PDF file(s) (assigned IDs a, b, c, ... in order)'); + print(' -- - Separator between input files and page specifications'); + print(' ... - Page specification (e.g., a, b[1-3], c[1,2,3])'); + print(''); + print('Page specification formats:'); + print(' a - All pages from file a'); + print(' a[1-10] - Pages 1-10 from file a'); + print(' a[1,2,3,4] - Pages 1,2,3,4 from file a'); + print(' a[1-3,5,6,7,10] - Pages 1,2,3,5,6,7,10 from file a (hybrid)'); + return 1; + } + + try { + await pdfrxInitialize(); + + // Parse arguments + String? outputFile; + final inputFiles = []; + final pageSpecArgs = []; + + var i = 0; + var foundSeparator = false; + + // Parse input files and -o flag until we hit -- + while (i < args.length) { + if (args[i] == '--') { + foundSeparator = true; + i++; + break; + } else if (args[i] == '-o') { + if (i + 1 >= args.length) { + print('Error: -o flag requires an output file path'); + return 1; + } + if (outputFile != null) { + print('Error: Multiple -o flags specified'); + return 1; + } + outputFile = args[i + 1]; + i += 2; + } else { + inputFiles.add(args[i]); + i++; + } + } + + if (!foundSeparator) { + print('Error: Missing -- separator between input files and page specifications'); + return 1; + } + + if (outputFile == null) { + print('Error: Missing -o flag for output file'); + return 1; + } + + // Remaining arguments are page specifications + while (i < args.length) { + pageSpecArgs.add(args[i]); + i++; + } + + // Validate inputs + if (inputFiles.isEmpty) { + print('Error: No input PDF files specified'); + return 1; + } + + if (pageSpecArgs.isEmpty) { + print('Error: No page specifications provided'); + return 1; + } + + // Assign file IDs (a, b, c, etc.) to input files + final fileMap = {}; + for (var i = 0; i < inputFiles.length; i++) { + final filePath = inputFiles[i]; + if (!File(filePath).existsSync()) { + print('Error: File not found: $filePath'); + return 1; + } + final fileId = String.fromCharCode(97 + i); // 'a' + i + fileMap[fileId] = filePath; + } + + // Parse page specifications + final pageSpecs = []; + for (final arg in pageSpecArgs) { + try { + pageSpecs.add(PageSpec.parse(arg)); + } catch (e) { + print('Error parsing page specification "$arg": $e'); + return 1; + } + } + + // Validate all file IDs in page specs exist + for (final spec in pageSpecs) { + if (!fileMap.containsKey(spec.fileId)) { + print('Error: Unknown file ID "${spec.fileId}" in page specification'); + print('Available file IDs: ${fileMap.keys.join(', ')}'); + return 1; + } + } + + print('Input files:'); + fileMap.forEach((id, path) => print(' $id = $path')); + print('Output file: $outputFile'); + print('Page specifications: ${pageSpecs.join(' ')}'); + print(''); + + // Open all PDF documents + final documents = {}; + try { + for (final entry in fileMap.entries) { + print('Opening ${entry.value}...'); + documents[entry.key] = await PdfDocument.openFile(entry.value); + } + + // Create a new document by combining pages + print(''); + print('Combining pages...'); + final firstSpec = pageSpecs.first; + final firstDoc = documents[firstSpec.fileId]!; + final firstPages = firstSpec.pages ?? List.generate(firstDoc.pages.length, (i) => i + 1); + + // Validate page numbers + for (final pageNum in firstPages) { + if (pageNum < 1 || pageNum > firstDoc.pages.length) { + print('Error: Page $pageNum out of range for file ${firstSpec.fileId} (has ${firstDoc.pages.length} pages)'); + return 1; + } + } + + // Start with pages from the first specification + final combinedPages = []; + for (final pageNum in firstPages) { + combinedPages.add(firstDoc.pages[pageNum - 1]); + print(' Adding page $pageNum from ${firstSpec.fileId}'); + } + + // Create a new document from the first set of pages + final outputDoc = firstDoc; + outputDoc.pages = combinedPages; + + // Add pages from remaining specifications + for (var i = 1; i < pageSpecs.length; i++) { + final spec = pageSpecs[i]; + final doc = documents[spec.fileId]!; + final pages = spec.pages ?? List.generate(doc.pages.length, (i) => i + 1); + + // Validate page numbers + for (final pageNum in pages) { + if (pageNum < 1 || pageNum > doc.pages.length) { + print('Error: Page $pageNum out of range for file ${spec.fileId} (has ${doc.pages.length} pages)'); + return 1; + } + } + + for (final pageNum in pages) { + combinedPages.add(doc.pages[pageNum - 1]); + print(' Adding page $pageNum from ${spec.fileId}'); + } + outputDoc.pages = combinedPages; + } + + // Encode and save the combined PDF + print(''); + print('Saving to $outputFile...'); + final pdfData = await outputDoc.encodePdf(); + await File(outputFile).writeAsBytes(pdfData); + + print(''); + print('Successfully combined ${combinedPages.length} pages into $outputFile'); + return 0; + } finally { + // Clean up - close all documents + for (final doc in documents.values) { + doc.dispose(); + } + } + } catch (e, stackTrace) { + print('Error: $e'); + print(stackTrace); + return 1; + } +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index b9d957c4..9c422848 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -12,6 +12,7 @@ import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import '../pdfrx_api.dart'; +import '../utils/shuffle_in_place.dart'; import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium.dart'; @@ -505,30 +506,28 @@ class _PdfDocumentPdfium extends PdfDocument { } _PdfDocumentPdfium? pdfDoc; try { - final result = await (await backgroundWorker).compute((docAddress) { + final result = await (await backgroundWorker).computeWithArena((arena, docAddress) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(docAddress); - return using((arena) { - Pointer formInfo = nullptr; - pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; - try { - final permissions = pdfium.FPDF_GetDocPermissions(doc); - final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); - - formInfo = calloc.allocate(sizeOf()); - formInfo.ref.version = 1; - formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); - return ( - permissions: permissions, - securityHandlerRevision: securityHandlerRevision, - formHandle: formHandle.address, - formInfo: formInfo.address, - ); - } catch (e) { - pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); - calloc.free(formInfo); - rethrow; - } - }); + Pointer formInfo = nullptr; + pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; + try { + final permissions = pdfium.FPDF_GetDocPermissions(doc); + final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); + + formInfo = calloc.allocate(sizeOf()); + formInfo.ref.version = 1; + formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); + return ( + permissions: permissions, + securityHandlerRevision: securityHandlerRevision, + formHandle: formHandle.address, + formInfo: formInfo.address, + ); + } catch (e) { + pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); + calloc.free(formInfo); + rethrow; + } }, doc.address); pdfDoc = _PdfDocumentPdfium._( @@ -585,7 +584,12 @@ class _PdfDocumentPdfium extends PdfDocument { if (isDisposed) return; _pages = List.unmodifiable(loaded.pages); - subject.add(PdfDocumentPageStatusChangedEvent(this, _pages.sublist(firstUnloadedPageIndex))); + // notify pages changed + final changes = { + for (var p in _pages.skip(firstUnloadedPageIndex).take(_pages.length - firstUnloadedPageIndex)) + p.pageNumber: PdfPageStatusModified(), + }; + subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); if (onPageLoadProgress != null) { final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); @@ -601,43 +605,41 @@ class _PdfDocumentPdfium extends PdfDocument { } /// Loads pages in the document in a time-limited manner. - Future<({List<_PdfPagePdfium> pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ - List<_PdfPagePdfium> pagesLoadedSoFar = const [], + Future<({List pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ + List pagesLoadedSoFar = const [], int? maxPageCountToLoadAdditionally, Duration? timeout, }) async { try { - final results = await (await backgroundWorker).compute( - (params) { + final results = await (await backgroundWorker).computeWithArena( + (arena, params) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); - return using((arena) { - final pageCount = pdfium.FPDF_GetPageCount(doc); - final end = maxPageCountToLoadAdditionally == null - ? pageCount - : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); - final t = params.timeoutUs != null ? (Stopwatch()..start()) : null; - final pages = <({double width, double height, int rotation, double bbLeft, double bbBottom})>[]; - for (var i = params.pagesCountLoadedSoFar; i < end; i++) { - final page = pdfium.FPDF_LoadPage(doc, i); - try { - final rect = arena.allocate(sizeOf()); - pdfium.FPDF_GetPageBoundingBox(page, rect); - pages.add(( - width: pdfium.FPDF_GetPageWidthF(page), - height: pdfium.FPDF_GetPageHeightF(page), - rotation: pdfium.FPDFPage_GetRotation(page), - bbLeft: rect.ref.left.toDouble(), - bbBottom: rect.ref.bottom.toDouble(), - )); - } finally { - pdfium.FPDF_ClosePage(page); - } - if (t != null && t.elapsedMicroseconds > params.timeoutUs!) { - break; - } + final pageCount = pdfium.FPDF_GetPageCount(doc); + final end = maxPageCountToLoadAdditionally == null + ? pageCount + : min(pageCount, params.pagesCountLoadedSoFar + params.maxPageCountToLoadAdditionally!); + final t = params.timeoutUs != null ? (Stopwatch()..start()) : null; + final pages = <({double width, double height, int rotation, double bbLeft, double bbBottom})>[]; + for (var i = params.pagesCountLoadedSoFar; i < end; i++) { + final page = pdfium.FPDF_LoadPage(doc, i); + try { + final rect = arena.allocate(sizeOf()); + pdfium.FPDF_GetPageBoundingBox(page, rect); + pages.add(( + width: pdfium.FPDF_GetPageWidthF(page), + height: pdfium.FPDF_GetPageHeightF(page), + rotation: pdfium.FPDFPage_GetRotation(page), + bbLeft: rect.ref.left.toDouble(), + bbBottom: rect.ref.bottom.toDouble(), + )); + } finally { + pdfium.FPDF_ClosePage(page); + } + if (t != null && t.elapsedMicroseconds > params.timeoutUs!) { + break; } - return (pages: pages, totalPageCount: pageCount); - }); + } + return (pages: pages, totalPageCount: pageCount); }, ( docAddress: document.address, @@ -688,9 +690,44 @@ class _PdfDocumentPdfium extends PdfDocument { } @override - List<_PdfPagePdfium> get pages => _pages; + List get pages => _pages; - List<_PdfPagePdfium> _pages = []; + @override + set pages(Iterable newPages) { + final pages = []; + final changes = {}; + for (final newPage in newPages) { + if (pages.length < _pages.length) { + final old = _pages[pages.length]; + if (identical(newPage, old)) { + pages.add(newPage); + continue; + } + } + + final newPageNumber = pages.length + 1; + final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); + if (oldPageIndex != -1) { + pages.add(newPage); + changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); + continue; + } + + if (newPage is! _PdfPagePdfium && newPage is! _PdfPageImported) { + throw ArgumentError('Unsupported PdfPage instances found at [${pages.length}]', 'newPages'); + } + if (newPage.document != this || newPage.pageNumber != newPageNumber) { + final imported = _PdfPageImported._(imported: newPage, pageNumber: newPageNumber); + pages.add(imported); + changes[newPageNumber] = PdfPageStatusChange.modified; + } + } + + _pages = pages; + subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); + } + + List _pages = []; @override bool isIdenticalDocumentHandle(Object? other) => @@ -718,13 +755,10 @@ class _PdfDocumentPdfium extends PdfDocument { @override Future> loadOutline() async => isDisposed ? [] - : await (await backgroundWorker).compute( - (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); - }), - (document: document.address), - ); + : await (await backgroundWorker).computeWithArena((arena, params) { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); + }, (document: document.address)); static List _getOutlineNodeSiblings( pdfium_bindings.FPDF_BOOKMARK bookmark, @@ -747,6 +781,128 @@ class _PdfDocumentPdfium extends PdfDocument { } return siblings; } + + @override + Future assemble() => _DocumentPageArranger.doShufflePagesInPlace(this); + + @override + Future encodePdf({bool incremental = false, bool removeSecurity = false}) async { + await assemble(); + final byteBuffer = BytesBuilder(); + return await (await backgroundWorker).computeWithArena((arena, params) { + int write(Pointer pThis, Pointer pData, int size) { + byteBuffer.add(Pointer.fromAddress(pData.address).asTypedList(size)); + return size; + } + + final nativeWriteCallable = _NativeFileWriteCallable.isolateLocal(write, exceptionalReturn: 0); + try { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + final fw = arena.allocate(sizeOf()); + fw.ref.version = 1; + fw.ref.WriteBlock = nativeWriteCallable.nativeFunction; + final int flags; + if (params.removeSecurity) { + flags = 3; // FPDF_SAVE_NO_SECURITY(3) + } else { + flags = params.incremental ? 1 : 2; // FPDF_INCREMENTAL(1) or FPDF_NO_INCREMENTAL(2) + } + pdfium.FPDF_SaveAsCopy(document, fw, flags); + return byteBuffer.toBytes(); + } finally { + nativeWriteCallable.close(); + } + }, (document: document.address, incremental: incremental, removeSecurity: removeSecurity)); + } +} + +typedef _NativeFileWriteCallable = + NativeCallable, Pointer, UnsignedLong)>; + +class _DocumentPageArranger with ShuffleItemsInPlaceMixin { + /// Shuffle pages in place according to the current order of pages in [document]. + /// Returns true if the pages was modified. + static Future doShufflePagesInPlace(_PdfDocumentPdfium document) async { + final indices = []; + final items = {}; + var modifiedCount = 0; + for (var i = 0; i < document.pages.length; i++) { + final page = document.pages[i]; + if (page is _PdfPageImported) { + final pdfiumPage = page.imported as _PdfPagePdfium; + indices.add(-i); + items[-i] = (document: pdfiumPage.document.document.address, pageNumber: pdfiumPage.pageNumber); + modifiedCount++; + continue; + } else if (page is _PdfPagePdfium) { + indices.add(page.pageNumber - 1); + if (page.pageNumber - 1 != i) { + modifiedCount++; + } + continue; + } + } + if (modifiedCount == 0) { + // No changes + return false; + } + + await (await backgroundWorker).computeWithArena((arena, params) { + final arranger = _DocumentPageArranger._( + pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document), + params.items, + ); + arranger.shuffleInPlaceAccordingToIndices(indices); + }, (document: document.document.address, indices: indices, items: items, length: document.pages.length)); + return true; + } + + _DocumentPageArranger._(this.document, this.items); + final pdfium_bindings.FPDF_DOCUMENT document; + final Map items; + + @override + int get length => pdfium.FPDF_GetPageCount(document); + + @override + void move(int fromIndex, int toIndex, int count) { + using((arena) { + final pageIndices = arena.allocate(sizeOf() * count); + for (var i = 0; i < count; i++) { + pageIndices[i] = fromIndex + i; + } + pdfium.FPDF_MovePages(document, pageIndices, count, toIndex); + }); + } + + @override + void remove(int index, int count) { + for (var i = count - 1; i >= 0; i--) { + pdfium.FPDFPage_Delete(document, index + i); + } + } + + @override + void duplicate(int fromIndex, int toIndex, int count) { + using((arena) { + final pageIndices = arena.allocate(sizeOf() * count); + for (var i = 0; i < count; i++) { + pageIndices[i] = fromIndex + i; + } + pdfium.FPDF_ImportPagesByIndex(document, document, pageIndices, count, toIndex); + }); + } + + @override + void insertNew(int index, int negativeItemIndex) async { + final page = items[negativeItemIndex]!; + final src = pdfium_bindings.FPDF_DOCUMENT.fromAddress(page.document); + using((arena) { + final pageIndices = arena.allocate(sizeOf()); + pageIndices.value = page.pageNumber - 1; + pdfium.FPDF_ImportPagesByIndex(document, src, pageIndices, 1, index); + }); + } } class _PdfPagePdfium extends PdfPage { @@ -931,37 +1087,34 @@ class _PdfPagePdfium extends PdfPage { @override Future loadText() async { if (document.isDisposed || !isLoaded) return null; - return await (await backgroundWorker).compute( - (params) => using((arena) { - final doubleSize = sizeOf(); - final rectBuffer = arena.allocate(4 * sizeOf()); - final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); - final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); - final textPage = pdfium.FPDFText_LoadPage(page); - try { - final charCount = pdfium.FPDFText_CountChars(textPage); - final sb = StringBuffer(); - final charRects = []; - for (var i = 0; i < charCount; i++) { - sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); - pdfium.FPDFText_GetCharBox( - textPage, - i, - rectBuffer, // L - rectBuffer.offset(doubleSize * 2), // R - rectBuffer.offset(doubleSize * 3), // B - rectBuffer.offset(doubleSize), // T - ); - charRects.add(_rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom)); - } - return PdfPageRawText(sb.toString(), charRects); - } finally { - pdfium.FPDFText_ClosePage(textPage); - pdfium.FPDF_ClosePage(page); + return await (await backgroundWorker).computeWithArena((arena, params) { + final doubleSize = sizeOf(); + final rectBuffer = arena.allocate(4 * sizeOf()); + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); + final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); + final textPage = pdfium.FPDFText_LoadPage(page); + try { + final charCount = pdfium.FPDFText_CountChars(textPage); + final sb = StringBuffer(); + final charRects = []; + for (var i = 0; i < charCount; i++) { + sb.writeCharCode(pdfium.FPDFText_GetUnicode(textPage, i)); + pdfium.FPDFText_GetCharBox( + textPage, + i, + rectBuffer, // L + rectBuffer.offset(doubleSize * 2), // R + rectBuffer.offset(doubleSize * 3), // B + rectBuffer.offset(doubleSize), // T + ); + charRects.add(_rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom)); } - }), - (docHandle: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom), - ); + return PdfPageRawText(sb.toString(), charRects); + } finally { + pdfium.FPDFText_ClosePage(textPage); + pdfium.FPDF_ClosePage(page); + } + }, (docHandle: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom)); } @override @@ -981,7 +1134,7 @@ class _PdfPagePdfium extends PdfPage { Future> _loadWebLinks() async => document.isDisposed ? [] - : await (await backgroundWorker).compute((params) { + : await (await backgroundWorker).computeWithArena((arena, params) { pdfium_bindings.FPDF_PAGE page = nullptr; pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; @@ -994,23 +1147,21 @@ class _PdfPagePdfium extends PdfPage { if (linkPage == nullptr) return []; final doubleSize = sizeOf(); - return using((arena) { - final rectBuffer = arena.allocate(4 * doubleSize); - return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), (index) { - final rects = List.generate(pdfium.FPDFLink_CountRects(linkPage, index), (rectIndex) { - pdfium.FPDFLink_GetRect( - linkPage, - index, - rectIndex, - rectBuffer, - rectBuffer.offset(doubleSize), - rectBuffer.offset(doubleSize * 2), - rectBuffer.offset(doubleSize * 3), - ); - return _rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom); - }); - return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); + final rectBuffer = arena.allocate(4 * doubleSize); + return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), (index) { + final rects = List.generate(pdfium.FPDFLink_CountRects(linkPage, index), (rectIndex) { + pdfium.FPDFLink_GetRect( + linkPage, + index, + rectIndex, + rectBuffer, + rectBuffer.offset(doubleSize), + rectBuffer.offset(doubleSize * 2), + rectBuffer.offset(doubleSize * 3), + ); + return _rectFromLTRBBuffer(rectBuffer, params.bbLeft, params.bbBottom); }); + return PdfLink(rects, url: Uri.tryParse(_getLinkUrl(linkPage, index, arena))); }); } finally { pdfium.FPDFLink_CloseWebLinks(linkPage); @@ -1050,45 +1201,42 @@ class _PdfPagePdfium extends PdfPage { Future> _loadAnnotLinks() async => document.isDisposed ? [] - : await (await backgroundWorker).compute( - (params) => using((arena) { - final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); - try { - final count = pdfium.FPDFPage_GetAnnotCount(page); - final rectf = arena.allocate(sizeOf()); - final links = []; - for (var i = 0; i < count; i++) { - final annot = pdfium.FPDFPage_GetAnnot(page, i); - pdfium.FPDFAnnot_GetRect(annot, rectf); - final r = rectf.ref; - final rect = PdfRect( - r.left, - r.top > r.bottom ? r.top : r.bottom, - r.right, - r.top > r.bottom ? r.bottom : r.top, - ).translate(-params.bbLeft, -params.bbBottom); - - final content = _getAnnotationContent(annot, arena); - - final dest = _processAnnotDest(annot, document, arena); - if (dest != nullptr) { - links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena), annotationContent: content)); - } else { - final uri = _processAnnotLink(annot, document, arena); - if (uri != null || content != null) { - links.add(PdfLink([rect], url: uri, annotationContent: content)); - } + : await (await backgroundWorker).computeWithArena((arena, params) { + final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); + final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); + try { + final count = pdfium.FPDFPage_GetAnnotCount(page); + final rectf = arena.allocate(sizeOf()); + final links = []; + for (var i = 0; i < count; i++) { + final annot = pdfium.FPDFPage_GetAnnot(page, i); + pdfium.FPDFAnnot_GetRect(annot, rectf); + final r = rectf.ref; + final rect = PdfRect( + r.left, + r.top > r.bottom ? r.top : r.bottom, + r.right, + r.top > r.bottom ? r.bottom : r.top, + ).translate(-params.bbLeft, -params.bbBottom); + + final content = _getAnnotationContent(annot, arena); + + final dest = _processAnnotDest(annot, document, arena); + if (dest != nullptr) { + links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena), annotationContent: content)); + } else { + final uri = _processAnnotLink(annot, document, arena); + if (uri != null || content != null) { + links.add(PdfLink([rect], url: uri, annotationContent: content)); } - pdfium.FPDFPage_CloseAnnot(annot); } - return links; - } finally { - pdfium.FPDF_ClosePage(page); + pdfium.FPDFPage_CloseAnnot(annot); } - }), - (document: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom), - ); + return links; + } finally { + pdfium.FPDF_ClosePage(page); + } + }, (document: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom)); static pdfium_bindings.FPDF_DEST _processAnnotDest( pdfium_bindings.FPDF_ANNOTATION annot, @@ -1144,6 +1292,67 @@ class _PdfPagePdfium extends PdfPage { } } +/// A PDF page that is imported from another document. +class _PdfPageImported extends PdfPage { + _PdfPageImported._({required PdfPage imported, required this.pageNumber}) + : imported = imported is _PdfPageImported ? imported.imported : imported; // Unwrap nested imports + + /// The imported page + final PdfPage imported; + @override + final int pageNumber; + + @override + PdfPageRenderCancellationToken createCancellationToken() => imported.createCancellationToken(); + + @override + PdfDocument get document => imported.document; + + @override + double get height => imported.height; + + @override + bool get isLoaded => imported.isLoaded; + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + imported.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + @override + Future loadText() => imported.loadText(); + + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) => imported.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + ); + + @override + PdfPageRotation get rotation => imported.rotation; + + @override + double get width => imported.width; +} + class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { PdfPageRenderCancellationTokenPdfium(this.page); final PdfPage page; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 0d97f068..0ee1d593 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -413,8 +413,19 @@ abstract class PdfDocument { }); /// Pages. + /// + /// The list is unmodifiable; you cannot add, remove, or replace pages directly. + /// To modify the pages, use [pages] setter to set a new list of pages. List get pages; + /// Set pages. + /// + /// You can add [PdfPage] instances from any [PdfDocument] instances and the resulting document works correctly + /// if the referenced [PdfDocument] instances are alive; it's your responsibility to manage the lifetime of those + /// instances. To make the document independent from the source documents, you should call [assemble] after setting + /// the pages. + set pages(List value); + /// Load outline (a.k.a. bookmark). Future> loadOutline(); @@ -422,6 +433,18 @@ abstract class PdfDocument { /// /// It does not mean the document contents (or the document files) are identical. bool isIdenticalDocumentHandle(Object? other); + + /// Assemble the document after modifying pages. + /// + /// You should call this function after modifying [pages] to make the document consistent and independent from + /// the other source documents. If [pages] contains pages from other documents, those documents must be alive + /// until this function returns. + Future assemble(); + + /// Save the PDF document. + /// + /// This function internally calls [assemble] before encoding the PDF. + Future encodePdf({bool incremental = false, bool removeSecurity = false}); } typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); @@ -444,7 +467,7 @@ abstract class PdfDocumentEvent { /// Event that is triggered when the status of PDF document pages has changed. class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { - PdfDocumentPageStatusChangedEvent(this.document, this.pages); + PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); @override PdfDocumentEventType get type => PdfDocumentEventType.pageStatusChanged; @@ -453,7 +476,55 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { final PdfDocument document; /// The pages that have changed. - final List pages; + /// + /// The map is from page number (1-based) to it's status change. + final Map changes; +} + +/// Base class for PDF page status change. +abstract class PdfPageStatusChange { + const PdfPageStatusChange(); + + /// Create [PdfPageStatusMoved]. + static PdfPageStatusChange moved({required int oldPageNumber}) => PdfPageStatusMoved(oldPageNumber: oldPageNumber); + + /// Return [PdfPageStatusModified]. + static const modified = PdfPageStatusModified(); +} + +/// Event that is triggered when a PDF page is moved inside the same document. +class PdfPageStatusMoved extends PdfPageStatusChange { + const PdfPageStatusMoved({required this.oldPageNumber}); + final int oldPageNumber; + + @override + int get hashCode => oldPageNumber.hashCode; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfPageStatusMoved && other.oldPageNumber == oldPageNumber; + } + + @override + String toString() => 'PdfPageStatusMoved(oldPageNumber: $oldPageNumber)'; +} + +/// Event that is triggered when a PDF page is modified or newly added. +class PdfPageStatusModified extends PdfPageStatusChange { + const PdfPageStatusModified(); + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfPageStatusModified; + } + + @override + String toString() => 'PdfPageStatusModified()'; } /// Event that is triggered when the list of missing fonts in the PDF document has changed. diff --git a/packages/pdfrx_engine/lib/src/utils/shuffle_in_place.dart b/packages/pdfrx_engine/lib/src/utils/shuffle_in_place.dart new file mode 100644 index 00000000..424aa4f1 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/utils/shuffle_in_place.dart @@ -0,0 +1,147 @@ +/// Mixin that provides in-place shuffling capabilities for an array-like collection of items. +mixin ShuffleItemsInPlaceMixin { + /// The current number of items. + int get length; + + /// Moves [count] consecutive item(s) starting at [fromIndex] to [toIndex]. + void move(int fromIndex, int toIndex, int count); + + /// Removes [count] item(s) starting from the given [index]. + void remove(int index, int count); + + /// Duplicates the item(s) at the given [fromIndex] and inserts them at [toIndex]. + void duplicate(int fromIndex, int toIndex, int count); + + /// Inserts a new item at the given index. The [negativeItemIndex] indicates + /// which new item, which is identified by the negative index. + void insertNew(int index, int negativeItemIndex); + + /// Shuffles the items in place according to the given list of resulting item indices. + /// + /// For example, if the current items are [A, B, C, D] and the resultingItemIndices is [2, 0, 1], + /// the resulting items will be [B, C, A]. + /// + /// If the index is negative, a new item (of the negative index) is inserted at that position using [insertNew]. + /// + /// If same index appears multiple times in resultingItemIndices, the item at the index should be duplicated + /// accordingly; only one item can be moved, but the others are created using [duplicate]. + void shuffleInPlaceAccordingToIndices(List resultingItemIndices) { + final originalLength = length; + if (resultingItemIndices.isEmpty) { + if (originalLength > 0) { + remove(0, originalLength); + } + return; + } + + final tokens = <_ArrayOfItemsToken>[ + for (var i = 0; i < originalLength; i++) _ArrayOfItemsToken(originalIndex: i, isOriginal: true), + ]; + + final usageCounts = List.filled(originalLength, 0); + for (var i = 0; i < resultingItemIndices.length; i++) { + final index = resultingItemIndices[i]; + if (index >= 0) { + if (index >= originalLength) { + throw RangeError('resultingItemIndices[$i] = $index is out of range for current length $originalLength'); + } + usageCounts[index]++; + } + } + + for (var i = originalLength - 1; i >= 0; i--) { + if (usageCounts[i] == 0) { + remove(i, 1); + tokens.removeAt(i); + } + } + + final placedCounts = List.filled(originalLength, 0); + var currentIndex = 0; + + while (currentIndex < resultingItemIndices.length) { + if (currentIndex > tokens.length) { + throw StateError('Destination index $currentIndex is out of range for current length ${tokens.length}.'); + } + + final target = resultingItemIndices[currentIndex]; + if (target >= 0) { + final isFirst = placedCounts[target] == 0; + if (isFirst) { + final fromIndex = tokens.indexWhere((token) => token.originalIndex == target && token.isOriginal); + if (fromIndex == -1) { + throw StateError('Item at index $target could not be found for initial placement.'); + } + + var chunkLength = 1; + while (currentIndex + chunkLength < resultingItemIndices.length && fromIndex + chunkLength < tokens.length) { + final nextTarget = resultingItemIndices[currentIndex + chunkLength]; + if (nextTarget < 0 || placedCounts[nextTarget] > 0) break; + final nextToken = tokens[fromIndex + chunkLength]; + if (!nextToken.isOriginal || nextToken.originalIndex != nextTarget) break; + chunkLength++; + } + + var placementIndex = currentIndex; + if (fromIndex != currentIndex) { + final removalIndices = List.generate(chunkLength, (offset) => fromIndex + offset); + move(fromIndex, currentIndex, chunkLength); + final removedTokens = <_ArrayOfItemsToken>[]; + for (var i = removalIndices.length - 1; i >= 0; i--) { + removedTokens.insert(0, tokens.removeAt(removalIndices[i])); + } + var insertIndex = currentIndex; + for (final index in removalIndices) { + if (index < currentIndex) { + insertIndex--; + } + } + if (insertIndex < 0) insertIndex = 0; + if (insertIndex > tokens.length) insertIndex = tokens.length; + tokens.insertAll(insertIndex, removedTokens); + placementIndex = insertIndex; + } + + for (var offset = 0; offset < chunkLength; offset++) { + final token = tokens[placementIndex + offset]; + final originalIndex = token.originalIndex; + if (originalIndex != null) { + placedCounts[originalIndex]++; + } + } + currentIndex += chunkLength; + continue; + } else { + final sourceIndex = tokens.indexWhere((token) => token.originalIndex == target); + if (sourceIndex == -1) { + throw StateError('Item at index $target could not be found for duplication.'); + } + duplicate(sourceIndex, currentIndex, 1); + final newToken = _ArrayOfItemsToken(originalIndex: target, isOriginal: false); + tokens.insert(currentIndex, newToken); + placedCounts[target]++; + } + } else { + insertNew(currentIndex, target); + tokens.insert(currentIndex, const _ArrayOfItemsToken(originalIndex: null, isOriginal: false)); + } + currentIndex++; + } + + final expectedLength = resultingItemIndices.length; + if (tokens.length > expectedLength) { + final extra = tokens.length - expectedLength; + remove(expectedLength, extra); + tokens.removeRange(expectedLength, tokens.length); + } else if (tokens.length < expectedLength) { + throw StateError('Internal length mismatch after shuffling (expected $expectedLength, got ${tokens.length}).'); + } + } +} + +class _ArrayOfItemsToken { + const _ArrayOfItemsToken({required this.originalIndex, required this.isOriginal}); + + final int? originalIndex; + final bool isOriginal; +} From 084b9f729e460a9332d491853fff66665ec696d0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:09:10 +0900 Subject: [PATCH 481/663] Add pdf_combine app example. --- .vscode/launch.json | 6 + packages/pdfrx/example/pdf_combine/.gitignore | 49 ++ packages/pdfrx/example/pdf_combine/.metadata | 45 ++ packages/pdfrx/example/pdf_combine/README.md | 85 +++ .../example/pdf_combine/analysis_options.yaml | 45 ++ .../example/pdf_combine/android/.gitignore | 14 + .../pdf_combine/android/app/build.gradle.kts | 44 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 ++ .../com/example/pdfcombine/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../pdf_combine/android/build.gradle.kts | 24 + .../pdf_combine/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../pdf_combine/android/settings.gradle.kts | 26 + .../pdfrx/example/pdf_combine/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../pdf_combine/ios/Flutter/Debug.xcconfig | 1 + .../pdf_combine/ios/Flutter/Release.xcconfig | 1 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../pdf_combine/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/pdf_combine/ios/Runner/Info.plist | 49 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/RunnerTests/RunnerTests.swift | 12 + .../pdfrx/example/pdf_combine/lib/main.dart | 533 ++++++++++++++++++ .../pdf_combine/lib/save_helper_io.dart | 28 + .../pdf_combine/lib/save_helper_web.dart | 21 + .../example/pdf_combine/linux/.gitignore | 1 + .../example/pdf_combine/linux/CMakeLists.txt | 128 +++++ .../pdf_combine/linux/flutter/CMakeLists.txt | 88 +++ .../flutter/generated_plugin_registrant.cc | 19 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 26 + .../pdf_combine/linux/runner/CMakeLists.txt | 26 + .../example/pdf_combine/linux/runner/main.cc | 6 + .../linux/runner/my_application.cc | 144 +++++ .../pdf_combine/linux/runner/my_application.h | 18 + .../example/pdf_combine/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 20 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 99 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 +++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../pdf_combine/macos/Runner/Info.plist | 32 ++ .../macos/Runner/MainFlutterWindow.swift | 15 + .../macos/Runner/Release.entitlements | 8 + .../macos/RunnerTests/RunnerTests.swift | 12 + .../pdfrx/example/pdf_combine/pubspec.yaml | 34 ++ .../pdfrx/example/pdf_combine/web/favicon.png | Bin 0 -> 917 bytes .../pdf_combine/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../pdf_combine/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes .../pdfrx/example/pdf_combine/web/index.html | 38 ++ .../example/pdf_combine/web/manifest.json | 35 ++ .../example/pdf_combine/windows/.gitignore | 17 + .../pdf_combine/windows/CMakeLists.txt | 108 ++++ .../windows/flutter/CMakeLists.txt | 109 ++++ .../flutter/generated_plugin_registrant.cc | 20 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 27 + .../pdf_combine/windows/runner/CMakeLists.txt | 40 ++ .../pdf_combine/windows/runner/Runner.rc | 121 ++++ .../windows/runner/flutter_window.cpp | 71 +++ .../windows/runner/flutter_window.h | 33 ++ .../pdf_combine/windows/runner/main.cpp | 43 ++ .../pdf_combine/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 14 + .../pdf_combine/windows/runner/utils.cpp | 65 +++ .../pdf_combine/windows/runner/utils.h | 19 + .../windows/runner/win32_window.cpp | 288 ++++++++++ .../pdf_combine/windows/runner/win32_window.h | 102 ++++ pubspec.yaml | 1 + 129 files changed, 3921 insertions(+) create mode 100644 packages/pdfrx/example/pdf_combine/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/.metadata create mode 100644 packages/pdfrx/example/pdf_combine/README.md create mode 100644 packages/pdfrx/example/pdf_combine/analysis_options.yaml create mode 100644 packages/pdfrx/example/pdf_combine/android/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/android/app/build.gradle.kts create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/AndroidManifest.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/kotlin/com/example/pdfcombine/MainActivity.kt create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/values-night/styles.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/main/res/values/styles.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/pdfrx/example/pdf_combine/android/build.gradle.kts create mode 100644 packages/pdfrx/example/pdf_combine/android/gradle.properties create mode 100644 packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/pdfrx/example/pdf_combine/android/settings.gradle.kts create mode 100644 packages/pdfrx/example/pdf_combine/ios/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/AppDelegate.swift create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Info.plist create mode 100644 packages/pdfrx/example/pdf_combine/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/pdfrx/example/pdf_combine/ios/RunnerTests/RunnerTests.swift create mode 100644 packages/pdfrx/example/pdf_combine/lib/main.dart create mode 100644 packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart create mode 100644 packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart create mode 100644 packages/pdfrx/example/pdf_combine/linux/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/linux/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/linux/flutter/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc create mode 100644 packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.h create mode 100644 packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake create mode 100644 packages/pdfrx/example/pdf_combine/linux/runner/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/linux/runner/main.cc create mode 100644 packages/pdfrx/example/pdf_combine/linux/runner/my_application.cc create mode 100644 packages/pdfrx/example/pdf_combine/linux/runner/my_application.h create mode 100644 packages/pdfrx/example/pdf_combine/macos/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/AppDelegate.swift create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Debug.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Release.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Warnings.xcconfig create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Info.plist create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/MainFlutterWindow.swift create mode 100644 packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements create mode 100644 packages/pdfrx/example/pdf_combine/macos/RunnerTests/RunnerTests.swift create mode 100644 packages/pdfrx/example/pdf_combine/pubspec.yaml create mode 100644 packages/pdfrx/example/pdf_combine/web/favicon.png create mode 100644 packages/pdfrx/example/pdf_combine/web/icons/Icon-192.png create mode 100644 packages/pdfrx/example/pdf_combine/web/icons/Icon-512.png create mode 100644 packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-192.png create mode 100644 packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-512.png create mode 100644 packages/pdfrx/example/pdf_combine/web/index.html create mode 100644 packages/pdfrx/example/pdf_combine/web/manifest.json create mode 100644 packages/pdfrx/example/pdf_combine/windows/.gitignore create mode 100644 packages/pdfrx/example/pdf_combine/windows/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/windows/flutter/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc create mode 100644 packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.h create mode 100644 packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/CMakeLists.txt create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/Runner.rc create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.cpp create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.h create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/main.cpp create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/resource.h create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/resources/app_icon.ico create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/runner.exe.manifest create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/utils.cpp create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/utils.h create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/win32_window.cpp create mode 100644 packages/pdfrx/example/pdf_combine/windows/runner/win32_window.h diff --git a/.vscode/launch.json b/.vscode/launch.json index 8ad875f4..134f4d1d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,6 +24,12 @@ "program": "packages/pdfrx/example/viewer/lib/main.dart", "args": ["-d", "web-server", "--wasm", "--web-port", "8080", "--web-hostname", "127.0.0.1"] }, + { + "name": "PDF Combine", + "request": "launch", + "type": "dart", + "program": "packages/pdfrx/example/pdf_combine/lib/main.dart" + }, { "name": "pdfrx:remove_wasm_modules", "request": "launch", diff --git a/packages/pdfrx/example/pdf_combine/.gitignore b/packages/pdfrx/example/pdf_combine/.gitignore new file mode 100644 index 00000000..1735b245 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/.gitignore @@ -0,0 +1,49 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# never ever commit the local test example file... + +pubspec_overrides.yaml diff --git a/packages/pdfrx/example/pdf_combine/.metadata b/packages/pdfrx/example/pdf_combine/.metadata new file mode 100644 index 00000000..5f4336f9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: android + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: ios + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: linux + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: macos + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: web + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: windows + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/pdfrx/example/pdf_combine/README.md b/packages/pdfrx/example/pdf_combine/README.md new file mode 100644 index 00000000..79a24b1d --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/README.md @@ -0,0 +1,85 @@ +# PDF Combine - Flutter Example + +A Flutter desktop application that demonstrates how to combine multiple PDF files using the pdfrx package with cross-document page import support. + +## Features + +- **Multi-file PDF picker**: Select multiple PDF files to combine +- **Page selection UI**: Visual grid view to select specific pages from each PDF +- **Input PDF preview**: Thumbnail previews of pages for easy selection +- **Live output preview**: See the combined PDF in real-time using PdfViewer +- **Cross-document import**: Import pages from different PDF documents +- **Save combined PDF**: Export the merged PDF to local storage + +## How It Works + +The app uses the new `assemble()` and `encodePdf()` APIs from pdfrx_engine: + +1. **Open PDFs**: Load multiple PDF documents using `PdfDocument.openFile()` +2. **Select Pages**: Choose which pages to include from each document +3. **Combine**: Merge pages by setting `document.pages` with pages from all documents +4. **Encode**: Call `document.encodePdf()` to generate the combined PDF bytes +5. **Preview & Save**: Display the result and save to disk + +## Implementation Highlights + +### Cross-Document Page Import + +```dart +// Combine pages from different documents +final doc1 = await PdfDocument.openFile('file1.pdf'); +final doc2 = await PdfDocument.openFile('file2.pdf'); + +doc1.pages = [ + doc1.pages[0], // Page 1 from doc1 + doc2.pages[0], // Page 1 from doc2 (imported!) + doc1.pages[1], // Page 2 from doc1 +]; + +// Encode the combined PDF +final bytes = await doc1.encodePdf(); +``` + +### Page Manipulation + +The app demonstrates: +- **Page reordering**: Change page sequence +- **Page deletion**: Exclude unwanted pages +- **Page duplication**: Include same page multiple times +- **Cross-document import**: Merge pages from different PDFs + +## UI Layout + +The app uses a three-panel layout: + +1. **Left Panel**: List of input PDF files with page counts +2. **Middle Panel**: Grid view for selecting pages from the currently selected PDF +3. **Right Panel**: Live preview of the combined PDF output + +## Running the App + +```bash +cd packages/pdfrx/example/pdfcombine +flutter run -d linux # or macos, windows +flutter run -d chrome # for Web +``` + +## Requirements + +- Flutter SDK +- Desktop platform (Linux, macOS, Windows) or Web browser +- Local pdfrx_engine with assemble/encodePdf support + +## Dependencies + +- `pdfrx`: PDF rendering and manipulation +- `file_selector`: File selection dialog (better Linux support than file_picker, works on Web) +- `share_plus`: File downloading on Web platform + +## Notes + +This example requires the latest pdfrx_engine with: +- `PdfDocument.pages` setter for page manipulation +- `PdfDocument.assemble()` for applying page changes +- `PdfDocument.encodePdf()` for encoding to bytes +- Cross-document page import support (both native and WASM backends) diff --git a/packages/pdfrx/example/pdf_combine/analysis_options.yaml b/packages/pdfrx/example/pdf_combine/analysis_options.yaml new file mode 100644 index 00000000..6be920c2 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/analysis_options.yaml @@ -0,0 +1,45 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + prefer_single_quotes: true + comment_references: true + prefer_relative_imports: true + use_key_in_widget_constructors: true + avoid_return_types_on_setters: true + avoid_types_on_closure_parameters: true + eol_at_end_of_file: true + sort_child_properties_last: true + sort_unnamed_constructors_first: true + sort_constructors_first: true + always_put_required_named_parameters_first: true + invalid_runtime_check_with_js_interop_types: true + + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options + +analyzer: + +formatter: + page_width: 120 diff --git a/packages/pdfrx/example/pdf_combine/android/.gitignore b/packages/pdfrx/example/pdf_combine/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/packages/pdfrx/example/pdf_combine/android/app/build.gradle.kts b/packages/pdfrx/example/pdf_combine/android/app/build.gradle.kts new file mode 100644 index 00000000..dd6eaba9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.pdfcombine" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.pdfcombine" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/debug/AndroidManifest.xml b/packages/pdfrx/example/pdf_combine/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/AndroidManifest.xml b/packages/pdfrx/example/pdf_combine/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..6af6aaa3 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/kotlin/com/example/pdfcombine/MainActivity.kt b/packages/pdfrx/example/pdf_combine/android/app/src/main/kotlin/com/example/pdfcombine/MainActivity.kt new file mode 100644 index 00000000..621dc8c3 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/kotlin/com/example/pdfcombine/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.pdfcombine + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable/launch_background.xml b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values-night/styles.xml b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values/styles.xml b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/app/src/profile/AndroidManifest.xml b/packages/pdfrx/example/pdf_combine/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/pdfrx/example/pdf_combine/android/build.gradle.kts b/packages/pdfrx/example/pdf_combine/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/pdfrx/example/pdf_combine/android/gradle.properties b/packages/pdfrx/example/pdf_combine/android/gradle.properties new file mode 100644 index 00000000..f018a618 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties b/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac3b4792 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts b/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts new file mode 100644 index 00000000..fb605bc8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.9.1" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/packages/pdfrx/example/pdf_combine/ios/.gitignore b/packages/pdfrx/example/pdf_combine/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/pdfrx/example/pdf_combine/ios/Flutter/AppFrameworkInfo.plist b/packages/pdfrx/example/pdf_combine/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig b/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig b/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/AppDelegate.swift b/packages/pdfrx/example/pdf_combine/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/Main.storyboard b/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Info.plist b/packages/pdfrx/example/pdf_combine/ios/Runner/Info.plist new file mode 100644 index 00000000..fb55ebc8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Pdfcombine + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + pdfcombine + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner/Runner-Bridging-Header.h b/packages/pdfrx/example/pdf_combine/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/pdfrx/example/pdf_combine/ios/RunnerTests/RunnerTests.swift b/packages/pdfrx/example/pdf_combine/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart new file mode 100644 index 00000000..3ea67035 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -0,0 +1,533 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:animated_reorderable_list/animated_reorderable_list.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:pdfrx/pdfrx.dart'; + +import 'save_helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; + +void main() { + pdfrxFlutterInitialize(); + runApp(const PdfCombineApp()); +} + +class PdfCombineApp extends StatelessWidget { + const PdfCombineApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'PDF Combine', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const PdfCombinePage(), + ); + } +} + +class PageItem { + PageItem({ + required this.documentId, + required this.documentName, + required this.pageIndex, + required this.page, + }); + + /// Unique ID for the document + final int documentId; + + /// Name of the source document + final String documentName; + + /// Page index + final int pageIndex; + + /// The PDF page + final PdfPage page; + + String get id => '${documentId}_$pageIndex'; +} + +/// Manages loaded PDF documents and tracks page usage +class DocumentManager { + DocumentManager(this.passwordProvider); + + final FutureOr Function(int docId, String name)? passwordProvider; + final Map _documents = {}; + final Map _pageRefCounts = {}; + int _nextDocId = 0; + + Future loadDocument(String name, String filePath) async { + final docId = _nextDocId++; + final doc = await PdfDocument.openFile( + filePath, + passwordProvider: passwordProvider != null + ? () => passwordProvider!(docId, name) + : null, + ); + _documents[docId] = doc; + _pageRefCounts[docId] = 0; + return docId; + } + + PdfDocument? getDocument(int docId) => _documents[docId]; + + void addReference(int docId) { + _pageRefCounts[docId] = (_pageRefCounts[docId] ?? 0) + 1; + } + + void removeReference(int docId) { + final count = (_pageRefCounts[docId] ?? 1) - 1; + _pageRefCounts[docId] = count; + + if (count <= 0) { + _disposeDocument(docId); + } + } + + void _disposeDocument(int docId) { + _documents[docId]?.dispose(); + _documents.remove(docId); + _pageRefCounts.remove(docId); + } + + void disposeAll() { + for (final doc in _documents.values) { + doc.dispose(); + } + _documents.clear(); + _pageRefCounts.clear(); + } +} + +class PdfCombinePage extends StatefulWidget { + const PdfCombinePage({super.key}); + + @override + State createState() => _PdfCombinePageState(); +} + +class _PdfCombinePageState extends State { + late final _docManager = DocumentManager( + (docId, name) => passwordDialog(name, context), + ); + final _pages = []; + final _scrollController = ScrollController(); + bool _isLoading = false; + bool _disableDragging = false; + bool _isTouchDevice = true; + + @override + void dispose() { + _docManager.disposeAll(); + _scrollController.dispose(); + super.dispose(); + } + + Future _pickPdfFiles() async { + final files = await openFiles( + acceptedTypeGroups: [ + XTypeGroup(label: 'PDFs', extensions: ['pdf']), + ], + ); + if (files.isEmpty) return; + + setState(() { + _isLoading = true; + }); + + try { + for (final file in files) { + final docId = await _docManager.loadDocument(file.name, file.path); + final doc = _docManager.getDocument(docId); + if (doc != null) { + for (var i = 0; i < doc.pages.length; i++) { + _docManager.addReference(docId); + _pages.add( + PageItem( + documentId: docId, + documentName: file.name, + pageIndex: i, + page: doc.pages[i], + ), + ); + } + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + void _removePage(int index) { + setState(() { + final pageItem = _pages[index]; + _pages.removeAt(index); + _docManager.removeReference(pageItem.documentId); + }); + } + + Future _navigateToPreview() async { + if (_pages.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please add some pages first')), + ); + return; + } + + await Navigator.push( + context, + MaterialPageRoute(builder: (context) => OutputPreviewPage(pages: _pages)), + ); + } + + Widget _disableDraggingOnChild(Widget child) { + return MouseRegion( + child: child, + onEnter: (_) { + setState(() { + _disableDragging = true; + }); + }, + onExit: (_) { + setState(() { + _disableDragging = false; + }); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('PDF Combine'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _pickPdfFiles, + tooltip: 'Add PDF files', + ), + const SizedBox(width: 8), + FilledButton.icon( + onPressed: _pages.isEmpty ? null : _navigateToPreview, + icon: const Icon(Icons.arrow_forward), + label: const Text('Preview & Save'), + ), + const SizedBox(width: 16), + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _pages.isEmpty + ? Center( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'Tap the following button to add PDF files!\n\n', + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: IconButton.filled( + icon: Icon(Icons.add), + onPressed: () => _pickPdfFiles(), + ), + ), + ], + ), + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ) + : LayoutBuilder( + builder: (context, constraints) { + final w = constraints.maxWidth; + final int crossAxisCount; + if (w < 120) { + crossAxisCount = 1; + } else if (w < 400) { + crossAxisCount = 2; + } else if (w < 800) { + crossAxisCount = 3; + } else if (w < 1200) { + crossAxisCount = 4; + } else { + crossAxisCount = w ~/ 300; + } + return Listener( + onPointerMove: (event) { + setState(() { + _isTouchDevice = event.kind == PointerDeviceKind.touch; + }); + }, + onPointerHover: (event) { + setState(() { + _isTouchDevice = event.kind == PointerDeviceKind.touch; + }); + }, + child: AnimatedReorderableGridView( + items: _pages, + isSameItem: (a, b) => a.id == b.id, + itemBuilder: (BuildContext context, int index) { + final pageItem = _pages[index]; + return _PageThumbnail( + key: ValueKey(pageItem.id), + page: pageItem.page, + onRemove: () => _removePage(index), + currentIndex: index, + dragDisabler: _disableDraggingOnChild, + ); + }, + sliverGridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + ), + insertDuration: const Duration(milliseconds: 300), + removeDuration: const Duration(milliseconds: 300), + dragStartDelay: _isTouchDevice || _disableDragging + ? const Duration(milliseconds: 200) + : Duration.zero, + onReorder: (int oldIndex, int newIndex) { + setState(() { + final removed = _pages.removeAt(oldIndex); + _pages.insert(newIndex, removed); + }); + }, + ), + ); + }, + ), + ); + } +} + +/// Widget for displaying a page thumbnail in the grid +class _PageThumbnail extends StatelessWidget { + const _PageThumbnail({ + super.key, + required this.page, + required this.onRemove, + required this.currentIndex, + required this.dragDisabler, + }); + + final PdfPage page; + final VoidCallback onRemove; + final int currentIndex; + final Widget Function(Widget child) dragDisabler; + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAlias, + child: Stack( + fit: StackFit.expand, + children: [ + Padding( + padding: const EdgeInsets.all(4.0), + child: PdfPageView( + document: page.document, + pageNumber: page.pageNumber, + ), + ), + // Delete button + Positioned( + top: 4, + right: 4, + child: dragDisabler( + Material( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + child: InkWell( + onTap: onRemove, + borderRadius: BorderRadius.circular(20), + child: const Padding( + padding: EdgeInsets.all(4), + child: Icon(Icons.close, color: Colors.white, size: 20), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class OutputPreviewPage extends StatefulWidget { + const OutputPreviewPage({super.key, required this.pages}); + + final List pages; + + @override + State createState() => _OutputPreviewPageState(); +} + +class _OutputPreviewPageState extends State { + Uint8List? _outputPdfBytes; + bool _isGenerating = false; + + @override + void initState() { + super.initState(); + _generatePdf(); + } + + Future _generatePdf() async { + setState(() { + _isGenerating = true; + }); + + try { + // Create a new document using the first page's document as base + final firstPageDoc = widget.pages.first.page.document; + final combinedDoc = firstPageDoc; + + // Set all selected pages + combinedDoc.pages = widget.pages.map((item) => item.page).toList(); + + // Encode to PDF + final bytes = await combinedDoc.encodePdf(); + + if (mounted) { + setState(() { + _outputPdfBytes = bytes; + _isGenerating = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _isGenerating = false; + }); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error generating PDF: $e'))); + } + } + } + + Future _savePdf() async { + if (_outputPdfBytes == null) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('PDF not ready yet'))); + return; + } + + try { + final timestamp = DateTime.now().millisecondsSinceEpoch; + await savePdf(_outputPdfBytes!, suggestedName: 'output_$timestamp.pdf'); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error saving PDF: $e'))); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Preview'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + actions: [ + FilledButton.icon( + onPressed: _outputPdfBytes == null ? null : _savePdf, + icon: const Icon(Icons.save), + label: const Text('Save PDF'), + ), + const SizedBox(width: 16), + ], + ), + body: _isGenerating + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Generating combined PDF...'), + ], + ), + ) + : _outputPdfBytes == null + ? const Center(child: Text('Failed to generate PDF')) + : Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + color: Theme.of(context).colorScheme.primaryContainer, + child: Row( + children: [ + const Icon(Icons.info_outline), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Combined ${widget.pages.length} pages. Review the PDF below, then save or go back to make changes.', + ), + ), + ], + ), + ), + Expanded( + child: PdfViewer.data( + _outputPdfBytes!, + sourceName: 'combined.pdf', + ), + ), + ], + ), + ); + } +} + +Future passwordDialog(String name, BuildContext context) async { + final textController = TextEditingController(); + return await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: Text('Enter password for "$name"'), + content: TextField( + controller: textController, + autofocus: true, + keyboardType: TextInputType.visiblePassword, + obscureText: true, + onSubmitted: (value) => Navigator.of(context).pop(value), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(textController.text), + child: const Text('OK'), + ), + ], + ); + }, + ); +} diff --git a/packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart new file mode 100644 index 00000000..b72d65e7 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart @@ -0,0 +1,28 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_selector/file_selector.dart'; +import 'package:share_plus/share_plus.dart'; + +Future savePdf(Uint8List bytes, {String? suggestedName}) async { + if (Platform.isIOS || Platform.isAndroid) { + final xFile = XFile.fromData( + bytes, + name: suggestedName ?? 'document.pdf', + mimeType: 'application/pdf', + ); + await Share.shareXFiles([xFile]); + return; + } + final savePath = await getSaveLocation( + suggestedName: suggestedName, + acceptedTypeGroups: [ + const XTypeGroup(label: 'PDF', extensions: ['pdf']), + ], + ); + + if (savePath != null) { + final file = File(savePath.path); + await file.writeAsBytes(bytes); + } +} diff --git a/packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart new file mode 100644 index 00000000..eac17f37 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart @@ -0,0 +1,21 @@ +import 'dart:js_interop'; +import 'dart:typed_data'; + +import 'package:web/web.dart' as web; + +Future savePdf(Uint8List bytes, {String? suggestedName}) async { + final blob = web.Blob( + [bytes].jsify() as JSArray, + web.BlobPropertyBag(type: 'application/pdf'), + ); + + final url = web.URL.createObjectURL(blob); + final anchor = web.HTMLAnchorElement(); + anchor.href = url; + anchor.download = suggestedName ?? 'document.pdf'; + web.document.body?.append(anchor); + anchor.click(); + + web.URL.revokeObjectURL(url); + anchor.remove(); +} diff --git a/packages/pdfrx/example/pdf_combine/linux/.gitignore b/packages/pdfrx/example/pdf_combine/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/pdfrx/example/pdf_combine/linux/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/linux/CMakeLists.txt new file mode 100644 index 00000000..bd8938c9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "pdfcombine") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.pdfcombine") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..7299b5cf --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.h b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..409ae740 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + pdfrx +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/pdfrx/example/pdf_combine/linux/runner/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/packages/pdfrx/example/pdf_combine/linux/runner/main.cc b/packages/pdfrx/example/pdf_combine/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/pdfrx/example/pdf_combine/linux/runner/my_application.cc b/packages/pdfrx/example/pdf_combine/linux/runner/my_application.cc new file mode 100644 index 00000000..8fe0f684 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/runner/my_application.cc @@ -0,0 +1,144 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView *view) +{ + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "pdfcombine"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "pdfcombine"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/pdfrx/example/pdf_combine/linux/runner/my_application.h b/packages/pdfrx/example/pdf_combine/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/pdfrx/example/pdf_combine/macos/.gitignore b/packages/pdfrx/example/pdf_combine/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..56883a1b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import path_provider_foundation +import pdfrx +import share_plus +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..605a6a4b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/AppDelegate.swift b/packages/pdfrx/example/pdf_combine/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/pdfrx/example/pdf_combine/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/AppInfo.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..060ad21b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = pdfcombine + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfcombine + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Debug.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Release.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Warnings.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements b/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Info.plist b/packages/pdfrx/example/pdf_combine/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/MainFlutterWindow.swift b/packages/pdfrx/example/pdf_combine/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements b/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/pdfrx/example/pdf_combine/macos/RunnerTests/RunnerTests.swift b/packages/pdfrx/example/pdf_combine/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml new file mode 100644 index 00000000..5957f765 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -0,0 +1,34 @@ +name: pdf_combine +description: "PDF Combine - Combine multiple PDF files into one with page selection" +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ^3.9.2 + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + cupertino_icons: ^1.0.8 + + pdfrx: + path: ../../ + + file_selector: ^1.0.3 + share_plus: ^10.0.3 + path_provider: ^2.1.5 + animated_reorderable_list: ^1.3.0 + web: ^1.1.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true diff --git a/packages/pdfrx/example/pdf_combine/web/favicon.png b/packages/pdfrx/example/pdf_combine/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/web/icons/Icon-192.png b/packages/pdfrx/example/pdf_combine/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/web/icons/Icon-512.png b/packages/pdfrx/example/pdf_combine/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-192.png b/packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-512.png b/packages/pdfrx/example/pdf_combine/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/web/index.html b/packages/pdfrx/example/pdf_combine/web/index.html new file mode 100644 index 00000000..a733a855 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + pdfcombine + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/web/manifest.json b/packages/pdfrx/example/pdf_combine/web/manifest.json new file mode 100644 index 00000000..207fecc9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "pdfcombine", + "short_name": "pdfcombine", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/pdfrx/example/pdf_combine/windows/.gitignore b/packages/pdfrx/example/pdf_combine/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/pdfrx/example/pdf_combine/windows/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/windows/CMakeLists.txt new file mode 100644 index 00000000..3f9f4183 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(pdfcombine LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "pdfcombine") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..5e62361f --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.h b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..99584d06 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + pdfrx +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/CMakeLists.txt b/packages/pdfrx/example/pdf_combine/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/Runner.rc b/packages/pdfrx/example/pdf_combine/windows/runner/Runner.rc new file mode 100644 index 00000000..1ae98029 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "pdfcombine" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "pdfcombine" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "pdfcombine.exe" "\0" + VALUE "ProductName", "pdfcombine" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.cpp b/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.h b/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/main.cpp b/packages/pdfrx/example/pdf_combine/windows/runner/main.cpp new file mode 100644 index 00000000..b60381be --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"pdfcombine", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/resource.h b/packages/pdfrx/example/pdf_combine/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/resources/app_icon.ico b/packages/pdfrx/example/pdf_combine/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/runner.exe.manifest b/packages/pdfrx/example/pdf_combine/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/utils.cpp b/packages/pdfrx/example/pdf_combine/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/utils.h b/packages/pdfrx/example/pdf_combine/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.cpp b/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.h b/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/pubspec.yaml b/pubspec.yaml index 0f03b046..07aa75c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,3 +6,4 @@ workspace: - packages/pdfrx_engine - packages/pdfrx_coregraphics - packages/pdfrx/example/viewer + - packages/pdfrx/example/pdf_combine From c5d3ef5ea45003cd9b88ea8fb777b8b78ac9ef23 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:36:29 +0900 Subject: [PATCH 482/663] Release pdfrx v2.2.8, pdfrx_engine v0.2.4, and pdfrx_coregraphics v0.1.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 4 ++-- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_coregraphics/CHANGELOG.md | 4 ++++ packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 6 ++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 8 files changed, 24 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0c2ecc6d..dc9eac34 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.7 + pdfrx: ^2.2.8 ``` ### For Pure Dart Applications @@ -54,7 +54,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.2.3 + pdfrx_engine: ^0.2.4 ``` ## Documentation diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index ba0945ef..469a706f 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.8 + +- NEW: `PdfDocument` now supports page re-arrangement and accepts `PdfPage` instances from other documents, enabling PDF combine/merge functionality +- Added `pdf_combine` app example demonstrating PDF merging capabilities +- Updated to pdfrx_engine 0.2.4 + # 2.2.7 - `PdfViewer.uri` now supports timeout parameter ([#508](https://github.com/espresso3389/pdfrx/issues/508)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b29e6689..4ac3ff7e 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.7 + pdfrx: ^2.2.8 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b9bc93ff..9d46cd6f 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.7 +version: 2.2.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.3 + pdfrx_engine: ^0.2.4 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index bbf237c7..f059f6c2 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.7 + +- Updated to pdfrx_engine 0.2.4 + ## 0.1.6 - Updated to pdfrx_engine 0.2.3 diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 110081e5..6a78799f 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.6 +version: 0.1.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.2.3 + pdfrx_engine: ^0.2.4 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index d09a4fe2..d7508260 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.4 + +- NEW: `PdfDocument` now supports page re-arrangement and accepts `PdfPage` instances from other documents, enabling PDF combine/merge functionality +- Added additional PDFium functions for page manipulation +- FIXED: Type parameter 'T' shadowing issue in pdfium.dart + ## 0.2.3 - Added configurable timeout parameter to `openUri` and `pdfDocumentFromUri` functions ([#509](https://github.com/espresso3389/pdfrx/pull/509)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index e65543db..6d694812 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. -version: 0.2.3 +version: 0.2.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From b3a4901fa410d9ed0ef6d8d6c0f7d4ad3693ab77 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:41:45 +0900 Subject: [PATCH 483/663] Update documentation to reflect PDF manipulation and editing features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated package descriptions to mention manipulation, editing, and combining capabilities - Changed "PDF rendering engine" to "PDF rendering and manipulation engine" - Changed "PDF viewer" to "PDF viewer and manipulation plugin" - Added references to page re-arrangement and document combining features - Updated all README files to reflect the new editing capabilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 14 ++++++++------ packages/pdfrx/README.md | 8 ++++---- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_engine/README.md | 2 +- packages/pdfrx_engine/pubspec.yaml | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dc9eac34..b5b83692 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,28 @@ # pdfrx -This repository contains three Dart/Flutter packages for PDF rendering and viewing: +This repository contains three Dart/Flutter packages for PDF rendering, viewing, and manipulation: ## Packages ### [pdfrx_engine](packages/pdfrx_engine/) -A platform-agnostic PDF rendering API built on top of PDFium. +A platform-agnostic PDF rendering and manipulation API built on top of PDFium. - Pure Dart package (no Flutter dependencies) -- Provides low-level PDF document API +- Provides low-level PDF document API for viewing and editing +- Supports page re-arrangement, PDF combining, and document manipulation - Can be used in CLI applications or non-Flutter Dart projects - Supports all platforms: Android, iOS, Windows, macOS, Linux ### [pdfrx](packages/pdfrx/) -A cross-platform PDF viewer plugin for Flutter. +A cross-platform PDF viewer and manipulation plugin for Flutter. - Flutter plugin with UI widgets - Built on top of pdfrx_engine - Provides high-level viewer widgets and overlays - Includes text selection, search, zoom controls, and more +- Supports PDF editing features like page manipulation and document combining ### [pdfrx_coregraphics](packages/pdfrx_coregraphics/) @@ -33,8 +35,8 @@ A cross-platform PDF viewer plugin for Flutter. ## When to Use Which Package -- **Use `pdfrx`** if you're building a Flutter application and need PDF viewing capabilities with UI -- **Use `pdfrx_engine`** if you need PDF rendering without Flutter dependencies (e.g., server-side PDF processing, CLI tools) +- **Use `pdfrx`** if you're building a Flutter application and need PDF viewing and manipulation capabilities with UI +- **Use `pdfrx_engine`** if you need PDF rendering and manipulation without Flutter dependencies (e.g., server-side PDF processing, CLI tools, PDF combining utilities) - **Use `pdfrx_coregraphics`** (experimental) if you want to use CoreGraphics/PDFKit instead of PDFium on iOS/macOS ## Getting Started diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 4ac3ff7e..5d08fa46 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -2,12 +2,12 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer plugin for Flutter. It provides ready-to-use widgets for displaying PDF documents in your Flutter applications. +[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer and manipulation plugin for Flutter. It provides ready-to-use widgets for displaying and editing PDF documents in your Flutter applications. -This plugin is built on top of [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine), which handles the low-level PDF rendering using [PDFium](https://pdfium.googlesource.com/pdfium/). The separation allows for a clean architecture where: +This plugin is built on top of [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine), which handles the low-level PDF rendering and manipulation using [PDFium](https://pdfium.googlesource.com/pdfium/). The separation allows for a clean architecture where: -- **pdfrx** (this package) - Provides Flutter widgets, UI components, and platform integration -- **pdfrx_engine** - Handles PDF parsing and rendering without Flutter dependencies +- **pdfrx** (this package) - Provides Flutter widgets, UI components, platform integration, and PDF editing features +- **pdfrx_engine** - Handles PDF parsing, rendering, and manipulation without Flutter dependencies The plugin supports Android, iOS, Windows, macOS, Linux, and Web. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 9d46cd6f..e9ba0f1e 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,5 +1,5 @@ name: pdfrx -description: pdfrx is a rich and fast PDF viewer implementation built on the top of PDFium. The plugin supports Android, iOS, Windows, macOS, Linux, and Web. +description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. version: 2.2.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 0f7ce17d..8071e8ac 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -2,7 +2,7 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side processing. +[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering and manipulation engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs for viewing, editing, and combining PDF documents without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side PDF processing. This package is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 6d694812..3ca330a2 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,5 +1,5 @@ name: pdfrx_engine -description: pdfrx_engine is a PDF rendering API built on top of PDFium, designed to be used with the pdfrx plugin. +description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. version: 0.2.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine From 79f2dbe0a2f2296eb9b5c2cbd8998de5e47c7906 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:46:54 +0900 Subject: [PATCH 484/663] Add comprehensive PDF Page Manipulation documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created detailed guide for page manipulation features in doc/PDF-Page-Manipulation.md - Covers page re-arrangement, combining multiple PDFs, and page extraction - Includes basic and advanced examples - Documents both CLI (pdfcombine.dart) and Flutter (pdf_combine app) examples - Provides best practices for memory management and document handling - Added practical use cases: merging service, page extractor, document splitter - Updated doc/README.md to include the new documentation in Advanced Topics section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- doc/PDF-Page-Manipulation.md | 481 +++++++++++++++++++++++++++++++++++ doc/README.md | 1 + 2 files changed, 482 insertions(+) create mode 100644 doc/PDF-Page-Manipulation.md diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md new file mode 100644 index 00000000..d141817f --- /dev/null +++ b/doc/PDF-Page-Manipulation.md @@ -0,0 +1,481 @@ +# PDF Page Manipulation + +pdfrx provides powerful APIs for manipulating PDF pages, including re-arranging pages within a document and combining pages from multiple PDF documents. This feature enables you to create PDF merge tools, page extractors, and document reorganization utilities. + +## Overview + +The page manipulation feature allows you to: + +- Re-arrange pages within a PDF document +- Combine pages from multiple PDF documents into one +- Extract specific pages from a document +- Duplicate pages within a document +- Create new PDF documents from selected pages + +## Key Concepts + +### PdfDocument.pages Property + +The `PdfDocument.pages` property is both readable and writable: + +```dart +// Read pages +final pages = document.pages; // List + +// Write/re-arrange pages +document.pages = [pages[2], pages[0], pages[1]]; // Re-arrange pages +``` + +### Cross-Document Page References + +`PdfPage` instances can be used across different `PdfDocument` instances. This means you can take pages from one document and add them to another: + +```dart +final doc1 = await PdfDocument.openFile('document1.pdf'); +final doc2 = await PdfDocument.openFile('document2.pdf'); + +// Combine pages from both documents +doc1.pages = [ + doc1.pages[0], // Page 1 from doc1 + doc2.pages[0], // Page 1 from doc2 + doc1.pages[1], // Page 2 from doc1 + doc2.pages[1], // Page 2 from doc2 +]; + +// Encode the combined document +final combinedPdf = await doc1.encodePdf(); +``` + +### Encoding to PDF + +After manipulating pages, use `encodePdf()` to generate the final PDF file: + +```dart +final pdfBytes = await document.encodePdf(); +await File('output.pdf').writeAsBytes(pdfBytes); +``` + +## Basic Examples + +### Re-arranging Pages Within a Document + +```dart +import 'dart:io'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +Future reorderPages() async { + await pdfrxInitialize(); + + // Open a PDF document + final doc = await PdfDocument.openFile('input.pdf'); + + // Get current pages + final pages = doc.pages; + + // Re-arrange pages (reverse order in this example) + doc.pages = pages.reversed.toList(); + + // Save the modified document + final bytes = await doc.encodePdf(); + await File('reversed.pdf').writeAsBytes(bytes); + + doc.dispose(); +} +``` + +### Extracting Specific Pages + +```dart +Future extractPages() async { + await pdfrxInitialize(); + + final doc = await PdfDocument.openFile('input.pdf'); + + // Extract pages 1, 3, and 5 (indices 0, 2, 4) + doc.pages = [doc.pages[0], doc.pages[2], doc.pages[4]]; + + final bytes = await doc.encodePdf(); + await File('extracted.pdf').writeAsBytes(bytes); + + doc.dispose(); +} +``` + +### Duplicating Pages + +```dart +Future duplicatePages() async { + await pdfrxInitialize(); + + final doc = await PdfDocument.openFile('input.pdf'); + + // Duplicate each page twice + final duplicated = []; + for (final page in doc.pages) { + duplicated.add(page); + duplicated.add(page); + } + + doc.pages = duplicated; + + final bytes = await doc.encodePdf(); + await File('duplicated.pdf').writeAsBytes(bytes); + + doc.dispose(); +} +``` + +## Advanced Examples + +### Combining Multiple PDF Documents + +```dart +Future combinePdfs() async { + await pdfrxInitialize(); + + // Open multiple documents + final doc1 = await PdfDocument.openFile('document1.pdf'); + final doc2 = await PdfDocument.openFile('document2.pdf'); + final doc3 = await PdfDocument.openFile('document3.pdf'); + + // Combine all pages from all documents + doc1.pages = [ + ...doc1.pages, // All pages from doc1 + ...doc2.pages, // All pages from doc2 + ...doc3.pages, // All pages from doc3 + ]; + + // Generate the combined PDF + final bytes = await doc1.encodePdf(); + await File('combined.pdf').writeAsBytes(bytes); + + // Clean up + doc1.dispose(); + doc2.dispose(); + doc3.dispose(); +} +``` + +### Selective Page Combining + +```dart +Future selectiveCombine() async { + await pdfrxInitialize(); + + final doc1 = await PdfDocument.openFile('document1.pdf'); + final doc2 = await PdfDocument.openFile('document2.pdf'); + + // Combine specific pages in custom order + doc1.pages = [ + doc1.pages[0], // Page 1 from doc1 + doc1.pages[1], // Page 2 from doc1 + doc2.pages[0], // Page 1 from doc2 + doc1.pages[2], // Page 3 from doc1 + doc2.pages[2], // Page 3 from doc2 + doc2.pages[3], // Page 4 from doc2 + ]; + + final bytes = await doc1.encodePdf(); + await File('selective.pdf').writeAsBytes(bytes); + + doc1.dispose(); + doc2.dispose(); +} +``` + +## Complete Working Examples + +### Command-Line PDF Combiner + +The [pdfcombine.dart](../packages/pdfrx_engine/example/pdfcombine.dart) example demonstrates a full-featured command-line tool for combining PDFs: + +```bash +# Combine entire documents +dart run pdfrx_engine:pdfcombine -o output.pdf doc1.pdf doc2.pdf doc3.pdf -- a b c + +# Combine specific page ranges +dart run pdfrx_engine:pdfcombine -o output.pdf doc1.pdf doc2.pdf -- a[1-10] b[5-15] + +# Mix pages from multiple documents +dart run pdfrx_engine:pdfcombine -o output.pdf doc1.pdf doc2.pdf -- a[1-3] b a[4-6] b[1-2] +``` + +Key features: +- Flexible page specification syntax +- Support for page ranges (`[1-10]`) and individual pages (`[1,3,5]`) +- Can interleave pages from multiple documents +- Validates page numbers and file existence + +### Flutter PDF Combine App + +The [pdf_combine](../packages/pdfrx/example/pdf_combine/) Flutter app provides a visual interface for combining PDFs: + +Key features: +- Drag-and-drop interface for page re-arrangement +- Visual thumbnails of PDF pages +- Support for multiple source documents +- Live preview of the combined result +- Export to file + +Core implementation: + +```dart +// Load pages from multiple documents +final doc1 = await PdfDocument.openFile('file1.pdf'); +final doc2 = await PdfDocument.openFile('file2.pdf'); + +// Collect pages in desired order +final selectedPages = [ + doc1.pages[0], + doc2.pages[3], + doc1.pages[2], + // ... add more pages as needed +]; + +// Create combined document +final outputDoc = doc1; +outputDoc.pages = selectedPages; + +// Generate the PDF +final bytes = await outputDoc.encodePdf(); + +// Save or display the result +await File('output.pdf').writeAsBytes(bytes); +``` + +## Document Management Best Practices + +### Memory Management + +When working with multiple documents, proper cleanup is important: + +```dart +final documents = []; + +try { + // Open documents + documents.add(await PdfDocument.openFile('doc1.pdf')); + documents.add(await PdfDocument.openFile('doc2.pdf')); + + // ... manipulate pages ... + + final bytes = await documents[0].encodePdf(); + await File('output.pdf').writeAsBytes(bytes); +} finally { + // Always dispose documents + for (final doc in documents) { + doc.dispose(); + } +} +``` + +### Reference Counting Pattern + +When sharing pages across documents in complex scenarios, consider implementing reference counting: + +```dart +class DocumentManager { + final Map _documents = {}; + final Map _refCounts = {}; + + Future loadDocument(String path) async { + final docId = _documents.length; + _documents[docId] = await PdfDocument.openFile(path); + _refCounts[docId] = 0; + return docId; + } + + void addReference(int docId) { + _refCounts[docId] = (_refCounts[docId] ?? 0) + 1; + } + + void removeReference(int docId) { + final count = (_refCounts[docId] ?? 1) - 1; + _refCounts[docId] = count; + + if (count <= 0) { + _documents[docId]?.dispose(); + _documents.remove(docId); + _refCounts.remove(docId); + } + } + + void disposeAll() { + for (final doc in _documents.values) { + doc.dispose(); + } + _documents.clear(); + _refCounts.clear(); + } +} +``` + +## Important Notes + +### Page Validation + +Always validate page indices before accessing: + +```dart +final doc = await PdfDocument.openFile('input.pdf'); + +// Safe page access +if (pageIndex >= 0 && pageIndex < doc.pages.length) { + final page = doc.pages[pageIndex]; + // ... use page ... +} +``` + +### Encoding Performance + +`encodePdf()` can be resource-intensive for large documents: + +```dart +// Show loading indicator for large documents +setState(() => isGenerating = true); + +try { + final bytes = await document.encodePdf(); + // ... save bytes ... +} finally { + setState(() => isGenerating = false); +} +``` + +### Original Document Preservation + +If you need to preserve the original document, work on a copy or keep the original reference: + +```dart +final original = await PdfDocument.openFile('original.pdf'); +final originalPages = List.from(original.pages); + +// Modify pages +original.pages = originalPages.reversed.toList(); + +// If you need to restore: +// original.pages = originalPages; +``` + +### Password-Protected PDFs + +When combining password-protected PDFs, provide the password when opening: + +```dart +final doc = await PdfDocument.openFile( + 'protected.pdf', + passwordProvider: () async { + // Return the password + return 'secret123'; + }, +); +``` + +## Use Cases + +### PDF Merging Service + +Create a service that merges multiple PDF files: + +```dart +class PdfMerger { + Future mergeFiles(List filePaths) async { + await pdfrxInitialize(); + + final documents = []; + + try { + // Load all documents + for (final path in filePaths) { + documents.add(await PdfDocument.openFile(path)); + } + + // Combine all pages + final allPages = []; + for (final doc in documents) { + allPages.addAll(doc.pages); + } + + // Set combined pages on first document + documents.first.pages = allPages; + + // Generate result + return await documents.first.encodePdf(); + } finally { + for (final doc in documents) { + doc.dispose(); + } + } + } +} +``` + +### Page Extractor + +Extract a range of pages from a PDF: + +```dart +Future extractPageRange( + String inputPath, + int startPage, + int endPage, +) async { + await pdfrxInitialize(); + + final doc = await PdfDocument.openFile(inputPath); + + try { + // Validate range + if (startPage < 1 || endPage > doc.pages.length || startPage > endPage) { + throw ArgumentError('Invalid page range'); + } + + // Extract pages (convert to 0-based indices) + final extractedPages = doc.pages.sublist(startPage - 1, endPage); + doc.pages = extractedPages; + + return await doc.encodePdf(); + } finally { + doc.dispose(); + } +} +``` + +### Document Splitter + +Split a PDF into multiple files: + +```dart +Future splitPdf(String inputPath, String outputDir) async { + await pdfrxInitialize(); + + final doc = await PdfDocument.openFile(inputPath); + + try { + // Create one file per page + for (var i = 0; i < doc.pages.length; i++) { + final singlePageDoc = doc; + singlePageDoc.pages = [doc.pages[i]]; + + final bytes = await singlePageDoc.encodePdf(); + final outputPath = '$outputDir/page_${i + 1}.pdf'; + await File(outputPath).writeAsBytes(bytes); + } + } finally { + doc.dispose(); + } +} +``` + +## Related Documentation + +- [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - For advanced PDFium operations +- [pdfrx Initialization](pdfrx-Initialization.md) - Proper initialization +- [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Working with encrypted files + +## API Reference + +For detailed API documentation, see: +- [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) +- [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) +- [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html) diff --git a/doc/README.md b/doc/README.md index a0f7c882..90ef2bbf 100644 --- a/doc/README.md +++ b/doc/README.md @@ -29,6 +29,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ### Advanced Topics +- [PDF Page Manipulation](PDF-Page-Manipulation.md) - Re-arrange, combine, and extract PDF pages - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays From baafe0dc9facc3bf97a89148ec5934932bcd2851 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 02:54:02 +0900 Subject: [PATCH 485/663] Document assemble() function for PDF page manipulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added comprehensive documentation for PdfDocument.assemble() function - Explains that assemble() re-organizes documents to remove dependencies on source documents - Documents that encodePdf() automatically calls assemble() internally - Shows when to explicitly call assemble() for early memory release - Updated all combining examples to demonstrate proper use of assemble() - Added dedicated section on using assemble() for memory management - Updated performance notes to include assembly timing considerations - Enhanced PDF merging service example with proper assemble() usage Key benefits documented: - Reduces memory footprint when working with large PDFs - Allows early disposal of source documents - Prevents holding references to potentially large source documents - Essential for efficient memory management in multi-document scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- doc/PDF-Page-Manipulation.md | 107 ++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index d141817f..c2b3be84 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -46,6 +46,30 @@ doc1.pages = [ final combinedPdf = await doc1.encodePdf(); ``` +### The assemble() Function + +When you combine pages from multiple documents, the resulting document maintains references to the source documents. The `assemble()` function re-organizes the document to be self-contained, removing dependencies on other `PdfDocument` instances: + +```dart +// After combining pages from multiple documents +doc1.pages = [...doc1.pages, ...doc2.pages, ...doc3.pages]; + +// Assemble to make doc1 independent +await doc1.assemble(); + +// Now you can safely dispose doc2 and doc3 +doc2.dispose(); +doc3.dispose(); + +// doc1 is still valid and can be encoded later +final bytes = await doc1.encodePdf(); +``` + +**Important Notes:** +- `assemble()` is automatically called by `encodePdf()`, so you don't need to call it explicitly before encoding +- Call `assemble()` explicitly when you want to release source documents early to free memory +- After calling `assemble()`, the document becomes independent and source documents can be safely disposed + ### Encoding to PDF After manipulating pages, use `encodePdf()` to generate the final PDF file: @@ -55,6 +79,8 @@ final pdfBytes = await document.encodePdf(); await File('output.pdf').writeAsBytes(pdfBytes); ``` +Note: `encodePdf()` automatically calls `assemble()` internally, so the document will be self-contained in the output. + ## Basic Examples ### Re-arranging Pages Within a Document @@ -145,14 +171,19 @@ Future combinePdfs() async { ...doc3.pages, // All pages from doc3 ]; - // Generate the combined PDF + // Assemble to make doc1 independent and release doc2/doc3 early + await doc1.assemble(); + + // Now safe to dispose source documents to free memory + doc2.dispose(); + doc3.dispose(); + + // Generate the combined PDF (assemble is called again internally, but that's OK) final bytes = await doc1.encodePdf(); await File('combined.pdf').writeAsBytes(bytes); // Clean up doc1.dispose(); - doc2.dispose(); - doc3.dispose(); } ``` @@ -175,11 +206,14 @@ Future selectiveCombine() async { doc2.pages[3], // Page 4 from doc2 ]; + // Assemble and release doc2 early + await doc1.assemble(); + doc2.dispose(); + final bytes = await doc1.encodePdf(); await File('selective.pdf').writeAsBytes(bytes); doc1.dispose(); - doc2.dispose(); } ``` @@ -269,6 +303,45 @@ try { } ``` +### Using assemble() for Early Memory Release + +When combining pages from multiple large documents, use `assemble()` to release source documents early: + +```dart +Future efficientCombine() async { + // Open source documents + final doc1 = await PdfDocument.openFile('large1.pdf'); + final doc2 = await PdfDocument.openFile('large2.pdf'); + final doc3 = await PdfDocument.openFile('large3.pdf'); + + try { + // Combine pages + doc1.pages = [...doc1.pages, ...doc2.pages, ...doc3.pages]; + + // Assemble to make doc1 independent + await doc1.assemble(); + + // Immediately dispose source documents to free memory + doc2.dispose(); + doc3.dispose(); + + // doc1 can still be used and encoded later + // Do other processing... + + final bytes = await doc1.encodePdf(); + await File('output.pdf').writeAsBytes(bytes); + } finally { + doc1.dispose(); + } +} +``` + +**Benefits of using assemble():** +- Reduces memory footprint when working with large PDFs +- Allows early disposal of source documents +- Prevents holding references to potentially large source documents +- Especially useful when processing multiple PDFs in sequence + ### Reference Counting Pattern When sharing pages across documents in complex scenarios, consider implementing reference counting: @@ -326,15 +399,19 @@ if (pageIndex >= 0 && pageIndex < doc.pages.length) { } ``` -### Encoding Performance +### Encoding and Assembly Performance -`encodePdf()` can be resource-intensive for large documents: +Both `assemble()` and `encodePdf()` can be resource-intensive for large documents: ```dart // Show loading indicator for large documents setState(() => isGenerating = true); try { + // Assemble can take time with many pages + await document.assemble(); + + // Encoding also takes time final bytes = await document.encodePdf(); // ... save bytes ... } finally { @@ -342,6 +419,11 @@ try { } ``` +**Performance Tips:** +- `assemble()` processes all pages and consolidates the document structure +- Call `assemble()` once; subsequent calls are safe but unnecessary +- `encodePdf()` calls `assemble()` internally, so you can skip explicit calls if encoding immediately + ### Original Document Preservation If you need to preserve the original document, work on a copy or keep the original reference: @@ -399,12 +481,19 @@ class PdfMerger { // Set combined pages on first document documents.first.pages = allPages; + // Assemble to make the first document independent + await documents.first.assemble(); + + // Dispose all source documents except the first one + for (var i = 1; i < documents.length; i++) { + documents[i].dispose(); + } + // Generate result return await documents.first.encodePdf(); } finally { - for (final doc in documents) { - doc.dispose(); - } + // Clean up the combined document + documents.first.dispose(); } } } From e83fb4a9ea4a11278c44baf74fd6b4140356c2ab Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 03:04:32 +0900 Subject: [PATCH 486/663] Doc updates. --- doc/PDF-Page-Manipulation.md | 498 ++--------------------------------- doc/README.md | 5 +- 2 files changed, 27 insertions(+), 476 deletions(-) diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index c2b3be84..808ca081 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -16,19 +16,20 @@ The page manipulation feature allows you to: ### PdfDocument.pages Property -The `PdfDocument.pages` property is both readable and writable: +The [PdfDocument.pages](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/pages.html) property is both readable and writable: ```dart // Read pages final pages = document.pages; // List -// Write/re-arrange pages -document.pages = [pages[2], pages[0], pages[1]]; // Re-arrange pages +// Re-arrange pages +// same page can be listed several times +document.pages = [pages[2], pages[0], pages[1], pages[1]]; ``` ### Cross-Document Page References -`PdfPage` instances can be used across different `PdfDocument` instances. This means you can take pages from one document and add them to another: +[PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) instances can be used across different [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) instances. This means you can take pages from one document and add them to another: ```dart final doc1 = await PdfDocument.openFile('document1.pdf'); @@ -41,14 +42,24 @@ doc1.pages = [ doc1.pages[1], // Page 2 from doc1 doc2.pages[1], // Page 2 from doc2 ]; +``` + +### Encoding to PDF + +After manipulating pages, use [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html) to generate the final PDF file: + +```dart +final pdfBytes = await document.encodePdf(); +await File('output.pdf').writeAsBytes(pdfBytes); -// Encode the combined document -final combinedPdf = await doc1.encodePdf(); +// dispose the documents after use +doc1.dispose(); +doc2.dispose(); ``` ### The assemble() Function -When you combine pages from multiple documents, the resulting document maintains references to the source documents. The `assemble()` function re-organizes the document to be self-contained, removing dependencies on other `PdfDocument` instances: +When you combine pages from multiple documents, the resulting document maintains references to the source documents. The [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html) function re-organizes the document to be self-contained, removing dependencies on other [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) instances: ```dart // After combining pages from multiple documents @@ -63,159 +74,14 @@ doc3.dispose(); // doc1 is still valid and can be encoded later final bytes = await doc1.encodePdf(); -``` - -**Important Notes:** -- `assemble()` is automatically called by `encodePdf()`, so you don't need to call it explicitly before encoding -- Call `assemble()` explicitly when you want to release source documents early to free memory -- After calling `assemble()`, the document becomes independent and source documents can be safely disposed - -### Encoding to PDF - -After manipulating pages, use `encodePdf()` to generate the final PDF file: - -```dart -final pdfBytes = await document.encodePdf(); -await File('output.pdf').writeAsBytes(pdfBytes); -``` - -Note: `encodePdf()` automatically calls `assemble()` internally, so the document will be self-contained in the output. - -## Basic Examples - -### Re-arranging Pages Within a Document - -```dart -import 'dart:io'; -import 'package:pdfrx_engine/pdfrx_engine.dart'; - -Future reorderPages() async { - await pdfrxInitialize(); - - // Open a PDF document - final doc = await PdfDocument.openFile('input.pdf'); - - // Get current pages - final pages = doc.pages; - // Re-arrange pages (reverse order in this example) - doc.pages = pages.reversed.toList(); - - // Save the modified document - final bytes = await doc.encodePdf(); - await File('reversed.pdf').writeAsBytes(bytes); - - doc.dispose(); -} +doc1.dispose(); ``` -### Extracting Specific Pages - -```dart -Future extractPages() async { - await pdfrxInitialize(); - - final doc = await PdfDocument.openFile('input.pdf'); - - // Extract pages 1, 3, and 5 (indices 0, 2, 4) - doc.pages = [doc.pages[0], doc.pages[2], doc.pages[4]]; - - final bytes = await doc.encodePdf(); - await File('extracted.pdf').writeAsBytes(bytes); - - doc.dispose(); -} -``` - -### Duplicating Pages - -```dart -Future duplicatePages() async { - await pdfrxInitialize(); - - final doc = await PdfDocument.openFile('input.pdf'); - - // Duplicate each page twice - final duplicated = []; - for (final page in doc.pages) { - duplicated.add(page); - duplicated.add(page); - } - - doc.pages = duplicated; - - final bytes = await doc.encodePdf(); - await File('duplicated.pdf').writeAsBytes(bytes); - - doc.dispose(); -} -``` - -## Advanced Examples - -### Combining Multiple PDF Documents - -```dart -Future combinePdfs() async { - await pdfrxInitialize(); - - // Open multiple documents - final doc1 = await PdfDocument.openFile('document1.pdf'); - final doc2 = await PdfDocument.openFile('document2.pdf'); - final doc3 = await PdfDocument.openFile('document3.pdf'); - - // Combine all pages from all documents - doc1.pages = [ - ...doc1.pages, // All pages from doc1 - ...doc2.pages, // All pages from doc2 - ...doc3.pages, // All pages from doc3 - ]; - - // Assemble to make doc1 independent and release doc2/doc3 early - await doc1.assemble(); - - // Now safe to dispose source documents to free memory - doc2.dispose(); - doc3.dispose(); - - // Generate the combined PDF (assemble is called again internally, but that's OK) - final bytes = await doc1.encodePdf(); - await File('combined.pdf').writeAsBytes(bytes); - - // Clean up - doc1.dispose(); -} -``` - -### Selective Page Combining - -```dart -Future selectiveCombine() async { - await pdfrxInitialize(); - - final doc1 = await PdfDocument.openFile('document1.pdf'); - final doc2 = await PdfDocument.openFile('document2.pdf'); - - // Combine specific pages in custom order - doc1.pages = [ - doc1.pages[0], // Page 1 from doc1 - doc1.pages[1], // Page 2 from doc1 - doc2.pages[0], // Page 1 from doc2 - doc1.pages[2], // Page 3 from doc1 - doc2.pages[2], // Page 3 from doc2 - doc2.pages[3], // Page 4 from doc2 - ]; - - // Assemble and release doc2 early - await doc1.assemble(); - doc2.dispose(); - - final bytes = await doc1.encodePdf(); - await File('selective.pdf').writeAsBytes(bytes); - - doc1.dispose(); -} -``` +**Important Notes:** +- [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html) is automatically called by [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html), so you don't need to call it explicitly before encoding +- Call [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html) explicitly when you want to release source documents early to free memory +- After calling [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html), the document becomes independent and source documents can be safely disposed ## Complete Working Examples @@ -250,321 +116,3 @@ Key features: - Support for multiple source documents - Live preview of the combined result - Export to file - -Core implementation: - -```dart -// Load pages from multiple documents -final doc1 = await PdfDocument.openFile('file1.pdf'); -final doc2 = await PdfDocument.openFile('file2.pdf'); - -// Collect pages in desired order -final selectedPages = [ - doc1.pages[0], - doc2.pages[3], - doc1.pages[2], - // ... add more pages as needed -]; - -// Create combined document -final outputDoc = doc1; -outputDoc.pages = selectedPages; - -// Generate the PDF -final bytes = await outputDoc.encodePdf(); - -// Save or display the result -await File('output.pdf').writeAsBytes(bytes); -``` - -## Document Management Best Practices - -### Memory Management - -When working with multiple documents, proper cleanup is important: - -```dart -final documents = []; - -try { - // Open documents - documents.add(await PdfDocument.openFile('doc1.pdf')); - documents.add(await PdfDocument.openFile('doc2.pdf')); - - // ... manipulate pages ... - - final bytes = await documents[0].encodePdf(); - await File('output.pdf').writeAsBytes(bytes); -} finally { - // Always dispose documents - for (final doc in documents) { - doc.dispose(); - } -} -``` - -### Using assemble() for Early Memory Release - -When combining pages from multiple large documents, use `assemble()` to release source documents early: - -```dart -Future efficientCombine() async { - // Open source documents - final doc1 = await PdfDocument.openFile('large1.pdf'); - final doc2 = await PdfDocument.openFile('large2.pdf'); - final doc3 = await PdfDocument.openFile('large3.pdf'); - - try { - // Combine pages - doc1.pages = [...doc1.pages, ...doc2.pages, ...doc3.pages]; - - // Assemble to make doc1 independent - await doc1.assemble(); - - // Immediately dispose source documents to free memory - doc2.dispose(); - doc3.dispose(); - - // doc1 can still be used and encoded later - // Do other processing... - - final bytes = await doc1.encodePdf(); - await File('output.pdf').writeAsBytes(bytes); - } finally { - doc1.dispose(); - } -} -``` - -**Benefits of using assemble():** -- Reduces memory footprint when working with large PDFs -- Allows early disposal of source documents -- Prevents holding references to potentially large source documents -- Especially useful when processing multiple PDFs in sequence - -### Reference Counting Pattern - -When sharing pages across documents in complex scenarios, consider implementing reference counting: - -```dart -class DocumentManager { - final Map _documents = {}; - final Map _refCounts = {}; - - Future loadDocument(String path) async { - final docId = _documents.length; - _documents[docId] = await PdfDocument.openFile(path); - _refCounts[docId] = 0; - return docId; - } - - void addReference(int docId) { - _refCounts[docId] = (_refCounts[docId] ?? 0) + 1; - } - - void removeReference(int docId) { - final count = (_refCounts[docId] ?? 1) - 1; - _refCounts[docId] = count; - - if (count <= 0) { - _documents[docId]?.dispose(); - _documents.remove(docId); - _refCounts.remove(docId); - } - } - - void disposeAll() { - for (final doc in _documents.values) { - doc.dispose(); - } - _documents.clear(); - _refCounts.clear(); - } -} -``` - -## Important Notes - -### Page Validation - -Always validate page indices before accessing: - -```dart -final doc = await PdfDocument.openFile('input.pdf'); - -// Safe page access -if (pageIndex >= 0 && pageIndex < doc.pages.length) { - final page = doc.pages[pageIndex]; - // ... use page ... -} -``` - -### Encoding and Assembly Performance - -Both `assemble()` and `encodePdf()` can be resource-intensive for large documents: - -```dart -// Show loading indicator for large documents -setState(() => isGenerating = true); - -try { - // Assemble can take time with many pages - await document.assemble(); - - // Encoding also takes time - final bytes = await document.encodePdf(); - // ... save bytes ... -} finally { - setState(() => isGenerating = false); -} -``` - -**Performance Tips:** -- `assemble()` processes all pages and consolidates the document structure -- Call `assemble()` once; subsequent calls are safe but unnecessary -- `encodePdf()` calls `assemble()` internally, so you can skip explicit calls if encoding immediately - -### Original Document Preservation - -If you need to preserve the original document, work on a copy or keep the original reference: - -```dart -final original = await PdfDocument.openFile('original.pdf'); -final originalPages = List.from(original.pages); - -// Modify pages -original.pages = originalPages.reversed.toList(); - -// If you need to restore: -// original.pages = originalPages; -``` - -### Password-Protected PDFs - -When combining password-protected PDFs, provide the password when opening: - -```dart -final doc = await PdfDocument.openFile( - 'protected.pdf', - passwordProvider: () async { - // Return the password - return 'secret123'; - }, -); -``` - -## Use Cases - -### PDF Merging Service - -Create a service that merges multiple PDF files: - -```dart -class PdfMerger { - Future mergeFiles(List filePaths) async { - await pdfrxInitialize(); - - final documents = []; - - try { - // Load all documents - for (final path in filePaths) { - documents.add(await PdfDocument.openFile(path)); - } - - // Combine all pages - final allPages = []; - for (final doc in documents) { - allPages.addAll(doc.pages); - } - - // Set combined pages on first document - documents.first.pages = allPages; - - // Assemble to make the first document independent - await documents.first.assemble(); - - // Dispose all source documents except the first one - for (var i = 1; i < documents.length; i++) { - documents[i].dispose(); - } - - // Generate result - return await documents.first.encodePdf(); - } finally { - // Clean up the combined document - documents.first.dispose(); - } - } -} -``` - -### Page Extractor - -Extract a range of pages from a PDF: - -```dart -Future extractPageRange( - String inputPath, - int startPage, - int endPage, -) async { - await pdfrxInitialize(); - - final doc = await PdfDocument.openFile(inputPath); - - try { - // Validate range - if (startPage < 1 || endPage > doc.pages.length || startPage > endPage) { - throw ArgumentError('Invalid page range'); - } - - // Extract pages (convert to 0-based indices) - final extractedPages = doc.pages.sublist(startPage - 1, endPage); - doc.pages = extractedPages; - - return await doc.encodePdf(); - } finally { - doc.dispose(); - } -} -``` - -### Document Splitter - -Split a PDF into multiple files: - -```dart -Future splitPdf(String inputPath, String outputDir) async { - await pdfrxInitialize(); - - final doc = await PdfDocument.openFile(inputPath); - - try { - // Create one file per page - for (var i = 0; i < doc.pages.length; i++) { - final singlePageDoc = doc; - singlePageDoc.pages = [doc.pages[i]]; - - final bytes = await singlePageDoc.encodePdf(); - final outputPath = '$outputDir/page_${i + 1}.pdf'; - await File(outputPath).writeAsBytes(bytes); - } - } finally { - doc.dispose(); - } -} -``` - -## Related Documentation - -- [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - For advanced PDFium operations -- [pdfrx Initialization](pdfrx-Initialization.md) - Proper initialization -- [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Working with encrypted files - -## API Reference - -For detailed API documentation, see: -- [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) -- [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) -- [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html) diff --git a/doc/README.md b/doc/README.md index 90ef2bbf..a1c19631 100644 --- a/doc/README.md +++ b/doc/README.md @@ -29,7 +29,6 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ### Advanced Topics -- [PDF Page Manipulation](PDF-Page-Manipulation.md) - Re-arrange, combine, and extract PDF pages - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays @@ -37,6 +36,10 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio - [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - Using PDFium function directly - [Interoperability with other PDFium Libraries](Interoperability-with-other-PDFium-Libraries.md) - Using pdfrx alongside other PDFium-based libraries +### PDF Editing + +- [PDF Page Manipulation](PDF-Page-Manipulation.md) - Re-arrange, combine, and extract PDF pages + ## Platform-Specific - [macOS: App Sandbox]([macOS]-Deal-with-App-Sandbox.md) - macOS sandbox configuration From eda7b07c6d45cd9a5f741d4691bd6f08b5a0c7d6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 03:28:03 +0900 Subject: [PATCH 487/663] Implement PdfDocument.createNew for all backends including WASM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for creating empty PDF documents via PdfDocument.createNew() across all backends: - Added createNew method to PdfrxEntryFunctions interface - Implemented createNew for native (PDFium) backend using FPDF_CreateNewDocument - Implemented createNew for WASM backend with createNewDocument worker command - Added stub implementation for CoreGraphics backend (throws UnimplementedError) - Added mock implementation for testing - Added API documentation for PdfDocument.createNew This feature enables creating empty PDF documents that can then be populated with pages from other documents, enabling document assembly workflows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx/assets/pdfium_worker.js | 10 ++++++++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 11 +++++++++++ .../pdfrx_coregraphics/lib/pdfrx_coregraphics.dart | 7 +++++++ packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 +++ .../pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 14 ++++++++++++++ packages/pdfrx_engine/lib/src/pdfrx_api.dart | 10 ++++++++++ 6 files changed, 55 insertions(+) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index d33148c4..9d7a9534 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1823,12 +1823,22 @@ function encodePdf(params) { } } +/** + * Create a new empty PDF document + * @returns {PdfDocument|PdfError} + */ +function createNewDocument() { + const docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); + return _loadDocument(docHandle, false, () => {}); +} + /** * Functions that can be called from the main thread */ const functions = { loadDocumentFromUrl, loadDocumentFromData, + createNewDocument, loadPagesProgressively, closeDocument, loadOutline, diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 78c44596..6b2fbe1b 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -300,6 +300,17 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { } } + @override + Future createNew({required String sourceName}) async { + await init(); + final result = await _sendCommand('createNewDocument'); + final errorCode = (result['errorCode'] as num?)?.toInt(); + if (errorCode != null) { + throw StateError('Failed to create new document: ${result['errorCodeStr']} ($errorCode)'); + } + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); + } + @override Future reloadFonts() async { await init(); diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index f40d0455..995e1c6b 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -180,6 +180,13 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { entryFunctions: this, ); + @override + Future createNew({required String sourceName}) async { + throw UnimplementedError( + 'createNew() is not implemented for CoreGraphics backend.', + ); + } + @override Future reloadFonts() async { // CoreGraphics reuses system font registrations; nothing to do. diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 17d80a74..f03164c9 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -77,6 +77,9 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { Duration? timeout, }) => unimplemented(); + @override + Future createNew({required String sourceName}) => unimplemented(); + @override Future reloadFonts() => unimplemented(); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 9c422848..fb840156 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -416,6 +416,20 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } } + @override + Future createNew({required String sourceName}) async { + await _init(); + final doc = await (await backgroundWorker).compute((params) { + return pdfium.FPDF_CreateNewDocument().address; + }, null); + return _PdfDocumentPdfium.fromPdfDocument( + pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), + sourceName: sourceName, + useProgressiveLoading: false, + disposeCallback: null, + ); + } + static String _getPdfiumErrorString([int? error]) { error ??= pdfium.FPDF_GetLastError(); final errStr = _errorMappings[error]; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 0ee1d593..ccec9800 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -148,6 +148,9 @@ abstract class PdfrxEntryFunctions { Duration? timeout, }); + /// See [PdfDocument.createNew]. + Future createNew({required String sourceName}); + /// Reload the fonts. Future reloadFonts(); @@ -312,6 +315,13 @@ abstract class PdfDocument { onDispose: onDispose, ); + /// Creating a new empty PDF document. + /// + /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// unique for each source, the viewer may not work correctly. + static Future createNew({required String sourceName}) => + PdfrxEntryFunctions.instance.createNew(sourceName: sourceName); + /// Opening the PDF from custom source. /// /// On Flutter Web, this function is not supported and throws an exception. From 954d9d828bd7c6e21257aa510a20ceb817e23bcd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 14:29:41 +0900 Subject: [PATCH 488/663] WIP --- doc/PDF-Page-Manipulation.md | 3 + packages/pdfrx/assets/pdfium_worker.js | 13 +- .../pdfrx/example/pdf_combine/lib/main.dart | 115 ++++-------------- 3 files changed, 41 insertions(+), 90 deletions(-) diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index 808ca081..5cb52503 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -79,6 +79,7 @@ doc1.dispose(); ``` **Important Notes:** + - [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html) is automatically called by [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html), so you don't need to call it explicitly before encoding - Call [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html) explicitly when you want to release source documents early to free memory - After calling [PdfDocument.assemble](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/assemble.html), the document becomes independent and source documents can be safely disposed @@ -101,6 +102,7 @@ dart run pdfrx_engine:pdfcombine -o output.pdf doc1.pdf doc2.pdf -- a[1-3] b a[4 ``` Key features: + - Flexible page specification syntax - Support for page ranges (`[1-10]`) and individual pages (`[1,3,5]`) - Can interleave pages from multiple documents @@ -111,6 +113,7 @@ Key features: The [pdf_combine](../packages/pdfrx/example/pdf_combine/) Flutter app provides a visual interface for combining PDFs: Key features: + - Drag-and-drop interface for page re-arrangement - Visual thumbnails of PDF pages - Support for multiple source documents diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 9d7a9534..1cef76db 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -619,7 +619,18 @@ const emEnv = { tm[8] = 0; // gmtoff }, _localtime_js: function (time, tmPtr) { - _notImplemented('_localtime_js'); + time = Number(time); + const date = new Date(time * 1000); + const tm = new Int32Array(Pdfium.memory.buffer, tmPtr, 9); + tm[0] = date.getSeconds(); + tm[1] = date.getMinutes(); + tm[2] = date.getHours(); + tm[3] = date.getDate(); + tm[4] = date.getMonth(); + tm[5] = date.getFullYear() - 1900; + tm[6] = date.getDay(); + tm[7] = 0; // dst + tm[8] = 0; // gmtoff }, _tzset_js: function () { }, emscripten_date_now: function () { diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 3ea67035..9a7c6a66 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -21,22 +21,14 @@ class PdfCombineApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'PDF Combine', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), + theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true), home: const PdfCombinePage(), ); } } class PageItem { - PageItem({ - required this.documentId, - required this.documentName, - required this.pageIndex, - required this.page, - }); + PageItem({required this.documentId, required this.documentName, required this.pageIndex, required this.page}); /// Unique ID for the document final int documentId; @@ -66,9 +58,7 @@ class DocumentManager { final docId = _nextDocId++; final doc = await PdfDocument.openFile( filePath, - passwordProvider: passwordProvider != null - ? () => passwordProvider!(docId, name) - : null, + passwordProvider: passwordProvider != null ? () => passwordProvider!(docId, name) : null, ); _documents[docId] = doc; _pageRefCounts[docId] = 0; @@ -113,9 +103,7 @@ class PdfCombinePage extends StatefulWidget { } class _PdfCombinePageState extends State { - late final _docManager = DocumentManager( - (docId, name) => passwordDialog(name, context), - ); + late final _docManager = DocumentManager((docId, name) => passwordDialog(name, context)); final _pages = []; final _scrollController = ScrollController(); bool _isLoading = false; @@ -148,22 +136,13 @@ class _PdfCombinePageState extends State { if (doc != null) { for (var i = 0; i < doc.pages.length; i++) { _docManager.addReference(docId); - _pages.add( - PageItem( - documentId: docId, - documentName: file.name, - pageIndex: i, - page: doc.pages[i], - ), - ); + _pages.add(PageItem(documentId: docId, documentName: file.name, pageIndex: i, page: doc.pages[i])); } } } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); } } finally { if (mounted) { @@ -184,16 +163,11 @@ class _PdfCombinePageState extends State { Future _navigateToPreview() async { if (_pages.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please add some pages first')), - ); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please add some pages first'))); return; } - await Navigator.push( - context, - MaterialPageRoute(builder: (context) => OutputPreviewPage(pages: _pages)), - ); + await Navigator.push(context, MaterialPageRoute(builder: (context) => OutputPreviewPage(pages: _pages))); } Widget _disableDraggingOnChild(Widget child) { @@ -219,11 +193,7 @@ class _PdfCombinePageState extends State { title: const Text('PDF Combine'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _pickPdfFiles, - tooltip: 'Add PDF files', - ), + IconButton(icon: const Icon(Icons.add), onPressed: _pickPdfFiles, tooltip: 'Add PDF files'), const SizedBox(width: 8), FilledButton.icon( onPressed: _pages.isEmpty ? null : _navigateToPreview, @@ -240,15 +210,10 @@ class _PdfCombinePageState extends State { child: Text.rich( TextSpan( children: [ - TextSpan( - text: 'Tap the following button to add PDF files!\n\n', - ), + TextSpan(text: 'Tap the following button to add PDF files!\n\n'), WidgetSpan( alignment: PlaceholderAlignment.middle, - child: IconButton.filled( - icon: Icon(Icons.add), - onPressed: () => _pickPdfFiles(), - ), + child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickPdfFiles()), ), ], ), @@ -285,7 +250,7 @@ class _PdfCombinePageState extends State { child: AnimatedReorderableGridView( items: _pages, isSameItem: (a, b) => a.id == b.id, - itemBuilder: (BuildContext context, int index) { + itemBuilder: (context, index) { final pageItem = _pages[index]; return _PageThumbnail( key: ValueKey(pageItem.id), @@ -295,16 +260,13 @@ class _PdfCombinePageState extends State { dragDisabler: _disableDraggingOnChild, ); }, - sliverGridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - ), + sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), insertDuration: const Duration(milliseconds: 300), removeDuration: const Duration(milliseconds: 300), dragStartDelay: _isTouchDevice || _disableDragging ? const Duration(milliseconds: 200) : Duration.zero, - onReorder: (int oldIndex, int newIndex) { + onReorder: (oldIndex, newIndex) { setState(() { final removed = _pages.removeAt(oldIndex); _pages.insert(newIndex, removed); @@ -321,11 +283,11 @@ class _PdfCombinePageState extends State { /// Widget for displaying a page thumbnail in the grid class _PageThumbnail extends StatelessWidget { const _PageThumbnail({ - super.key, required this.page, required this.onRemove, required this.currentIndex, required this.dragDisabler, + super.key, }); final PdfPage page; @@ -342,10 +304,7 @@ class _PageThumbnail extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(4.0), - child: PdfPageView( - document: page.document, - pageNumber: page.pageNumber, - ), + child: PdfPageView(document: page.document, pageNumber: page.pageNumber), ), // Delete button Positioned( @@ -373,7 +332,7 @@ class _PageThumbnail extends StatelessWidget { } class OutputPreviewPage extends StatefulWidget { - const OutputPreviewPage({super.key, required this.pages}); + const OutputPreviewPage({required this.pages, super.key}); final List pages; @@ -397,9 +356,8 @@ class _OutputPreviewPageState extends State { }); try { - // Create a new document using the first page's document as base - final firstPageDoc = widget.pages.first.page.document; - final combinedDoc = firstPageDoc; + // Create a new PDF document + final combinedDoc = await PdfDocument.createNew(sourceName: 'combined.pdf'); // Set all selected pages combinedDoc.pages = widget.pages.map((item) => item.page).toList(); @@ -418,18 +376,14 @@ class _OutputPreviewPageState extends State { setState(() { _isGenerating = false; }); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error generating PDF: $e'))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error generating PDF: $e'))); } } } Future _savePdf() async { if (_outputPdfBytes == null) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('PDF not ready yet'))); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('PDF not ready yet'))); return; } @@ -438,9 +392,7 @@ class _OutputPreviewPageState extends State { await savePdf(_outputPdfBytes!, suggestedName: 'output_$timestamp.pdf'); } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error saving PDF: $e'))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error saving PDF: $e'))); } } } @@ -464,11 +416,7 @@ class _OutputPreviewPageState extends State { ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('Generating combined PDF...'), - ], + children: [CircularProgressIndicator(), SizedBox(height: 16), Text('Generating combined PDF...')], ), ) : _outputPdfBytes == null @@ -490,12 +438,7 @@ class _OutputPreviewPageState extends State { ], ), ), - Expanded( - child: PdfViewer.data( - _outputPdfBytes!, - sourceName: 'combined.pdf', - ), - ), + Expanded(child: PdfViewer.data(_outputPdfBytes!, sourceName: 'combined.pdf')), ], ), ); @@ -518,14 +461,8 @@ Future passwordDialog(String name, BuildContext context) async { onSubmitted: (value) => Navigator.of(context).pop(value), ), actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(null), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(textController.text), - child: const Text('OK'), - ), + TextButton(onPressed: () => Navigator.of(context).pop(null), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(context).pop(textController.text), child: const Text('OK')), ], ); }, From 45ac7bae8f2be0ec460719db72411fbf5d7aa3b4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 14:43:12 +0900 Subject: [PATCH 489/663] PDF combine examples now uses PdfDocument.createNew --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 4 +-- packages/pdfrx_engine/example/pdfcombine.dart | 33 ++++--------------- .../lib/src/native/pdfrx_pdfium.dart | 4 +-- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 6b2fbe1b..65aa2d9a 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -531,8 +531,8 @@ class _PdfDocumentWasm extends PdfDocument { final page = pages[i]; if (page is _PdfPageImported) { final wasmPage = page.imported as _PdfPageWasm; - indices.add(-i); - importedPages[-i] = { + indices.add(-(i + 1)); + importedPages[-(i + 1)] = { 'docHandle': wasmPage.document.document['docHandle'], 'pageNumber': wasmPage.pageNumber - 1, // 0-based }; diff --git a/packages/pdfrx_engine/example/pdfcombine.dart b/packages/pdfrx_engine/example/pdfcombine.dart index 24005cdc..3fd7acc0 100644 --- a/packages/pdfrx_engine/example/pdfcombine.dart +++ b/packages/pdfrx_engine/example/pdfcombine.dart @@ -184,41 +184,18 @@ Future main(List args) async { // Open all PDF documents final documents = {}; + PdfDocument? outputDoc; try { for (final entry in fileMap.entries) { print('Opening ${entry.value}...'); documents[entry.key] = await PdfDocument.openFile(entry.value); } - // Create a new document by combining pages - print(''); + // Combine pages from all specifications print('Combining pages...'); - final firstSpec = pageSpecs.first; - final firstDoc = documents[firstSpec.fileId]!; - final firstPages = firstSpec.pages ?? List.generate(firstDoc.pages.length, (i) => i + 1); - - // Validate page numbers - for (final pageNum in firstPages) { - if (pageNum < 1 || pageNum > firstDoc.pages.length) { - print('Error: Page $pageNum out of range for file ${firstSpec.fileId} (has ${firstDoc.pages.length} pages)'); - return 1; - } - } - - // Start with pages from the first specification final combinedPages = []; - for (final pageNum in firstPages) { - combinedPages.add(firstDoc.pages[pageNum - 1]); - print(' Adding page $pageNum from ${firstSpec.fileId}'); - } - // Create a new document from the first set of pages - final outputDoc = firstDoc; - outputDoc.pages = combinedPages; - - // Add pages from remaining specifications - for (var i = 1; i < pageSpecs.length; i++) { - final spec = pageSpecs[i]; + for (final spec in pageSpecs) { final doc = documents[spec.fileId]!; final pages = spec.pages ?? List.generate(doc.pages.length, (i) => i + 1); @@ -234,12 +211,13 @@ Future main(List args) async { combinedPages.add(doc.pages[pageNum - 1]); print(' Adding page $pageNum from ${spec.fileId}'); } - outputDoc.pages = combinedPages; } // Encode and save the combined PDF print(''); print('Saving to $outputFile...'); + outputDoc = await PdfDocument.createNew(sourceName: outputFile); + outputDoc.pages = combinedPages; final pdfData = await outputDoc.encodePdf(); await File(outputFile).writeAsBytes(pdfData); @@ -251,6 +229,7 @@ Future main(List args) async { for (final doc in documents.values) { doc.dispose(); } + outputDoc?.dispose(); } } catch (e, stackTrace) { print('Error: $e'); diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index fb840156..13143c58 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -844,8 +844,8 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { final page = document.pages[i]; if (page is _PdfPageImported) { final pdfiumPage = page.imported as _PdfPagePdfium; - indices.add(-i); - items[-i] = (document: pdfiumPage.document.document.address, pageNumber: pdfiumPage.pageNumber); + indices.add(-(i + 1)); + items[-(i + 1)] = (document: pdfiumPage.document.document.address, pageNumber: pdfiumPage.pageNumber); modifiedCount++; continue; } else if (page is _PdfPagePdfium) { From 2fb768dfc1af8ad695d49a0b9329fef2f4f885f6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 15:35:51 +0900 Subject: [PATCH 490/663] Add simple mechanism to localize GUI texts. --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 20 ++++++++++++++-- .../lib/src/widgets/pdf_viewer_params.dart | 24 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e320d97e..a556d02c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2707,13 +2707,13 @@ class _PdfViewerState extends State params.textSelectionDelegate.hasSelectedText) ContextMenuButtonItem( onPressed: () => params.textSelectionDelegate.copyTextSelection(), - label: 'Copy', + label: _l10n(PdfViewerL10nKey.copy), type: ContextMenuButtonType.copy, ), if (params.isTextSelectionEnabled && !params.textSelectionDelegate.isSelectingAllText) ContextMenuButtonItem( onPressed: () => params.textSelectionDelegate.selectAllText(), - label: 'Select All', + label: _l10n(PdfViewerL10nKey.selectAll), type: ContextMenuButtonType.selectAll, ), ]; @@ -3053,6 +3053,22 @@ class _PdfViewerState extends State _magnifierImageCache.releaseAllImages(); _invalidate(); } + + /// Get the localized string for the given key. + /// + /// If a custom localization delegate is provided in the widget parameters, it will be used. + /// Otherwise, default English strings will be returned. + String _l10n(PdfViewerL10nKey key, [List? args]) { + var result = widget.params.l10nDelegate?.call(key, args); + if (result != null) return result; + + switch (key) { + case PdfViewerL10nKey.copy: + return 'Copy'; + case PdfViewerL10nKey.selectAll: + return 'Select All'; + } + } } class _PdfPageImageCache { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 8a9b9c8f..2615e516 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -70,6 +70,7 @@ class PdfViewerParams { this.onKey, this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.behaviorControlParams = const PdfViewerBehaviorControlParams(), + this.l10nDelegate, this.forceReload = false, this.scrollPhysics, this.scrollPhysicsScale, @@ -528,8 +529,12 @@ class PdfViewerParams { /// Parameters to customize key handling. final PdfViewerKeyHandlerParams keyHandlerParams; + /// Parameters to control viewer behaviors. final PdfViewerBehaviorControlParams behaviorControlParams; + /// Delegate for localization. + final PdfViewerL10nDelegate? l10nDelegate; + /// Force reload the viewer. /// /// Normally whether to reload the viewer is determined by the changes of the parameters but @@ -662,6 +667,7 @@ class PdfViewerParams { other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && + other.l10nDelegate == l10nDelegate && other.forceReload == forceReload && other.scrollPhysics == scrollPhysics; } @@ -721,6 +727,7 @@ class PdfViewerParams { onKey.hashCode ^ keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ + l10nDelegate.hashCode ^ forceReload.hashCode ^ scrollPhysics.hashCode; } @@ -1477,3 +1484,20 @@ class PdfViewerBehaviorControlParams { pageImageCachingDelay.hashCode ^ partialImageLoadingDelay.hashCode; } + +/// Delegate for localization. +/// +/// The [key] is the localization key. See [PdfViewerL10nKey] for more details. +/// The [args] are the arguments for the localization string. +/// +/// If the function returns null, the default localization string will be used. +typedef PdfViewerL10nDelegate = String? Function(PdfViewerL10nKey key, [List? args]); + +/// Localization keys for the PDF viewer. +enum PdfViewerL10nKey { + /// "Copy" action label. + copy, + + /// "Select All" action label. + selectAll, +} From ec6d3c665c98c880a41804ffa58e3955263b045b Mon Sep 17 00:00:00 2001 From: james Date: Fri, 31 Oct 2025 17:47:17 +1030 Subject: [PATCH 491/663] WIP margin improvements and preserve zoom and position on rotation --- .../pdfrx/example/viewer/ios/Podfile.lock | 8 +- .../ios/Runner.xcodeproj/project.pbxproj | 10 +- packages/pdfrx/example/viewer/lib/main.dart | 8 +- .../lib/src/widgets/pdf_page_layout.dart | 2 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 312 ++++++++++++++---- 5 files changed, 270 insertions(+), 70 deletions(-) diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index b7d68ca6..b18a867f 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -31,11 +31,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 + file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - pdfrx: 36950d29badb73470a8c0e10f34824cb651371ab - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + pdfrx: 8b94a416168f5da3cdff56390f6412edf92ec915 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 2dd02ad7..779b3f8e 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -245,7 +245,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; @@ -480,7 +480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -663,7 +663,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -690,7 +690,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = XRDM278W3T; + DEVELOPMENT_TEAM = ERC2H3WBFG; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -743,7 +743,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index febe6c63..71ef3c7d 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -310,9 +310,11 @@ class _MainPageState extends State with WidgetsBindingObserver { ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), maxScale: 8, - //fitMode: FitMode.fill, - //pageTransition: PageTransition.discrete, - // scrollPhysics: PdfViewerParams.getScrollPhysics(context), + margin: 10, + boundaryMargin: EdgeInsets.all(10), + fitMode: FitMode.fill, + pageTransition: PageTransition.discrete, + scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart index c1cf8201..a86d4899 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_layout.dart @@ -190,6 +190,8 @@ class PdfPageLayout { ? (documentSize.width - getMaxWidth()) / 2 : (documentSize.height - getMaxHeight()) / 2; + double get impliedMargin => _impliedMargin; + bool _isPageNumberValid(int? pageNumber) { return pageNumber != null && pageNumber >= 1 && pageNumber <= pageLayouts.length; } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e5ee24d7..67a57f47 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -539,7 +539,7 @@ class _PdfViewerState extends State ); } - Offset _calcOverscroll(Matrix4 m, {required Size viewSize}) { + Offset _calcOverscroll(Matrix4 m, {required Size viewSize, bool allowExtendedBoundaries = true}) { final layout = _layout!; final visible = m.calcVisibleRect(viewSize); var dxDoc = 0.0; @@ -555,6 +555,7 @@ class _PdfViewerState extends State final discreteBounds = _getDiscreteBoundaryRect( visible, Size(layout.documentSize.width, layout.documentSize.height), + zoom: m.zoom, ); if (discreteBounds == null) { return Offset.zero; @@ -574,12 +575,14 @@ class _PdfViewerState extends State } // Special case: if the discrete bounds are smaller than the viewport, center them - if (discreteBounds.width < viewSize.width) { + // Note: both discreteBounds and visible are in document space, so we need to compare + // with viewSize converted to document space (viewSize / zoom = visible.size) + if (discreteBounds.width < visible.width) { final desiredCenter = (discreteBounds.left + discreteBounds.right) / 2; final currentCenter = (visible.left + visible.right) / 2; dxDoc = desiredCenter - currentCenter; } - if (discreteBounds.height < viewSize.height) { + if (discreteBounds.height < visible.height) { final desiredCenter = (discreteBounds.top + discreteBounds.bottom) / 2; final currentCenter = (visible.top + visible.bottom) / 2; dyDoc = desiredCenter - currentCenter; @@ -624,6 +627,50 @@ class _PdfViewerState extends State return candidate.clone()..translateByDouble(-overScroll.dx, -overScroll.dy, 0, 1); } + /// Snaps the viewport to effective bounds when positioned between document origin and bounds. + /// Used after layout changes to ensure proper boundary alignment. + Matrix4 _calcMatrixForMarginSnappedToNearestBoundary( + Matrix4 candidate, { + required int pageNumber, + required Size viewSize, + }) { + final layout = _layout; + if (layout == null) return candidate; + + _adjustBoundaryMargins(viewSize, candidate.zoom); + + // Get the effective page bounds (includes margins and boundary margins) + final effectiveBounds = _getEffectivePageBounds(pageNumber, layout, anchor: PdfPageAnchor.all); + // Use spread bounds (without layout-applied margins) for comparison + final pageRect = layout.getSpreadBounds(pageNumber); + + // Calculate visible rect from candidate matrix + final visible = candidate.calcVisibleRect(viewSize); + + var dxDoc = 0.0; + var dyDoc = 0.0; + + // Snap threshold in screen pixels, converted to document space + const snapThresholdPx = 5.0; // 5 pixels on screen + final snapThreshold = snapThresholdPx / candidate.zoom; + + if (visible.left > effectiveBounds.left && visible.left < pageRect.left + snapThreshold) { + dxDoc = effectiveBounds.left - visible.left; + } + // Top edge: if visible is between effectiveBounds.top and just before page origin, snap to effectiveBounds.top + if (visible.top > effectiveBounds.top && visible.top < pageRect.top + snapThreshold) { + dyDoc = effectiveBounds.top - visible.top; + } + + if (dxDoc == 0.0 && dyDoc == 0.0) { + return candidate; + } + + // Convert document space offset to screen space (multiply by zoom) + final zoom = candidate.zoom; + return candidate.clone()..translateByDouble(-dxDoc * zoom, -dyDoc * zoom, 0, 1); + } + void _updateLayout(Size viewSize) { if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); @@ -634,6 +681,12 @@ class _PdfViewerState extends State final isViewSizeChanged = oldSize != viewSize; _viewSize = viewSize; + // Clear active gesture state before relayout to prevent extended boundaries + // from being baked into the layout + if (isViewSizeChanged) { + _isActiveGesture = false; + } + final isLayoutChanged = _relayoutPages(); _calcFitScale(); @@ -701,34 +754,64 @@ class _PdfViewerState extends State } else if (isLayoutChanged || isViewSizeChanged) { Future.microtask(() async { if (mounted) { - // preserve the current zoom whilst respecting the new fitScale - final zoomTo = _currentZoom < _fitScale || _currentZoom == oldFitScale ? _fitScale : _currentZoom; + // Preserve the visual page size by comparing actual page dimensions in layouts + double zoomTo; + if (_currentZoom < _fitScale || _currentZoom == oldFitScale) { + // User was at fit scale or below minimum - use new fit scale + zoomTo = _fitScale; + } else if (oldLayout != null && currentPageNumber != null && _layout != null) { + // Calculate zoom to maintain same visual page size + // Visual size = pageRect.size * zoom, so we scale zoom by page size ratio + final oldPageRect = oldLayout.pageLayouts[currentPageNumber - 1]; + final newPageRect = _layout!.pageLayouts[currentPageNumber - 1]; + + // Use the primary axis size to determine scaling + final isPrimaryVertical = _layout!.primaryAxis == Axis.vertical; + final oldPageSize = isPrimaryVertical ? oldPageRect.height : oldPageRect.width; + final newPageSize = isPrimaryVertical ? newPageRect.height : newPageRect.width; + + if (newPageSize > 0) { + final calculatedZoom = _currentZoom * (oldPageSize / newPageSize); + // Clamp to min/max scale constraints + zoomTo = calculatedZoom.clamp(minScale, widget.params.maxScale); + } else { + zoomTo = _currentZoom; + } + } else { + zoomTo = _currentZoom; + } + if (isLayoutChanged) { // if the layout changed, calculate the top-left position in the document // before the layout change and go to that position in the new layout if (oldLayout != null && currentPageNumber != null) { - // The top-left position of the screen (oldVisibleRect.topLeft) may be - // in the boundary margin, or a margin between pages, and it could be - // the current page or one of the neighboring pages + // Get the hit point in PDF page coordinates (stable across layout changes) + final hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); - final pageNumber = hit?.page.pageNumber ?? currentPageNumber; - - // Compute relative position within the old pageRect - final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; - final newPageRect = _layout!.pageLayouts[pageNumber - 1]; - final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; - final fracX = oldOffset.dx / oldPageRect.width; - final fracY = oldOffset.dy / oldPageRect.height; - - // Map into new layoutRect - final newOffset = Offset( - newPageRect.left + fracX * newPageRect.width, - newPageRect.top + fracY * newPageRect.height, - ); + + Offset newOffset; + if (hit == null) { + // Hit is null - top left was in margin area + // Use the page's top-left as the reference point + newOffset = _layout!.pageLayouts[currentPageNumber - 1].topLeft; + print( + "no hit: page ${currentPageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[currentPageNumber - 1]}", + ); + } else { + // Got a valid hit - convert PDF coordinates to new layout + newOffset = hit.offset.toOffsetInDocument( + page: hit.page, + pageRect: _layout!.pageLayouts[hit.page.pageNumber - 1], + ); + print( + "hit: ${hit.offset} ${hit.page.pageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[hit.page.pageNumber - 1]}", + ); + } // preserve the position after a layout change - await _goToPosition(documentOffset: newOffset, zoom: zoomTo); + // Pass pageNumber and boundary information to enable margin snapping + await _goToPosition(documentOffset: newOffset, zoom: zoomTo, pageNumber: currentPageNumber); } } else { if (zoomTo != _currentZoom) { @@ -788,9 +871,9 @@ class _PdfViewerState extends State } PdfPageHitTestResult? _getClosestPageHit(int currentPageNumber, PdfPageLayout oldLayout, ui.Rect oldVisibleRect) { - for (final pageIndex in [currentPageNumber, currentPageNumber - 1, currentPageNumber + 1]) { - if (pageIndex >= 1 && pageIndex <= oldLayout.pageLayouts.length) { - final rec = _nudgeHitTest(oldVisibleRect.topLeft, layout: oldLayout, pageNumber: pageIndex); + for (final pageIndex in [currentPageNumber - 1, currentPageNumber - 2, currentPageNumber]) { + if (pageIndex >= 0 && pageIndex < oldLayout.pageLayouts.length) { + final rec = _nudgeHitTest(oldVisibleRect.topLeft, layout: oldLayout, pageIndex: pageIndex); if (rec != null) { return rec.hit; } @@ -803,17 +886,17 @@ class _PdfViewerState extends State PdfPageHitTestResult? _hitTestWithLayout({ required Offset point, required PdfPageLayout layout, - required int pageNumber, + required int pageIndex, }) { final pages = _document?.pages; if (pages == null) return null; - if (pageNumber >= layout.pageLayouts.length) { + if (pageIndex >= layout.pageLayouts.length) { return null; } - final rect = layout.pageLayouts[pageNumber]; + final rect = layout.pageLayouts[pageIndex]; if (rect.contains(point)) { - final page = pages[pageNumber]; + final page = pages[pageIndex]; final local = point - rect.topLeft; final pdfOffset = local.toPdfPoint(page: page, scaledPageSize: rect.size); return PdfPageHitTestResult(page: page, offset: pdfOffset); @@ -822,24 +905,99 @@ class _PdfViewerState extends State } } - // Attempts to nudge the point on the x axis until a valid page hit is found. - ({Offset point, PdfPageHitTestResult hit})? _nudgeHitTest(Offset start, {PdfPageLayout? layout, int? pageNumber}) { + // Attempts to nudge the point to find the nearest page content. + // Intelligently determines nudge direction based on page layout and visible rect. + ({Offset point, PdfPageHitTestResult hit})? _nudgeHitTest(Offset start, {PdfPageLayout? layout, int? pageIndex}) { const epsViewPx = 1.0; final epsDoc = epsViewPx / _currentZoom; - var tryPoint = start; - var tryOffset = Offset.zero; - final useLayout = layout; - for (var i = 0; i < 500; i++) { - final result = useLayout != null && pageNumber != null - ? _hitTestWithLayout(point: tryPoint, layout: useLayout, pageNumber: pageNumber) - : _getPdfPageHitTestResult(tryPoint, useDocumentLayoutCoordinates: true); - if (result != null) { - return (point: tryOffset, hit: result); + final useLayout = layout ?? _layout; + if (useLayout == null) return null; + + // Try the original point first + final initialResult = pageIndex != null + ? _hitTestWithLayout(point: start, layout: useLayout, pageIndex: pageIndex) + : _getPdfPageHitTestResult(start, useDocumentLayoutCoordinates: true); + if (initialResult != null) { + return (point: Offset.zero, hit: initialResult); + } + + // Find the nearest page by checking which page rect is closest to the start point + Rect? nearestPageRect; + int? nearestPageIndex; + var minDistance = double.infinity; + + for (var i = 0; i < useLayout.pageLayouts.length; i++) { + final pageRect = useLayout.pageLayouts[i]; + + // Calculate distance from point to page rect + final dx = start.dx < pageRect.left + ? pageRect.left - start.dx + : start.dx > pageRect.right + ? start.dx - pageRect.right + : 0.0; + final dy = start.dy < pageRect.top + ? pageRect.top - start.dy + : start.dy > pageRect.bottom + ? start.dy - pageRect.bottom + : 0.0; + final distance = dx * dx + dy * dy; // Squared distance (no need for sqrt for comparison) + + if (distance < minDistance) { + minDistance = distance; + nearestPageRect = pageRect; + nearestPageIndex = i; } - tryOffset += Offset(epsDoc, 0); - tryPoint = tryPoint.translate(epsDoc, 0); } + + if (nearestPageRect == null || nearestPageIndex == null) return null; + + // Determine the direction to nudge based on where start is relative to the nearest page + double nudgeDx = 0; + double nudgeDy = 0; + + if (start.dx < nearestPageRect.left) { + // Point is to the left of the page - nudge right + nudgeDx = nearestPageRect.left - start.dx + epsDoc; + } else if (start.dx > nearestPageRect.right) { + // Point is to the right of the page - nudge left + nudgeDx = nearestPageRect.right - start.dx - epsDoc; + } /*else { + // Point is horizontally within page bounds - nudge slightly right to ensure we're inside + nudgeDx = epsDoc; + } */ + + if (start.dy < nearestPageRect.top) { + // Point is above the page - nudge down + nudgeDy = nearestPageRect.top - start.dy + epsDoc; + } else if (start.dy > nearestPageRect.bottom) { + // Point is below the page - nudge up + nudgeDy = nearestPageRect.bottom - start.dy - epsDoc; + } /* else { + // Point is vertically within page bounds - nudge slightly down to ensure we're inside + nudgeDy = epsDoc; + } */ + + final nudgeOffset = Offset(nudgeDx, nudgeDy); + final tryPoint = start.translate(nudgeDx, nudgeDy); + + final result = _hitTestWithLayout(point: tryPoint, layout: useLayout, pageIndex: nearestPageIndex); + if (result != null) { + return (point: nudgeOffset, hit: result); + } + + /* // If that didn't work, try nudging to the top-left corner of the nearest page + final cornerOffset = Offset( + nearestPageRect.left + epsDoc - start.dx, + nearestPageRect.top + epsDoc - start.dy, + ); + final cornerPoint = Offset(nearestPageRect.left + epsDoc, nearestPageRect.top + epsDoc); + + final cornerResult = _hitTestWithLayout(point: cornerPoint, layout: useLayout, pageIndex: nearestPageIndex); + if (cornerResult != null) { + return (point: cornerOffset, hit: cornerResult); + } */ + return null; } @@ -913,6 +1071,7 @@ class _PdfViewerState extends State } } widget.params.onInteractionUpdate?.call(details); + print("x:${_txController.value.x} y:${_txController.value.y}"); } void _onAnimationEnd() { @@ -1660,7 +1819,7 @@ class _PdfViewerState extends State // The page content (without margins) starts at: offset + padding + scaled margin final scaledPageWidth = pageRect.width * pageScale; final scaledPageHeight = pageRect.height * pageScale; - final pageContentStart = offset + viewportPaddingPrimary + (margin * pageScale); + final pageContentStart = offset + viewportPaddingPrimary /*+ (margin * pageScale) */; // Position page on cross-axis final crossAxisPageSizeWithMargins = isPrimaryVertical @@ -1672,8 +1831,8 @@ class _PdfViewerState extends State final viewportPaddingCross = max(0.0, (crossAxisViewport - crossAxisPageSizeWithMargins) / 2); // Page content starts at: padding + scaled margin - final crossAxisPageContentStart = viewportPaddingCross + (margin * pageScale); - + final crossAxisPageContentStart = viewportPaddingCross /*+ (margin * pageScale) */; + print('_addDiscreteSpacing: scaledPageWidth=$scaledPageWidth'); final newRect = isPrimaryVertical ? Rect.fromLTWH(crossAxisPageContentStart, pageContentStart, scaledPageWidth, scaledPageHeight) : Rect.fromLTWH(pageContentStart, crossAxisPageContentStart, scaledPageWidth, scaledPageHeight); @@ -1736,7 +1895,7 @@ class _PdfViewerState extends State /// Returns the boundary rect for discrete mode (current page/spread bounds). /// Used by boundaryProvider to restrict scrolling to current page. - Rect? _getDiscreteBoundaryRect(Rect visibleRect, Size childSize) { + Rect? _getDiscreteBoundaryRect(Rect visibleRect, Size childSize, {double? zoom}) { final layout = _layout; final currentPageNumber = _gotoTargetPageNumber ?? _pageNumber; @@ -1754,11 +1913,11 @@ class _PdfViewerState extends State var result = baseBounds.inflate(widget.params.margin); // Add boundary margins for discrete mode - final userBoundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; + final userBoundaryMargin = (widget.params.boundaryMargin ?? EdgeInsets.zero); if (!userBoundaryMargin.containsInfinite && _viewSize != null) { final isPrimaryVertical = layout.primaryAxis == Axis.vertical; - final currentZoom = _currentZoom; - + final currentZoom = zoom ?? _currentZoom; + // result = (userBoundaryMargin * currentZoom).inflateRect(baseBounds); // Check if page content extends beyond viewport on each axis at current zoom final primaryAxisSize = isPrimaryVertical ? result.height : result.width; final crossAxisSize = isPrimaryVertical ? result.width : result.height; @@ -1770,12 +1929,12 @@ class _PdfViewerState extends State // For positive margins: only apply when content exceeds viewport to prevent unwanted scrolling // For negative margins: always apply (they're meant to restrict boundaries regardless of zoom) final needsPrimaryAxisMargin = - scaledPrimaryAxisSize > primaryAxisViewport || + scaledPrimaryAxisSize >= primaryAxisViewport || (isPrimaryVertical ? (userBoundaryMargin.top < 0 || userBoundaryMargin.bottom < 0) : (userBoundaryMargin.left < 0 || userBoundaryMargin.right < 0)); final needsCrossAxisMargin = - scaledCrossAxisSize > crossAxisViewport || + scaledCrossAxisSize >= crossAxisViewport || (isPrimaryVertical ? (userBoundaryMargin.left < 0 || userBoundaryMargin.right < 0) : (userBoundaryMargin.top < 0 || userBoundaryMargin.bottom < 0)); @@ -2520,13 +2679,34 @@ class _PdfViewerState extends State var result = baseRect.inflate(widget.params.margin); // Add boundary margins for positioning when appropriate - // Continuous mode: use adjusted boundary margins (with centering adjustments) - // Discrete mode: always include user's boundary margins for proper positioning if (anchor != null) { if (widget.params.pageTransition == PageTransition.continuous) { - result = _adjustedBoundaryMargins.inflateRectIfFinite(result); + // Continuous mode: apply boundary margins on cross-axis throughout, + // and on primary axis only at document ends + final margins = _adjustedBoundaryMargins; + final isPrimaryVertical = layout.primaryAxis == Axis.vertical; + final isFirstPage = pageNumber == 1; + final isLastPage = pageNumber == layout.pageLayouts.length; + + if (isPrimaryVertical) { + // Vertical scrolling: margins on left/right (cross-axis), top/bottom only at ends + result = Rect.fromLTRB( + result.left - margins.left, // Cross-axis: left margin + isFirstPage ? result.top - margins.top : result.top, // Primary: top margin only on first page + result.right + margins.right, // Cross-axis: right margin + isLastPage ? result.bottom + margins.bottom : result.bottom, // Primary: bottom margin only on last page + ); + } else { + // Horizontal scrolling: margins on top/bottom (cross-axis), left/right only at ends + result = Rect.fromLTRB( + isFirstPage ? result.left - margins.left : result.left, // Primary: left margin only on first page + result.top - margins.top, // Cross-axis: top margin + isLastPage ? result.right + margins.right : result.right, // Primary: right margin only on last page + result.bottom + margins.bottom, // Cross-axis: bottom margin + ); + } } else { - // In discrete mode, always use the user's boundary margins for positioning + // Discrete mode: always use the user's boundary margins for positioning final userBoundaryMargin = widget.params.boundaryMargin ?? EdgeInsets.zero; result = userBoundaryMargin.inflateRectIfFinite(result); } @@ -2664,6 +2844,7 @@ class _PdfViewerState extends State await _animController.animateTo(1.0, duration: duration, curve: curve); } finally { _animGoTo?.removeListener(update); + print("_goTo: completed x:${_txController.value.x} y:${_txController.value.y}"); } } @@ -2741,10 +2922,15 @@ class _PdfViewerState extends State /// Scrolls/zooms so that the specified PDF document coordinate appears at /// the top-left corner of the viewport. + /// + /// If [pageNumber] and [wasAtBoundaries] are provided, applies margin snapping + /// to ensure points that were at boundaries are positioned at the correct boundaries + /// with new margins applied. Future _goToPosition({ required Offset documentOffset, Duration duration = const Duration(milliseconds: 0), double? zoom, + int? pageNumber, }) async { // Clear any cached partial images to avoid stale tiles after // going to the new matrix @@ -2753,11 +2939,21 @@ class _PdfViewerState extends State zoom = zoom ?? _currentZoom; final tx = -documentOffset.dx * zoom; final ty = -documentOffset.dy * zoom; - + print("_goToPosition: tx:$tx ty:$ty zoom:$zoom (docOffset:$documentOffset)"); final m = Matrix4.compose(vec.Vector3(tx, ty, 0), vec.Quaternion.identity(), vec.Vector3(zoom, zoom, zoom)); _adjustBoundaryMargins(_viewSize!, zoom); - final clamped = _calcMatrixForClampedToNearestBoundary(m, viewSize: _viewSize!); + + // Apply margin snapping if page number and boundary info are provided + // DISABLED: Margin snapping conflicts with zoom-to-maintain-page-size during rotation + final marginAdjusted = pageNumber != null + ? _calcMatrixForMarginSnappedToNearestBoundary(m, pageNumber: pageNumber, viewSize: _viewSize!) + : m; + + // Then clamp to nearest boundary to handle out-of-bounds cases + // When preserving position (wasAtBoundaries provided), don't use extended boundaries + final clamped = _calcMatrixForClampedToNearestBoundary(marginAdjusted, viewSize: _viewSize!); + await _goTo(clamped, duration: duration); } From af80fd07972d12a5afacf76ef7e936362623bba3 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 31 Oct 2025 20:19:28 +0900 Subject: [PATCH 492/663] Text flow analysis should be done in the original coordinate. --- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index ccec9800..ee19cf92 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -801,12 +801,6 @@ abstract class PdfPage { handleLine(lineStart, inputFullText.length); } - if (rotation.index != 0) { - for (var i = 0; i < outputCharRects.length; i++) { - outputCharRects[i] = outputCharRects[i].rotateReverse(rotation.index, this); - } - } - final fragments = []; final text = PdfPageText( pageNumber: pageNumber, @@ -843,12 +837,6 @@ abstract class PdfPage { return null; } - if (rotation.index != 0) { - for (var i = 0; i < input.charRects.length; i++) { - input.charRects[i] = input.charRects[i].rotate(rotation.index, this); - } - } - final fullText = StringBuffer(); final charRects = []; From d853f2c27b3fe44c542372e9b56ced58e6ca2fcd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 02:54:17 +0900 Subject: [PATCH 493/663] Supports page rotation Introducing PdfPage.withRotation by implementing PdfPageProxy and PdfPage.assemble. --- packages/pdfrx/assets/pdfium_worker.js | 32 ++++- .../pdfrx/example/pdf_combine/lib/main.dart | 68 ++++++++- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 73 ++++++---- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 56 +++++++- .../Sources/PdfrxCoregraphicsPlugin.swift | 5 +- .../lib/pdfrx_coregraphics.dart | 4 + .../lib/src/native/pdfrx_pdfium.dart | 108 +++++++++----- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 135 ++++++++++++++++++ 8 files changed, 407 insertions(+), 74 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 1cef76db..aac39b72 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1072,6 +1072,7 @@ function closePage(params) { * fullWidth: number, * fullHeight: number, * backgroundColor: number, + * rotation: number, * annotationRenderingMode: number, * flags: number, * formHandle: number @@ -1094,6 +1095,7 @@ function renderPage(params) { fullWidth = width, fullHeight = height, backgroundColor, + rotation, annotationRenderingMode = 0, flags = 0, formHandle, @@ -1130,10 +1132,10 @@ function renderPage(params) { const pdfiumFlags = (flags & 0xffff) | (annotationRenderingMode !== PdfAnnotationRenderingMode_none ? FPDF_ANNOT : 0); - Pdfium.wasmExports.FPDF_RenderPageBitmap(bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, pdfiumFlags); + Pdfium.wasmExports.FPDF_RenderPageBitmap(bitmap, pageHandle, -x, -y, fullWidth, fullHeight, rotation, pdfiumFlags); if (formHandle && annotationRenderingMode == PdfAnnotationRenderingMode_annotationAndForms) { - Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, 0, flags); + Pdfium.wasmExports.FPDF_FFLDraw(formHandle, bitmap, pageHandle, -x, -y, fullWidth, fullHeight, rotation, flags); } const src = new Uint8Array(Pdfium.memory.buffer, bufferPtr, bufferSize); let copiedBuffer = new ArrayBuffer(bufferSize); @@ -1520,11 +1522,11 @@ function clearAllFontData() { /** * Assemble the document (apply page manipulations if any) - * @param {{docHandle: number, pageIndices: number[]|undefined, importedPages: Object.|undefined}} params + * @param {{docHandle: number, pageIndices: number[]|undefined, importedPages: Object.|undefined, rotations: (number|null)[]|undefined}} params * @returns {{modified: boolean}} */ function assemble(params) { - const { docHandle, pageIndices, importedPages } = params; + const { docHandle, pageIndices, importedPages, rotations } = params; // If no page indices specified, no modifications needed if (!pageIndices || pageIndices.length === 0) { @@ -1544,6 +1546,16 @@ function assemble(params) { } } + // Check for rotation changes + if (!hasChanges && rotations) { + for (let i = 0; i < rotations.length; i++) { + if (rotations[i] != null) { + hasChanges = true; + break; + } + } + } + if (!hasChanges) { return { modified: false }; } @@ -1551,6 +1563,18 @@ function assemble(params) { // Perform the shuffle using the PDFium page manipulation functions _shuffleInPlaceAccordingToIndices(docHandle, pageIndices, originalLength, importedPages); + // Apply rotations if specified + if (rotations) { + for (let i = 0; i < rotations.length; i++) { + const rotation = rotations[i]; + if (rotation != null) { + const page = Pdfium.wasmExports.FPDF_LoadPage(docHandle, i); + Pdfium.wasmExports.FPDFPage_SetRotation(page, rotation); + Pdfium.wasmExports.FPDF_ClosePage(page); + } + } + } + return { modified: true }; } diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 9a7c6a66..3c82f41d 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -28,7 +28,13 @@ class PdfCombineApp extends StatelessWidget { } class PageItem { - PageItem({required this.documentId, required this.documentName, required this.pageIndex, required this.page}); + PageItem({ + required this.documentId, + required this.documentName, + required this.pageIndex, + required this.page, + this.rotationOverride, + }); /// Unique ID for the document final int documentId; @@ -42,7 +48,27 @@ class PageItem { /// The PDF page final PdfPage page; + /// Rotation override for the page + final PdfPageRotation? rotationOverride; + String get id => '${documentId}_$pageIndex'; + + PageItem copyWith({PdfPage? page, PdfPageRotation? rotationOverride}) { + return PageItem( + documentId: documentId, + documentName: documentName, + pageIndex: pageIndex, + page: page ?? this.page, + rotationOverride: rotationOverride ?? this.rotationOverride, + ); + } + + PdfPage createProxy() { + if (rotationOverride != null) { + return page.withRotation(rotationOverride!); + } + return page; + } } /// Manages loaded PDF documents and tracks page usage @@ -161,6 +187,13 @@ class _PdfCombinePageState extends State { }); } + void _rotatePageLeft(int index) { + setState(() { + final page = _pages[index]; + _pages[index] = page.copyWith(rotationOverride: (page.rotationOverride ?? page.page.rotation).rotateCCW90); + }); + } + Future _navigateToPreview() async { if (_pages.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please add some pages first'))); @@ -255,7 +288,9 @@ class _PdfCombinePageState extends State { return _PageThumbnail( key: ValueKey(pageItem.id), page: pageItem.page, + rotationOverride: pageItem.rotationOverride, onRemove: () => _removePage(index), + onRotateLeft: () => _rotatePageLeft(index), currentIndex: index, dragDisabler: _disableDraggingOnChild, ); @@ -285,13 +320,17 @@ class _PageThumbnail extends StatelessWidget { const _PageThumbnail({ required this.page, required this.onRemove, + required this.onRotateLeft, required this.currentIndex, required this.dragDisabler, + this.rotationOverride, super.key, }); final PdfPage page; + final PdfPageRotation? rotationOverride; final VoidCallback onRemove; + final VoidCallback onRotateLeft; final int currentIndex; final Widget Function(Widget child) dragDisabler; @@ -304,7 +343,11 @@ class _PageThumbnail extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(4.0), - child: PdfPageView(document: page.document, pageNumber: page.pageNumber), + child: PdfPageView( + document: page.document, + pageNumber: page.pageNumber, + rotationOverride: rotationOverride, + ), ), // Delete button Positioned( @@ -325,6 +368,25 @@ class _PageThumbnail extends StatelessWidget { ), ), ), + // Rotate left button + Positioned( + top: 45, + right: 4, + child: dragDisabler( + Material( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + child: InkWell( + onTap: onRotateLeft, + borderRadius: BorderRadius.circular(20), + child: const Padding( + padding: EdgeInsets.all(4), + child: Icon(Icons.rotate_90_degrees_ccw, color: Colors.white, size: 20), + ), + ), + ), + ), + ), ], ), ); @@ -360,7 +422,7 @@ class _OutputPreviewPageState extends State { final combinedDoc = await PdfDocument.createNew(sourceName: 'combined.pdf'); // Set all selected pages - combinedDoc.pages = widget.pages.map((item) => item.page).toList(); + combinedDoc.pages = widget.pages.map((item) => item.createProxy()).toList(); // Encode to PDF final bytes = await combinedDoc.encodePdf(); diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 65aa2d9a..2343bde8 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -456,12 +456,14 @@ class _PdfDocumentWasm extends PdfDocument { continue; } - if (newPage is! _PdfPageWasm && newPage is! _PdfPageImported) { + // Unwrap PdfPageRotated to get the base page + final unwrappedPage = newPage.unwrap<_PdfPageWasm>() ?? newPage.unwrap<_PdfPageImported>(); + if (unwrappedPage == null) { throw ArgumentError('Unsupported PdfPage instances found at [${pagesList.length}]', 'newPages'); } - if (newPage.document != this || newPage.pageNumber != newPageNumber) { - final imported = _PdfPageImported._(imported: newPage, pageNumber: newPageNumber); + if (unwrappedPage.document != this || unwrappedPage.pageNumber != newPageNumber) { + final imported = _PdfPageImported._(basePage: newPage, pageNumber: newPageNumber); pagesList.add(imported); changes[newPageNumber] = PdfPageStatusChange.modified; } else { @@ -522,29 +524,38 @@ class _PdfDocumentWasm extends PdfDocument { @override Future assemble() async { - // Build the indices and imported pages map + // Build the indices, imported pages map, and rotations final indices = []; final importedPages = >{}; + final rotations = []; var modifiedCount = 0; for (var i = 0; i < pages.length; i++) { final page = pages[i]; - if (page is _PdfPageImported) { - final wasmPage = page.imported as _PdfPageWasm; - indices.add(-(i + 1)); - importedPages[-(i + 1)] = { + final wasmPage = page.unwrap<_PdfPageWasm>()!; + // if rotation is different, we need to modify the page + if (page.rotation.index != wasmPage.rotation.index) { + rotations.add(page.rotation.index); + modifiedCount++; + } else { + rotations.add(null); + } + if (page.document != this) { + // the page is from another document; need to import + final importId = -(i + 1); + indices.add(importId); + importedPages[importId] = { 'docHandle': wasmPage.document.document['docHandle'], 'pageNumber': wasmPage.pageNumber - 1, // 0-based }; modifiedCount++; - } else if (page is _PdfPageWasm) { - indices.add(page.pageNumber - 1); - if (page.pageNumber - 1 != i) { + } else { + indices.add(wasmPage.pageNumber - 1); + if (wasmPage.pageNumber - 1 != i) { modifiedCount++; } } } - if (modifiedCount == 0) { // No changes return false; @@ -555,6 +566,7 @@ class _PdfDocumentWasm extends PdfDocument { parameters: { 'docHandle': document['docHandle'], 'pageIndices': indices, + 'rotations': rotations, if (importedPages.isNotEmpty) 'importedPages': importedPages, }, ); @@ -702,6 +714,7 @@ class _PdfPageWasm extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -725,6 +738,7 @@ class _PdfPageWasm extends PdfPage { 'fullWidth': fullWidth, 'fullHeight': fullHeight, 'backgroundColor': backgroundColor, + 'rotation': rotationOverride != null ? (rotationOverride.index - rotation.index + 4) & 3 : 0, 'annotationRenderingMode': annotationRenderingMode.index, 'flags': flags, 'formHandle': document.document['formHandle'], @@ -766,33 +780,39 @@ class PdfImageWeb extends PdfImage { } /// A PDF page that is imported from another document or position. -class _PdfPageImported extends PdfPage { - _PdfPageImported._({required PdfPage imported, required this.pageNumber}) - : imported = imported is _PdfPageImported ? imported.imported : imported; // Unwrap nested imports +class _PdfPageImported extends PdfPageProxy { + _PdfPageImported._({required this.basePage, required this.pageNumber}); /// The imported page - final PdfPage imported; + @override + final PdfPage basePage; @override final int pageNumber; @override - PdfPageRenderCancellationToken createCancellationToken() => imported.createCancellationToken(); + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); @override - PdfDocument get document => imported.document; + PdfDocument get document => basePage.document; @override - double get height => imported.height; + PdfPageRotation get rotation => basePage.rotation; @override - bool get isLoaded => imported.isLoaded; + double get width => basePage.width; + + @override + double get height => basePage.height; + + @override + bool get isLoaded => basePage.isLoaded; @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - imported.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); @override - Future loadText() => imported.loadText(); + Future loadText() => basePage.loadText(); @override Future render({ @@ -803,10 +823,11 @@ class _PdfPageImported extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, - }) => imported.render( + }) => basePage.render( x: x, y: y, width: width, @@ -814,16 +835,14 @@ class _PdfPageImported extends PdfPage { fullWidth: fullWidth, fullHeight: fullHeight, backgroundColor: backgroundColor, + rotationOverride: rotationOverride, annotationRenderingMode: annotationRenderingMode, flags: flags, cancellationToken: cancellationToken, ); @override - PdfPageRotation get rotation => imported.rotation; - - @override - double get width => imported.width; + Future loadStructuredText() => basePage.loadStructuredText(); } PdfDest? _pdfDestFromMap(dynamic dest) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 1ffbd72b..57709651 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -167,9 +167,10 @@ typedef PdfDocumentViewBuilderFunction = Widget Function(BuildContext context, P /// /// [biggestSize] is the size of the widget. /// [page] is the page to be displayed. +/// [rotationOverride] is the rotation to override the page rotation. /// /// The function returns the size of the page. -typedef PdfPageViewSizeCallback = Size Function(Size biggestSize, PdfPage page); +typedef PdfPageViewSizeCallback = Size Function(Size biggestSize, PdfPage page, PdfPageRotation? rotationOverride); /// Function to build a widget that wraps the page image. /// @@ -190,6 +191,7 @@ class PdfPageView extends StatefulWidget { const PdfPageView({ required this.document, required this.pageNumber, + this.rotationOverride, this.maximumDpi = 300, this.alignment = Alignment.center, this.decoration, @@ -205,6 +207,9 @@ class PdfPageView extends StatefulWidget { /// The page number to be displayed. (The first page is 1). final int pageNumber; + /// The rotation to override the page rotation. + final PdfPageRotation? rotationOverride; + /// The maximum DPI of the page image. The default value is 300. /// /// The value is used to limit the actual image size to avoid excessive memory usage. @@ -238,18 +243,52 @@ class _PdfPageViewState extends State { ui.Image? _image; Size? _pageSize; PdfPageRenderCancellationToken? _cancellationToken; + StreamSubscription? _eventSubscription; @override void initState() { super.initState(); - pdfrxFlutterInitialize(); + _subscribeToDocumentEvents(); } @override void dispose() { + _eventSubscription?.cancel(); + _clearCache(refresh: false); + super.dispose(); + } + + @override + void didUpdateWidget(covariant PdfPageView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.document != oldWidget.document || + widget.pageNumber != oldWidget.pageNumber || + widget.rotationOverride != oldWidget.rotationOverride) { + _clearCache(); + _subscribeToDocumentEvents(); + } + } + + void _clearCache({bool refresh = true}) { _image?.dispose(); + _image = null; + _pageSize = null; _cancellationToken?.cancel(); - super.dispose(); + _cancellationToken = null; + if (refresh && mounted) { + setState(() {}); + } + } + + void _subscribeToDocumentEvents() { + _eventSubscription?.cancel(); + _eventSubscription = widget.document?.events.listen((event) { + if (event is PdfDocumentPageStatusChangedEvent) { + if (event.changes[widget.pageNumber] == PdfPageStatusChange.modified) { + _clearCache(); + } + } + }); } Widget _defaultDecorationBuilder(BuildContext context, Size pageSize, PdfPage page, RawImage? pageImage) { @@ -312,10 +351,14 @@ class _PdfPageViewState extends State { final Size pageSize; if (widget.pageSizeCallback != null) { - pageSize = widget.pageSizeCallback!(size, page); + pageSize = widget.pageSizeCallback!(size, page, widget.rotationOverride); } else { - final scale = min(widget.maximumDpi / 72, min(size.width / page.width, size.height / page.height)); - pageSize = Size(page.width * scale, page.height * scale); + final swapWH = ((widget.rotationOverride ?? page.rotation).index - page.rotation.index) & 1 == 1; + final w = swapWH ? page.height : page.width; + final h = swapWH ? page.width : page.height; + + final scale = min(widget.maximumDpi / 72, min(size.width / w, size.height / h)); + pageSize = Size(w * scale, h * scale); } if (pageSize == _pageSize) return; @@ -326,6 +369,7 @@ class _PdfPageViewState extends State { final pageImage = await page.render( fullWidth: pageSize.width, fullHeight: pageSize.height, + rotationOverride: widget.rotationOverride, cancellationToken: _cancellationToken, ); if (pageImage == null) return; diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index 148a4255..f600d142 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -145,7 +145,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let width = args["width"] as? Int, let height = args["height"] as? Int, let fullWidth = args["fullWidth"] as? Int, - let fullHeight = args["fullHeight"] as? Int + let fullHeight = args["fullHeight"] as? Int, + let rotation = args["rotation"] as? Int else { result( FlutterError( @@ -211,6 +212,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { let pdfX = CGFloat(x) let pdfBottom = CGFloat(fullHeight - (y + height)) + // FIXME: We should handle page rotation here properly + context.translateBy(x: -pdfX, y: -pdfBottom) context.scaleBy(x: scaleX, y: scaleY) diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 995e1c6b..a94234da 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -473,6 +473,7 @@ class _CoreGraphicsPdfPage extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, @@ -507,6 +508,9 @@ class _CoreGraphicsPdfPage extends PdfPage { 'fullWidth': targetFullWidth, 'fullHeight': targetFullHeight, 'backgroundColor': backgroundColor ?? 0xffffffff, + 'rotation': rotationOverride != null + ? (rotationOverride.index - rotation.index + 4) & 3 + : 0, 'flags': flags, 'renderAnnotations': annotationRenderingMode != PdfAnnotationRenderingMode.none, diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 13143c58..7671ba8f 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -727,12 +727,11 @@ class _PdfDocumentPdfium extends PdfDocument { continue; } - if (newPage is! _PdfPagePdfium && newPage is! _PdfPageImported) { + if (newPage.pdfium == null) { throw ArgumentError('Unsupported PdfPage instances found at [${pages.length}]', 'newPages'); } if (newPage.document != this || newPage.pageNumber != newPageNumber) { - final imported = _PdfPageImported._(imported: newPage, pageNumber: newPageNumber); - pages.add(imported); + pages.add(_PdfPageImported._(basePage: newPage, pageNumber: newPageNumber)); changes[newPageNumber] = PdfPageStatusChange.modified; } } @@ -838,22 +837,30 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { /// Returns true if the pages was modified. static Future doShufflePagesInPlace(_PdfDocumentPdfium document) async { final indices = []; + final rotations = []; final items = {}; var modifiedCount = 0; for (var i = 0; i < document.pages.length; i++) { final page = document.pages[i]; - if (page is _PdfPageImported) { - final pdfiumPage = page.imported as _PdfPagePdfium; - indices.add(-(i + 1)); - items[-(i + 1)] = (document: pdfiumPage.document.document.address, pageNumber: pdfiumPage.pageNumber); + final pdfiumPage = page.pdfium!; + // if rotation is different, we need to modify the page + if (page.rotation.index != pdfiumPage.rotation.index) { + rotations.add(page.rotation.index); modifiedCount++; - continue; - } else if (page is _PdfPagePdfium) { + } else { + rotations.add(null); + } + if (page.document != document) { + // the page is from another document; need to import + final importId = -(i + 1); + indices.add(importId); + items[importId] = (document: pdfiumPage.document.document.address, pageNumber: pdfiumPage.pageNumber); + modifiedCount++; + } else { indices.add(page.pageNumber - 1); if (page.pageNumber - 1 != i) { modifiedCount++; } - continue; } } if (modifiedCount == 0) { @@ -861,13 +868,30 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { return false; } - await (await backgroundWorker).computeWithArena((arena, params) { - final arranger = _DocumentPageArranger._( - pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document), - params.items, - ); - arranger.shuffleInPlaceAccordingToIndices(indices); - }, (document: document.document.address, indices: indices, items: items, length: document.pages.length)); + await (await backgroundWorker).computeWithArena( + (arena, params) { + final arranger = _DocumentPageArranger._( + pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document), + params.items, + ); + arranger.shuffleInPlaceAccordingToIndices(indices); + + for (var i = 0; i < params.length; i++) { + final rotation = params.rotations[i]; + if (rotation == null) continue; + final page = pdfium.FPDF_LoadPage(arranger.document, i); + pdfium.FPDFPage_SetRotation(page, rotation); + pdfium.FPDF_ClosePage(page); + } + }, + ( + document: document.document.address, + indices: indices, + rotations: rotations, + items: items, + length: document.pages.length, + ), + ); return true; } @@ -961,6 +985,7 @@ class _PdfPagePdfium extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -1017,7 +1042,7 @@ class _PdfPagePdfium extends PdfPage { -params.y, params.fullWidth, params.fullHeight, - 0, + params.rotation, params.flags | (params.annotationRenderingMode != PdfAnnotationRenderingMode.none ? pdfium_bindings.FPDF_ANNOT @@ -1034,7 +1059,7 @@ class _PdfPagePdfium extends PdfPage { -params.y, params.fullWidth, params.fullHeight, - 0, + params.rotation, params.flags, ); } @@ -1055,6 +1080,7 @@ class _PdfPagePdfium extends PdfPage { fullWidth: fullWidth!.toInt(), fullHeight: fullHeight!.toInt(), backgroundColor: backgroundColor, + rotation: rotationOverride != null ? ((rotationOverride.index - rotation.index + 4) & 3) : 0, annotationRenderingMode: annotationRenderingMode, flags: flags & 0xffff, // Ensure flags are within 16-bit range formHandle: document.formHandle.address, @@ -1306,34 +1332,45 @@ class _PdfPagePdfium extends PdfPage { } } +extension _PdfPagePdfiumExtension on PdfPage { + /// The final underlying [_PdfPagePdfium] if this page is backed by PDFium. + _PdfPagePdfium? get pdfium => unwrap<_PdfPagePdfium>(); + + /// The imported page wrapper if this page is an imported page. + _PdfPageImported? get imported => unwrap<_PdfPageImported>(); + + /// The rotated page wrapper if this page is a rotated page. + PdfPageRotated? get rotated => unwrap(); +} + /// A PDF page that is imported from another document. -class _PdfPageImported extends PdfPage { - _PdfPageImported._({required PdfPage imported, required this.pageNumber}) - : imported = imported is _PdfPageImported ? imported.imported : imported; // Unwrap nested imports +class _PdfPageImported implements PdfPageProxy { + _PdfPageImported._({required this.basePage, required this.pageNumber}); + + @override + final PdfPage basePage; - /// The imported page - final PdfPage imported; @override final int pageNumber; @override - PdfPageRenderCancellationToken createCancellationToken() => imported.createCancellationToken(); + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); @override - PdfDocument get document => imported.document; + PdfDocument get document => basePage.document; @override - double get height => imported.height; + double get height => basePage.height; @override - bool get isLoaded => imported.isLoaded; + bool get isLoaded => basePage.isLoaded; @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - imported.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); @override - Future loadText() => imported.loadText(); + Future loadText() => basePage.loadText(); @override Future render({ @@ -1344,10 +1381,11 @@ class _PdfPageImported extends PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, - }) => imported.render( + }) => basePage.render( x: x, y: y, width: width, @@ -1358,13 +1396,17 @@ class _PdfPageImported extends PdfPage { annotationRenderingMode: annotationRenderingMode, flags: flags, cancellationToken: cancellationToken, + rotationOverride: rotationOverride, ); @override - PdfPageRotation get rotation => imported.rotation; + Future loadStructuredText() => basePage.loadStructuredText(); + + @override + PdfPageRotation get rotation => basePage.rotation; @override - double get width => imported.width; + double get width => basePage.width; } class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index ee19cf92..9c820792 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -608,6 +608,7 @@ abstract class PdfPage { double? fullWidth, double? fullHeight, int? backgroundColor, + PdfPageRotation? rotationOverride, PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, int flags = PdfPageRenderFlags.none, PdfPageRenderCancellationToken? cancellationToken, @@ -894,6 +895,132 @@ abstract class PdfPage { Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); } +/// Extension to add rotation capability to [PdfPage]. +extension PdfPageWithRotationExtension on PdfPage { + /// Returns a proxy page with the specified absolute rotation. + /// + /// The rotation replaces the page's native rotation from the PDF. + /// The proxy page maintains all functionality of the original page but with transformed + /// coordinates for text, links, and rendering. + /// + /// Example: + /// ```dart + /// // Set a page to 90 degrees clockwise rotation + /// final rotatedPage = page.withRotation(PdfPageRotation.clockwise90); + /// + /// // Use it like a normal page + /// print('${rotatedPage.width} x ${rotatedPage.height}'); + /// final image = await rotatedPage.render(); + /// ``` + PdfPage withRotation(PdfPageRotation rotation) { + if (rotation == this.rotation) { + return this; // No rotation change needed + } + return PdfPageRotated(this, rotation); + } +} + +/// Proxy interface for [PdfPage]. +/// +/// Used for creating proxy pages that modify behavior of the base page. +abstract class PdfPageProxy implements PdfPage { + PdfPage get basePage; +} + +/// Extension to unwrap [PdfPageProxy] on [PdfPage]. +extension PdfPageProxyExtension on PdfPage { + /// Unwrap the page to get the base page of type [T]. + T? unwrap() { + final pThis = this; + if (pThis is T) return pThis; + if (pThis is PdfPageProxy) return pThis.basePage.unwrap(); + return null; + } +} + +/// PDF page wrapper that applies an absolute rotation to the base page. +class PdfPageRotated implements PdfPageProxy { + PdfPageRotated(this.basePage, this.rotation); + + @override + final PdfPage basePage; + + // Override rotation to return the effective rotation + @override + final PdfPageRotation rotation; + + /// Check if dimensions need to be swapped (for 90° or 270° rotations). + /// This is relative to the source page's rotation. + bool get _swapWH => shouldSwapWH(rotation); + + bool shouldSwapWH(PdfPageRotation rotation) => ((rotation.index - basePage.rotation.index) & 1) == 1; + + // Delegate basic properties + @override + PdfDocument get document => basePage.document; + + @override + int get pageNumber => basePage.pageNumber; + + @override + bool get isLoaded => basePage.isLoaded; + + // Swap width/height if additional rotation is 90° or 270° + @override + double get width => _swapWH ? basePage.height : basePage.width; + + @override + double get height => _swapWH ? basePage.width : basePage.height; + + // Override render to pass the effective rotation as rotationOverride + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfPageRotation? rotationOverride, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) { + return basePage.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + rotationOverride: rotationOverride ?? rotation, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + ); + } + + // All other methods just delegate - text/links work correctly because they use `rotation` property + @override + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); + + @override + Future loadText() => basePage.loadText(); + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + // Text methods don't depend on rotation - just delegate to source + @override + Future loadStructuredText() => basePage.loadStructuredText(); + + @override + Future _loadFormattedText() => basePage._loadFormattedText(); +} + /// PDF's raw text and its associated character bounding boxes. class PdfPageRawText { PdfPageRawText(this.fullText, this.charRects); @@ -918,6 +1045,14 @@ class PdfPageRawText { /// Page rotation. enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } +extension PdfPageRotationEnumExtension on PdfPageRotation { + /// Get counter-clockwise 90 degree rotation value from the current rotation. + PdfPageRotation get rotateCCW90 => PdfPageRotation.values[(index + 3) % 4]; + + /// Get clockwise 90 degree rotation value from the current rotation. + PdfPageRotation get rotateCW90 => PdfPageRotation.values[(index + 1) % 4]; +} + /// Annotation rendering mode. enum PdfAnnotationRenderingMode { /// Do not render annotations. From 18924c67ea3f349ea6b30352fc83ae7930a64f48 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 03:56:53 +0900 Subject: [PATCH 494/663] WIP --- doc/PDF-Page-Manipulation.md | 23 +++++++ .../pdfrx/example/pdf_combine/lib/main.dart | 2 +- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 62 ++++++++++++++----- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index 5cb52503..54e8a4bc 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -10,6 +10,7 @@ The page manipulation feature allows you to: - Combine pages from multiple PDF documents into one - Extract specific pages from a document - Duplicate pages within a document +- Rotate pages to different orientations - Create new PDF documents from selected pages ## Key Concepts @@ -44,6 +45,28 @@ doc1.pages = [ ]; ``` +### Page Rotation + +The [PdfPageWithRotationExtension](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageWithRotationExtension.html) provides several page rotation methods to apply rotation to pages when manipulating PDF documents. + +```dart +final doc = await PdfDocument.openFile('document.pdf'); + +// Use the rotated page in page manipulation +doc.pages = [ + doc.pages[0], // Page 2 with original rotation + doc.pages[1], // Page 2 with original rotation + doc.pages[2].rotatedCW90(), // Page 3 rotated right +]; +``` + +Technically, the functions on [PdfPageWithRotationExtension](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageWithRotationExtension.html) creates a ([PdfPageProxy](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageProxy-class.html)) with the specified rotation, which can be used in page manipulation operations. + +**Important Notes:** + +- If the specified rotation matches the page's current rotation, the original page is returned unchanged +- The proxy page can be used in any context where a regular [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) is expected + ### Encoding to PDF After manipulating pages, use [PdfDocument.encodePdf](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/encodePdf.html) to generate the final PDF file: diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 3c82f41d..0ad83035 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -65,7 +65,7 @@ class PageItem { PdfPage createProxy() { if (rotationOverride != null) { - return page.withRotation(rotationOverride!); + return page.rotatedTo(rotationOverride!); } return page; } diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 9c820792..9709a7ea 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -896,28 +896,54 @@ abstract class PdfPage { } /// Extension to add rotation capability to [PdfPage]. +/// +/// Use these functions to create rotated pages when reorganizing or combining PDFs. +/// +/// The following example shows how to fix page orientations: +/// +/// ```dart +/// final doc = await PdfDocument.openFile('document.pdf'); +/// doc.pages = [ +/// doc.pages[0], +/// doc.pages[1].rotatedTo(PdfPageRotation.clockwise90), +/// doc.pages[2].rotatedBy(PdfPageRotation.clockwise90), +/// doc.pages[3].rotatedCW90(), +/// ]; +/// await File('fixed.pdf').writeAsBytes(await doc.encodePdf()); +/// ``` extension PdfPageWithRotationExtension on PdfPage { - /// Returns a proxy page with the specified absolute rotation. + /// Rotates a page with the specified rotation. /// - /// The rotation replaces the page's native rotation from the PDF. - /// The proxy page maintains all functionality of the original page but with transformed - /// coordinates for text, links, and rendering. - /// - /// Example: - /// ```dart - /// // Set a page to 90 degrees clockwise rotation - /// final rotatedPage = page.withRotation(PdfPageRotation.clockwise90); - /// - /// // Use it like a normal page - /// print('${rotatedPage.width} x ${rotatedPage.height}'); - /// final image = await rotatedPage.render(); - /// ``` - PdfPage withRotation(PdfPageRotation rotation) { + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedTo(PdfPageRotation rotation) { if (rotation == this.rotation) { return this; // No rotation change needed } return PdfPageRotated(this, rotation); } + + /// Rotates a page with rotation added to the current rotation. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedBy(PdfPageRotation delta) { + final newRotation = PdfPageRotation.values[(rotation.index + delta.index) & 3]; + return rotatedTo(newRotation); + } + + /// Rotates a page clockwise by 90 degrees. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedCW90() => rotatedBy(PdfPageRotation.clockwise90); + + /// Rotates a page counter-clockwise by 90 degrees. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedCCW90() => rotatedBy(PdfPageRotation.clockwise270); + + /// Rotates a page clockwise by 180 degrees. + /// + /// Returns newly created [PdfPageProxy] that applies the rotation. + PdfPage rotated180() => rotatedBy(PdfPageRotation.clockwise180); } /// Proxy interface for [PdfPage]. @@ -1051,6 +1077,12 @@ extension PdfPageRotationEnumExtension on PdfPageRotation { /// Get clockwise 90 degree rotation value from the current rotation. PdfPageRotation get rotateCW90 => PdfPageRotation.values[(index + 1) % 4]; + + /// Get 180 degree rotation value from the current rotation. + PdfPageRotation get rotate180 => PdfPageRotation.values[(index + 2) % 4]; + + /// Add two rotations. + PdfPageRotation operator +(PdfPageRotation other) => PdfPageRotation.values[(index + other.index) % 4]; } /// Annotation rendering mode. From a5dc8781ee0a72791ab26d40164f770ac6aef1cb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 05:18:45 +0900 Subject: [PATCH 495/663] WIP --- .../example/viewer/lib/thumbnails_view.dart | 8 ++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 100 +++-------------- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 7 +- .../lib/src/native/pdfrx_pdfium.dart | 95 ++-------------- packages/pdfrx_engine/lib/src/pdfrx_api.dart | 104 +++++++++++++++++- 5 files changed, 137 insertions(+), 177 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/thumbnails_view.dart b/packages/pdfrx/example/viewer/lib/thumbnails_view.dart index 17480565..d58ab664 100644 --- a/packages/pdfrx/example/viewer/lib/thumbnails_view.dart +++ b/packages/pdfrx/example/viewer/lib/thumbnails_view.dart @@ -27,9 +27,11 @@ class ThumbnailsView extends StatelessWidget { child: Column( children: [ SizedBox( + key: ValueKey('thumb_${document!.hashCode}_$index'), height: 220, child: InkWell( onTap: () => controller!.goToPage(pageNumber: index + 1, anchor: PdfPageAnchor.top), + onDoubleTap: () => onDoubleTap(document, index + 1), child: PdfPageView(document: document, pageNumber: index + 1, alignment: Alignment.center), ), ), @@ -42,4 +44,10 @@ class ThumbnailsView extends StatelessWidget { ), ); } + + void onDoubleTap(PdfDocument document, int pageNumber) { + final pages = document.pages.toList(); + //pages[pageNumber - 1] = pages[pageNumber - 1].rotatedCCW90(); + document.pages = pages..removeAt(pageNumber - 1); + } } diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 2343bde8..bdb20393 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -436,42 +436,34 @@ class _PdfDocumentWasm extends PdfDocument { @override set pages(Iterable newPages) { - final pagesList = []; + final pages = []; final changes = {}; for (final newPage in newPages) { - if (pagesList.length < _pages.length) { - final old = _pages[pagesList.length]; + if (pages.length < _pages.length) { + final old = _pages[pages.length]; if (identical(newPage, old)) { - pagesList.add(newPage); + pages.add(newPage); continue; } } - final newPageNumber = pagesList.length + 1; - final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); - if (oldPageIndex != -1) { - pagesList.add(newPage); - changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); - continue; + if (newPage.unwrap<_PdfPageWasm>() == null) { + throw ArgumentError('Unsupported PdfPage instances found at [${pages.length}]', 'newPages'); } - // Unwrap PdfPageRotated to get the base page - final unwrappedPage = newPage.unwrap<_PdfPageWasm>() ?? newPage.unwrap<_PdfPageImported>(); - if (unwrappedPage == null) { - throw ArgumentError('Unsupported PdfPage instances found at [${pagesList.length}]', 'newPages'); - } + final newPageNumber = pages.length + 1; + pages.add(newPage.withPageNumber(newPageNumber)); - if (unwrappedPage.document != this || unwrappedPage.pageNumber != newPageNumber) { - final imported = _PdfPageImported._(basePage: newPage, pageNumber: newPageNumber); - pagesList.add(imported); - changes[newPageNumber] = PdfPageStatusChange.modified; + final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); + if (oldPageIndex != -1) { + changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); } else { - pagesList.add(newPage); + changes[newPageNumber] = PdfPageStatusChange.modified; } } - _pages = pagesList; + _pages = pages; subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); } @@ -779,72 +771,6 @@ class PdfImageWeb extends PdfImage { void dispose() {} } -/// A PDF page that is imported from another document or position. -class _PdfPageImported extends PdfPageProxy { - _PdfPageImported._({required this.basePage, required this.pageNumber}); - - /// The imported page - @override - final PdfPage basePage; - @override - final int pageNumber; - - @override - PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); - - @override - PdfDocument get document => basePage.document; - - @override - PdfPageRotation get rotation => basePage.rotation; - - @override - double get width => basePage.width; - - @override - double get height => basePage.height; - - @override - bool get isLoaded => basePage.isLoaded; - - @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); - - @override - Future loadText() => basePage.loadText(); - - @override - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - int? backgroundColor, - PdfPageRotation? rotationOverride, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }) => basePage.render( - x: x, - y: y, - width: width, - height: height, - fullWidth: fullWidth, - fullHeight: fullHeight, - backgroundColor: backgroundColor, - rotationOverride: rotationOverride, - annotationRenderingMode: annotationRenderingMode, - flags: flags, - cancellationToken: cancellationToken, - ); - - @override - Future loadStructuredText() => basePage.loadStructuredText(); -} - PdfDest? _pdfDestFromMap(dynamic dest) { if (dest == null) return null; final params = dest['params'] as List; diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 57709651..62e16a91 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -119,6 +119,7 @@ class _PdfDocumentViewBuilderState extends State { widget.documentRef.resolveListenable() ..addListener(_onDocumentChanged) ..load(); + _onDocumentChanged(); } @override @@ -150,7 +151,9 @@ class _PdfDocumentViewBuilderState extends State { } }); document?.loadPagesProgressively(); - setState(() {}); + if (mounted) { + setState(() {}); + } } } @@ -284,7 +287,7 @@ class _PdfPageViewState extends State { _eventSubscription?.cancel(); _eventSubscription = widget.document?.events.listen((event) { if (event is PdfDocumentPageStatusChangedEvent) { - if (event.changes[widget.pageNumber] == PdfPageStatusChange.modified) { + if (event.changes.keys.contains(widget.pageNumber)) { _clearCache(); } } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 7671ba8f..7bc75605 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -719,19 +719,17 @@ class _PdfDocumentPdfium extends PdfDocument { } } + if (newPage.unwrap<_PdfPagePdfium>() == null) { + throw ArgumentError('Unsupported PdfPage instances found at [${pages.length}]', 'newPages'); + } + final newPageNumber = pages.length + 1; + pages.add(newPage.withPageNumber(newPageNumber)); + final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); if (oldPageIndex != -1) { - pages.add(newPage); changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); - continue; - } - - if (newPage.pdfium == null) { - throw ArgumentError('Unsupported PdfPage instances found at [${pages.length}]', 'newPages'); - } - if (newPage.document != this || newPage.pageNumber != newPageNumber) { - pages.add(_PdfPageImported._(basePage: newPage, pageNumber: newPageNumber)); + } else { changes[newPageNumber] = PdfPageStatusChange.modified; } } @@ -842,7 +840,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { var modifiedCount = 0; for (var i = 0; i < document.pages.length; i++) { final page = document.pages[i]; - final pdfiumPage = page.pdfium!; + final pdfiumPage = page.unwrap<_PdfPagePdfium>()!; // if rotation is different, we need to modify the page if (page.rotation.index != pdfiumPage.rotation.index) { rotations.add(page.rotation.index); @@ -1332,83 +1330,6 @@ class _PdfPagePdfium extends PdfPage { } } -extension _PdfPagePdfiumExtension on PdfPage { - /// The final underlying [_PdfPagePdfium] if this page is backed by PDFium. - _PdfPagePdfium? get pdfium => unwrap<_PdfPagePdfium>(); - - /// The imported page wrapper if this page is an imported page. - _PdfPageImported? get imported => unwrap<_PdfPageImported>(); - - /// The rotated page wrapper if this page is a rotated page. - PdfPageRotated? get rotated => unwrap(); -} - -/// A PDF page that is imported from another document. -class _PdfPageImported implements PdfPageProxy { - _PdfPageImported._({required this.basePage, required this.pageNumber}); - - @override - final PdfPage basePage; - - @override - final int pageNumber; - - @override - PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); - - @override - PdfDocument get document => basePage.document; - - @override - double get height => basePage.height; - - @override - bool get isLoaded => basePage.isLoaded; - - @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); - - @override - Future loadText() => basePage.loadText(); - - @override - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - int? backgroundColor, - PdfPageRotation? rotationOverride, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }) => basePage.render( - x: x, - y: y, - width: width, - height: height, - fullWidth: fullWidth, - fullHeight: fullHeight, - backgroundColor: backgroundColor, - annotationRenderingMode: annotationRenderingMode, - flags: flags, - cancellationToken: cancellationToken, - rotationOverride: rotationOverride, - ); - - @override - Future loadStructuredText() => basePage.loadStructuredText(); - - @override - PdfPageRotation get rotation => basePage.rotation; - - @override - double get width => basePage.width; -} - class PdfPageRenderCancellationTokenPdfium extends PdfPageRenderCancellationToken { PdfPageRenderCancellationTokenPdfium(this.page); final PdfPage page; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart index 9709a7ea..64f08391 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_api.dart @@ -946,6 +946,21 @@ extension PdfPageWithRotationExtension on PdfPage { PdfPage rotated180() => rotatedBy(PdfPageRotation.clockwise180); } +/// Extension to add page renumbering capability to [PdfPage]. +/// +/// This is used internally when assembling documents, but can also be used manually. +extension PdfPageRenumberedExtension on PdfPage { + /// Renumbers a page with the specified page number. + /// + /// See usage example in [PdfPageRenumberedExtension]. + PdfPage withPageNumber(int pageNumber) { + if (pageNumber == this.pageNumber) { + return this; // No page number change needed + } + return PdfPageRenumbered(this, pageNumber: pageNumber); + } +} + /// Proxy interface for [PdfPage]. /// /// Used for creating proxy pages that modify behavior of the base page. @@ -956,17 +971,104 @@ abstract class PdfPageProxy implements PdfPage { /// Extension to unwrap [PdfPageProxy] on [PdfPage]. extension PdfPageProxyExtension on PdfPage { /// Unwrap the page to get the base page of type [T]. + /// + /// If the base page of type [T] is not found, returns null. T? unwrap() { final pThis = this; if (pThis is T) return pThis; if (pThis is PdfPageProxy) return pThis.basePage.unwrap(); return null; } + + /// Unwrap the page until [stopCondition] is met. + /// + /// If the condition is met, returns null. + PdfPage? unwrapUntil(bool Function(PdfPage page) stopCondition) { + var current = this; + while (true) { + if (stopCondition(current)) return current; + if (current is PdfPageProxy) { + current = current.basePage; + } else { + return null; + } + } + } +} + +/// PDF page wrapper that renumbers the page number. +class PdfPageRenumbered implements PdfPageProxy { + PdfPageRenumbered(PdfPage basePage, {required this.pageNumber}) + : basePage = basePage.unwrapUntil((p) => p is! PdfPageRenumbered)!; + + @override + final PdfPage basePage; + + @override + final int pageNumber; + + @override + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); + + @override + PdfDocument get document => basePage.document; + + @override + PdfPageRotation get rotation => basePage.rotation; + + @override + double get width => basePage.width; + + @override + double get height => basePage.height; + + @override + bool get isLoaded => basePage.isLoaded; + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + @override + Future loadText() => basePage.loadText(); + + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfPageRotation? rotationOverride, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) => basePage.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + rotationOverride: rotationOverride, + ); + + @override + Future loadStructuredText() => basePage.loadStructuredText(); + + @override + Future _loadFormattedText() => basePage._loadFormattedText(); } /// PDF page wrapper that applies an absolute rotation to the base page. class PdfPageRotated implements PdfPageProxy { - PdfPageRotated(this.basePage, this.rotation); + PdfPageRotated(PdfPage basePage, this.rotation) : basePage = basePage.unwrapUntil((p) => p is! PdfPageRotated)!; @override final PdfPage basePage; From dbc97a0ff608248870eeb63a7df471d7bf704fe1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 06:35:45 +0900 Subject: [PATCH 496/663] pdfrx_engine file structure refactoring --- .vscode/settings.json | 1 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 2 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 3 + packages/pdfrx_engine/lib/pdfrx_engine.dart | 16 +- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 3 +- .../lib/src/native/apple_direct_lookup.dart | 2 +- .../lib/src/native/pdf_file_cache.dart | 5 +- .../pdfrx_engine/lib/src/native/pdfium.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 16 +- .../pdfrx_engine/lib/src/native/worker.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_dest.dart | 65 + .../lib/src/pdf_document_event.dart | 50 + .../pdfrx_engine/lib/src/pdf_exception.dart | 11 + .../pdfrx_engine/lib/src/pdf_font_query.dart | 81 + packages/pdfrx_engine/lib/src/pdf_link.dart | 50 + .../lib/src/pdf_outline_node.dart | 33 + packages/pdfrx_engine/lib/src/pdf_page.dart | 253 ++ .../lib/src/pdf_page_proxies.dart | 189 ++ .../lib/src/pdf_page_status_change.dart | 45 + .../pdfrx_engine/lib/src/pdf_permissions.dart | 34 + packages/pdfrx_engine/lib/src/pdf_point.dart | 82 + packages/pdfrx_engine/lib/src/pdf_rect.dart | 185 ++ packages/pdfrx_engine/lib/src/pdf_text.dart | 335 +++ .../lib/src/pdf_text_formatter.dart | 280 +++ packages/pdfrx_engine/lib/src/pdfrx.dart | 60 + packages/pdfrx_engine/lib/src/pdfrx_api.dart | 2119 ----------------- .../pdfrx_engine/lib/src/pdfrx_document.dart | 281 +++ .../lib/src/pdfrx_entry_functions.dart | 127 + .../lib/src/utils/list_equals.dart | 20 + 29 files changed, 2226 insertions(+), 2126 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/pdf_dest.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_document_event.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_exception.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_font_query.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_link.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_outline_node.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_page.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_page_proxies.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_page_status_change.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_permissions.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_point.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_rect.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_text.dart create mode 100644 packages/pdfrx_engine/lib/src/pdf_text_formatter.dart create mode 100644 packages/pdfrx_engine/lib/src/pdfrx.dart delete mode 100644 packages/pdfrx_engine/lib/src/pdfrx_api.dart create mode 100644 packages/pdfrx_engine/lib/src/pdfrx_document.dart create mode 100644 packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart create mode 100644 packages/pdfrx_engine/lib/src/utils/list_equals.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a2d0c87..a69df4b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -64,6 +64,7 @@ "FILEATTACHMENT", "FILEHANDLER", "FILEIDTYPE", + "FILEWRITE", "fillmode", "findwhat", "FITBH", diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index bdb20393..844308b6 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -5,6 +5,8 @@ import 'dart:ui_web' as ui_web; import 'package:crypto/crypto.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; +// ignore: implementation_imports +import 'package:pdfrx_engine/src/pdf_page_proxies.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:web/web.dart' as web; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index a556d02c..58d93085 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -406,6 +406,9 @@ class _PdfViewerState extends State _imageCache.removeCacheImagesForPage(change.key); _magnifierImageCache.removeCacheImagesForPage(change.key); } + _canvasLinkPainter.resetAll(); + _textCache.clear(); + _clearTextSelections(invalidate: false); _invalidate(); } } diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index c3b0c6b1..660f8f7f 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -1,5 +1,19 @@ library; export 'src/mock/pdfrx_initialize_mock.dart' if (dart.library.io) 'src/pdfrx_initialize_dart.dart'; -export 'src/pdfrx_api.dart'; +export 'src/pdf_dest.dart'; +export 'src/pdf_document_event.dart'; +export 'src/pdf_exception.dart'; +export 'src/pdf_font_query.dart'; +export 'src/pdf_link.dart'; +export 'src/pdf_outline_node.dart'; +export 'src/pdf_page.dart'; +export 'src/pdf_page_status_change.dart'; +export 'src/pdf_permissions.dart'; +export 'src/pdf_point.dart'; +export 'src/pdf_rect.dart'; +export 'src/pdf_text.dart'; +export 'src/pdfrx.dart'; export 'src/pdfrx_dart.dart'; +export 'src/pdfrx_document.dart'; +export 'src/pdfrx_entry_functions.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index f03164c9..460bd951 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:typed_data'; -import '../../pdfrx_engine.dart'; +import '../pdfrx_document.dart'; +import '../pdfrx_entry_functions.dart'; /// This is an empty implementation of [PdfrxEntryFunctions] that just throws [UnimplementedError]. /// diff --git a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart index 894de828..a5e85fae 100644 --- a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart +++ b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart @@ -1,6 +1,6 @@ import 'dart:ffi' as ffi; -import '../pdfrx_api.dart'; +import '../pdfrx.dart'; import 'pdfium.dart' as pdfium_native; import 'pdfium_bindings.dart' as pdfium_bindings; import 'pdfium_interop.dart' as file_access_helpers; diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 84c365a0..9406853f 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -9,7 +9,10 @@ import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:synchronized/extension.dart'; -import '../pdfrx_api.dart'; +import '../pdf_exception.dart'; +import '../pdfrx.dart'; +import '../pdfrx_document.dart'; +import '../pdfrx_entry_functions.dart'; import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; import 'native_utils.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index c380a592..8c15d443 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -3,7 +3,7 @@ import 'dart:ffi' as ffi; import 'dart:ffi'; import 'dart:io'; -import '../pdfrx_api.dart'; +import '../pdfrx.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; /// Get the module file name for pdfium. diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 7bc75605..94308468 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -11,7 +11,21 @@ import 'package:ffi/ffi.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; -import '../pdfrx_api.dart'; +import '../pdf_dest.dart'; +import '../pdf_document_event.dart'; +import '../pdf_exception.dart'; +import '../pdf_font_query.dart'; +import '../pdf_link.dart'; +import '../pdf_outline_node.dart'; +import '../pdf_page.dart'; +import '../pdf_page_proxies.dart'; +import '../pdf_page_status_change.dart'; +import '../pdf_permissions.dart'; +import '../pdf_rect.dart'; +import '../pdf_text.dart'; +import '../pdfrx.dart'; +import '../pdfrx_document.dart'; +import '../pdfrx_entry_functions.dart'; import '../utils/shuffle_in_place.dart'; import 'native_utils.dart'; import 'pdf_file_cache.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 4278d28d..133a77f4 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -5,7 +5,7 @@ import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import '../pdfrx_api.dart'; +import '../pdfrx.dart'; import 'apple_direct_lookup.dart'; typedef PdfrxComputeCallback = FutureOr Function(M message); diff --git a/packages/pdfrx_engine/lib/src/pdf_dest.dart b/packages/pdfrx_engine/lib/src/pdf_dest.dart new file mode 100644 index 00000000..60a22ae6 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_dest.dart @@ -0,0 +1,65 @@ +import 'utils/list_equals.dart'; + +/// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. +class PdfDest { + /// Create a [PdfDest]. + const PdfDest(this.pageNumber, this.command, this.params); + + /// Page number to jump to. + final int pageNumber; + + /// Destination command. + final PdfDestCommand command; + + /// Destination parameters. For more info, see [PdfDestCommand]. + final List? params; + + @override + String toString() => 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; + + /// Compact the destination. + /// + /// The method is used to compact the destination to reduce memory usage. + /// [params] is typically growable and also modifiable. The method ensures that [params] is unmodifiable. + PdfDest compact() { + return params == null ? this : PdfDest(pageNumber, command, List.unmodifiable(params!)); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfDest && + other.pageNumber == pageNumber && + other.command == command && + listEquals(other.params, params); + } + + @override + int get hashCode => pageNumber.hashCode ^ command.hashCode ^ params.hashCode; +} + +/// [PDF 32000-1:2008, 12.3.2.2 Explicit Destinations, Table 151](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) +enum PdfDestCommand { + unknown('unknown'), + xyz('xyz'), + fit('fit'), + fitH('fith'), + fitV('fitv'), + fitR('fitr'), + fitB('fitb'), + fitBH('fitbh'), + fitBV('fitbv'); + + /// Create a [PdfDestCommand] with the specified command name. + const PdfDestCommand(this.name); + + /// Parse the command name to [PdfDestCommand]. + factory PdfDestCommand.parse(String name) { + final nameLow = name.toLowerCase(); + return PdfDestCommand.values.firstWhere((e) => e.name == nameLow, orElse: () => PdfDestCommand.unknown); + } + + /// Command name. + final String name; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart new file mode 100644 index 00000000..d2821335 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -0,0 +1,50 @@ +import 'pdf_font_query.dart'; +import 'pdf_page_status_change.dart'; +import 'pdfrx_document.dart'; + +/// PDF document event types. +enum PdfDocumentEventType { + /// [PdfDocumentPageStatusChangedEvent]: Page status changed. + pageStatusChanged, + missingFonts, // [PdfDocumentMissingFontsEvent]: Missing fonts changed. +} + +/// Base class for PDF document events. +abstract class PdfDocumentEvent { + /// Event type. + PdfDocumentEventType get type; + + /// Document that this event is related to. + PdfDocument get document; +} + +/// Event that is triggered when the status of PDF document pages has changed. +class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { + PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); + + @override + PdfDocumentEventType get type => PdfDocumentEventType.pageStatusChanged; + + @override + final PdfDocument document; + + /// The pages that have changed. + /// + /// The map is from page number (1-based) to it's status change. + final Map changes; +} + +/// Event that is triggered when the list of missing fonts in the PDF document has changed. +class PdfDocumentMissingFontsEvent implements PdfDocumentEvent { + /// Create a [PdfDocumentMissingFontsEvent]. + PdfDocumentMissingFontsEvent(this.document, this.missingFonts); + + @override + PdfDocumentEventType get type => PdfDocumentEventType.missingFonts; + + @override + final PdfDocument document; + + /// The list of missing fonts. + final List missingFonts; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_exception.dart b/packages/pdfrx_engine/lib/src/pdf_exception.dart new file mode 100644 index 00000000..bcfdc0dd --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_exception.dart @@ -0,0 +1,11 @@ +class PdfException implements Exception { + const PdfException(this.message, [this.errorCode]); + final String message; + final int? errorCode; + @override + String toString() => 'PdfException: $message'; +} + +class PdfPasswordException extends PdfException { + const PdfPasswordException(super.message); +} diff --git a/packages/pdfrx_engine/lib/src/pdf_font_query.dart b/packages/pdfrx_engine/lib/src/pdf_font_query.dart new file mode 100644 index 00000000..7a134aee --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_font_query.dart @@ -0,0 +1,81 @@ +class PdfFontQuery { + const PdfFontQuery({ + required this.face, + required this.weight, + required this.isItalic, + required this.charset, + required this.pitchFamily, + }); + + /// Font face name. + final String face; + + /// Font weight. + final int weight; + + /// Whether the font is italic. + final bool isItalic; + + /// PDFium's charset ID. + final PdfFontCharset charset; + + /// Pitch family flags. + /// + /// It can be any combination of the following values: + /// - `fixed` = 1 + /// - `roman` = 16 + /// - `script` = 64 + final int pitchFamily; + + bool get isFixed => (pitchFamily & 1) != 0; + bool get isRoman => (pitchFamily & 16) != 0; + bool get isScript => (pitchFamily & 64) != 0; + + String _getPitchFamily() { + return [if (isFixed) 'fixed', if (isRoman) 'roman', if (isScript) 'script'].join(','); + } + + @override + String toString() => + 'PdfFontQuery(face: "$face", weight: $weight, italic: $isItalic, charset: $charset, pitchFamily: $pitchFamily=[${_getPitchFamily()}])'; +} + +/// PDFium font charset ID. +/// +enum PdfFontCharset { + ansi(0), + default_(1), + symbol(2), + + /// Japanese + shiftJis(128), + + /// Korean + hangul(129), + + /// Chinese Simplified + gb2312(134), + + /// Chinese Traditional + chineseBig5(136), + greek(161), + vietnamese(163), + hebrew(177), + arabic(178), + cyrillic(204), + thai(222), + easternEuropean(238); + + const PdfFontCharset(this.pdfiumCharsetId); + + /// PDFium's charset ID. + final int pdfiumCharsetId; + + static final _value2Enum = {for (final e in PdfFontCharset.values) e.pdfiumCharsetId: e}; + + /// Convert PDFium's charset ID to [PdfFontCharset]. + static PdfFontCharset fromPdfiumCharsetId(int id) => _value2Enum[id]!; + + @override + String toString() => '$name($pdfiumCharsetId)'; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_link.dart b/packages/pdfrx_engine/lib/src/pdf_link.dart new file mode 100644 index 00000000..042b8b56 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_link.dart @@ -0,0 +1,50 @@ +import '../pdfrx_engine.dart' show PdfPage; +import 'pdf_dest.dart'; +import 'pdf_rect.dart'; +import 'utils/list_equals.dart'; + +/// Link in PDF page. +/// +/// Either one of [url] or [dest] is valid (not null). +/// See [PdfPage.loadLinks]. +class PdfLink { + const PdfLink(this.rects, {this.url, this.dest, this.annotationContent}); + + /// Link URL. + final Uri? url; + + /// Link destination (link to page). + final PdfDest? dest; + + /// Link location(s) inside the associated PDF page. + /// + /// Sometimes a link can span multiple rectangles, e.g., a link across multiple lines. + final List rects; + + /// Annotation content if available. + final String? annotationContent; + + /// Compact the link. + /// + /// The method is used to compact the link to reduce memory usage. + /// [rects] is typically growable and also modifiable. The method ensures that [rects] is unmodifiable. + /// [dest] is also compacted by calling [PdfDest.compact]. + PdfLink compact() { + return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact(), annotationContent: annotationContent); + } + + @override + String toString() { + return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects, annotationContent: $annotationContent }'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfLink && other.url == url && other.dest == dest && listEquals(other.rects, rects); + } + + @override + int get hashCode => url.hashCode ^ dest.hashCode ^ rects.hashCode; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_outline_node.dart b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart new file mode 100644 index 00000000..552fcfe6 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart @@ -0,0 +1,33 @@ +import '../pdfrx_engine.dart' show PdfDocument; +import 'pdf_dest.dart'; +import 'pdfrx_document.dart' show PdfDocument; +import 'utils/list_equals.dart'; + +/// Outline (a.k.a. Bookmark) node in PDF document. +/// +/// See [PdfDocument.loadOutline]. +class PdfOutlineNode { + const PdfOutlineNode({required this.title, required this.dest, required this.children}); + + /// Outline node title. + final String title; + + /// Outline node destination. + final PdfDest? dest; + + /// Outline child nodes. + final List children; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfOutlineNode && + other.title == title && + other.dest == dest && + listEquals(other.children, children); + } + + @override + int get hashCode => title.hashCode ^ dest.hashCode ^ children.hashCode; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart new file mode 100644 index 00000000..1749492b --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -0,0 +1,253 @@ +import 'dart:typed_data'; + +import '../pdfrx_engine.dart' show PdfLink; +import 'pdf_link.dart' show PdfLink; +import 'pdf_page_proxies.dart'; +import 'pdf_text.dart'; +import 'pdf_text_formatter.dart'; +import 'pdfrx_document.dart'; + +/// Handles a PDF page in [PdfDocument]. +/// +/// See [PdfDocument.pages]. +abstract class PdfPage { + /// PDF document. + PdfDocument get document; + + /// Page number. The first page is 1. + int get pageNumber; + + /// PDF page width in points (width in pixels at 72 dpi) (rotated). + double get width; + + /// PDF page height in points (height in pixels at 72 dpi) (rotated). + double get height; + + /// PDF page rotation. + PdfPageRotation get rotation; + + /// Whether the page is really loaded or not. + /// + /// If the value is false, the page's [width], [height], and [rotation] are just guessed values and + /// will be updated when the page is really loaded. + bool get isLoaded; + + /// Render a sub-area or full image of specified PDF file. + /// Returned image should be disposed after use. + /// [x], [y], [width], [height] specify sub-area to render in pixels. + /// [fullWidth], [fullHeight] specify virtual full size of the page to render in pixels. + /// - If [x], [y] are not specified, (0,0) is used. + /// - If [width], [height] are not specified, [fullWidth], [fullHeight] are used. + /// - If [fullWidth], [fullHeight] are not specified, [PdfPage.width] and [PdfPage.height] are used (it means rendered at 72-dpi). + /// [backgroundColor] is `AARRGGBB` integer color notation used to fill the background of the page. If no color is specified, 0xffffffff (white) is used. + /// - [annotationRenderingMode] controls to render annotations or not. The default is [PdfAnnotationRenderingMode.annotationAndForms]. + /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderFlags.none]. + /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. + /// + /// The following code extract the area of (20,30)-(120,130) from the page image rendered at 1000x1500 pixels: + /// ```dart + /// final image = await page.render( + /// x: 20, + /// y: 30, + /// width: 100, + /// height: 100, + /// fullWidth: 1000, + /// fullHeight: 1500, + /// ); + /// ``` + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfPageRotation? rotationOverride, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }); + + /// Create [PdfPageRenderCancellationToken] to cancel the rendering process. + PdfPageRenderCancellationToken createCancellationToken(); + + /// Load structured text with character bounding boxes. + /// + /// The function internally does test flow analysis (reading order) and line segmentation to detect + /// text direction and line breaks. + /// + /// To access the raw text, use [loadText]. + Future loadStructuredText() => PdfTextFormatter.loadStructuredText(this, pageNumberOverride: pageNumber); + + /// Load plain text for the page. + /// + /// For text with character bounding boxes, use [loadStructuredText]. + Future loadText(); + + /// Load links. + /// + /// If [compact] is true, it tries to reduce memory usage by compacting the link data. + /// See [PdfLink.compact] for more info. + /// + /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. + /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. + /// The default is true. + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); +} + +/// Extension to add rotation capability to [PdfPage]. +/// +/// Use these functions to create rotated pages when reorganizing or combining PDFs. +/// +/// The following example shows how to fix page orientations: +/// +/// ```dart +/// final doc = await PdfDocument.openFile('document.pdf'); +/// doc.pages = [ +/// doc.pages[0], +/// doc.pages[1].rotatedTo(PdfPageRotation.clockwise90), +/// doc.pages[2].rotatedBy(PdfPageRotation.clockwise90), +/// doc.pages[3].rotatedCW90(), +/// ]; +/// await File('fixed.pdf').writeAsBytes(await doc.encodePdf()); +/// ``` +extension PdfPageWithRotationExtension on PdfPage { + /// Rotates a page with the specified rotation. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedTo(PdfPageRotation rotation) { + if (rotation == this.rotation) { + return this; // No rotation change needed + } + return PdfPageRotated(this, rotation); + } + + /// Rotates a page with rotation added to the current rotation. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedBy(PdfPageRotation delta) { + final newRotation = PdfPageRotation.values[(rotation.index + delta.index) & 3]; + return rotatedTo(newRotation); + } + + /// Rotates a page clockwise by 90 degrees. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedCW90() => rotatedBy(PdfPageRotation.clockwise90); + + /// Rotates a page counter-clockwise by 90 degrees. + /// + /// See usage example in [PdfPageWithRotationExtension]. + PdfPage rotatedCCW90() => rotatedBy(PdfPageRotation.clockwise270); + + /// Rotates a page clockwise by 180 degrees. + /// + /// Returns newly created [PdfPageProxy] that applies the rotation. + PdfPage rotated180() => rotatedBy(PdfPageRotation.clockwise180); +} + +/// Extension to add page renumbering capability to [PdfPage]. +/// +/// This is used internally when assembling documents, but can also be used manually. +extension PdfPageRenumberedExtension on PdfPage { + /// Renumbers a page with the specified page number. + /// + /// See usage example in [PdfPageRenumberedExtension]. + PdfPage withPageNumber(int pageNumber) { + if (pageNumber == this.pageNumber) { + return this; // No page number change needed + } + return PdfPageRenumbered(this, pageNumber: pageNumber); + } +} + +/// Page rotation. +enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } + +extension PdfPageRotationEnumExtension on PdfPageRotation { + /// Get counter-clockwise 90 degree rotation value from the current rotation. + PdfPageRotation get rotateCCW90 => PdfPageRotation.values[(index + 3) % 4]; + + /// Get clockwise 90 degree rotation value from the current rotation. + PdfPageRotation get rotateCW90 => PdfPageRotation.values[(index + 1) % 4]; + + /// Get 180 degree rotation value from the current rotation. + PdfPageRotation get rotate180 => PdfPageRotation.values[(index + 2) % 4]; + + /// Add two rotations. + PdfPageRotation operator +(PdfPageRotation other) => PdfPageRotation.values[(index + other.index) % 4]; +} + +/// Annotation rendering mode. +enum PdfAnnotationRenderingMode { + /// Do not render annotations. + none, + + /// Render annotations. + annotation, + + /// Render annotations and forms. + annotationAndForms, +} + +/// Flags for [PdfPage.render]. +/// +/// Basically, they are PDFium's `FPDF_RENDER_*` flags and not supported on PDF.js. +abstract class PdfPageRenderFlags { + /// None. + static const none = 0; + + /// `FPDF_LCD_TEXT` flag. + static const lcdText = 0x0002; + + /// `FPDF_GRAYSCALE` flag. + static const grayscale = 0x0008; + + /// `FPDF_RENDER_LIMITEDIMAGECACHE` flag. + static const limitedImageCache = 0x0200; + + /// `FPDF_RENDER_FORCEHALFTONE` flag. + static const forceHalftone = 0x0400; + + /// `FPDF_PRINTING` flag. + static const printing = 0x0800; + + /// `FPDF_RENDER_NO_SMOOTHTEXT` flag. + static const noSmoothText = 0x1000; + + /// `FPDF_RENDER_NO_SMOOTHIMAGE` flag. + static const noSmoothImage = 0x2000; + + /// `FPDF_RENDER_NO_SMOOTHPATH` flag. + static const noSmoothPath = 0x4000; + + /// Output image is in premultiplied alpha format. + static const premultipliedAlpha = 0x80000000; +} + +/// Token to try to cancel the rendering process. +abstract class PdfPageRenderCancellationToken { + /// Cancel the rendering process. + void cancel(); + + /// Determine whether the rendering process is canceled or not. + bool get isCanceled; +} + +/// Image rendered from PDF page. +/// +/// See [PdfPage.render]. +abstract class PdfImage { + /// Number of pixels in horizontal direction. + int get width; + + /// Number of pixels in vertical direction. + int get height; + + /// BGRA8888 Raw pixel data. + Uint8List get pixels; + + /// Dispose the image. + void dispose(); +} diff --git a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart new file mode 100644 index 00000000..e12fef91 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart @@ -0,0 +1,189 @@ +import 'pdf_link.dart'; +import 'pdf_page.dart'; +import 'pdf_text.dart'; +import 'pdf_text_formatter.dart'; +import 'pdfrx_document.dart'; + +/// Proxy interface for [PdfPage]. +/// +/// Used for creating proxy pages that modify behavior of the base page. +/// +/// For implementation, see [PdfPageRenumbered] and [PdfPageRotated]. +abstract class PdfPageProxy implements PdfPage { + PdfPage get basePage; +} + +/// Extension to unwrap [PdfPageProxy] on [PdfPage]. +extension PdfPageProxyExtension on PdfPage { + /// Unwrap the page to get the base page of type [T]. + /// + /// If the base page of type [T] is not found, returns null. + T? unwrap() { + final pThis = this; + if (pThis is T) return pThis; + if (pThis is PdfPageProxy) return pThis.basePage.unwrap(); + return null; + } + + /// Unwrap the page until [stopCondition] is met. + /// + /// If the condition is met, returns null. + PdfPage? unwrapUntil(bool Function(PdfPage page) stopCondition) { + var current = this; + while (true) { + if (stopCondition(current)) return current; + if (current is PdfPageProxy) { + current = current.basePage; + } else { + return null; + } + } + } +} + +/// PDF page wrapper that renumbers the page number. +class PdfPageRenumbered implements PdfPageProxy { + PdfPageRenumbered(PdfPage basePage, {required this.pageNumber}) + : basePage = basePage.unwrapUntil((p) => p is! PdfPageRenumbered)!; + + @override + final PdfPage basePage; + + @override + final int pageNumber; + + @override + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); + + @override + PdfDocument get document => basePage.document; + + @override + PdfPageRotation get rotation => basePage.rotation; + + @override + double get width => basePage.width; + + @override + double get height => basePage.height; + + @override + bool get isLoaded => basePage.isLoaded; + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + @override + Future loadText() => basePage.loadText(); + + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfPageRotation? rotationOverride, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) => basePage.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + rotationOverride: rotationOverride, + ); + + @override + Future loadStructuredText() => PdfTextFormatter.loadStructuredText(this, pageNumberOverride: pageNumber); +} + +/// PDF page wrapper that applies an absolute rotation to the base page. +class PdfPageRotated implements PdfPageProxy { + PdfPageRotated(PdfPage basePage, this.rotation) : basePage = basePage.unwrapUntil((p) => p is! PdfPageRotated)!; + + @override + final PdfPage basePage; + + // Override rotation to return the effective rotation + @override + final PdfPageRotation rotation; + + /// Check if dimensions need to be swapped (for 90° or 270° rotations). + /// This is relative to the source page's rotation. + bool get _swapWH => shouldSwapWH(rotation); + + bool shouldSwapWH(PdfPageRotation rotation) => ((rotation.index - basePage.rotation.index) & 1) == 1; + + // Delegate basic properties + @override + PdfDocument get document => basePage.document; + + @override + int get pageNumber => basePage.pageNumber; + + @override + bool get isLoaded => basePage.isLoaded; + + // Swap width/height if additional rotation is 90° or 270° + @override + double get width => _swapWH ? basePage.height : basePage.width; + + @override + double get height => _swapWH ? basePage.width : basePage.height; + + // Override render to pass the effective rotation as rotationOverride + @override + Future render({ + int x = 0, + int y = 0, + int? width, + int? height, + double? fullWidth, + double? fullHeight, + int? backgroundColor, + PdfPageRotation? rotationOverride, + PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, + int flags = PdfPageRenderFlags.none, + PdfPageRenderCancellationToken? cancellationToken, + }) { + return basePage.render( + x: x, + y: y, + width: width, + height: height, + fullWidth: fullWidth, + fullHeight: fullHeight, + backgroundColor: backgroundColor, + rotationOverride: rotationOverride ?? rotation, + annotationRenderingMode: annotationRenderingMode, + flags: flags, + cancellationToken: cancellationToken, + ); + } + + // All other methods just delegate - text/links work correctly because they use `rotation` property + @override + PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); + + @override + Future loadText() => basePage.loadText(); + + @override + Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => + basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); + + // Text methods don't depend on rotation - just delegate to source + @override + Future loadStructuredText() => basePage.loadStructuredText(); +} diff --git a/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart new file mode 100644 index 00000000..f4b1b930 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart @@ -0,0 +1,45 @@ +/// Base class for PDF page status change. +abstract class PdfPageStatusChange { + const PdfPageStatusChange(); + + /// Create [PdfPageStatusMoved]. + static PdfPageStatusChange moved({required int oldPageNumber}) => PdfPageStatusMoved(oldPageNumber: oldPageNumber); + + /// Return [PdfPageStatusModified]. + static const modified = PdfPageStatusModified(); +} + +/// Event that is triggered when a PDF page is moved inside the same document. +class PdfPageStatusMoved extends PdfPageStatusChange { + const PdfPageStatusMoved({required this.oldPageNumber}); + final int oldPageNumber; + + @override + int get hashCode => oldPageNumber.hashCode; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfPageStatusMoved && other.oldPageNumber == oldPageNumber; + } + + @override + String toString() => 'PdfPageStatusMoved(oldPageNumber: $oldPageNumber)'; +} + +/// Event that is triggered when a PDF page is modified or newly added. +class PdfPageStatusModified extends PdfPageStatusChange { + const PdfPageStatusModified(); + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfPageStatusModified; + } + + @override + String toString() => 'PdfPageStatusModified()'; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_permissions.dart b/packages/pdfrx_engine/lib/src/pdf_permissions.dart new file mode 100644 index 00000000..8be23988 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_permissions.dart @@ -0,0 +1,34 @@ +/// PDF permissions defined on PDF 32000-1:2008, Table 22. +class PdfPermissions { + const PdfPermissions(this.permissions, this.securityHandlerRevision); + + /// User access permissions on on PDF 32000-1:2008, Table 22. + final int permissions; + + /// Security handler revision. + final int securityHandlerRevision; + + /// Determine whether the PDF file allows copying of the contents. + bool get allowsCopying => (permissions & 4) != 0; + + /// Determine whether the PDF file allows document assembly. + bool get allowsDocumentAssembly => (permissions & 8) != 0; + + /// Determine whether the PDF file allows printing of the pages. + bool get allowsPrinting => (permissions & 16) != 0; + + /// Determine whether the PDF file allows modifying annotations, form fields, and their associated + bool get allowsModifyAnnotations => (permissions & 32) != 0; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPermissions && + other.permissions == permissions && + other.securityHandlerRevision == securityHandlerRevision; + } + + @override + int get hashCode => permissions.hashCode ^ securityHandlerRevision.hashCode; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_point.dart b/packages/pdfrx_engine/lib/src/pdf_point.dart new file mode 100644 index 00000000..9a4dccc0 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_point.dart @@ -0,0 +1,82 @@ +import 'package:vector_math/vector_math_64.dart' hide Colors; + +import 'pdf_page.dart'; + +/// PDF page coordinates point. +/// +/// In Pdf page coordinates, the origin is at the bottom-left corner and Y-axis is pointing upward. +/// The unit is normally in points (1/72 inch). +class PdfPoint { + const PdfPoint(this.x, this.y); + + /// X coordinate. + final double x; + + /// Y coordinate. + final double y; + + /// Calculate the vector to another point. + Vector2 differenceTo(PdfPoint other) => Vector2(other.x - x, other.y - y); + + @override + String toString() => 'PdfOffset($x, $y)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPoint && other.x == x && other.y == y; + } + + @override + int get hashCode => x.hashCode ^ y.hashCode; + + double distanceSquaredTo(PdfPoint other) { + final dx = x - other.x; + final dy = y - other.y; + return dx * dx + dy * dy; + } + + /// Rotate the point. + PdfPoint rotate(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfPoint(y, width - x); + case 2: + return PdfPoint(width - x, height - y); + case 3: + return PdfPoint(height - y, x); + default: + throw ArgumentError.value(rotate, 'rotate'); + } + } + + /// Rotate the point in reverse direction. + PdfPoint rotateReverse(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfPoint(width - y, x); + case 2: + return PdfPoint(width - x, height - y); + case 3: + return PdfPoint(y, height - x); + default: + throw ArgumentError.value(rotate, 'rotate'); + } + } + + /// Translate the point. + /// + /// [dx] is added to x, and [dy] is added to y. + PdfPoint translate(double dx, double dy) => PdfPoint(x + dx, y + dy); +} diff --git a/packages/pdfrx_engine/lib/src/pdf_rect.dart b/packages/pdfrx_engine/lib/src/pdf_rect.dart new file mode 100644 index 00000000..15afd534 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_rect.dart @@ -0,0 +1,185 @@ +import 'pdf_page.dart'; +import 'pdf_point.dart'; + +/// Rectangle in PDF page coordinates. +/// +/// Please note that PDF page coordinates is different from Flutter's coordinate. +/// PDF page coordinates's origin is at the bottom-left corner and Y-axis is pointing upward; +/// [bottom] is generally smaller than [top]. +/// The unit is normally in points (1/72 inch). +class PdfRect { + const PdfRect(this.left, this.top, this.right, this.bottom) + : assert(left <= right, 'Left coordinate must be less than or equal to right coordinate.'), + assert(top >= bottom, 'Top coordinate must be greater than or equal to bottom coordinate.'); + + /// Left coordinate. + final double left; + + /// Top coordinate (bigger than [bottom]). + final double top; + + /// Right coordinate. + final double right; + + /// Bottom coordinate (smaller than [top]). + final double bottom; + + /// Determine whether the rectangle is empty. + bool get isEmpty => left >= right || top <= bottom; + + /// Determine whether the rectangle is *NOT* empty. + bool get isNotEmpty => !isEmpty; + + /// Width of the rectangle. + double get width => right - left; + + /// Height of the rectangle. + double get height => top - bottom; + + /// Top-left point of the rectangle. + PdfPoint get topLeft => PdfPoint(left, top); + + /// Top-right point of the rectangle. + PdfPoint get topRight => PdfPoint(right, top); + + /// Bottom-left point of the rectangle. + PdfPoint get bottomLeft => PdfPoint(left, bottom); + + /// Bottom-right point of the rectangle. + PdfPoint get bottomRight => PdfPoint(right, bottom); + + /// Center point of the rectangle. + PdfPoint get center => PdfPoint((left + right) / 2, (top + bottom) / 2); + + /// Merge two rectangles. + PdfRect merge(PdfRect other) { + return PdfRect( + left < other.left ? left : other.left, + top > other.top ? top : other.top, + right > other.right ? right : other.right, + bottom < other.bottom ? bottom : other.bottom, + ); + } + + /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). + bool containsXy(double x, double y, {double margin = 0}) => + x >= left - margin && x <= right + margin && y >= bottom - margin && y <= top + margin; + + /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). + bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); + + double distanceSquaredTo(PdfPoint point) { + if (containsPoint(point)) { + return 0.0; // inside the rectangle + } + final dx = point.x.clamp(left, right) - point.x; + final dy = point.y.clamp(bottom, top) - point.y; + return dx * dx + dy * dy; + } + + /// Determine whether the rectangle overlaps the specified rectangle (in the PDF page coordinates). + bool overlaps(PdfRect other) { + return left < other.right && + right > other.left && + top > other.bottom && + bottom < other.top; // PDF page coordinates: top is bigger than bottom + } + + /// Empty rectangle. + static const empty = PdfRect(0, 0, 0, 0); + + /// Rotate the rectangle. + PdfRect rotate(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfRect(bottom, width - left, top, width - right); + case 2: + return PdfRect(width - right, height - bottom, width - left, height - top); + case 3: + return PdfRect(height - top, right, height - bottom, left); + default: + throw ArgumentError.value(rotation, 'rotation'); + } + } + + /// Rotate the rectangle in reverse direction. + PdfRect rotateReverse(int rotation, PdfPage page) { + final swap = (page.rotation.index & 1) == 1; + final width = swap ? page.height : page.width; + final height = swap ? page.width : page.height; + switch (rotation & 3) { + case 0: + return this; + case 1: + return PdfRect(width - top, right, width - bottom, left); + case 2: + return PdfRect(width - right, height - bottom, width - left, height - top); + case 3: + return PdfRect(bottom, height - left, top, height - right); + default: + throw ArgumentError.value(rotation, 'rotation'); + } + } + + /// Inflate (or deflate) the rectangle. + /// + /// [dx] is added to left and right, and [dy] is added to top and bottom. + PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); + + /// Translate the rectangle. + /// + /// [dx] is added to left and right, and [dy] is added to top and bottom. + PdfRect translate(double dx, double dy) => PdfRect(left + dx, top + dy, right + dx, bottom + dy); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfRect && other.left == left && other.top == top && other.right == right && other.bottom == bottom; + } + + @override + int get hashCode => left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; + + @override + String toString() { + return 'PdfRect(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +/// Extension methods for List of [PdfRect]. +extension PdfRectsExt on Iterable { + /// Calculate the bounding rectangle of the list of rectangles. + PdfRect boundingRect({int? start, int? end}) { + start ??= 0; + end ??= length; + var left = double.infinity; + var top = double.negativeInfinity; + var right = double.negativeInfinity; + var bottom = double.infinity; + for (final r in skip(start).take(end - start)) { + if (r.left < left) { + left = r.left; + } + if (r.top > top) { + top = r.top; + } + if (r.right > right) { + right = r.right; + } + if (r.bottom < bottom) { + bottom = r.bottom; + } + } + if (left == double.infinity) { + // no rects + throw StateError('No rects'); + } + return PdfRect(left, top, right, bottom); + } +} diff --git a/packages/pdfrx_engine/lib/src/pdf_text.dart b/packages/pdfrx_engine/lib/src/pdf_text.dart new file mode 100644 index 00000000..617c0715 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_text.dart @@ -0,0 +1,335 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; + +import '../pdfrx_engine.dart' show PdfPage; +import 'pdf_rect.dart'; +import 'utils/list_equals.dart'; + +/// PDF's raw text and its associated character bounding boxes. +class PdfPageRawText { + PdfPageRawText(this.fullText, this.charRects); + + /// Full text of the page. + final String fullText; + + /// Bounds corresponding to characters in the full text. + final List charRects; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageRawText && other.fullText == fullText && listEquals(other.charRects, charRects); + } + + @override + int get hashCode => fullText.hashCode ^ charRects.hashCode; +} + +/// Handles text extraction from PDF page. +/// +/// See [PdfPage.loadText]. +class PdfPageText { + const PdfPageText({ + required this.pageNumber, + required this.fullText, + required this.charRects, + required this.fragments, + }); + + /// Page number. The first page is 1. + final int pageNumber; + + /// Full text of the page. + final String fullText; + + /// Bounds corresponding to characters in the full text. + final List charRects; + + /// Get text fragments that organizes the full text structure. + /// + /// The [fullText] is the composed result of all fragments' text. + /// Any character in [fullText] must be included in one of the fragments. + final List fragments; + + /// Find text fragment index for the specified text index. + /// + /// If the specified text index is out of range, it returns -1; + /// only the exception is [textIndex] is equal to [fullText].length, + /// which means the end of the text and it returns [fragments].length. + int getFragmentIndexForTextIndex(int textIndex) { + if (textIndex == fullText.length) { + return fragments.length; // the end of the text + } + final searchIndex = PdfPageTextFragment( + pageText: this, + index: textIndex, + length: 0, + bounds: PdfRect.empty, + charRects: const [], + direction: PdfTextDirection.unknown, + ); + final index = fragments.lowerBound(searchIndex, (a, b) => a.index - b.index); + if (index > fragments.length) { + return -1; // range error + } + if (index == fragments.length) { + final f = fragments.last; + if (textIndex >= f.index + f.length) { + return -1; // range error + } + return index - 1; + } + + final f = fragments[index]; + if (textIndex < f.index) { + return index - 1; + } + return index; + } + + /// Get text fragment for the specified text index. + /// + /// If the specified text index is out of range, it returns null. + PdfPageTextFragment? getFragmentForTextIndex(int textIndex) { + final index = getFragmentIndexForTextIndex(textIndex); + if (index < 0 || index >= fragments.length) { + return null; // range error + } + return fragments[index]; + } + + /// Search text with [pattern]. + /// + /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. + /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. + Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { + final String text; + if (pattern is RegExp) { + caseInsensitive = pattern.isCaseSensitive; + text = fullText; + } else if (pattern is String) { + pattern = caseInsensitive ? pattern.toLowerCase() : pattern; + text = caseInsensitive ? fullText.toLowerCase() : fullText; + } else { + throw ArgumentError.value(pattern, 'pattern'); + } + final matches = pattern.allMatches(text); + for (final match in matches) { + if (match.start == match.end) continue; + final m = PdfPageTextRange(pageText: this, start: match.start, end: match.end); + yield m; + } + } + + /// Create a [PdfPageTextRange] from two character indices. + /// + /// Unlike [PdfPageTextRange.end], both [a] and [b] are inclusive character indices in [fullText] and + /// [a] and [b] can be in any order (e.g., [a] can be greater than [b]). + PdfPageTextRange getRangeFromAB(int a, int b) { + final min = a < b ? a : b; + final max = a < b ? b : a; + if (min < 0 || max > fullText.length) { + throw RangeError('Indices out of range: $min, $max for fullText length ${fullText.length}.'); + } + return PdfPageTextRange(pageText: this, start: min, end: max + 1); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageText && + other.pageNumber == pageNumber && + other.fullText == fullText && + listEquals(other.charRects, charRects) && + listEquals(other.fragments, fragments); + } + + @override + int get hashCode => pageNumber.hashCode ^ fullText.hashCode ^ charRects.hashCode ^ fragments.hashCode; +} + +/// Text direction in PDF page. +enum PdfTextDirection { + /// Left to Right + ltr, + + /// Right to Left + rtl, + + /// Vertical (top to bottom), Right to Left. + vrtl, + + /// Unknown direction, e.g., no text or no text direction can be determined. + unknown, +} + +/// Text fragment in PDF page. +class PdfPageTextFragment { + const PdfPageTextFragment({ + required this.pageText, + required this.index, + required this.length, + required this.bounds, + required this.charRects, + required this.direction, + }); + + /// Owner of the fragment. + final PdfPageText pageText; + + /// Fragment's index on [PdfPageText.fullText]; [text] is the substring of [PdfPageText.fullText] at [index]. + final int index; + + /// Length of the text fragment. + final int length; + + /// End index of the text fragment on [PdfPageText.fullText]. + int get end => index + length; + + /// Bounds of the text fragment in PDF page coordinates. + final PdfRect bounds; + + /// The fragment's child character bounding boxes in PDF page coordinates. + final List charRects; + + /// Text direction of the fragment. + final PdfTextDirection direction; + + /// Text for the fragment. + String get text => pageText.fullText.substring(index, index + length); + + @override + bool operator ==(covariant PdfPageTextFragment other) { + if (identical(this, other)) return true; + + return other.index == index && + other.bounds == bounds && + listEquals(other.charRects, charRects) && + other.text == text; + } + + @override + int get hashCode => index.hashCode ^ bounds.hashCode ^ text.hashCode; +} + +/// Text range in a PDF page, which is typically used to describe text selection. +class PdfPageTextRange { + /// Create a [PdfPageTextRange]. + /// + /// [start] is inclusive and [end] is exclusive. + const PdfPageTextRange({required this.pageText, required this.start, required this.end}); + + /// The page text the text range are associated with. + final PdfPageText pageText; + + /// Text start index in [PdfPageText.fullText]. + final int start; + + /// Text end index in [PdfPageText.fullText]. + final int end; + + /// Page number of the text range. + int get pageNumber => pageText.pageNumber; + + /// The composed text of the text range. + String get text => pageText.fullText.substring(start, end); + + /// The bounding rectangle of the text range in PDF page coordinates. + PdfRect get bounds => pageText.charRects.boundingRect(start: start, end: end); + + /// Get the first text fragment index corresponding to the text range. + /// + /// It can be used with [PdfPageText.fragments] to get the first text fragment in the range. + int get firstFragmentIndex => pageText.getFragmentIndexForTextIndex(start); + + /// Get the last text fragment index corresponding to the text range. + /// + /// It can be used with [PdfPageText.fragments] to get the last text fragment in the range. + int get lastFragmentIndex => pageText.getFragmentIndexForTextIndex(end - 1); + + /// Get the first text fragment in the range. + PdfPageTextFragment? get firstFragment { + final index = firstFragmentIndex; + if (index < 0 || index >= pageText.fragments.length) { + return null; // range error + } + return pageText.fragments[index]; + } + + /// Get the last text fragment in the range. + PdfPageTextFragment? get lastFragment { + final index = lastFragmentIndex; + if (index < 0 || index >= pageText.fragments.length) { + return null; // range error + } + return pageText.fragments[index]; + } + + /// Enumerate all the fragment bounding rectangles for the text range. + /// + /// The function is useful when you implement text selection algorithm or such. + Iterable enumerateFragmentBoundingRects() sync* { + final fStart = firstFragmentIndex; + final fEnd = lastFragmentIndex; + for (var i = fStart; i <= fEnd; i++) { + final f = pageText.fragments[i]; + if (f.end <= start || end <= f.index) continue; + yield PdfTextFragmentBoundingRect(f, max(start - f.index, 0), min(end - f.index, f.length)); + } + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfPageTextRange && other.pageText == pageText && other.start == start && other.end == end; + } + + @override + int get hashCode => pageText.hashCode ^ start.hashCode ^ end.hashCode; +} + +/// Bounding rectangle for a text range in a PDF page. +class PdfTextFragmentBoundingRect { + const PdfTextFragmentBoundingRect(this.fragment, this.sif, this.eif); + + /// Associated text fragment. + final PdfPageTextFragment fragment; + + /// In fragment text start index (Start-In-Fragment) + /// + /// It is the character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] + /// of the associated [fragment]. + final int sif; + + /// In fragment text end index (End-In-Fragment). + /// + /// It is the end character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] + /// of the associated [fragment]. + final int eif; + + /// Rectangle in PDF page coordinates. + PdfRect get bounds => fragment.pageText.charRects.boundingRect(start: start, end: end); + + /// Start index of the text range in page's full text. + int get start => fragment.index + sif; + + /// End index of the text range in page's full text. + int get end => fragment.index + eif; + + /// Text direction of the text range. + PdfTextDirection get direction => fragment.direction; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfTextFragmentBoundingRect && other.fragment == fragment && other.sif == sif && other.eif == eif; + } + + @override + int get hashCode => fragment.hashCode ^ sif.hashCode ^ eif.hashCode; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart new file mode 100644 index 00000000..67722d8c --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart @@ -0,0 +1,280 @@ +import 'dart:collection'; + +import 'package:vector_math/vector_math_64.dart' hide Colors; + +import './mock/string_buffer_wrapper.dart' if (dart.library.io) './native/string_buffer_wrapper.dart'; +import 'pdf_page.dart'; +import 'pdf_page_proxies.dart' show PdfPageProxy; +import 'pdf_rect.dart'; +import 'pdf_text.dart'; +import 'utils/unmodifiable_list.dart'; + +/// Text formatter to load structured text from PDF page. +/// +/// The class provides functions to load structured text with character bounding boxes. +final class PdfTextFormatter { + static final _reSpaces = RegExp(r'(\s+)', unicode: true); + static final _reNewLine = RegExp(r'\r?\n', unicode: true); + + /// Load structured text with character bounding boxes for the page. + /// + /// The function internally does test flow analysis (reading order) and line segmentation to detect + /// text direction and line breaks. + /// + /// To access the raw text, use [PdfPage.loadText]. + /// + /// This implementation is shared among multiple [PdfPage] and [PdfPageProxy] implementations. + static Future loadStructuredText(PdfPage page, {required int? pageNumberOverride}) async { + pageNumberOverride ??= page.pageNumber; + final raw = await _loadFormattedText(page); + if (raw == null) { + return PdfPageText(pageNumber: pageNumberOverride, fullText: '', charRects: [], fragments: []); + } + final inputCharRects = raw.charRects; + final inputFullText = raw.fullText; + + final fragmentsTmp = <({int length, PdfTextDirection direction})>[]; + + /// Ugly workaround for WASM+Safari StringBuffer issue (#483). + final outputText = createStringBufferForWorkaroundSafariWasm(); + final outputCharRects = []; + + PdfTextDirection vector2direction(Vector2 v) { + if (v.x.abs() > v.y.abs()) { + return v.x > 0 ? PdfTextDirection.ltr : PdfTextDirection.rtl; + } else { + return PdfTextDirection.vrtl; + } + } + + PdfTextDirection getLineDirection(int start, int end) { + if (start == end || start + 1 == end) return PdfTextDirection.unknown; + return vector2direction(inputCharRects[start].center.differenceTo(inputCharRects[end - 1].center)); + } + + void addWord( + int wordStart, + int wordEnd, + PdfTextDirection dir, + PdfRect bounds, { + bool isSpace = false, + bool isNewLine = false, + }) { + if (wordStart < wordEnd) { + final pos = outputText.length; + if (isSpace) { + if (wordStart > 0 && wordEnd < inputCharRects.length) { + // combine several spaces into one space + final a = inputCharRects[wordStart - 1]; + final b = inputCharRects[wordEnd]; + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + outputCharRects.add(PdfRect(a.right, bounds.top, a.right < b.left ? b.left : a.right, bounds.bottom)); + case PdfTextDirection.rtl: + outputCharRects.add(PdfRect(b.right, bounds.top, b.right < a.left ? a.left : b.right, bounds.bottom)); + case PdfTextDirection.vrtl: + outputCharRects.add(PdfRect(bounds.left, a.bottom, bounds.right, a.bottom > b.top ? b.top : a.bottom)); + } + outputText.write(' '); + } + } else if (isNewLine) { + if (wordStart > 0) { + // new line (\n) + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.unknown: + outputCharRects.add(PdfRect(bounds.right, bounds.top, bounds.right, bounds.bottom)); + case PdfTextDirection.rtl: + outputCharRects.add(PdfRect(bounds.left, bounds.top, bounds.left, bounds.bottom)); + case PdfTextDirection.vrtl: + outputCharRects.add(PdfRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom)); + } + outputText.write('\n'); + } + } else { + // Adjust character bounding box based on text direction. + switch (dir) { + case PdfTextDirection.ltr: + case PdfTextDirection.rtl: + case PdfTextDirection.unknown: + for (var i = wordStart; i < wordEnd; i++) { + final r = inputCharRects[i]; + outputCharRects.add(PdfRect(r.left, bounds.top, r.right, bounds.bottom)); + } + case PdfTextDirection.vrtl: + for (var i = wordStart; i < wordEnd; i++) { + final r = inputCharRects[i]; + outputCharRects.add(PdfRect(bounds.left, r.top, bounds.right, r.bottom)); + } + } + outputText.write(inputFullText.substring(wordStart, wordEnd)); + } + if (outputText.length > pos) fragmentsTmp.add((length: outputText.length - pos, direction: dir)); + } + } + + int addWords(int start, int end, PdfTextDirection dir, PdfRect bounds) { + final firstIndex = fragmentsTmp.length; + final matches = _reSpaces.allMatches(inputFullText.substring(start, end)); + var wordStart = start; + for (final match in matches) { + final spaceStart = start + match.start; + addWord(wordStart, spaceStart, dir, bounds); + wordStart = start + match.end; + addWord(spaceStart, wordStart, dir, bounds, isSpace: true); + } + addWord(wordStart, end, dir, bounds); + return fragmentsTmp.length - firstIndex; + } + + Vector2 charVec(int index, Vector2 prev) { + if (index + 1 >= inputCharRects.length) { + return prev; + } + final next = inputCharRects[index + 1]; + if (next.isEmpty) { + return prev; + } + final cur = inputCharRects[index]; + return cur.center.differenceTo(next.center); + } + + List<({int start, int end, PdfTextDirection dir})> splitLine(int start, int end) { + final list = <({int start, int end, PdfTextDirection dir})>[]; + final lineThreshold = 1.5; // radians + final last = end - 1; + var curStart = start; + var curVec = charVec(start, Vector2(1, 0)); + for (var next = start + 1; next < last;) { + final nextVec = charVec(next, curVec); + if (curVec.angleTo(nextVec) > lineThreshold) { + list.add((start: curStart, end: next + 1, dir: vector2direction(curVec))); + curStart = next + 1; + if (next + 2 == end) break; + curVec = charVec(next + 1, nextVec); + next += 2; + continue; + } + curVec += nextVec; + next++; + } + if (curStart < end) { + list.add((start: curStart, end: end, dir: vector2direction(curVec))); + } + return list; + } + + void handleLine(int start, int end, {int? newLineEnd}) { + final dir = getLineDirection(start, end); + final segments = splitLine(start, end).toList(); + if (segments.length >= 2) { + for (var i = 0; i < segments.length; i++) { + final seg = segments[i]; + final bounds = inputCharRects.boundingRect(start: seg.start, end: seg.end); + addWords(seg.start, seg.end, seg.dir, bounds); + if (i + 1 == segments.length && newLineEnd != null) { + addWord(seg.end, newLineEnd, seg.dir, bounds, isNewLine: true); + } + } + } else { + final bounds = inputCharRects.boundingRect(start: start, end: end); + addWords(start, end, dir, bounds); + if (newLineEnd != null) { + addWord(end, newLineEnd, dir, bounds, isNewLine: true); + } + } + } + + var lineStart = 0; + for (final match in _reNewLine.allMatches(inputFullText)) { + if (lineStart < match.start) { + handleLine(lineStart, match.start, newLineEnd: match.end); + } else { + final lastRect = outputCharRects.last; + outputCharRects.add(PdfRect(lastRect.left, lastRect.top, lastRect.left, lastRect.bottom)); + outputText.write('\n'); + } + lineStart = match.end; + } + if (lineStart < inputFullText.length) { + handleLine(lineStart, inputFullText.length); + } + + final fragments = []; + final text = PdfPageText( + pageNumber: pageNumberOverride, + fullText: outputText.toString(), + charRects: outputCharRects, + fragments: UnmodifiableListView(fragments), + ); + + var start = 0; + for (var i = 0; i < fragmentsTmp.length; i++) { + final length = fragmentsTmp[i].length; + final direction = fragmentsTmp[i].direction; + final end = start + length; + final fragmentRects = UnmodifiableSublist(outputCharRects, start: start, end: end); + fragments.add( + PdfPageTextFragment( + pageText: text, + index: start, + length: length, + charRects: fragmentRects, + bounds: fragmentRects.boundingRect(), + direction: direction, + ), + ); + start = end; + } + + return text; + } + + static Future _loadFormattedText(PdfPage page) async { + final input = await page.loadText(); + if (input == null) { + return null; + } + + final fullText = StringBuffer(); + final charRects = []; + + // Process the whole text + final lnMatches = _reNewLine.allMatches(input.fullText).toList(); + var lineStart = 0; + var prevEnd = 0; + for (var i = 0; i < lnMatches.length; i++) { + lineStart = prevEnd; + final match = lnMatches[i]; + fullText.write(input.fullText.substring(lineStart, match.start)); + charRects.addAll(input.charRects.sublist(lineStart, match.start)); + prevEnd = match.end; + + // Microsoft Word sometimes outputs vertical text like this: "縦\n書\nき\nの\nテ\nキ\nス\nト\nで\nす\n。\n" + // And, we want to remove these line-feeds. + if (i + 1 < lnMatches.length) { + final next = lnMatches[i + 1]; + final len = match.start - lineStart; + final nextLen = next.start - match.end; + if (len == 1 && nextLen == 1) { + final rect = input.charRects[lineStart]; + final nextRect = input.charRects[match.end]; + final nextCenterX = nextRect.center.x; + if (rect.left < nextCenterX && nextCenterX < rect.right && rect.top > nextRect.top) { + // The line is vertical, and the line-feed is virtual + continue; + } + } + } + fullText.write(input.fullText.substring(match.start, match.end)); + charRects.addAll(input.charRects.sublist(match.start, match.end)); + } + if (prevEnd < input.fullText.length) { + fullText.write(input.fullText.substring(prevEnd)); + charRects.addAll(input.charRects.sublist(prevEnd)); + } + + return PdfPageRawText(fullText.toString(), charRects); + } +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx.dart b/packages/pdfrx_engine/lib/src/pdfrx.dart new file mode 100644 index 00000000..c7140dcf --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx.dart @@ -0,0 +1,60 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; + +/// Class to provide Pdfrx's configuration. +/// The parameters should be set before calling any Pdfrx's functions. +/// +class Pdfrx { + Pdfrx._(); + + /// Explicitly specify pdfium module path for special purpose. + /// + /// It is not supported on Flutter Web. + static String? pdfiumModulePath; + + /// Font paths scanned by pdfium if supported. + /// + /// It should be set before calling any Pdfrx's functions. + /// + /// It is not supported on Flutter Web. + static final fontPaths = []; + + /// Overriding the default HTTP client for PDF download. + /// + /// It is not supported on Flutter Web. + static http.Client Function()? createHttpClient; + + /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. + static String? pdfiumWasmModulesUrl; + + /// HTTP headers to use when fetching the PDFium WASM module. + /// This is useful for authentication on protected servers. + /// Only supported on Flutter Web. + static Map? pdfiumWasmHeaders; + + /// Whether to include credentials (cookies) when fetching the PDFium WASM module. + /// This is useful for authentication on protected servers. + /// Only supported on Flutter Web. + static bool pdfiumWasmWithCredentials = false; + + /// Function to load asset data. + /// + /// This function is used to load PDF files from assets. + /// It is used to isolate pdfrx API implementation from Flutter framework. + /// + /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. + /// For Dart only, you can set this function to load assets from your own asset management system. + static Future Function(String name)? loadAsset; + + /// Function to determine the cache directory. + /// + /// You can override the default cache directory by setting this variable. + /// + /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. + /// For Dart only, you can set this function to obtain the cache directory from your own file system. + static FutureOr Function()? getCacheDirectory; + + static Map? pdfiumNativeBindings; +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_api.dart b/packages/pdfrx_engine/lib/src/pdfrx_api.dart deleted file mode 100644 index 64f08391..00000000 --- a/packages/pdfrx_engine/lib/src/pdfrx_api.dart +++ /dev/null @@ -1,2119 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -/// @docImport 'native/pdfrx_pdfium.dart'; - -/// Pdfrx API -library; - -import 'dart:async'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:collection/collection.dart'; -import 'package:http/http.dart' as http; -import 'package:vector_math/vector_math_64.dart' hide Colors; - -import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; -import './mock/string_buffer_wrapper.dart' if (dart.library.io) './native/string_buffer_wrapper.dart'; -import 'utils/unmodifiable_list.dart'; - -/// Class to provide Pdfrx's configuration. -/// The parameters should be set before calling any Pdfrx's functions. -/// -class Pdfrx { - Pdfrx._(); - - /// Explicitly specify pdfium module path for special purpose. - /// - /// It is not supported on Flutter Web. - static String? pdfiumModulePath; - - /// Font paths scanned by pdfium if supported. - /// - /// It should be set before calling any Pdfrx's functions. - /// - /// It is not supported on Flutter Web. - static final fontPaths = []; - - /// Overriding the default HTTP client for PDF download. - /// - /// It is not supported on Flutter Web. - static http.Client Function()? createHttpClient; - - /// To override the default pdfium WASM modules directory URL. It must be terminated by '/'. - static String? pdfiumWasmModulesUrl; - - /// HTTP headers to use when fetching the PDFium WASM module. - /// This is useful for authentication on protected servers. - /// Only supported on Flutter Web. - static Map? pdfiumWasmHeaders; - - /// Whether to include credentials (cookies) when fetching the PDFium WASM module. - /// This is useful for authentication on protected servers. - /// Only supported on Flutter Web. - static bool pdfiumWasmWithCredentials = false; - - /// Function to load asset data. - /// - /// This function is used to load PDF files from assets. - /// It is used to isolate pdfrx API implementation from Flutter framework. - /// - /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. - /// For Dart only, you can set this function to load assets from your own asset management system. - static Future Function(String name)? loadAsset; - - /// Function to determine the cache directory. - /// - /// You can override the default cache directory by setting this variable. - /// - /// For Flutter, `pdfrxFlutterInitialize` should be called explicitly or implicitly before using this class. - /// For Dart only, you can set this function to obtain the cache directory from your own file system. - static FutureOr Function()? getCacheDirectory; - - static Map? pdfiumNativeBindings; -} - -/// The class is used to implement Pdfrx's backend functions. -/// -/// In normal usage, you should use [PdfDocument]'s static functions to open PDF files instead of using this class directly. -/// -/// [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) provide an alternative implementation of this -/// class for Apple platforms. -abstract class PdfrxEntryFunctions { - /// Singleton instance of [PdfrxEntryFunctions]. - /// - /// [PdfDocument] internally calls this instance to open PDF files. - static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); - - /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. - /// - /// For actual apps, call `pdfrxFlutterInitialize` (for Flutter) or [pdfrxInitialize] (for Dart only) instead of this function. - Future init(); - - /// This function blocks pdfrx internally calls PDFium functions during the execution of [action]. - /// - /// Because PDFium is not thread-safe, if your app is calling some other libraries that potentially calls PDFium - /// functions, pdfrx may interfere with those calls and cause crashes or data corruption. - /// To avoid such problems, you can wrap the code that calls those libraries with this function. - Future suspendPdfiumWorkerDuringAction(FutureOr Function() action); - - /// See [PdfDocument.openAsset]. - Future openAsset( - String name, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }); - - /// See [PdfDocument.openData]. - Future openData( - Uint8List data, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - String? sourceName, - bool allowDataOwnershipTransfer = false, // only for Web - bool useProgressiveLoading = false, - void Function()? onDispose, - }); - - /// See [PdfDocument.openFile]. - Future openFile( - String filePath, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }); - - /// See [PdfDocument.openCustom]. - Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, - required int fileSize, - required String sourceName, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - int? maxSizeToCacheOnMemory, - void Function()? onDispose, - }); - - /// See [PdfDocument.openUri]. - Future openUri( - Uri uri, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - PdfDownloadProgressCallback? progressCallback, - bool preferRangeAccess = false, - Map? headers, - bool withCredentials = false, - Duration? timeout, - }); - - /// See [PdfDocument.createNew]. - Future createNew({required String sourceName}); - - /// Reload the fonts. - Future reloadFonts(); - - /// Add font data to font cache. - /// - /// For Web platform, this is the only way to add custom fonts (the fonts are cached on memory). - /// - /// For other platforms, the font data is cached on temporary files in the cache directory; if you want to keep - /// the font data permanently, you should save the font data to some other persistent storage and set its path - /// to [Pdfrx.fontPaths]. - Future addFontData({required String face, required Uint8List data}); - - /// Clear all font data added by [addFontData]. - Future clearAllFontData(); - - /// Backend in use. - PdfrxBackend get backend; -} - -/// Pdfrx backend types. -enum PdfrxBackend { - /// PDFium backend. - pdfium, - - /// PDFium WebAssembly backend for Web platform. - /// - /// The implementation for this is provided by [pdfrx](https://pub.dev/packages/pdfrx) package. - pdfiumWasm, - - /// pdfKit (CoreGraphics) backend for Apple platforms. - /// - /// The implementation for this is provided by [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) package. - pdfKit, - - /// Mock backend for internal consistency. - mock, - - /// Unknown backend. - unknown, -} - -/// 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. -/// It is repeatedly called until the function returns null or a valid password. -/// -/// [createSimplePasswordProvider] is a helper function to create [PdfPasswordProvider] that returns the password -/// only once. -typedef PdfPasswordProvider = FutureOr Function(); - -/// Create [PdfPasswordProvider] that returns the password only once. -/// -/// The returned [PdfPasswordProvider] returns the password only once and returns null afterwards. -/// If [password] is null, the returned [PdfPasswordProvider] returns null always. -PdfPasswordProvider createSimplePasswordProvider(String? password) { - return () { - final ret = password; - password = null; - return ret; - }; -} - -/// Handles PDF document loaded on memory. -abstract class PdfDocument { - /// Constructor to force initialization of sourceName. - PdfDocument({required this.sourceName}); - - /// File path, `asset:[ASSET_PATH]` or `memory:` depending on the content opened. - final String sourceName; - - /// Permission flags. - PdfPermissions? get permissions; - - /// Determine whether the PDF file is encrypted or not. - bool get isEncrypted; - - /// PdfDocument must have [dispose] function. - Future dispose(); - - /// Stream to notify change events in the document. - Stream get events; - - /// Opening the specified file. - /// For Web, [filePath] can be relative path from `index.html` or any arbitrary URL but it may be restricted by CORS. - /// - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty - /// password or not. For more info, see [PdfPasswordProvider]. - /// - /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages - /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. - static Future openFile( - String filePath, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }) => PdfrxEntryFunctions.instance.openFile( - filePath, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - ); - - /// Opening the specified asset. - /// - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty - /// password or not. For more info, see [PdfPasswordProvider]. - /// - /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages - /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. - static Future openAsset( - String name, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - }) => PdfrxEntryFunctions.instance.openAsset( - name, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - ); - - /// Opening the PDF on memory. - /// - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password - /// or not. For more info, see [PdfPasswordProvider]. - /// - /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages - /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. - /// - /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not - /// unique for each source, the viewer may not work correctly. - /// - /// Web only: [allowDataOwnershipTransfer] is used to determine if the data buffer can be transferred to - /// the worker thread. - static Future openData( - Uint8List data, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - String? sourceName, - bool allowDataOwnershipTransfer = false, - void Function()? onDispose, - }) => PdfrxEntryFunctions.instance.openData( - data, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - sourceName: sourceName, - allowDataOwnershipTransfer: allowDataOwnershipTransfer, - onDispose: onDispose, - ); - - /// Creating a new empty PDF document. - /// - /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not - /// unique for each source, the viewer may not work correctly. - static Future createNew({required String sourceName}) => - PdfrxEntryFunctions.instance.createNew(sourceName: sourceName); - - /// Opening the PDF from custom source. - /// - /// On Flutter Web, this function is not supported and throws an exception. - /// It is also not supported if pdfrx is running without libpdfrx (**typically on Dart only**). - /// - /// [maxSizeToCacheOnMemory] is the maximum size of the PDF to cache on memory in bytes; the custom loading process - /// may be heavy because of FFI overhead and it may be better to cache the PDF on memory if it's not too large. - /// The default size is 1MB. - /// - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty - /// password or not. For more info, see [PdfPasswordProvider]. - /// - /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages - /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. - /// - /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not - /// unique for each source, the viewer may not work correctly. - static Future openCustom({ - required FutureOr Function(Uint8List buffer, int position, int size) read, - required int fileSize, - required String sourceName, - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - int? maxSizeToCacheOnMemory, - void Function()? onDispose, - }) => PdfrxEntryFunctions.instance.openCustom( - read: read, - fileSize: fileSize, - sourceName: sourceName, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, - onDispose: onDispose, - ); - - /// Opening the PDF from URI. - /// - /// 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. - /// - /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. - /// - /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty - /// password or not. For more info, see [PdfPasswordProvider]. - /// - /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages - /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. - /// - /// [progressCallback] is called when the download progress is updated. - /// - /// [preferRangeAccess] to prefer range access to download the PDF. The default is false (Not supported on Web). - /// It is not supported if pdfrx is running without libpdfrx (**typically on Dart only**). - /// - /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. - /// - /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). - /// - /// [timeout] is used to specify the timeout duration for each HTTP request (Only supported on non-Web platforms). - static Future openUri( - Uri uri, { - PdfPasswordProvider? passwordProvider, - bool firstAttemptByEmptyPassword = true, - bool useProgressiveLoading = false, - PdfDownloadProgressCallback? progressCallback, - bool preferRangeAccess = false, - Map? headers, - bool withCredentials = false, - Duration? timeout, - }) => PdfrxEntryFunctions.instance.openUri( - uri, - passwordProvider: passwordProvider, - firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, - useProgressiveLoading: useProgressiveLoading, - progressCallback: progressCallback, - preferRangeAccess: preferRangeAccess, - headers: headers, - withCredentials: withCredentials, - timeout: timeout, - ); - - /// Load pages progressively. - /// - /// This function loads pages progressively if the pages are not loaded yet. - /// It calls [onPageLoadProgress] for each [loadUnitDuration] duration until all pages are loaded or the loading - /// is cancelled. - /// When [onPageLoadProgress] is called, it should return true to continue loading process or false to stop loading. - /// [data] is an optional data that can be used to pass additional information to the callback. - /// - /// It's always safe to call this function even if the pages are already loaded. - Future loadPagesProgressively({ - PdfPageLoadingCallback? onPageLoadProgress, - T? data, - Duration loadUnitDuration = const Duration(milliseconds: 250), - }); - - /// Pages. - /// - /// The list is unmodifiable; you cannot add, remove, or replace pages directly. - /// To modify the pages, use [pages] setter to set a new list of pages. - List get pages; - - /// Set pages. - /// - /// You can add [PdfPage] instances from any [PdfDocument] instances and the resulting document works correctly - /// if the referenced [PdfDocument] instances are alive; it's your responsibility to manage the lifetime of those - /// instances. To make the document independent from the source documents, you should call [assemble] after setting - /// the pages. - set pages(List value); - - /// Load outline (a.k.a. bookmark). - Future> loadOutline(); - - /// Determine whether document handles are identical or not. - /// - /// It does not mean the document contents (or the document files) are identical. - bool isIdenticalDocumentHandle(Object? other); - - /// Assemble the document after modifying pages. - /// - /// You should call this function after modifying [pages] to make the document consistent and independent from - /// the other source documents. If [pages] contains pages from other documents, those documents must be alive - /// until this function returns. - Future assemble(); - - /// Save the PDF document. - /// - /// This function internally calls [assemble] before encoding the PDF. - Future encodePdf({bool incremental = false, bool removeSecurity = false}); -} - -typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); - -/// PDF document event types. -enum PdfDocumentEventType { - /// [PdfDocumentPageStatusChangedEvent]: Page status changed. - pageStatusChanged, - missingFonts, // [PdfDocumentMissingFontsEvent]: Missing fonts changed. -} - -/// Base class for PDF document events. -abstract class PdfDocumentEvent { - /// Event type. - PdfDocumentEventType get type; - - /// Document that this event is related to. - PdfDocument get document; -} - -/// Event that is triggered when the status of PDF document pages has changed. -class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { - PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); - - @override - PdfDocumentEventType get type => PdfDocumentEventType.pageStatusChanged; - - @override - final PdfDocument document; - - /// The pages that have changed. - /// - /// The map is from page number (1-based) to it's status change. - final Map changes; -} - -/// Base class for PDF page status change. -abstract class PdfPageStatusChange { - const PdfPageStatusChange(); - - /// Create [PdfPageStatusMoved]. - static PdfPageStatusChange moved({required int oldPageNumber}) => PdfPageStatusMoved(oldPageNumber: oldPageNumber); - - /// Return [PdfPageStatusModified]. - static const modified = PdfPageStatusModified(); -} - -/// Event that is triggered when a PDF page is moved inside the same document. -class PdfPageStatusMoved extends PdfPageStatusChange { - const PdfPageStatusMoved({required this.oldPageNumber}); - final int oldPageNumber; - - @override - int get hashCode => oldPageNumber.hashCode; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is PdfPageStatusMoved && other.oldPageNumber == oldPageNumber; - } - - @override - String toString() => 'PdfPageStatusMoved(oldPageNumber: $oldPageNumber)'; -} - -/// Event that is triggered when a PDF page is modified or newly added. -class PdfPageStatusModified extends PdfPageStatusChange { - const PdfPageStatusModified(); - - @override - int get hashCode => 0; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is PdfPageStatusModified; - } - - @override - String toString() => 'PdfPageStatusModified()'; -} - -/// Event that is triggered when the list of missing fonts in the PDF document has changed. -class PdfDocumentMissingFontsEvent implements PdfDocumentEvent { - /// Create a [PdfDocumentMissingFontsEvent]. - PdfDocumentMissingFontsEvent(this.document, this.missingFonts); - - @override - PdfDocumentEventType get type => PdfDocumentEventType.missingFonts; - - @override - final PdfDocument document; - - /// The list of missing fonts. - final List missingFonts; -} - -/// Handles a PDF page in [PdfDocument]. -/// -/// See [PdfDocument.pages]. -abstract class PdfPage { - /// PDF document. - PdfDocument get document; - - /// Page number. The first page is 1. - int get pageNumber; - - /// PDF page width in points (width in pixels at 72 dpi) (rotated). - double get width; - - /// PDF page height in points (height in pixels at 72 dpi) (rotated). - double get height; - - /// PDF page rotation. - PdfPageRotation get rotation; - - /// Whether the page is really loaded or not. - /// - /// If the value is false, the page's [width], [height], and [rotation] are just guessed values and - /// will be updated when the page is really loaded. - bool get isLoaded; - - /// Render a sub-area or full image of specified PDF file. - /// Returned image should be disposed after use. - /// [x], [y], [width], [height] specify sub-area to render in pixels. - /// [fullWidth], [fullHeight] specify virtual full size of the page to render in pixels. - /// - If [x], [y] are not specified, (0,0) is used. - /// - If [width], [height] are not specified, [fullWidth], [fullHeight] are used. - /// - If [fullWidth], [fullHeight] are not specified, [PdfPage.width] and [PdfPage.height] are used (it means rendered at 72-dpi). - /// [backgroundColor] is `AARRGGBB` integer color notation used to fill the background of the page. If no color is specified, 0xffffffff (white) is used. - /// - [annotationRenderingMode] controls to render annotations or not. The default is [PdfAnnotationRenderingMode.annotationAndForms]. - /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderFlags.none]. - /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. - /// - /// The following code extract the area of (20,30)-(120,130) from the page image rendered at 1000x1500 pixels: - /// ```dart - /// final image = await page.render( - /// x: 20, - /// y: 30, - /// width: 100, - /// height: 100, - /// fullWidth: 1000, - /// fullHeight: 1500, - /// ); - /// ``` - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - int? backgroundColor, - PdfPageRotation? rotationOverride, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }); - - /// Create [PdfPageRenderCancellationToken] to cancel the rendering process. - PdfPageRenderCancellationToken createCancellationToken(); - - static final _reSpaces = RegExp(r'(\s+)', unicode: true); - static final _reNewLine = RegExp(r'\r?\n', unicode: true); - - /// Load structured text with character bounding boxes. - /// - /// The function internally does test flow analysis (reading order) and line segmentation to detect - /// text direction and line breaks. - /// - /// To access the raw text, use [loadText]. - Future loadStructuredText() async { - final raw = await _loadFormattedText(); - if (raw == null) { - return PdfPageText(pageNumber: pageNumber, fullText: '', charRects: [], fragments: []); - } - final inputCharRects = raw.charRects; - final inputFullText = raw.fullText; - - final fragmentsTmp = <({int length, PdfTextDirection direction})>[]; - - /// Ugly workaround for WASM+Safari StringBuffer issue (#483). - final outputText = createStringBufferForWorkaroundSafariWasm(); - final outputCharRects = []; - - PdfTextDirection vector2direction(Vector2 v) { - if (v.x.abs() > v.y.abs()) { - return v.x > 0 ? PdfTextDirection.ltr : PdfTextDirection.rtl; - } else { - return PdfTextDirection.vrtl; - } - } - - PdfTextDirection getLineDirection(int start, int end) { - if (start == end || start + 1 == end) return PdfTextDirection.unknown; - return vector2direction(inputCharRects[start].center.differenceTo(inputCharRects[end - 1].center)); - } - - void addWord( - int wordStart, - int wordEnd, - PdfTextDirection dir, - PdfRect bounds, { - bool isSpace = false, - bool isNewLine = false, - }) { - if (wordStart < wordEnd) { - final pos = outputText.length; - if (isSpace) { - if (wordStart > 0 && wordEnd < inputCharRects.length) { - // combine several spaces into one space - final a = inputCharRects[wordStart - 1]; - final b = inputCharRects[wordEnd]; - switch (dir) { - case PdfTextDirection.ltr: - case PdfTextDirection.unknown: - outputCharRects.add(PdfRect(a.right, bounds.top, a.right < b.left ? b.left : a.right, bounds.bottom)); - case PdfTextDirection.rtl: - outputCharRects.add(PdfRect(b.right, bounds.top, b.right < a.left ? a.left : b.right, bounds.bottom)); - case PdfTextDirection.vrtl: - outputCharRects.add(PdfRect(bounds.left, a.bottom, bounds.right, a.bottom > b.top ? b.top : a.bottom)); - } - outputText.write(' '); - } - } else if (isNewLine) { - if (wordStart > 0) { - // new line (\n) - switch (dir) { - case PdfTextDirection.ltr: - case PdfTextDirection.unknown: - outputCharRects.add(PdfRect(bounds.right, bounds.top, bounds.right, bounds.bottom)); - case PdfTextDirection.rtl: - outputCharRects.add(PdfRect(bounds.left, bounds.top, bounds.left, bounds.bottom)); - case PdfTextDirection.vrtl: - outputCharRects.add(PdfRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom)); - } - outputText.write('\n'); - } - } else { - // Adjust character bounding box based on text direction. - switch (dir) { - case PdfTextDirection.ltr: - case PdfTextDirection.rtl: - case PdfTextDirection.unknown: - for (var i = wordStart; i < wordEnd; i++) { - final r = inputCharRects[i]; - outputCharRects.add(PdfRect(r.left, bounds.top, r.right, bounds.bottom)); - } - case PdfTextDirection.vrtl: - for (var i = wordStart; i < wordEnd; i++) { - final r = inputCharRects[i]; - outputCharRects.add(PdfRect(bounds.left, r.top, bounds.right, r.bottom)); - } - } - outputText.write(inputFullText.substring(wordStart, wordEnd)); - } - if (outputText.length > pos) fragmentsTmp.add((length: outputText.length - pos, direction: dir)); - } - } - - int addWords(int start, int end, PdfTextDirection dir, PdfRect bounds) { - final firstIndex = fragmentsTmp.length; - final matches = _reSpaces.allMatches(inputFullText.substring(start, end)); - var wordStart = start; - for (final match in matches) { - final spaceStart = start + match.start; - addWord(wordStart, spaceStart, dir, bounds); - wordStart = start + match.end; - addWord(spaceStart, wordStart, dir, bounds, isSpace: true); - } - addWord(wordStart, end, dir, bounds); - return fragmentsTmp.length - firstIndex; - } - - Vector2 charVec(int index, Vector2 prev) { - if (index + 1 >= inputCharRects.length) { - return prev; - } - final next = inputCharRects[index + 1]; - if (next.isEmpty) { - return prev; - } - final cur = inputCharRects[index]; - return cur.center.differenceTo(next.center); - } - - List<({int start, int end, PdfTextDirection dir})> splitLine(int start, int end) { - final list = <({int start, int end, PdfTextDirection dir})>[]; - final lineThreshold = 1.5; // radians - final last = end - 1; - var curStart = start; - var curVec = charVec(start, Vector2(1, 0)); - for (var next = start + 1; next < last;) { - final nextVec = charVec(next, curVec); - if (curVec.angleTo(nextVec) > lineThreshold) { - list.add((start: curStart, end: next + 1, dir: vector2direction(curVec))); - curStart = next + 1; - if (next + 2 == end) break; - curVec = charVec(next + 1, nextVec); - next += 2; - continue; - } - curVec += nextVec; - next++; - } - if (curStart < end) { - list.add((start: curStart, end: end, dir: vector2direction(curVec))); - } - return list; - } - - void handleLine(int start, int end, {int? newLineEnd}) { - final dir = getLineDirection(start, end); - final segments = splitLine(start, end).toList(); - if (segments.length >= 2) { - for (var i = 0; i < segments.length; i++) { - final seg = segments[i]; - final bounds = inputCharRects.boundingRect(start: seg.start, end: seg.end); - addWords(seg.start, seg.end, seg.dir, bounds); - if (i + 1 == segments.length && newLineEnd != null) { - addWord(seg.end, newLineEnd, seg.dir, bounds, isNewLine: true); - } - } - } else { - final bounds = inputCharRects.boundingRect(start: start, end: end); - addWords(start, end, dir, bounds); - if (newLineEnd != null) { - addWord(end, newLineEnd, dir, bounds, isNewLine: true); - } - } - } - - var lineStart = 0; - for (final match in _reNewLine.allMatches(inputFullText)) { - if (lineStart < match.start) { - handleLine(lineStart, match.start, newLineEnd: match.end); - } else { - final lastRect = outputCharRects.last; - outputCharRects.add(PdfRect(lastRect.left, lastRect.top, lastRect.left, lastRect.bottom)); - outputText.write('\n'); - } - lineStart = match.end; - } - if (lineStart < inputFullText.length) { - handleLine(lineStart, inputFullText.length); - } - - final fragments = []; - final text = PdfPageText( - pageNumber: pageNumber, - fullText: outputText.toString(), - charRects: outputCharRects, - fragments: UnmodifiableListView(fragments), - ); - - var start = 0; - for (var i = 0; i < fragmentsTmp.length; i++) { - final length = fragmentsTmp[i].length; - final direction = fragmentsTmp[i].direction; - final end = start + length; - final fragmentRects = UnmodifiableSublist(outputCharRects, start: start, end: end); - fragments.add( - PdfPageTextFragment( - pageText: text, - index: start, - length: length, - charRects: fragmentRects, - bounds: fragmentRects.boundingRect(), - direction: direction, - ), - ); - start = end; - } - - return text; - } - - Future _loadFormattedText() async { - final input = await loadText(); - if (input == null) { - return null; - } - - final fullText = StringBuffer(); - final charRects = []; - - // Process the whole text - final lnMatches = _reNewLine.allMatches(input.fullText).toList(); - var lineStart = 0; - var prevEnd = 0; - for (var i = 0; i < lnMatches.length; i++) { - lineStart = prevEnd; - final match = lnMatches[i]; - fullText.write(input.fullText.substring(lineStart, match.start)); - charRects.addAll(input.charRects.sublist(lineStart, match.start)); - prevEnd = match.end; - - // Microsoft Word sometimes outputs vertical text like this: "縦\n書\nき\nの\nテ\nキ\nス\nト\nで\nす\n。\n" - // And, we want to remove these line-feeds. - if (i + 1 < lnMatches.length) { - final next = lnMatches[i + 1]; - final len = match.start - lineStart; - final nextLen = next.start - match.end; - if (len == 1 && nextLen == 1) { - final rect = input.charRects[lineStart]; - final nextRect = input.charRects[match.end]; - final nextCenterX = nextRect.center.x; - if (rect.left < nextCenterX && nextCenterX < rect.right && rect.top > nextRect.top) { - // The line is vertical, and the line-feed is virtual - continue; - } - } - } - fullText.write(input.fullText.substring(match.start, match.end)); - charRects.addAll(input.charRects.sublist(match.start, match.end)); - } - if (prevEnd < input.fullText.length) { - fullText.write(input.fullText.substring(prevEnd)); - charRects.addAll(input.charRects.sublist(prevEnd)); - } - - return PdfPageRawText(fullText.toString(), charRects); - } - - /// Load plain text for the page. - /// - /// For text with character bounding boxes, use [loadStructuredText]. - Future loadText(); - - /// Load links. - /// - /// If [compact] is true, it tries to reduce memory usage by compacting the link data. - /// See [PdfLink.compact] for more info. - /// - /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. - /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. - /// The default is true. - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); -} - -/// Extension to add rotation capability to [PdfPage]. -/// -/// Use these functions to create rotated pages when reorganizing or combining PDFs. -/// -/// The following example shows how to fix page orientations: -/// -/// ```dart -/// final doc = await PdfDocument.openFile('document.pdf'); -/// doc.pages = [ -/// doc.pages[0], -/// doc.pages[1].rotatedTo(PdfPageRotation.clockwise90), -/// doc.pages[2].rotatedBy(PdfPageRotation.clockwise90), -/// doc.pages[3].rotatedCW90(), -/// ]; -/// await File('fixed.pdf').writeAsBytes(await doc.encodePdf()); -/// ``` -extension PdfPageWithRotationExtension on PdfPage { - /// Rotates a page with the specified rotation. - /// - /// See usage example in [PdfPageWithRotationExtension]. - PdfPage rotatedTo(PdfPageRotation rotation) { - if (rotation == this.rotation) { - return this; // No rotation change needed - } - return PdfPageRotated(this, rotation); - } - - /// Rotates a page with rotation added to the current rotation. - /// - /// See usage example in [PdfPageWithRotationExtension]. - PdfPage rotatedBy(PdfPageRotation delta) { - final newRotation = PdfPageRotation.values[(rotation.index + delta.index) & 3]; - return rotatedTo(newRotation); - } - - /// Rotates a page clockwise by 90 degrees. - /// - /// See usage example in [PdfPageWithRotationExtension]. - PdfPage rotatedCW90() => rotatedBy(PdfPageRotation.clockwise90); - - /// Rotates a page counter-clockwise by 90 degrees. - /// - /// See usage example in [PdfPageWithRotationExtension]. - PdfPage rotatedCCW90() => rotatedBy(PdfPageRotation.clockwise270); - - /// Rotates a page clockwise by 180 degrees. - /// - /// Returns newly created [PdfPageProxy] that applies the rotation. - PdfPage rotated180() => rotatedBy(PdfPageRotation.clockwise180); -} - -/// Extension to add page renumbering capability to [PdfPage]. -/// -/// This is used internally when assembling documents, but can also be used manually. -extension PdfPageRenumberedExtension on PdfPage { - /// Renumbers a page with the specified page number. - /// - /// See usage example in [PdfPageRenumberedExtension]. - PdfPage withPageNumber(int pageNumber) { - if (pageNumber == this.pageNumber) { - return this; // No page number change needed - } - return PdfPageRenumbered(this, pageNumber: pageNumber); - } -} - -/// Proxy interface for [PdfPage]. -/// -/// Used for creating proxy pages that modify behavior of the base page. -abstract class PdfPageProxy implements PdfPage { - PdfPage get basePage; -} - -/// Extension to unwrap [PdfPageProxy] on [PdfPage]. -extension PdfPageProxyExtension on PdfPage { - /// Unwrap the page to get the base page of type [T]. - /// - /// If the base page of type [T] is not found, returns null. - T? unwrap() { - final pThis = this; - if (pThis is T) return pThis; - if (pThis is PdfPageProxy) return pThis.basePage.unwrap(); - return null; - } - - /// Unwrap the page until [stopCondition] is met. - /// - /// If the condition is met, returns null. - PdfPage? unwrapUntil(bool Function(PdfPage page) stopCondition) { - var current = this; - while (true) { - if (stopCondition(current)) return current; - if (current is PdfPageProxy) { - current = current.basePage; - } else { - return null; - } - } - } -} - -/// PDF page wrapper that renumbers the page number. -class PdfPageRenumbered implements PdfPageProxy { - PdfPageRenumbered(PdfPage basePage, {required this.pageNumber}) - : basePage = basePage.unwrapUntil((p) => p is! PdfPageRenumbered)!; - - @override - final PdfPage basePage; - - @override - final int pageNumber; - - @override - PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); - - @override - PdfDocument get document => basePage.document; - - @override - PdfPageRotation get rotation => basePage.rotation; - - @override - double get width => basePage.width; - - @override - double get height => basePage.height; - - @override - bool get isLoaded => basePage.isLoaded; - - @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); - - @override - Future loadText() => basePage.loadText(); - - @override - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - int? backgroundColor, - PdfPageRotation? rotationOverride, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }) => basePage.render( - x: x, - y: y, - width: width, - height: height, - fullWidth: fullWidth, - fullHeight: fullHeight, - backgroundColor: backgroundColor, - annotationRenderingMode: annotationRenderingMode, - flags: flags, - cancellationToken: cancellationToken, - rotationOverride: rotationOverride, - ); - - @override - Future loadStructuredText() => basePage.loadStructuredText(); - - @override - Future _loadFormattedText() => basePage._loadFormattedText(); -} - -/// PDF page wrapper that applies an absolute rotation to the base page. -class PdfPageRotated implements PdfPageProxy { - PdfPageRotated(PdfPage basePage, this.rotation) : basePage = basePage.unwrapUntil((p) => p is! PdfPageRotated)!; - - @override - final PdfPage basePage; - - // Override rotation to return the effective rotation - @override - final PdfPageRotation rotation; - - /// Check if dimensions need to be swapped (for 90° or 270° rotations). - /// This is relative to the source page's rotation. - bool get _swapWH => shouldSwapWH(rotation); - - bool shouldSwapWH(PdfPageRotation rotation) => ((rotation.index - basePage.rotation.index) & 1) == 1; - - // Delegate basic properties - @override - PdfDocument get document => basePage.document; - - @override - int get pageNumber => basePage.pageNumber; - - @override - bool get isLoaded => basePage.isLoaded; - - // Swap width/height if additional rotation is 90° or 270° - @override - double get width => _swapWH ? basePage.height : basePage.width; - - @override - double get height => _swapWH ? basePage.width : basePage.height; - - // Override render to pass the effective rotation as rotationOverride - @override - Future render({ - int x = 0, - int y = 0, - int? width, - int? height, - double? fullWidth, - double? fullHeight, - int? backgroundColor, - PdfPageRotation? rotationOverride, - PdfAnnotationRenderingMode annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, - int flags = PdfPageRenderFlags.none, - PdfPageRenderCancellationToken? cancellationToken, - }) { - return basePage.render( - x: x, - y: y, - width: width, - height: height, - fullWidth: fullWidth, - fullHeight: fullHeight, - backgroundColor: backgroundColor, - rotationOverride: rotationOverride ?? rotation, - annotationRenderingMode: annotationRenderingMode, - flags: flags, - cancellationToken: cancellationToken, - ); - } - - // All other methods just delegate - text/links work correctly because they use `rotation` property - @override - PdfPageRenderCancellationToken createCancellationToken() => basePage.createCancellationToken(); - - @override - Future loadText() => basePage.loadText(); - - @override - Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => - basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); - - // Text methods don't depend on rotation - just delegate to source - @override - Future loadStructuredText() => basePage.loadStructuredText(); - - @override - Future _loadFormattedText() => basePage._loadFormattedText(); -} - -/// PDF's raw text and its associated character bounding boxes. -class PdfPageRawText { - PdfPageRawText(this.fullText, this.charRects); - - /// Full text of the page. - final String fullText; - - /// Bounds corresponding to characters in the full text. - final List charRects; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfPageRawText && other.fullText == fullText && _listEquals(other.charRects, charRects); - } - - @override - int get hashCode => fullText.hashCode ^ charRects.hashCode; -} - -/// Page rotation. -enum PdfPageRotation { none, clockwise90, clockwise180, clockwise270 } - -extension PdfPageRotationEnumExtension on PdfPageRotation { - /// Get counter-clockwise 90 degree rotation value from the current rotation. - PdfPageRotation get rotateCCW90 => PdfPageRotation.values[(index + 3) % 4]; - - /// Get clockwise 90 degree rotation value from the current rotation. - PdfPageRotation get rotateCW90 => PdfPageRotation.values[(index + 1) % 4]; - - /// Get 180 degree rotation value from the current rotation. - PdfPageRotation get rotate180 => PdfPageRotation.values[(index + 2) % 4]; - - /// Add two rotations. - PdfPageRotation operator +(PdfPageRotation other) => PdfPageRotation.values[(index + other.index) % 4]; -} - -/// Annotation rendering mode. -enum PdfAnnotationRenderingMode { - /// Do not render annotations. - none, - - /// Render annotations. - annotation, - - /// Render annotations and forms. - annotationAndForms, -} - -/// Flags for [PdfPage.render]. -/// -/// Basically, they are PDFium's `FPDF_RENDER_*` flags and not supported on PDF.js. -abstract class PdfPageRenderFlags { - /// None. - static const none = 0; - - /// `FPDF_LCD_TEXT` flag. - static const lcdText = 0x0002; - - /// `FPDF_GRAYSCALE` flag. - static const grayscale = 0x0008; - - /// `FPDF_RENDER_LIMITEDIMAGECACHE` flag. - static const limitedImageCache = 0x0200; - - /// `FPDF_RENDER_FORCEHALFTONE` flag. - static const forceHalftone = 0x0400; - - /// `FPDF_PRINTING` flag. - static const printing = 0x0800; - - /// `FPDF_RENDER_NO_SMOOTHTEXT` flag. - static const noSmoothText = 0x1000; - - /// `FPDF_RENDER_NO_SMOOTHIMAGE` flag. - static const noSmoothImage = 0x2000; - - /// `FPDF_RENDER_NO_SMOOTHPATH` flag. - static const noSmoothPath = 0x4000; - - /// Output image is in premultiplied alpha format. - static const premultipliedAlpha = 0x80000000; -} - -/// Token to try to cancel the rendering process. -abstract class PdfPageRenderCancellationToken { - /// Cancel the rendering process. - void cancel(); - - /// Determine whether the rendering process is canceled or not. - bool get isCanceled; -} - -/// PDF permissions defined on PDF 32000-1:2008, Table 22. -class PdfPermissions { - const PdfPermissions(this.permissions, this.securityHandlerRevision); - - /// User access permissions on on PDF 32000-1:2008, Table 22. - final int permissions; - - /// Security handler revision. - final int securityHandlerRevision; - - /// Determine whether the PDF file allows copying of the contents. - bool get allowsCopying => (permissions & 4) != 0; - - /// Determine whether the PDF file allows document assembly. - bool get allowsDocumentAssembly => (permissions & 8) != 0; - - /// Determine whether the PDF file allows printing of the pages. - bool get allowsPrinting => (permissions & 16) != 0; - - /// Determine whether the PDF file allows modifying annotations, form fields, and their associated - bool get allowsModifyAnnotations => (permissions & 32) != 0; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfPermissions && - other.permissions == permissions && - other.securityHandlerRevision == securityHandlerRevision; - } - - @override - int get hashCode => permissions.hashCode ^ securityHandlerRevision.hashCode; -} - -/// Image rendered from PDF page. -/// -/// See [PdfPage.render]. -abstract class PdfImage { - /// Number of pixels in horizontal direction. - int get width; - - /// Number of pixels in vertical direction. - int get height; - - /// BGRA8888 Raw pixel data. - Uint8List get pixels; - - /// Dispose the image. - void dispose(); -} - -/// Handles text extraction from PDF page. -/// -/// See [PdfPage.loadText]. -class PdfPageText { - const PdfPageText({ - required this.pageNumber, - required this.fullText, - required this.charRects, - required this.fragments, - }); - - /// Page number. The first page is 1. - final int pageNumber; - - /// Full text of the page. - final String fullText; - - /// Bounds corresponding to characters in the full text. - final List charRects; - - /// Get text fragments that organizes the full text structure. - /// - /// The [fullText] is the composed result of all fragments' text. - /// Any character in [fullText] must be included in one of the fragments. - final List fragments; - - /// Find text fragment index for the specified text index. - /// - /// If the specified text index is out of range, it returns -1; - /// only the exception is [textIndex] is equal to [fullText].length, - /// which means the end of the text and it returns [fragments].length. - int getFragmentIndexForTextIndex(int textIndex) { - if (textIndex == fullText.length) { - return fragments.length; // the end of the text - } - final searchIndex = PdfPageTextFragment( - pageText: this, - index: textIndex, - length: 0, - bounds: PdfRect.empty, - charRects: const [], - direction: PdfTextDirection.unknown, - ); - final index = fragments.lowerBound(searchIndex, (a, b) => a.index - b.index); - if (index > fragments.length) { - return -1; // range error - } - if (index == fragments.length) { - final f = fragments.last; - if (textIndex >= f.index + f.length) { - return -1; // range error - } - return index - 1; - } - - final f = fragments[index]; - if (textIndex < f.index) { - return index - 1; - } - return index; - } - - /// Get text fragment for the specified text index. - /// - /// If the specified text index is out of range, it returns null. - PdfPageTextFragment? getFragmentForTextIndex(int textIndex) { - final index = getFragmentIndexForTextIndex(textIndex); - if (index < 0 || index >= fragments.length) { - return null; // range error - } - return fragments[index]; - } - - /// Search text with [pattern]. - /// - /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. - /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { - final String text; - if (pattern is RegExp) { - caseInsensitive = pattern.isCaseSensitive; - text = fullText; - } else if (pattern is String) { - pattern = caseInsensitive ? pattern.toLowerCase() : pattern; - text = caseInsensitive ? fullText.toLowerCase() : fullText; - } else { - throw ArgumentError.value(pattern, 'pattern'); - } - final matches = pattern.allMatches(text); - for (final match in matches) { - if (match.start == match.end) continue; - final m = PdfPageTextRange(pageText: this, start: match.start, end: match.end); - yield m; - } - } - - /// Create a [PdfPageTextRange] from two character indices. - /// - /// Unlike [PdfPageTextRange.end], both [a] and [b] are inclusive character indices in [fullText] and - /// [a] and [b] can be in any order (e.g., [a] can be greater than [b]). - PdfPageTextRange getRangeFromAB(int a, int b) { - final min = a < b ? a : b; - final max = a < b ? b : a; - if (min < 0 || max > fullText.length) { - throw RangeError('Indices out of range: $min, $max for fullText length ${fullText.length}.'); - } - return PdfPageTextRange(pageText: this, start: min, end: max + 1); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfPageText && - other.pageNumber == pageNumber && - other.fullText == fullText && - _listEquals(other.charRects, charRects) && - _listEquals(other.fragments, fragments); - } - - @override - int get hashCode => pageNumber.hashCode ^ fullText.hashCode ^ charRects.hashCode ^ fragments.hashCode; -} - -/// Text direction in PDF page. -enum PdfTextDirection { - /// Left to Right - ltr, - - /// Right to Left - rtl, - - /// Vertical (top to bottom), Right to Left. - vrtl, - - /// Unknown direction, e.g., no text or no text direction can be determined. - unknown, -} - -/// Text fragment in PDF page. -class PdfPageTextFragment { - const PdfPageTextFragment({ - required this.pageText, - required this.index, - required this.length, - required this.bounds, - required this.charRects, - required this.direction, - }); - - /// Owner of the fragment. - final PdfPageText pageText; - - /// Fragment's index on [PdfPageText.fullText]; [text] is the substring of [PdfPageText.fullText] at [index]. - final int index; - - /// Length of the text fragment. - final int length; - - /// End index of the text fragment on [PdfPageText.fullText]. - int get end => index + length; - - /// Bounds of the text fragment in PDF page coordinates. - final PdfRect bounds; - - /// The fragment's child character bounding boxes in PDF page coordinates. - final List charRects; - - /// Text direction of the fragment. - final PdfTextDirection direction; - - /// Text for the fragment. - String get text => pageText.fullText.substring(index, index + length); - - @override - bool operator ==(covariant PdfPageTextFragment other) { - if (identical(this, other)) return true; - - return other.index == index && - other.bounds == bounds && - _listEquals(other.charRects, charRects) && - other.text == text; - } - - @override - int get hashCode => index.hashCode ^ bounds.hashCode ^ text.hashCode; -} - -/// Text range in a PDF page, which is typically used to describe text selection. -class PdfPageTextRange { - /// Create a [PdfPageTextRange]. - /// - /// [start] is inclusive and [end] is exclusive. - const PdfPageTextRange({required this.pageText, required this.start, required this.end}); - - /// The page text the text range are associated with. - final PdfPageText pageText; - - /// Text start index in [PdfPageText.fullText]. - final int start; - - /// Text end index in [PdfPageText.fullText]. - final int end; - - /// Page number of the text range. - int get pageNumber => pageText.pageNumber; - - /// The composed text of the text range. - String get text => pageText.fullText.substring(start, end); - - /// The bounding rectangle of the text range in PDF page coordinates. - PdfRect get bounds => pageText.charRects.boundingRect(start: start, end: end); - - /// Get the first text fragment index corresponding to the text range. - /// - /// It can be used with [PdfPageText.fragments] to get the first text fragment in the range. - int get firstFragmentIndex => pageText.getFragmentIndexForTextIndex(start); - - /// Get the last text fragment index corresponding to the text range. - /// - /// It can be used with [PdfPageText.fragments] to get the last text fragment in the range. - int get lastFragmentIndex => pageText.getFragmentIndexForTextIndex(end - 1); - - /// Get the first text fragment in the range. - PdfPageTextFragment? get firstFragment { - final index = firstFragmentIndex; - if (index < 0 || index >= pageText.fragments.length) { - return null; // range error - } - return pageText.fragments[index]; - } - - /// Get the last text fragment in the range. - PdfPageTextFragment? get lastFragment { - final index = lastFragmentIndex; - if (index < 0 || index >= pageText.fragments.length) { - return null; // range error - } - return pageText.fragments[index]; - } - - /// Enumerate all the fragment bounding rectangles for the text range. - /// - /// The function is useful when you implement text selection algorithm or such. - Iterable enumerateFragmentBoundingRects() sync* { - final fStart = firstFragmentIndex; - final fEnd = lastFragmentIndex; - for (var i = fStart; i <= fEnd; i++) { - final f = pageText.fragments[i]; - if (f.end <= start || end <= f.index) continue; - yield PdfTextFragmentBoundingRect(f, max(start - f.index, 0), min(end - f.index, f.length)); - } - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfPageTextRange && other.pageText == pageText && other.start == start && other.end == end; - } - - @override - int get hashCode => pageText.hashCode ^ start.hashCode ^ end.hashCode; -} - -/// Rectangle in PDF page coordinates. -/// -/// Please note that PDF page coordinates is different from Flutter's coordinate. -/// PDF page coordinates's origin is at the bottom-left corner and Y-axis is pointing upward; -/// [bottom] is generally smaller than [top]. -/// The unit is normally in points (1/72 inch). -class PdfRect { - const PdfRect(this.left, this.top, this.right, this.bottom) - : assert(left <= right, 'Left coordinate must be less than or equal to right coordinate.'), - assert(top >= bottom, 'Top coordinate must be greater than or equal to bottom coordinate.'); - - /// Left coordinate. - final double left; - - /// Top coordinate (bigger than [bottom]). - final double top; - - /// Right coordinate. - final double right; - - /// Bottom coordinate (smaller than [top]). - final double bottom; - - /// Determine whether the rectangle is empty. - bool get isEmpty => left >= right || top <= bottom; - - /// Determine whether the rectangle is *NOT* empty. - bool get isNotEmpty => !isEmpty; - - /// Width of the rectangle. - double get width => right - left; - - /// Height of the rectangle. - double get height => top - bottom; - - /// Top-left point of the rectangle. - PdfPoint get topLeft => PdfPoint(left, top); - - /// Top-right point of the rectangle. - PdfPoint get topRight => PdfPoint(right, top); - - /// Bottom-left point of the rectangle. - PdfPoint get bottomLeft => PdfPoint(left, bottom); - - /// Bottom-right point of the rectangle. - PdfPoint get bottomRight => PdfPoint(right, bottom); - - /// Center point of the rectangle. - PdfPoint get center => PdfPoint((left + right) / 2, (top + bottom) / 2); - - /// Merge two rectangles. - PdfRect merge(PdfRect other) { - return PdfRect( - left < other.left ? left : other.left, - top > other.top ? top : other.top, - right > other.right ? right : other.right, - bottom < other.bottom ? bottom : other.bottom, - ); - } - - /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool containsXy(double x, double y, {double margin = 0}) => - x >= left - margin && x <= right + margin && y >= bottom - margin && y <= top + margin; - - /// Determine whether the rectangle contains the specified point (in the PDF page coordinates). - bool containsPoint(PdfPoint offset, {double margin = 0}) => containsXy(offset.x, offset.y, margin: margin); - - double distanceSquaredTo(PdfPoint point) { - if (containsPoint(point)) { - return 0.0; // inside the rectangle - } - final dx = point.x.clamp(left, right) - point.x; - final dy = point.y.clamp(bottom, top) - point.y; - return dx * dx + dy * dy; - } - - /// Determine whether the rectangle overlaps the specified rectangle (in the PDF page coordinates). - bool overlaps(PdfRect other) { - return left < other.right && - right > other.left && - top > other.bottom && - bottom < other.top; // PDF page coordinates: top is bigger than bottom - } - - /// Empty rectangle. - static const empty = PdfRect(0, 0, 0, 0); - - /// Rotate the rectangle. - PdfRect rotate(int rotation, PdfPage page) { - final swap = (page.rotation.index & 1) == 1; - final width = swap ? page.height : page.width; - final height = swap ? page.width : page.height; - switch (rotation & 3) { - case 0: - return this; - case 1: - return PdfRect(bottom, width - left, top, width - right); - case 2: - return PdfRect(width - right, height - bottom, width - left, height - top); - case 3: - return PdfRect(height - top, right, height - bottom, left); - default: - throw ArgumentError.value(rotation, 'rotation'); - } - } - - /// Rotate the rectangle in reverse direction. - PdfRect rotateReverse(int rotation, PdfPage page) { - final swap = (page.rotation.index & 1) == 1; - final width = swap ? page.height : page.width; - final height = swap ? page.width : page.height; - switch (rotation & 3) { - case 0: - return this; - case 1: - return PdfRect(width - top, right, width - bottom, left); - case 2: - return PdfRect(width - right, height - bottom, width - left, height - top); - case 3: - return PdfRect(bottom, height - left, top, height - right); - default: - throw ArgumentError.value(rotation, 'rotation'); - } - } - - /// Inflate (or deflate) the rectangle. - /// - /// [dx] is added to left and right, and [dy] is added to top and bottom. - PdfRect inflate(double dx, double dy) => PdfRect(left - dx, top + dy, right + dx, bottom - dy); - - /// Translate the rectangle. - /// - /// [dx] is added to left and right, and [dy] is added to top and bottom. - PdfRect translate(double dx, double dy) => PdfRect(left + dx, top + dy, right + dx, bottom + dy); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfRect && other.left == left && other.top == top && other.right == right && other.bottom == bottom; - } - - @override - int get hashCode => left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; - - @override - String toString() { - return 'PdfRect(left: $left, top: $top, right: $right, bottom: $bottom)'; - } -} - -/// Extension methods for List of [PdfRect]. -extension PdfRectsExt on Iterable { - /// Calculate the bounding rectangle of the list of rectangles. - PdfRect boundingRect({int? start, int? end}) { - start ??= 0; - end ??= length; - var left = double.infinity; - var top = double.negativeInfinity; - var right = double.negativeInfinity; - var bottom = double.infinity; - for (final r in skip(start).take(end - start)) { - if (r.left < left) { - left = r.left; - } - if (r.top > top) { - top = r.top; - } - if (r.right > right) { - right = r.right; - } - if (r.bottom < bottom) { - bottom = r.bottom; - } - } - if (left == double.infinity) { - // no rects - throw StateError('No rects'); - } - return PdfRect(left, top, right, bottom); - } -} - -/// Bounding rectangle for a text range in a PDF page. -class PdfTextFragmentBoundingRect { - const PdfTextFragmentBoundingRect(this.fragment, this.sif, this.eif); - - /// Associated text fragment. - final PdfPageTextFragment fragment; - - /// In fragment text start index (Start-In-Fragment) - /// - /// It is the character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] - /// of the associated [fragment]. - final int sif; - - /// In fragment text end index (End-In-Fragment). - /// - /// It is the end character index in the [PdfPageTextFragment.charRects]/[PdfPageTextFragment.text] - /// of the associated [fragment]. - final int eif; - - /// Rectangle in PDF page coordinates. - PdfRect get bounds => fragment.pageText.charRects.boundingRect(start: start, end: end); - - /// Start index of the text range in page's full text. - int get start => fragment.index + sif; - - /// End index of the text range in page's full text. - int get end => fragment.index + eif; - - /// Text direction of the text range. - PdfTextDirection get direction => fragment.direction; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfTextFragmentBoundingRect && other.fragment == fragment && other.sif == sif && other.eif == eif; - } - - @override - int get hashCode => fragment.hashCode ^ sif.hashCode ^ eif.hashCode; -} - -/// PDF [Explicit Destination](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) the page and inner-page location to jump to. -class PdfDest { - /// Create a [PdfDest]. - const PdfDest(this.pageNumber, this.command, this.params); - - /// Page number to jump to. - final int pageNumber; - - /// Destination command. - final PdfDestCommand command; - - /// Destination parameters. For more info, see [PdfDestCommand]. - final List? params; - - @override - String toString() => 'PdfDest{pageNumber: $pageNumber, command: $command, params: $params}'; - - /// Compact the destination. - /// - /// The method is used to compact the destination to reduce memory usage. - /// [params] is typically growable and also modifiable. The method ensures that [params] is unmodifiable. - PdfDest compact() { - return params == null ? this : PdfDest(pageNumber, command, List.unmodifiable(params!)); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfDest && - other.pageNumber == pageNumber && - other.command == command && - _listEquals(other.params, params); - } - - @override - int get hashCode => pageNumber.hashCode ^ command.hashCode ^ params.hashCode; -} - -/// [PDF 32000-1:2008, 12.3.2.2 Explicit Destinations, Table 151](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=374) -enum PdfDestCommand { - unknown('unknown'), - xyz('xyz'), - fit('fit'), - fitH('fith'), - fitV('fitv'), - fitR('fitr'), - fitB('fitb'), - fitBH('fitbh'), - fitBV('fitbv'); - - /// Create a [PdfDestCommand] with the specified command name. - const PdfDestCommand(this.name); - - /// Command name. - final String name; - - /// Parse the command name to [PdfDestCommand]. - factory PdfDestCommand.parse(String name) { - final nameLow = name.toLowerCase(); - return PdfDestCommand.values.firstWhere((e) => e.name == nameLow, orElse: () => PdfDestCommand.unknown); - } -} - -/// Link in PDF page. -/// -/// Either one of [url] or [dest] is valid (not null). -/// See [PdfPage.loadLinks]. -class PdfLink { - const PdfLink(this.rects, {this.url, this.dest, this.annotationContent}); - - /// Link URL. - final Uri? url; - - /// Link destination (link to page). - final PdfDest? dest; - - /// Link location(s) inside the associated PDF page. - /// - /// Sometimes a link can span multiple rectangles, e.g., a link across multiple lines. - final List rects; - - /// Annotation content if available. - final String? annotationContent; - - /// Compact the link. - /// - /// The method is used to compact the link to reduce memory usage. - /// [rects] is typically growable and also modifiable. The method ensures that [rects] is unmodifiable. - /// [dest] is also compacted by calling [PdfDest.compact]. - PdfLink compact() { - return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact(), annotationContent: annotationContent); - } - - @override - String toString() { - return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects, annotationContent: $annotationContent }'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfLink && other.url == url && other.dest == dest && _listEquals(other.rects, rects); - } - - @override - int get hashCode => url.hashCode ^ dest.hashCode ^ rects.hashCode; -} - -/// Outline (a.k.a. Bookmark) node in PDF document. -/// -/// See [PdfDocument.loadOutline]. -class PdfOutlineNode { - const PdfOutlineNode({required this.title, required this.dest, required this.children}); - - /// Outline node title. - final String title; - - /// Outline node destination. - final PdfDest? dest; - - /// Outline child nodes. - final List children; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfOutlineNode && - other.title == title && - other.dest == dest && - _listEquals(other.children, children); - } - - @override - int get hashCode => title.hashCode ^ dest.hashCode ^ children.hashCode; -} - -class PdfException implements Exception { - const PdfException(this.message, [this.errorCode]); - final String message; - final int? errorCode; - @override - String toString() => 'PdfException: $message'; -} - -class PdfPasswordException extends PdfException { - const PdfPasswordException(super.message); -} - -/// PDF page coordinates point. -/// -/// In Pdf page coordinates, the origin is at the bottom-left corner and Y-axis is pointing upward. -/// The unit is normally in points (1/72 inch). -class PdfPoint { - const PdfPoint(this.x, this.y); - - /// X coordinate. - final double x; - - /// Y coordinate. - final double y; - - /// Calculate the vector to another point. - Vector2 differenceTo(PdfPoint other) => Vector2(other.x - x, other.y - y); - - @override - String toString() => 'PdfOffset($x, $y)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is PdfPoint && other.x == x && other.y == y; - } - - @override - int get hashCode => x.hashCode ^ y.hashCode; - - double distanceSquaredTo(PdfPoint other) { - final dx = x - other.x; - final dy = y - other.y; - return dx * dx + dy * dy; - } - - /// Rotate the point. - PdfPoint rotate(int rotation, PdfPage page) { - final swap = (page.rotation.index & 1) == 1; - final width = swap ? page.height : page.width; - final height = swap ? page.width : page.height; - switch (rotation & 3) { - case 0: - return this; - case 1: - return PdfPoint(y, width - x); - case 2: - return PdfPoint(width - x, height - y); - case 3: - return PdfPoint(height - y, x); - default: - throw ArgumentError.value(rotate, 'rotate'); - } - } - - /// Rotate the point in reverse direction. - PdfPoint rotateReverse(int rotation, PdfPage page) { - final swap = (page.rotation.index & 1) == 1; - final width = swap ? page.height : page.width; - final height = swap ? page.width : page.height; - switch (rotation & 3) { - case 0: - return this; - case 1: - return PdfPoint(width - y, x); - case 2: - return PdfPoint(width - x, height - y); - case 3: - return PdfPoint(y, height - x); - default: - throw ArgumentError.value(rotate, 'rotate'); - } - } - - /// Translate the point. - /// - /// [dx] is added to x, and [dy] is added to y. - PdfPoint translate(double dx, double dy) => PdfPoint(x + dx, y + dy); -} - -/// Compares two lists for element-by-element equality. -/// -/// **NOTE: This function is copied from flutter's `foundation` library to remove dependency to Flutter** -bool _listEquals(List? a, List? b) { - if (a == null) { - return b == null; - } - if (b == null || a.length != b.length) { - return false; - } - if (identical(a, b)) { - return true; - } - for (var index = 0; index < a.length; index += 1) { - if (a[index] != b[index]) { - return false; - } - } - return true; -} - -class PdfFontQuery { - const PdfFontQuery({ - required this.face, - required this.weight, - required this.isItalic, - required this.charset, - required this.pitchFamily, - }); - - /// Font face name. - final String face; - - /// Font weight. - final int weight; - - /// Whether the font is italic. - final bool isItalic; - - /// PDFium's charset ID. - final PdfFontCharset charset; - - /// Pitch family flags. - /// - /// It can be any combination of the following values: - /// - `fixed` = 1 - /// - `roman` = 16 - /// - `script` = 64 - final int pitchFamily; - - bool get isFixed => (pitchFamily & 1) != 0; - bool get isRoman => (pitchFamily & 16) != 0; - bool get isScript => (pitchFamily & 64) != 0; - - String _getPitchFamily() { - return [if (isFixed) 'fixed', if (isRoman) 'roman', if (isScript) 'script'].join(','); - } - - @override - String toString() => - 'PdfFontQuery(face: "$face", weight: $weight, italic: $isItalic, charset: $charset, pitchFamily: $pitchFamily=[${_getPitchFamily()}])'; -} - -/// PDFium font charset ID. -/// -enum PdfFontCharset { - ansi(0), - default_(1), - symbol(2), - - /// Japanese - shiftJis(128), - - /// Korean - hangul(129), - - /// Chinese Simplified - gb2312(134), - - /// Chinese Traditional - chineseBig5(136), - greek(161), - vietnamese(163), - hebrew(177), - arabic(178), - cyrillic(204), - thai(222), - easternEuropean(238); - - const PdfFontCharset(this.pdfiumCharsetId); - - /// PDFium's charset ID. - final int pdfiumCharsetId; - - static final _value2Enum = {for (final e in PdfFontCharset.values) e.pdfiumCharsetId: e}; - - /// Convert PDFium's charset ID to [PdfFontCharset]. - static PdfFontCharset fromPdfiumCharsetId(int id) => _value2Enum[id]!; - - @override - String toString() => '$name($pdfiumCharsetId)'; -} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_document.dart b/packages/pdfrx_engine/lib/src/pdfrx_document.dart new file mode 100644 index 00000000..7385121d --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx_document.dart @@ -0,0 +1,281 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +/// @docImport 'native/pdfrx_pdfium.dart'; + +/// Pdfrx API +library; + +import 'dart:async'; +import 'dart:typed_data'; + +import 'pdf_document_event.dart'; +import 'pdf_outline_node.dart'; +import 'pdf_page.dart'; +import 'pdf_permissions.dart'; +import 'pdfrx_entry_functions.dart'; + +/// Handles PDF document loaded on memory. +abstract class PdfDocument { + /// Constructor to force initialization of sourceName. + PdfDocument({required this.sourceName}); + + /// File path, `asset:[ASSET_PATH]` or `memory:` depending on the content opened. + final String sourceName; + + /// Permission flags. + PdfPermissions? get permissions; + + /// Determine whether the PDF file is encrypted or not. + bool get isEncrypted; + + /// PdfDocument must have [dispose] function. + Future dispose(); + + /// Stream to notify change events in the document. + Stream get events; + + /// Opening the specified file. + /// For Web, [filePath] can be relative path from `index.html` or any arbitrary URL but it may be restricted by CORS. + /// + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + static Future openFile( + String filePath, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) => PdfrxEntryFunctions.instance.openFile( + filePath, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + ); + + /// Opening the specified asset. + /// + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + static Future openAsset( + String name, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }) => PdfrxEntryFunctions.instance.openAsset( + name, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + ); + + /// Opening the PDF on memory. + /// + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty password + /// or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// + /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// unique for each source, the viewer may not work correctly. + /// + /// Web only: [allowDataOwnershipTransfer] is used to determine if the data buffer can be transferred to + /// the worker thread. + static Future openData( + Uint8List data, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + String? sourceName, + bool allowDataOwnershipTransfer = false, + void Function()? onDispose, + }) => PdfrxEntryFunctions.instance.openData( + data, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + sourceName: sourceName, + allowDataOwnershipTransfer: allowDataOwnershipTransfer, + onDispose: onDispose, + ); + + /// Creating a new empty PDF document. + /// + /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// unique for each source, the viewer may not work correctly. + static Future createNew({required String sourceName}) => + PdfrxEntryFunctions.instance.createNew(sourceName: sourceName); + + /// Opening the PDF from custom source. + /// + /// On Flutter Web, this function is not supported and throws an exception. + /// It is also not supported if pdfrx is running without libpdfrx (**typically on Dart only**). + /// + /// [maxSizeToCacheOnMemory] is the maximum size of the PDF to cache on memory in bytes; the custom loading process + /// may be heavy because of FFI overhead and it may be better to cache the PDF on memory if it's not too large. + /// The default size is 1MB. + /// + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// + /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// unique for each source, the viewer may not work correctly. + static Future openCustom({ + required FutureOr Function(Uint8List buffer, int position, int size) read, + required int fileSize, + required String sourceName, + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + int? maxSizeToCacheOnMemory, + void Function()? onDispose, + }) => PdfrxEntryFunctions.instance.openCustom( + read: read, + fileSize: fileSize, + sourceName: sourceName, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + maxSizeToCacheOnMemory: maxSizeToCacheOnMemory, + onDispose: onDispose, + ); + + /// Opening the PDF from URI. + /// + /// 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. + /// + /// [passwordProvider] is used to provide password for encrypted PDF. See [PdfPasswordProvider] for more info. + /// + /// [firstAttemptByEmptyPassword] is used to determine whether the first attempt to open the PDF is by empty + /// password or not. For more info, see [PdfPasswordProvider]. + /// + /// If [useProgressiveLoading] is true, only the first page is loaded initially and the rest of the pages + /// are loaded progressively when [PdfDocument.loadPagesProgressively] is called explicitly. + /// + /// [progressCallback] is called when the download progress is updated. + /// + /// [preferRangeAccess] to prefer range access to download the PDF. The default is false (Not supported on Web). + /// It is not supported if pdfrx is running without libpdfrx (**typically on Dart only**). + /// + /// [headers] is used to specify additional HTTP headers especially for authentication/authorization. + /// + /// [withCredentials] is used to specify whether to include credentials in the request (Only supported on Web). + /// + /// [timeout] is used to specify the timeout duration for each HTTP request (Only supported on non-Web platforms). + static Future openUri( + Uri uri, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + PdfDownloadProgressCallback? progressCallback, + bool preferRangeAccess = false, + Map? headers, + bool withCredentials = false, + Duration? timeout, + }) => PdfrxEntryFunctions.instance.openUri( + uri, + passwordProvider: passwordProvider, + firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, + progressCallback: progressCallback, + preferRangeAccess: preferRangeAccess, + headers: headers, + withCredentials: withCredentials, + timeout: timeout, + ); + + /// Load pages progressively. + /// + /// This function loads pages progressively if the pages are not loaded yet. + /// It calls [onPageLoadProgress] for each [loadUnitDuration] duration until all pages are loaded or the loading + /// is cancelled. + /// When [onPageLoadProgress] is called, it should return true to continue loading process or false to stop loading. + /// [data] is an optional data that can be used to pass additional information to the callback. + /// + /// It's always safe to call this function even if the pages are already loaded. + Future loadPagesProgressively({ + PdfPageLoadingCallback? onPageLoadProgress, + T? data, + Duration loadUnitDuration = const Duration(milliseconds: 250), + }); + + /// Pages. + /// + /// The list is unmodifiable; you cannot add, remove, or replace pages directly. + /// To modify the pages, use [pages] setter to set a new list of pages. + List get pages; + + /// Set pages. + /// + /// You can add [PdfPage] instances from any [PdfDocument] instances and the resulting document works correctly + /// if the referenced [PdfDocument] instances are alive; it's your responsibility to manage the lifetime of those + /// instances. To make the document independent from the source documents, you should call [assemble] after setting + /// the pages. + set pages(List value); + + /// Load outline (a.k.a. bookmark). + Future> loadOutline(); + + /// Determine whether document handles are identical or not. + /// + /// It does not mean the document contents (or the document files) are identical. + bool isIdenticalDocumentHandle(Object? other); + + /// Assemble the document after modifying pages. + /// + /// You should call this function after modifying [pages] to make the document consistent and independent from + /// the other source documents. If [pages] contains pages from other documents, those documents must be alive + /// until this function returns. + Future assemble(); + + /// Save the PDF document. + /// + /// This function internally calls [assemble] before encoding the PDF. + Future encodePdf({bool incremental = false, bool removeSecurity = false}); +} + +typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); + +/// 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. +/// It is repeatedly called until the function returns null or a valid password. +/// +/// [createSimplePasswordProvider] is a helper function to create [PdfPasswordProvider] that returns the password +/// only once. +typedef PdfPasswordProvider = FutureOr Function(); + +/// Create [PdfPasswordProvider] that returns the password only once. +/// +/// The returned [PdfPasswordProvider] returns the password only once and returns null afterwards. +/// If [password] is null, the returned [PdfPasswordProvider] returns null always. +PdfPasswordProvider createSimplePasswordProvider(String? password) { + return () { + final ret = password; + password = null; + return ret; + }; +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart new file mode 100644 index 00000000..5ceda7f1 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -0,0 +1,127 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import '../pdfrx_engine.dart' show Pdfrx; +import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; +import 'pdfrx.dart' show Pdfrx; +import 'pdfrx_document.dart'; + +/// The class is used to implement Pdfrx's backend functions. +/// +/// In normal usage, you should use [PdfDocument]'s static functions to open PDF files instead of using this class directly. +/// +/// [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) provide an alternative implementation of this +/// class for Apple platforms. +abstract class PdfrxEntryFunctions { + /// Singleton instance of [PdfrxEntryFunctions]. + /// + /// [PdfDocument] internally calls this instance to open PDF files. + static PdfrxEntryFunctions instance = PdfrxEntryFunctionsImpl(); + + /// Call `FPDF_InitLibraryWithConfig` to initialize the PDFium library. + /// + /// For actual apps, call `pdfrxFlutterInitialize` (for Flutter) or `pdfrxInitialize` (for Dart only) instead of this function. + Future init(); + + /// This function blocks pdfrx internally calls PDFium functions during the execution of [action]. + /// + /// Because PDFium is not thread-safe, if your app is calling some other libraries that potentially calls PDFium + /// functions, pdfrx may interfere with those calls and cause crashes or data corruption. + /// To avoid such problems, you can wrap the code that calls those libraries with this function. + Future suspendPdfiumWorkerDuringAction(FutureOr Function() action); + + /// See [PdfDocument.openAsset]. + Future openAsset( + String name, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }); + + /// See [PdfDocument.openData]. + Future openData( + Uint8List data, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + String? sourceName, + bool allowDataOwnershipTransfer = false, // only for Web + bool useProgressiveLoading = false, + void Function()? onDispose, + }); + + /// See [PdfDocument.openFile]. + Future openFile( + String filePath, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + }); + + /// See [PdfDocument.openCustom]. + Future openCustom({ + required FutureOr Function(Uint8List buffer, int position, int size) read, + required int fileSize, + required String sourceName, + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + int? maxSizeToCacheOnMemory, + void Function()? onDispose, + }); + + /// See [PdfDocument.openUri]. + Future openUri( + Uri uri, { + PdfPasswordProvider? passwordProvider, + bool firstAttemptByEmptyPassword = true, + bool useProgressiveLoading = false, + PdfDownloadProgressCallback? progressCallback, + bool preferRangeAccess = false, + Map? headers, + bool withCredentials = false, + Duration? timeout, + }); + + /// See [PdfDocument.createNew]. + Future createNew({required String sourceName}); + + /// Reload the fonts. + Future reloadFonts(); + + /// Add font data to font cache. + /// + /// For Web platform, this is the only way to add custom fonts (the fonts are cached on memory). + /// + /// For other platforms, the font data is cached on temporary files in the cache directory; if you want to keep + /// the font data permanently, you should save the font data to some other persistent storage and set its path + /// to [Pdfrx.fontPaths]. + Future addFontData({required String face, required Uint8List data}); + + /// Clear all font data added by [addFontData]. + Future clearAllFontData(); + + /// Backend in use. + PdfrxBackend get backend; +} + +/// Pdfrx backend types. +enum PdfrxBackend { + /// PDFium backend. + pdfium, + + /// PDFium WebAssembly backend for Web platform. + /// + /// The implementation for this is provided by [pdfrx](https://pub.dev/packages/pdfrx) package. + pdfiumWasm, + + /// pdfKit (CoreGraphics) backend for Apple platforms. + /// + /// The implementation for this is provided by [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) package. + pdfKit, + + /// Mock backend for internal consistency. + mock, + + /// Unknown backend. + unknown, +} diff --git a/packages/pdfrx_engine/lib/src/utils/list_equals.dart b/packages/pdfrx_engine/lib/src/utils/list_equals.dart new file mode 100644 index 00000000..d3d502eb --- /dev/null +++ b/packages/pdfrx_engine/lib/src/utils/list_equals.dart @@ -0,0 +1,20 @@ +/// Compares two lists for element-by-element equality. +/// +/// **NOTE: This function is copied from flutter's `foundation` library to remove dependency to Flutter** +bool listEquals(List? a, List? b) { + if (a == null) { + return b == null; + } + if (b == null || a.length != b.length) { + return false; + } + if (identical(a, b)) { + return true; + } + for (var index = 0; index < a.length; index += 1) { + if (a[index] != b[index]) { + return false; + } + } + return true; +} From 06cc40c02243624afc50e7f747e5d9d806d56c33 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 06:40:42 +0900 Subject: [PATCH 497/663] WIP --- packages/pdfrx_engine/lib/src/native/native_utils.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_link.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_outline_node.dart | 3 +-- packages/pdfrx_engine/lib/src/pdf_page.dart | 3 +-- packages/pdfrx_engine/lib/src/pdf_text.dart | 2 +- packages/pdfrx_engine/lib/src/pdfrx_dart.dart | 2 +- packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart | 1 - packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart | 3 ++- 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/native_utils.dart b/packages/pdfrx_engine/lib/src/native/native_utils.dart index 3a4e1077..ad4a02c5 100644 --- a/packages/pdfrx_engine/lib/src/native/native_utils.dart +++ b/packages/pdfrx_engine/lib/src/native/native_utils.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:path/path.dart' as path; -import '../../pdfrx_engine.dart'; +import '../pdfrx.dart'; /// Helper function to get the cache directory for a specific purpose and name. Future getCacheDirectory( diff --git a/packages/pdfrx_engine/lib/src/pdf_link.dart b/packages/pdfrx_engine/lib/src/pdf_link.dart index 042b8b56..06a13fff 100644 --- a/packages/pdfrx_engine/lib/src/pdf_link.dart +++ b/packages/pdfrx_engine/lib/src/pdf_link.dart @@ -1,5 +1,5 @@ -import '../pdfrx_engine.dart' show PdfPage; import 'pdf_dest.dart'; +import 'pdf_page.dart'; import 'pdf_rect.dart'; import 'utils/list_equals.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdf_outline_node.dart b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart index 552fcfe6..803d961c 100644 --- a/packages/pdfrx_engine/lib/src/pdf_outline_node.dart +++ b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart @@ -1,6 +1,5 @@ -import '../pdfrx_engine.dart' show PdfDocument; import 'pdf_dest.dart'; -import 'pdfrx_document.dart' show PdfDocument; +import 'pdfrx_document.dart'; import 'utils/list_equals.dart'; /// Outline (a.k.a. Bookmark) node in PDF document. diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index 1749492b..17354ac9 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -1,7 +1,6 @@ import 'dart:typed_data'; -import '../pdfrx_engine.dart' show PdfLink; -import 'pdf_link.dart' show PdfLink; +import 'pdf_link.dart'; import 'pdf_page_proxies.dart'; import 'pdf_text.dart'; import 'pdf_text_formatter.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdf_text.dart b/packages/pdfrx_engine/lib/src/pdf_text.dart index 617c0715..94eb9db3 100644 --- a/packages/pdfrx_engine/lib/src/pdf_text.dart +++ b/packages/pdfrx_engine/lib/src/pdf_text.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:collection/collection.dart'; -import '../pdfrx_engine.dart' show PdfPage; +import 'pdf_page.dart'; import 'pdf_rect.dart'; import 'utils/list_equals.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart index 695ca51a..05ae93e6 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart @@ -1,6 +1,6 @@ import 'package:image/image.dart'; -import '../pdfrx_engine.dart'; +import 'pdf_page.dart'; extension PdfImageDartExt on PdfImage { /// Create [Image] (of [image package](https://pub.dev/packages/image)) from the rendered image. diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 5ceda7f1..19a0863f 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; -import '../pdfrx_engine.dart' show Pdfrx; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; import 'pdfrx.dart' show Pdfrx; import 'pdfrx_document.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 37025a4c..c8945aea 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -3,7 +3,8 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:http/http.dart' as http; -import '../pdfrx_engine.dart'; +import 'pdfrx.dart'; +import 'pdfrx_entry_functions.dart'; bool _isInitialized = false; From 44dafe40f5ba7e05f60404eae87283ff69e897fe Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 1 Nov 2025 06:45:22 +0900 Subject: [PATCH 498/663] WIP --- .../pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart | 4 +++- packages/pdfrx_engine/lib/src/pdf_exception.dart | 8 ++++++++ packages/pdfrx_engine/lib/src/pdf_font_query.dart | 1 + packages/pdfrx_engine/lib/src/pdf_point.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_text_formatter.dart | 4 ++-- packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart index 5600bfc1..253e1a10 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_initialize_mock.dart @@ -1,6 +1,8 @@ -/// @docImport '../pdfrx_api.dart'; library; +import '../pdfrx.dart'; +import '../pdfrx_entry_functions.dart'; + /// Initializes the Pdfrx library for Dart. /// /// This function sets up the following: diff --git a/packages/pdfrx_engine/lib/src/pdf_exception.dart b/packages/pdfrx_engine/lib/src/pdf_exception.dart index bcfdc0dd..0ec84ea7 100644 --- a/packages/pdfrx_engine/lib/src/pdf_exception.dart +++ b/packages/pdfrx_engine/lib/src/pdf_exception.dart @@ -1,11 +1,19 @@ +/// PDF exception class. class PdfException implements Exception { + /// Creates a new [PdfException]. const PdfException(this.message, [this.errorCode]); + + /// Exception message. final String message; + + /// Optional error code. final int? errorCode; @override String toString() => 'PdfException: $message'; } +/// PDF exception for password related errors. class PdfPasswordException extends PdfException { + /// Creates a new [PdfPasswordException]. const PdfPasswordException(super.message); } diff --git a/packages/pdfrx_engine/lib/src/pdf_font_query.dart b/packages/pdfrx_engine/lib/src/pdf_font_query.dart index 7a134aee..2f9a6ed9 100644 --- a/packages/pdfrx_engine/lib/src/pdf_font_query.dart +++ b/packages/pdfrx_engine/lib/src/pdf_font_query.dart @@ -1,3 +1,4 @@ +/// PDF font query parameters. class PdfFontQuery { const PdfFontQuery({ required this.face, diff --git a/packages/pdfrx_engine/lib/src/pdf_point.dart b/packages/pdfrx_engine/lib/src/pdf_point.dart index 9a4dccc0..104c9918 100644 --- a/packages/pdfrx_engine/lib/src/pdf_point.dart +++ b/packages/pdfrx_engine/lib/src/pdf_point.dart @@ -1,4 +1,4 @@ -import 'package:vector_math/vector_math_64.dart' hide Colors; +import 'package:vector_math/vector_math_64.dart'; import 'pdf_page.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart index 67722d8c..ccf8eca9 100644 --- a/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart +++ b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart @@ -1,10 +1,10 @@ import 'dart:collection'; -import 'package:vector_math/vector_math_64.dart' hide Colors; +import 'package:vector_math/vector_math_64.dart'; import './mock/string_buffer_wrapper.dart' if (dart.library.io) './native/string_buffer_wrapper.dart'; import 'pdf_page.dart'; -import 'pdf_page_proxies.dart' show PdfPageProxy; +import 'pdf_page_proxies.dart'; import 'pdf_rect.dart'; import 'pdf_text.dart'; import 'utils/unmodifiable_list.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 19a0863f..8f7f2d3f 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:typed_data'; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; -import 'pdfrx.dart' show Pdfrx; +import 'pdfrx.dart'; import 'pdfrx_document.dart'; /// The class is used to implement Pdfrx's backend functions. From 9a9d0072bbfca2d7d20b860f794fe4c95e0163a8 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 1 Nov 2025 08:51:02 +1030 Subject: [PATCH 499/663] WIP - simplify zoom in several _calc functions --- packages/pdfrx/example/viewer/lib/main.dart | 3 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 94 +++++++------------ 2 files changed, 38 insertions(+), 59 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 71ef3c7d..eb946f4b 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -309,10 +309,11 @@ class _MainPageState extends State with WidgetsBindingObserver { }, ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), + minScale: 0.2, maxScale: 8, margin: 10, boundaryMargin: EdgeInsets.all(10), - fitMode: FitMode.fill, + fitMode: FitMode.fit, pageTransition: PageTransition.discrete, scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 67a57f47..56b67383 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -640,7 +640,7 @@ class _PdfViewerState extends State _adjustBoundaryMargins(viewSize, candidate.zoom); // Get the effective page bounds (includes margins and boundary margins) - final effectiveBounds = _getEffectivePageBounds(pageNumber, layout, anchor: PdfPageAnchor.all); + final effectiveBounds = _getEffectivePageBounds(pageNumber, layout); // Use spread bounds (without layout-applied margins) for comparison final pageRect = layout.getSpreadBounds(pageNumber); @@ -1241,12 +1241,7 @@ class _PdfViewerState extends State bool _isAtBoundary(Rect visibleRect, int pageNumber, PdfPageLayout layout, bool isPrimaryVertical, int direction) { final isMovingForward = direction > 0; - // Determine anchor based on direction to include boundary margins - final anchor = isMovingForward - ? (isPrimaryVertical ? PdfPageAnchor.bottomLeft : PdfPageAnchor.topRight) - : (isPrimaryVertical ? PdfPageAnchor.topLeft : PdfPageAnchor.topLeft); - - final pageRect = _getEffectivePageBounds(pageNumber, layout, anchor: anchor); + final pageRect = _getEffectivePageBounds(pageNumber, layout); final currentScale = _txController.value.getMaxScaleOnAxis(); // Increase tolerance for clamping physics - user may not be able to get exactly to the boundary const baseTolerance = 10; // pixels in screen space @@ -2585,49 +2580,29 @@ class _PdfViewerState extends State return _fitScale; } - Matrix4 _calcMatrixForRect( - Rect rect, { - double? zoomMax, - double? margin, - bool maintainCurrentZoom = false, - bool forceZoomMax = false, - }) { + Matrix4 _calcMatrixForRect(Rect rect, {double? zoomMax, double? margin}) { margin ??= 0; - final double zoom; - if (maintainCurrentZoom && _currentZoom > 0) { - // Use existing zoom for positioning only - zoom = _currentZoom; - } else if (forceZoomMax && zoomMax != null) { - // Force the zoomMax value (used in discrete mode with resetScale) - zoom = zoomMax; - } else { - // Calculate zoom to fit rect - var calculatedZoom = min( - (_viewSize!.width - margin * 2) / rect.width, - (_viewSize!.height - margin * 2) / rect.height, - ); - if (zoomMax != null && calculatedZoom > zoomMax) calculatedZoom = zoomMax; - zoom = calculatedZoom; + // Calculate zoom to fit rect in viewport with margins + var calculatedZoom = min( + (_viewSize!.width - margin * 2) / rect.width, + (_viewSize!.height - margin * 2) / rect.height, + ); + + // Clamp to zoomMax if provided + if (zoomMax != null && calculatedZoom > zoomMax) { + calculatedZoom = zoomMax; } - return _calcMatrixFor(rect.center, zoom: zoom, viewSize: _viewSize!); + return _calcMatrixFor(rect.center, zoom: calculatedZoom, viewSize: _viewSize!); } - Matrix4 _calcMatrixForArea({ - required Rect rect, - double? zoomMax, - double? margin, - PdfPageAnchor? anchor, - bool maintainCurrentZoom = false, - bool forceZoomMax = false, - }) => _calcMatrixForRect( - _calcRectForArea(rect: rect, anchor: anchor ?? widget.params.pageAnchor), - zoomMax: zoomMax, - margin: margin, - maintainCurrentZoom: maintainCurrentZoom, - forceZoomMax: forceZoomMax, - ); + Matrix4 _calcMatrixForArea({required Rect rect, double? zoomMax, double? margin, PdfPageAnchor? anchor}) => + _calcMatrixForRect( + _calcRectForArea(rect: rect, anchor: anchor ?? widget.params.pageAnchor), + zoomMax: zoomMax, + margin: margin, + ); /// The function calculate the rectangle which should be shown in the view. /// @@ -2673,13 +2648,13 @@ class _PdfViewerState extends State /// Gets the effective page bounds for a given page, including margins. /// Optionally includes boundary margins for positioning purposes. - Rect _getEffectivePageBounds(int pageNumber, PdfPageLayout layout, {PdfPageAnchor? anchor}) { + Rect _getEffectivePageBounds(int pageNumber, PdfPageLayout layout, {bool includingBoundaryMargins = true}) { final baseRect = layout.getSpreadBounds(pageNumber); var result = baseRect.inflate(widget.params.margin); // Add boundary margins for positioning when appropriate - if (anchor != null) { + if (includingBoundaryMargins) { if (widget.params.pageTransition == PageTransition.continuous) { // Continuous mode: apply boundary margins on cross-axis throughout, // and on primary axis only at document ends @@ -2711,27 +2686,30 @@ class _PdfViewerState extends State result = userBoundaryMargin.inflateRectIfFinite(result); } } - return result; } Matrix4 _calcMatrixForPage({ required int pageNumber, PdfPageAnchor? anchor, - double? zoomMax, - bool maintainCurrentZoom = false, double? forceScale, + bool maintainCurrentZoom = false, }) { final layout = _layout!; - final targetRect = _getEffectivePageBounds(pageNumber, layout, anchor: anchor); + final targetRect = _getEffectivePageBounds(pageNumber, layout); - return _calcMatrixForArea( - rect: targetRect, - anchor: anchor, - zoomMax: forceScale ?? zoomMax ?? _currentZoom, - maintainCurrentZoom: forceScale == null && maintainCurrentZoom, - forceZoomMax: forceScale != null, - ); + // Simple priority: forceScale > maintainCurrentZoom > calculate fit + final double zoom; + if (forceScale != null) { + zoom = forceScale; + } else if (maintainCurrentZoom) { + zoom = _currentZoom; + } else { + // Calculate zoom to fit page in viewport + zoom = min(_viewSize!.width / targetRect.width, _viewSize!.height / targetRect.height); + } + + return _calcMatrixForArea(rect: targetRect, anchor: anchor, zoomMax: zoom); } Rect _calcRectForRectInsidePage({required int pageNumber, required PdfRect rect}) { @@ -2890,7 +2868,7 @@ class _PdfViewerState extends State required int pageNumber, PdfPageAnchor? anchor, Duration duration = const Duration(milliseconds: 200), - bool maintainCurrentZoom = false, + bool maintainCurrentZoom = true, double? forceScale, }) async { final pageCount = _document!.pages.length; From 30f0fb7549260a0b7cc0aabfcb8f61b3a5aadb3c Mon Sep 17 00:00:00 2001 From: james Date: Sat, 1 Nov 2025 09:43:49 +1030 Subject: [PATCH 500/663] minScale - pageTransition.discrete set to _fitScale --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 56b67383..5a463418 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2567,16 +2567,15 @@ class _PdfViewerState extends State /// The minimum zoom ratio allowed. double get minScale { - // Always honor explicit minScale first + // In discrete mode, prevent zooming out below fit scale + if (widget.params.pageTransition == PageTransition.discrete) { + return _fitScale; + } + if (widget.params.minScale != null) { return widget.params.minScale!; } - // Legacy behavior: use fitScale as minimum when flag is true - if (widget.params.useAlternativeFitScaleAsMinScale) { - return _fitScale; - } - // Modern behavior: use fitScale as minimum (matches fitMode) - // This ensures that when fitMode is set, the minimum scale respects it + return _fitScale; } From b769117853ed13b206ba3a11293a54425d24a19a Mon Sep 17 00:00:00 2001 From: james Date: Sat, 1 Nov 2025 11:57:26 +1030 Subject: [PATCH 501/663] remove async Future.microtask in _updateLayout to improve performance on device rotation and layout changes --- packages/pdfrx/example/viewer/lib/main.dart | 4 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 155 +++++++++--------- 2 files changed, 76 insertions(+), 83 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index eb946f4b..3b5a6c22 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -309,12 +309,12 @@ class _MainPageState extends State with WidgetsBindingObserver { }, ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), - minScale: 0.2, + // minScale: 0.2, maxScale: 8, margin: 10, boundaryMargin: EdgeInsets.all(10), fitMode: FitMode.fit, - pageTransition: PageTransition.discrete, + // pageTransition: PageTransition.discrete, scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 5a463418..63c87407 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -666,9 +666,7 @@ class _PdfViewerState extends State return candidate; } - // Convert document space offset to screen space (multiply by zoom) - final zoom = candidate.zoom; - return candidate.clone()..translateByDouble(-dxDoc * zoom, -dyDoc * zoom, 0, 1); + return candidate.clone()..translateByDouble(-dxDoc, -dyDoc, 0, 1); } void _updateLayout(Size viewSize) { @@ -752,90 +750,86 @@ class _PdfViewerState extends State callOnViewerSizeChanged(); }); } else if (isLayoutChanged || isViewSizeChanged) { - Future.microtask(() async { - if (mounted) { - // Preserve the visual page size by comparing actual page dimensions in layouts - double zoomTo; - if (_currentZoom < _fitScale || _currentZoom == oldFitScale) { - // User was at fit scale or below minimum - use new fit scale - zoomTo = _fitScale; - } else if (oldLayout != null && currentPageNumber != null && _layout != null) { - // Calculate zoom to maintain same visual page size - // Visual size = pageRect.size * zoom, so we scale zoom by page size ratio - final oldPageRect = oldLayout.pageLayouts[currentPageNumber - 1]; - final newPageRect = _layout!.pageLayouts[currentPageNumber - 1]; - - // Use the primary axis size to determine scaling - final isPrimaryVertical = _layout!.primaryAxis == Axis.vertical; - final oldPageSize = isPrimaryVertical ? oldPageRect.height : oldPageRect.width; - final newPageSize = isPrimaryVertical ? newPageRect.height : newPageRect.width; - - if (newPageSize > 0) { - final calculatedZoom = _currentZoom * (oldPageSize / newPageSize); - // Clamp to min/max scale constraints - zoomTo = calculatedZoom.clamp(minScale, widget.params.maxScale); - } else { - zoomTo = _currentZoom; - } - } else { - zoomTo = _currentZoom; - } - - if (isLayoutChanged) { - // if the layout changed, calculate the top-left position in the document - // before the layout change and go to that position in the new layout + // Handle layout/size changes synchronously to prevent flash + // Preserve the visual page size by comparing actual page dimensions in layouts + double zoomTo; + if (_currentZoom < _fitScale || _currentZoom == oldFitScale) { + // User was at fit scale or below minimum - use new fit scale + zoomTo = _fitScale; + } else if (oldLayout != null && currentPageNumber != null && _layout != null) { + // Calculate zoom to maintain same visual page size + // Visual size = pageRect.size * zoom, so we scale zoom by page size ratio + final oldPageRect = oldLayout.pageLayouts[currentPageNumber - 1]; + final newPageRect = _layout!.pageLayouts[currentPageNumber - 1]; + + // Use the primary axis size to determine scaling + final isPrimaryVertical = _layout!.primaryAxis == Axis.vertical; + final oldPageSize = isPrimaryVertical ? oldPageRect.height : oldPageRect.width; + final newPageSize = isPrimaryVertical ? newPageRect.height : newPageRect.width; + + if (newPageSize > 0) { + final calculatedZoom = _currentZoom * (oldPageSize / newPageSize); + // Clamp to min/max scale constraints + zoomTo = calculatedZoom.clamp(minScale, widget.params.maxScale); + } else { + zoomTo = _currentZoom; + } + } else { + zoomTo = _currentZoom; + } - if (oldLayout != null && currentPageNumber != null) { - // Get the hit point in PDF page coordinates (stable across layout changes) + if (isLayoutChanged) { + // if the layout changed, calculate the top-left position in the document + // before the layout change and go to that position in the new layout - final hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); + if (oldLayout != null && currentPageNumber != null) { + // Get the hit point in PDF page coordinates (stable across layout changes) - Offset newOffset; - if (hit == null) { - // Hit is null - top left was in margin area - // Use the page's top-left as the reference point - newOffset = _layout!.pageLayouts[currentPageNumber - 1].topLeft; - print( - "no hit: page ${currentPageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[currentPageNumber - 1]}", - ); - } else { - // Got a valid hit - convert PDF coordinates to new layout - newOffset = hit.offset.toOffsetInDocument( - page: hit.page, - pageRect: _layout!.pageLayouts[hit.page.pageNumber - 1], - ); - print( - "hit: ${hit.offset} ${hit.page.pageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[hit.page.pageNumber - 1]}", - ); - } + final hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); - // preserve the position after a layout change - // Pass pageNumber and boundary information to enable margin snapping - await _goToPosition(documentOffset: newOffset, zoom: zoomTo, pageNumber: currentPageNumber); - } + Offset newOffset; + if (hit == null) { + // Hit is null - top left was in margin area + // Use the page's top-left as the reference point + newOffset = _layout!.pageLayouts[currentPageNumber - 1].topLeft; + print("no hit: oldVisibleRect.topLeft ${oldVisibleRect.topLeft} "); } else { - if (zoomTo != _currentZoom) { - // layout hasn't changed, but size and zoom has - final zoomChange = zoomTo / _currentZoom; - final pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); - - final pivotScale = Matrix4.identity() - ..translateByVector3(pivot) - ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) - ..translateByVector3(-pivot / zoomChange); - - final Matrix4 zoomPivoted = pivotScale * _txController.value; - _adjustBoundaryMargins(viewSize, zoomTo); - _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); - } else { - // size changes (e.g. rotation) can still cause out-of-bounds matrices - // so clamp here - _clampToNearestBoundary(_txController.value, viewSize: viewSize); - } - callOnViewerSizeChanged(); + // Got a valid hit - convert PDF coordinates to new layout + newOffset = hit.offset.toOffsetInDocument( + page: hit.page, + pageRect: _layout!.pageLayouts[hit.page.pageNumber - 1], + ); + print( + "hit: ${hit.offset} ${hit.page.pageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[hit.page.pageNumber - 1]}", + ); } + + // preserve the position after a layout change + // Call _goToPosition without await - with Duration.zero it completes synchronously + // This ensures the matrix is updated before the widget rebuilds, preventing flash + _goToPosition(documentOffset: newOffset, zoom: zoomTo, pageNumber: currentPageNumber); } - }); + } else { + if (zoomTo != _currentZoom) { + // layout hasn't changed, but size and zoom has + final zoomChange = zoomTo / _currentZoom; + final pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); + + final pivotScale = Matrix4.identity() + ..translateByVector3(pivot) + ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) + ..translateByVector3(-pivot / zoomChange); + + final Matrix4 zoomPivoted = pivotScale * _txController.value; + _adjustBoundaryMargins(viewSize, zoomTo); + _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); + } else { + // size changes (e.g. rotation) can still cause out-of-bounds matrices + // so clamp here + _clampToNearestBoundary(_txController.value, viewSize: viewSize); + } + callOnViewerSizeChanged(); + } } else if (currentPageNumber != null && _pageNumber != currentPageNumber) { // In discrete mode, only allow page changes via _snapToPage (not guessed page number) // Exception: during initialization when _pageNumber is null @@ -2922,7 +2916,6 @@ class _PdfViewerState extends State _adjustBoundaryMargins(_viewSize!, zoom); // Apply margin snapping if page number and boundary info are provided - // DISABLED: Margin snapping conflicts with zoom-to-maintain-page-size during rotation final marginAdjusted = pageNumber != null ? _calcMatrixForMarginSnappedToNearestBoundary(m, pageNumber: pageNumber, viewSize: _viewSize!) : m; From d696419cccb922eda74f62518f8c0037b67b44f2 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 1 Nov 2025 12:07:03 +1030 Subject: [PATCH 502/663] remove debug and testing flags --- packages/pdfrx/example/viewer/lib/main.dart | 9 +++------ packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 8 -------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 3b5a6c22..0208fc3b 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -309,13 +309,10 @@ class _MainPageState extends State with WidgetsBindingObserver { }, ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), - // minScale: 0.2, maxScale: 8, - margin: 10, - boundaryMargin: EdgeInsets.all(10), - fitMode: FitMode.fit, - // pageTransition: PageTransition.discrete, - scrollPhysics: PdfViewerParams.getScrollPhysics(context), + //fitMode: FitMode.fill, + pageTransition: PageTransition.discrete, + //scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 63c87407..1237a264 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -792,16 +792,12 @@ class _PdfViewerState extends State // Hit is null - top left was in margin area // Use the page's top-left as the reference point newOffset = _layout!.pageLayouts[currentPageNumber - 1].topLeft; - print("no hit: oldVisibleRect.topLeft ${oldVisibleRect.topLeft} "); } else { // Got a valid hit - convert PDF coordinates to new layout newOffset = hit.offset.toOffsetInDocument( page: hit.page, pageRect: _layout!.pageLayouts[hit.page.pageNumber - 1], ); - print( - "hit: ${hit.offset} ${hit.page.pageNumber} -> $newOffset pagerect: ${_layout!.pageLayouts[hit.page.pageNumber - 1]}", - ); } // preserve the position after a layout change @@ -1065,7 +1061,6 @@ class _PdfViewerState extends State } } widget.params.onInteractionUpdate?.call(details); - print("x:${_txController.value.x} y:${_txController.value.y}"); } void _onAnimationEnd() { @@ -1821,7 +1816,6 @@ class _PdfViewerState extends State // Page content starts at: padding + scaled margin final crossAxisPageContentStart = viewportPaddingCross /*+ (margin * pageScale) */; - print('_addDiscreteSpacing: scaledPageWidth=$scaledPageWidth'); final newRect = isPrimaryVertical ? Rect.fromLTWH(crossAxisPageContentStart, pageContentStart, scaledPageWidth, scaledPageHeight) : Rect.fromLTWH(pageContentStart, crossAxisPageContentStart, scaledPageWidth, scaledPageHeight); @@ -2815,7 +2809,6 @@ class _PdfViewerState extends State await _animController.animateTo(1.0, duration: duration, curve: curve); } finally { _animGoTo?.removeListener(update); - print("_goTo: completed x:${_txController.value.x} y:${_txController.value.y}"); } } @@ -2910,7 +2903,6 @@ class _PdfViewerState extends State zoom = zoom ?? _currentZoom; final tx = -documentOffset.dx * zoom; final ty = -documentOffset.dy * zoom; - print("_goToPosition: tx:$tx ty:$ty zoom:$zoom (docOffset:$documentOffset)"); final m = Matrix4.compose(vec.Vector3(tx, ty, 0), vec.Quaternion.identity(), vec.Vector3(zoom, zoom, zoom)); _adjustBoundaryMargins(_viewSize!, zoom); From ec2072f307c3ac37fdf6226e453b29b21f0f4bac Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 2 Nov 2025 02:34:47 +0900 Subject: [PATCH 503/663] pdf_combine now supports drag-and-drop of platform's files --- .../pdfrx/example/pdf_combine/lib/main.dart | 296 +++++++++++++----- .../flutter/generated_plugin_registrant.cc | 8 + .../linux/flutter/generated_plugins.cmake | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 6 + .../pdfrx/example/pdf_combine/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 2 + 7 files changed, 244 insertions(+), 77 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 0ad83035..8cdc3add 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -6,6 +6,7 @@ import 'package:animated_reorderable_list/animated_reorderable_list.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; +import 'package:super_drag_and_drop/super_drag_and_drop.dart'; import 'save_helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; @@ -91,6 +92,17 @@ class DocumentManager { return docId; } + Future loadDocumentFromBytes(String name, Uint8List bytes) async { + final docId = _nextDocId++; + final doc = await PdfDocument.openData( + bytes, + passwordProvider: passwordProvider != null ? () => passwordProvider!(docId, name) : null, + ); + _documents[docId] = doc; + _pageRefCounts[docId] = 0; + return docId; + } + PdfDocument? getDocument(int docId) => _documents[docId]; void addReference(int docId) { @@ -135,6 +147,7 @@ class _PdfCombinePageState extends State { bool _isLoading = false; bool _disableDragging = false; bool _isTouchDevice = true; + bool _isDraggingOver = false; @override void dispose() { @@ -157,12 +170,60 @@ class _PdfCombinePageState extends State { try { for (final file in files) { - final docId = await _docManager.loadDocument(file.name, file.path); - final doc = _docManager.getDocument(docId); - if (doc != null) { - for (var i = 0; i < doc.pages.length; i++) { - _docManager.addReference(docId); - _pages.add(PageItem(documentId: docId, documentName: file.name, pageIndex: i, page: doc.pages[i])); + try { + final docId = await _docManager.loadDocument(file.name, file.path); + final doc = _docManager.getDocument(docId); + if (doc != null) { + for (var i = 0; i < doc.pages.length; i++) { + _docManager.addReference(docId); + _pages.add(PageItem(documentId: docId, documentName: file.name, pageIndex: i, page: doc.pages[i])); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading "${file.name}": $e'))); + } + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + int _droppedCount = 0; + + Future _loadDropFiles(DropSession session) async { + setState(() { + _isLoading = true; + }); + + try { + for (final item in session.items) { + try { + final file = await _loadDataFromSessionItem(item, Formats.pdf); + if (file != null) { + ++_droppedCount; + final fileName = file.fileName ?? 'dropped_file_$_droppedCount.pdf'; + final docId = await _docManager.loadDocumentFromBytes(fileName, file.data); + final doc = _docManager.getDocument(docId); + if (doc != null) { + for (var i = 0; i < doc.pages.length; i++) { + _docManager.addReference(docId); + _pages.add(PageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); + } + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading dropped file: $e'))); } } } @@ -179,6 +240,26 @@ class _PdfCombinePageState extends State { } } + Future<({String? fileName, Uint8List data})?> _loadDataFromSessionItem(DropItem item, FileFormat format) async { + final reader = item.dataReader!; + if (!reader.canProvide(format)) { + return null; + } + var fileName = await reader.getSuggestedName(); + final completer = Completer(); + try { + final result = reader.getFile(format, (file) async { + completer.complete(await file.readAll()); + }); + if (result == null) { + completer.completeError(Exception('Not supported format: $format')); + } + } catch (e) { + completer.completeError(e); + } + return (fileName: fileName, data: await completer.future); + } + void _removePage(int index) { setState(() { final pageItem = _pages[index]; @@ -236,81 +317,142 @@ class _PdfCombinePageState extends State { const SizedBox(width: 16), ], ), - body: _isLoading - ? const Center(child: CircularProgressIndicator()) - : _pages.isEmpty - ? Center( - child: Text.rich( - TextSpan( - children: [ - TextSpan(text: 'Tap the following button to add PDF files!\n\n'), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickPdfFiles()), + body: DropRegion( + formats: Formats.standardFormats, + hitTestBehavior: HitTestBehavior.opaque, + onDropOver: (event) { + if (event.session.items.any((item) => item.canProvide(Formats.fileUri))) { + setState(() { + _isDraggingOver = true; + }); + return DropOperation.copy; + } + return DropOperation.none; + }, + onDropLeave: (event) { + setState(() { + _isDraggingOver = false; + }); + }, + onPerformDrop: (event) async { + await _loadDropFiles(event.session); + setState(() { + _isDraggingOver = false; + }); + }, + child: Stack( + children: [ + _isLoading + ? const Center(child: CircularProgressIndicator()) + : _pages.isEmpty + ? Center( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'Tap the following button to add PDF files or drag & drop PDF files here!\n\n', + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickPdfFiles()), + ), + ], + ), + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, ), - ], - ), - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ), - ) - : LayoutBuilder( - builder: (context, constraints) { - final w = constraints.maxWidth; - final int crossAxisCount; - if (w < 120) { - crossAxisCount = 1; - } else if (w < 400) { - crossAxisCount = 2; - } else if (w < 800) { - crossAxisCount = 3; - } else if (w < 1200) { - crossAxisCount = 4; - } else { - crossAxisCount = w ~/ 300; - } - return Listener( - onPointerMove: (event) { - setState(() { - _isTouchDevice = event.kind == PointerDeviceKind.touch; - }); - }, - onPointerHover: (event) { - setState(() { - _isTouchDevice = event.kind == PointerDeviceKind.touch; - }); - }, - child: AnimatedReorderableGridView( - items: _pages, - isSameItem: (a, b) => a.id == b.id, - itemBuilder: (context, index) { - final pageItem = _pages[index]; - return _PageThumbnail( - key: ValueKey(pageItem.id), - page: pageItem.page, - rotationOverride: pageItem.rotationOverride, - onRemove: () => _removePage(index), - onRotateLeft: () => _rotatePageLeft(index), - currentIndex: index, - dragDisabler: _disableDraggingOnChild, + ) + : LayoutBuilder( + builder: (context, constraints) { + final w = constraints.maxWidth; + final int crossAxisCount; + if (w < 120) { + crossAxisCount = 1; + } else if (w < 400) { + crossAxisCount = 2; + } else if (w < 800) { + crossAxisCount = 3; + } else if (w < 1200) { + crossAxisCount = 4; + } else { + crossAxisCount = w ~/ 300; + } + return Listener( + onPointerMove: (event) { + setState(() { + _isTouchDevice = event.kind == PointerDeviceKind.touch; + }); + }, + onPointerHover: (event) { + setState(() { + _isTouchDevice = event.kind == PointerDeviceKind.touch; + }); + }, + child: AnimatedReorderableGridView( + items: _pages, + isSameItem: (a, b) => a.id == b.id, + itemBuilder: (context, index) { + final pageItem = _pages[index]; + return _PageThumbnail( + key: ValueKey(pageItem.id), + page: pageItem.page, + rotationOverride: pageItem.rotationOverride, + onRemove: () => _removePage(index), + onRotateLeft: () => _rotatePageLeft(index), + currentIndex: index, + dragDisabler: _disableDraggingOnChild, + ); + }, + sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), + insertDuration: const Duration(milliseconds: 300), + removeDuration: const Duration(milliseconds: 300), + dragStartDelay: _isTouchDevice || _disableDragging + ? const Duration(milliseconds: 200) + : Duration.zero, + onReorder: (oldIndex, newIndex) { + setState(() { + final removed = _pages.removeAt(oldIndex); + _pages.insert(newIndex, removed); + }); + }, + ), ); }, - sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), - insertDuration: const Duration(milliseconds: 300), - removeDuration: const Duration(milliseconds: 300), - dragStartDelay: _isTouchDevice || _disableDragging - ? const Duration(milliseconds: 200) - : Duration.zero, - onReorder: (oldIndex, newIndex) { - setState(() { - final removed = _pages.removeAt(oldIndex); - _pages.insert(newIndex, removed); - }); - }, ), - ); - }, - ), + if (_isDraggingOver) + Container( + color: Colors.blue.withValues(alpha: 0.1), + child: Center( + child: Container( + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 3, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.file_upload, size: 64, color: Theme.of(context).colorScheme.primary), + const SizedBox(height: 16), + Text( + 'Drop PDF files here', + style: Theme.of( + context, + ).textTheme.headlineSmall?.copyWith(color: Theme.of(context).colorScheme.primary), + ), + ], + ), + ), + ), + ), + ], + ), + ), ); } } diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc index 7299b5cf..9876374e 100644 --- a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,20 @@ #include "generated_plugin_registrant.h" #include +#include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); + irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); + g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); + super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake index 409ae740..cf30839a 100644 --- a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake @@ -4,6 +4,8 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + irondash_engine_context + super_native_extensions url_launcher_linux ) diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift index 56883a1b..c530339c 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,16 +5,22 @@ import FlutterMacOS import Foundation +import device_info_plus import file_selector_macos +import irondash_engine_context import path_provider_foundation import pdfrx import share_plus +import super_native_extensions import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 5957f765..343dd9b6 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: path_provider: ^2.1.5 animated_reorderable_list: ^1.3.0 web: ^1.1.1 + super_drag_and_drop: ^0.9.0 dev_dependencies: flutter_test: diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc index 5e62361f..0edb4238 100644 --- a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc @@ -7,14 +7,20 @@ #include "generated_plugin_registrant.h" #include +#include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + IrondashEngineContextPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + SuperNativeExtensionsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake index 99584d06..56b35bbb 100644 --- a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake @@ -4,7 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + irondash_engine_context share_plus + super_native_extensions url_launcher_windows ) From 95d4b59140d9c1a9e7740c22b96260dd293d96e2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 2 Nov 2025 16:40:36 +0900 Subject: [PATCH 504/663] WIP --- packages/pdfrx/assets/pdfium_worker.js | 82 +++++++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 27 +++ .../Sources/PdfrxCoregraphicsPlugin.swift | 160 ++++++++++++++++++ .../lib/pdfrx_coregraphics.dart | 85 +++++++++- packages/pdfrx_engine/lib/pdfrx_engine.dart | 2 +- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 11 +- .../lib/src/native/pdf_file_cache.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 65 ++++++- ...{pdfrx_document.dart => pdf_document.dart} | 13 ++ .../lib/src/pdf_document_event.dart | 2 +- .../lib/src/pdf_outline_node.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_page.dart | 2 +- .../lib/src/pdf_page_proxies.dart | 2 +- .../lib/src/pdfrx_entry_functions.dart | 11 +- 14 files changed, 451 insertions(+), 15 deletions(-) rename packages/pdfrx_engine/lib/src/{pdfrx_document.dart => pdf_document.dart} (94%) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index aac39b72..3491179b 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1867,6 +1867,87 @@ function createNewDocument() { return _loadDocument(docHandle, false, () => {}); } +/** + * Set pixel data for an image object + * @param {number} imageObj Image object handle + * @param {ArrayBuffer} pixels BGRA8888 pixel data + * @param {number} pixelWidth Image width in pixels + * @param {number} pixelHeight Image height in pixels + * @returns {PdfDocument|PdfError} + */ +function _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight) { + const pixelDataPtr = Pdfium.wasmExports._malloc(pixels.byteLength); + if (!pixelDataPtr) throw new Error('Failed to allocate memory for image pixels'); + HEAP8.set(new Uint8Array(pixels), pixelDataPtr); + const FPDFBitmap_BGRA = 4; + const bitmapHandle = Pdfium.wasmExports.FPDFBitmap_CreateEx( + pixelWidth, + pixelHeight, + FPDFBitmap_BGRA, + pixelDataPtr, + pixelWidth * 4 + ); + if (!bitmapHandle) { + Pdfium.wasmExports._free(pixelDataPtr); + throw new Error('Failed to create bitmap for image object'); + } + const pageArrayPtr = Pdfium.wasmExports._malloc(4); // Allocate space for one pointer + HEAP32[pageArrayPtr >> 2] = pageHandle; + const result = Pdfium.wasmExports.FPDFImageObj_SetBitmap(pageArrayPtr, 1, imageObj, bitmapHandle); + Pdfium.wasmExports._free(pageArrayPtr); + Pdfium.wasmExports._free(pixelDataPtr); + Pdfium.wasmExports.FPDFBitmap_Destroy(bitmapHandle); + if (!result) { + throw new Error('Failed to set bitmap for image object'); + } +} + +/** + * Create a PDF document from an image + * @param {ArrayBuffer} pixels BGRA8888 pixel data + * @param {number} pixelWidth Image width in pixels + * @param {number} pixelHeight Image height in pixels + * @param {number} width PDF page width in points + * @param {number} height PDF page height in points + * @returns {PdfDocument|PdfError} + */ +function createDocumentFromImage(pixels, pixelWidth, pixelHeight, width, height) { + let docHandle = 0, pageHandle = 0, imageObj = 0; + try { + docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); + if (!docHandle) throw new Error('Failed to create new PDF document'); + pageHandle = Pdfium.wasmExports.FPDFPage_New(docHandle, 0, width, height); + if (!pageHandle) throw new Error('Failed to create new PDF page'); + imageObj = Pdfium.wasmExports.FPDFPageObj_NewImageObj(docHandle); + if (!imageObj) throw new Error('Failed to create new image object'); + _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight); + + const scaleX = width / pixelWidth; + const scaleY = height / pixelHeight; + Pdfium.wasmExports.FPDFImageObj_SetMatrix( + imageObj, + scaleX, // a: horizontal scaling + 0, // b: vertical skewing + 0, // c: horizontal skewing + -scaleY, // d: vertical scaling (negative to flip Y-axis) + 0, // e: horizontal translation + height // f: vertical translation (move to top after flip) + ); + Pdfium.wasmExports.FPDFPage_InsertObject(pageHandle, imageObj); + imageObj = 0; // ownership transferred to pageHandle + Pdfium.wasmExports.FPDFPage_GenerateContent(pageHandle); + const docHandleReturn = docHandle; + docHandle = 0; // prevent cleanup in finally + return _loadDocument(docHandleReturn, false, () => {}); + } catch (e) { + return { errorCode: -1, errorCodeStr: 'Exception: ' + e.toString() }; + } finally { + Pdfium.wasmExports.FPDFPageObj_Destroy(imageObj); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + } +} + /** * Functions that can be called from the main thread */ @@ -1874,6 +1955,7 @@ const functions = { loadDocumentFromUrl, loadDocumentFromData, createNewDocument, + createDocumentFromImage, loadPagesProgressively, closeDocument, loadOutline, diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 844308b6..e943cf82 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -313,6 +313,33 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); } + @override + Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }) async { + await init(); + final jsPixels = image.pixels.buffer.toJS; + final result = await _sendCommand( + 'createDocumentFromImage', + parameters: { + 'pixels': jsPixels, + 'pixelWidth': image.width, + 'pixelHeight': image.height, + 'width': width, + 'height': height, + }, + transfer: [jsPixels].toJS, + ); + final errorCode = (result['errorCode'] as num?)?.toInt(); + if (errorCode != null) { + throw StateError('Failed to create document from image: ${result['errorCodeStr']} ($errorCode)'); + } + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); + } + @override Future reloadFonts() async { await init(); diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index f600d142..2a164074 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -28,6 +28,10 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { result(nil) case "openDocument": openDocument(arguments: call.arguments, result: result) + case "createNewDocument": + createNewDocument(arguments: call.arguments, result: result) + case "createDocumentFromImage": + createDocumentFromImage(arguments: call.arguments, result: result) case "renderPage": renderPage(arguments: call.arguments, result: result) case "loadPageText": @@ -136,6 +140,162 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } + /// Creates a new empty PDF document + private func createNewDocument(arguments: Any?, result: @escaping FlutterResult) { + // Create a truly empty PDF document with no pages + // PDFDocument() creates an empty document directly + guard let pdfDocument = PDFDocument() else { + result( + FlutterError( + code: "pdf-document-failure", message: "Failed to create empty PDFDocument.", details: nil + )) + return + } + + // Register the document + let handle = nextHandle + nextHandle += 1 + documents[handle] = pdfDocument + + result([ + "handle": handle, + "isEncrypted": false, + "pages": [] + ]) + } + + /// Creates a PDF document from BGRA8888 image data + private func createDocumentFromImage(arguments: Any?, result: @escaping FlutterResult) { + guard let args = arguments as? [String: Any] else { + result( + FlutterError( + code: "bad-arguments", message: "Invalid arguments for createDocumentFromImage.", details: nil + )) + return + } + + guard + let pixels = args["pixels"] as? FlutterStandardTypedData, + let pixelWidth = args["pixelWidth"] as? Int, + let pixelHeight = args["pixelHeight"] as? Int, + let width = args["width"] as? Double, + let height = args["height"] as? Double + else { + result( + FlutterError( + code: "bad-arguments", message: "Missing required parameters for createDocumentFromImage.", details: nil + )) + return + } + + // Create a CGContext from BGRA pixel data + let pixelData = pixels.data + let bytesPerPixel = 4 + let bytesPerRow = pixelWidth * bytesPerPixel + let expectedDataSize = bytesPerRow * pixelHeight + + guard pixelData.count == expectedDataSize else { + result( + FlutterError( + code: "invalid-data", message: "Pixel data size mismatch. Expected \(expectedDataSize) bytes, got \(pixelData.count).", details: nil + )) + return + } + + // Create a mutable copy of pixel data since CGContext requires mutable data + var mutablePixelData = pixelData + + guard + let context = mutablePixelData.withUnsafeMutableBytes({ (ptr: UnsafeMutableRawBufferPointer) -> CGContext? in + CGContext( + data: ptr.baseAddress, + width: pixelWidth, + height: pixelHeight, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue + ) + }) + else { + result( + FlutterError( + code: "context-failure", message: "Failed to create bitmap context from image data.", details: nil + )) + return + } + + guard let cgImage = context.makeImage() else { + result( + FlutterError( + code: "image-failure", message: "Failed to create CGImage from context.", details: nil + )) + return + } + + // Create a PDF document using NSMutableData to capture the output + let pdfData = NSMutableData() + var mediaBox = CGRect(x: 0, y: 0, width: width, height: height) + + guard let dataConsumer = CGDataConsumer(data: pdfData) else { + result( + FlutterError( + code: "data-consumer-failure", message: "Failed to create PDF data consumer.", details: nil + )) + return + } + + guard let pdfContext = CGContext(consumer: dataConsumer, mediaBox: &mediaBox, nil) else { + result( + FlutterError( + code: "pdf-context-failure", message: "Failed to create PDF context.", details: nil + )) + return + } + + // Begin PDF page + pdfContext.beginPDFPage(nil) + + // Draw the image to fill the entire page + pdfContext.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + + // End PDF page + pdfContext.endPDFPage() + + // Close the PDF context + pdfContext.closePDF() + + // Create PDFDocument from data + guard let pdfDocument = PDFDocument(data: pdfData) else { + result( + FlutterError( + code: "pdf-document-failure", message: "Failed to create PDFDocument from generated PDF data.", details: nil + )) + return + } + + // Register the document + let handle = nextHandle + nextHandle += 1 + documents[handle] = pdfDocument + + var pageInfos: [[String: Any]] = [] + if let page = pdfDocument.page(at: 0) { + let bounds = page.bounds(for: .mediaBox) + pageInfos.append([ + "width": Double(bounds.width), + "height": Double(bounds.height), + "rotation": page.rotation + ]) + } + + result([ + "handle": handle, + "isEncrypted": false, + "pages": pageInfos + ]) + } + /// Renders the requested page using a CoreGraphics bitmap context and returns ARGB pixels. private func renderPage(arguments: Any?, result: @escaping FlutterResult) { guard diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index a94234da..aa791051 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -5,6 +5,8 @@ import 'package:flutter/services.dart'; import 'package:pdfrx_engine/pdfrx_engine.dart'; // ignore: implementation_imports import 'package:pdfrx_engine/src/native/pdf_file_cache.dart'; +// ignore: implementation_imports +import 'package:pdfrx_engine/src/pdf_page_proxies.dart'; const _kPasswordErrorCode = 'wrong-password'; @@ -182,8 +184,49 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { @override Future createNew({required String sourceName}) async { - throw UnimplementedError( - 'createNew() is not implemented for CoreGraphics backend.', + await init(); + final result = await _channel.invokeMapMethod( + 'createNewDocument', + {'sourceName': sourceName}, + ); + if (result == null) { + throw const PdfException('Failed to create empty PDF document.'); + } + return _CoreGraphicsPdfDocument.fromPlatformMap( + channel: _channel, + result: result, + sourceName: sourceName, + useProgressiveLoading: false, + onDispose: null, + ); + } + + @override + Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }) async { + await init(); + final result = await _channel + .invokeMapMethod('createDocumentFromImage', { + 'pixels': image.pixels, + 'pixelWidth': image.width, + 'pixelHeight': image.height, + 'width': width, + 'height': height, + 'sourceName': sourceName, + }); + if (result == null) { + throw const PdfException('Failed to create PDF document from image.'); + } + return _CoreGraphicsPdfDocument.fromPlatformMap( + channel: _channel, + result: result, + sourceName: sourceName, + useProgressiveLoading: false, + onDispose: null, ); } @@ -404,10 +447,40 @@ class _CoreGraphicsPdfDocument extends PdfDocument { } @override - set pages(List value) { - throw UnimplementedError( - 'Setting pages is not implemented for CoreGraphics backend.', - ); + set pages(List newPages) { + final pages = []; + final changes = {}; + for (final newPage in newPages) { + if (pages.length < _pages.length) { + final old = _pages[pages.length]; + if (identical(newPage, old)) { + pages.add(newPage); + continue; + } + } + + if (newPage.unwrap<_CoreGraphicsPdfPage>() == null) { + throw ArgumentError( + 'Unsupported PdfPage instances found at [${pages.length}]', + 'newPages', + ); + } + + final newPageNumber = pages.length + 1; + pages.add(newPage.withPageNumber(newPageNumber)); + + final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); + if (oldPageIndex != -1) { + changes[newPageNumber] = PdfPageStatusChange.moved( + oldPageNumber: oldPageIndex + 1, + ); + } else { + changes[newPageNumber] = PdfPageStatusChange.modified; + } + } + + _pages = pages; + subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); } @override diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 660f8f7f..11557e2d 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -2,6 +2,7 @@ library; export 'src/mock/pdfrx_initialize_mock.dart' if (dart.library.io) 'src/pdfrx_initialize_dart.dart'; export 'src/pdf_dest.dart'; +export 'src/pdf_document.dart'; export 'src/pdf_document_event.dart'; export 'src/pdf_exception.dart'; export 'src/pdf_font_query.dart'; @@ -15,5 +16,4 @@ export 'src/pdf_rect.dart'; export 'src/pdf_text.dart'; export 'src/pdfrx.dart'; export 'src/pdfrx_dart.dart'; -export 'src/pdfrx_document.dart'; export 'src/pdfrx_entry_functions.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 460bd951..7eb562c7 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:typed_data'; -import '../pdfrx_document.dart'; +import '../pdf_document.dart'; +import '../pdf_page.dart'; import '../pdfrx_entry_functions.dart'; /// This is an empty implementation of [PdfrxEntryFunctions] that just throws [UnimplementedError]. @@ -81,6 +82,14 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future createNew({required String sourceName}) => unimplemented(); + @override + Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }) => unimplemented(); + @override Future reloadFonts() => unimplemented(); diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index 9406853f..f5c7c8d9 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -9,9 +9,9 @@ import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:synchronized/extension.dart'; +import '../pdf_document.dart'; import '../pdf_exception.dart'; import '../pdfrx.dart'; -import '../pdfrx_document.dart'; import '../pdfrx_entry_functions.dart'; import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 94308468..a6b3368b 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -12,6 +12,7 @@ import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import '../pdf_dest.dart'; +import '../pdf_document.dart'; import '../pdf_document_event.dart'; import '../pdf_exception.dart'; import '../pdf_font_query.dart'; @@ -24,7 +25,6 @@ import '../pdf_permissions.dart'; import '../pdf_rect.dart'; import '../pdf_text.dart'; import '../pdfrx.dart'; -import '../pdfrx_document.dart'; import '../pdfrx_entry_functions.dart'; import '../utils/shuffle_in_place.dart'; import 'native_utils.dart'; @@ -444,6 +444,69 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { ); } + @override + Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }) async { + await _init(); + final doc = await (await backgroundWorker).computeWithArena( + (arena, params) { + final document = pdfium.FPDF_CreateNewDocument(); + final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); + final newPages = arena.allocate(sizeOf>()); + newPages.value = newPage; + + final memBuffer = arena.allocate(params.pixels.length); + final bufferList = memBuffer.asTypedList(params.pixels.length); + bufferList.setAll(0, params.pixels); + + final bitmap = pdfium.FPDFBitmap_CreateEx( + params.pixelWidth, + params.pixelHeight, + pdfium_bindings.FPDFBitmap_BGRA, + memBuffer.cast(), + params.pixelWidth * 4, + ); + final imageObj = pdfium.FPDFPageObj_NewImageObj(document); + pdfium.FPDFImageObj_SetBitmap(newPages, 1, imageObj, bitmap); + + final scaleX = params.width / params.pixelWidth; + final scaleY = -params.height / params.pixelHeight; + pdfium.FPDFImageObj_SetMatrix( + imageObj, + scaleX, // a: horizontal scaling + 0, // b: vertical skewing + 0, // c: horizontal skewing + -scaleY, // d: vertical scaling (negative to flip Y-axis) + 0, // e: horizontal translation + params.height, // f: vertical translation (move to top after flip + ); + pdfium.FPDFPage_InsertObject(newPage, imageObj); // imageObj is now owned by the page + pdfium.FPDFPage_GenerateContent(newPage); + pdfium.FPDF_ClosePage(newPage); + pdfium.FPDFBitmap_Destroy(bitmap); + return document.address; + }, + ( + pixels: image.pixels, + pixelWidth: image.width, + pixelHeight: image.height, + width: width, + height: height, + sourceName: sourceName, + ), + ); + return _PdfDocumentPdfium.fromPdfDocument( + pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), + sourceName: sourceName, + useProgressiveLoading: false, + disposeCallback: null, + ); + } + static String _getPdfiumErrorString([int? error]) { error ??= pdfium.FPDF_GetLastError(); final errStr = _errorMappings[error]; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_document.dart b/packages/pdfrx_engine/lib/src/pdf_document.dart similarity index 94% rename from packages/pdfrx_engine/lib/src/pdfrx_document.dart rename to packages/pdfrx_engine/lib/src/pdf_document.dart index 7385121d..7e88b885 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_document.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document.dart @@ -116,6 +116,19 @@ abstract class PdfDocument { static Future createNew({required String sourceName}) => PdfrxEntryFunctions.instance.createNew(sourceName: sourceName); + /// Creating a PDF document from an image. + /// + /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not + /// unique for each source, the viewer may not work correctly. + /// [width] and [height] are the dimensions of the image in PDF units (1/72 inch). + /// [image] is the PDF image to create the document from. + static Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }) => PdfrxEntryFunctions.instance.createFromImage(image, width: width, height: height, sourceName: sourceName); + /// Opening the PDF from custom source. /// /// On Flutter Web, this function is not supported and throws an exception. diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart index d2821335..08885d90 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document_event.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -1,6 +1,6 @@ +import 'pdf_document.dart'; import 'pdf_font_query.dart'; import 'pdf_page_status_change.dart'; -import 'pdfrx_document.dart'; /// PDF document event types. enum PdfDocumentEventType { diff --git a/packages/pdfrx_engine/lib/src/pdf_outline_node.dart b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart index 803d961c..5e22c0d3 100644 --- a/packages/pdfrx_engine/lib/src/pdf_outline_node.dart +++ b/packages/pdfrx_engine/lib/src/pdf_outline_node.dart @@ -1,5 +1,5 @@ import 'pdf_dest.dart'; -import 'pdfrx_document.dart'; +import 'pdf_document.dart'; import 'utils/list_equals.dart'; /// Outline (a.k.a. Bookmark) node in PDF document. diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index 17354ac9..f9573b55 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -1,10 +1,10 @@ import 'dart:typed_data'; +import 'pdf_document.dart'; import 'pdf_link.dart'; import 'pdf_page_proxies.dart'; import 'pdf_text.dart'; import 'pdf_text_formatter.dart'; -import 'pdfrx_document.dart'; /// Handles a PDF page in [PdfDocument]. /// diff --git a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart index e12fef91..f974ed98 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart @@ -1,8 +1,8 @@ +import 'pdf_document.dart'; import 'pdf_link.dart'; import 'pdf_page.dart'; import 'pdf_text.dart'; import 'pdf_text_formatter.dart'; -import 'pdfrx_document.dart'; /// Proxy interface for [PdfPage]. /// diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 8f7f2d3f..8fcfc6f9 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'dart:typed_data'; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; +import 'pdf_document.dart'; +import 'pdf_page.dart'; import 'pdfrx.dart'; -import 'pdfrx_document.dart'; /// The class is used to implement Pdfrx's backend functions. /// @@ -84,6 +85,14 @@ abstract class PdfrxEntryFunctions { /// See [PdfDocument.createNew]. Future createNew({required String sourceName}); + /// See [PdfDocument.createFromImage]. + Future createFromImage( + PdfImage image, { + required double width, + required double height, + required String sourceName, + }); + /// Reload the fonts. Future reloadFonts(); From 7e76cb98cf764583ae01050f0381a0fc9a75cbc6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 2 Nov 2025 23:42:32 +0900 Subject: [PATCH 505/663] WIP --- .../example/pdf_combine/lib/file_data.dart | 92 +++++++++++++ .../pdfrx/example/pdf_combine/lib/main.dart | 128 ++++++------------ .../pdfrx/example/pdf_combine/pubspec.yaml | 3 +- packages/pdfrx_engine/lib/src/pdf_page.dart | 29 ++++ 4 files changed, 168 insertions(+), 84 deletions(-) create mode 100644 packages/pdfrx/example/pdf_combine/lib/file_data.dart diff --git a/packages/pdfrx/example/pdf_combine/lib/file_data.dart b/packages/pdfrx/example/pdf_combine/lib/file_data.dart new file mode 100644 index 00000000..82d997de --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/lib/file_data.dart @@ -0,0 +1,92 @@ +import 'dart:async'; + +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/foundation.dart'; +import 'package:super_clipboard/super_clipboard.dart'; +import 'package:super_drag_and_drop/super_drag_and_drop.dart'; + +/// Provide access to files without exposing platform-specific APIs. +abstract class FileIterator { + /// Iterates over the files, invoking [action] for each file. + Future iterateFiles(FutureOr Function(FileData fileData) action); + + /// Creates a [FileIterator] from a [DropSession]. + static FileIterator fromDropSession(DropSession session) { + return _FileIteratorFromDropSession(session); + } + + /// Creates a [FileIterator] from a list of [XFile]s. + static FileIterator fromXFileList(List xFileList) { + return _FileIteratorFromXFileList(xFileList); + } +} + +class _FileIteratorFromDropSession implements FileIterator { + _FileIteratorFromDropSession(this.session); + final DropSession session; + + @override + Future iterateFiles(FutureOr Function(FileData fileData) action) async { + for (final item in session.items) { + try { + final reader = item.dataReader; + if (reader != null) { + final fileUri = await _getValue(reader, Formats.fileUri); + await action(_FileDataFromDataFunction(fileUri?.toFilePath(), () => _getFile(reader))); + } + } catch (e) { + debugPrint('Error reading dropped file item: $e'); + } + } + } + + Future _getValue(DataReader reader, SimpleValueFormat format) async { + if (!reader.canProvide(format)) return null; + final completer = Completer(); + reader.getValue(format, (value) => completer.complete(value), onError: (error) => completer.completeError(error)); + return completer.future; + } + + Future _getFile(DataReader reader, [FileFormat? format]) async { + final completer = Completer(); + reader.getFile( + format, + (dataReaderFile) => completer.complete(dataReaderFile.readAll()), + onError: (error) => completer.completeError(error), + ); + return completer.future; + } +} + +class _FileIteratorFromXFileList implements FileIterator { + _FileIteratorFromXFileList(this.xFileList); + final List xFileList; + + @override + Future iterateFiles(FutureOr Function(FileData fileData) action) async { + for (final xFile in xFileList) { + final fileData = _FileDataFromDataFunction(xFile.path, () => xFile.readAsBytes()); + await action(fileData); + } + } +} + +/// Abstraction for file data access. +abstract class FileData { + /// The file path, if available. + String? get filePath; + + /// Loads the file data as bytes. + Future loadData(); +} + +/// FileData implementation that loads data using a provided function. +class _FileDataFromDataFunction implements FileData { + _FileDataFromDataFunction(this.filePath, this.loadDataFunction); + @override + final String? filePath; + final Future Function() loadDataFunction; + + @override + Future loadData() => loadDataFunction(); +} diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 8cdc3add..c3d29ee1 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; import 'package:super_drag_and_drop/super_drag_and_drop.dart'; +import 'file_data.dart'; import 'save_helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; void main() { @@ -76,28 +77,41 @@ class PageItem { class DocumentManager { DocumentManager(this.passwordProvider); - final FutureOr Function(int docId, String name)? passwordProvider; + final FutureOr Function(String name)? passwordProvider; final Map _documents = {}; final Map _pageRefCounts = {}; int _nextDocId = 0; Future loadDocument(String name, String filePath) async { - final docId = _nextDocId++; final doc = await PdfDocument.openFile( filePath, - passwordProvider: passwordProvider != null ? () => passwordProvider!(docId, name) : null, + passwordProvider: passwordProvider != null ? () => passwordProvider!(name) : null, ); + final docId = _nextDocId++; _documents[docId] = doc; _pageRefCounts[docId] = 0; return docId; } Future loadDocumentFromBytes(String name, Uint8List bytes) async { + PdfDocument doc; + try { + doc = await PdfDocument.openData( + bytes, + passwordProvider: passwordProvider != null ? () => passwordProvider!(name) : null, + ); + } catch (e) { + final image = await decodeImageFromList(bytes); + final width = image.width * 300 / 72; + final height = image.height * 300 / 72; + final pdfImage = PdfImage.createFromBgraData( + (await image.toByteData(format: ImageByteFormat.rawRgba))!.buffer.asUint8List(), + width: image.width, + height: image.height, + ); + doc = await PdfDocument.createFromImage(pdfImage, width: width, height: height, sourceName: name); + } final docId = _nextDocId++; - final doc = await PdfDocument.openData( - bytes, - passwordProvider: passwordProvider != null ? () => passwordProvider!(docId, name) : null, - ); _documents[docId] = doc; _pageRefCounts[docId] = 0; return docId; @@ -141,7 +155,7 @@ class PdfCombinePage extends StatefulWidget { } class _PdfCombinePageState extends State { - late final _docManager = DocumentManager((docId, name) => passwordDialog(name, context)); + late final _docManager = DocumentManager((name) => passwordDialog(name, context)); final _pages = []; final _scrollController = ScrollController(); bool _isLoading = false; @@ -163,70 +177,43 @@ class _PdfCombinePageState extends State { ], ); if (files.isEmpty) return; + await _processFiles(FileIterator.fromXFileList(files)); + } + int _fileId = 0; + + Future _processFiles(FileIterator provider) async { setState(() { _isLoading = true; }); try { - for (final file in files) { + await provider.iterateFiles((fileData) async { try { - final docId = await _docManager.loadDocument(file.name, file.path); + final filePath = fileData.filePath; + final int docId; + final String fileName; + if (filePath != null && filePath.toLowerCase().endsWith('.pdf')) { + docId = await _docManager.loadDocument(filePath, filePath); + fileName = filePath.split('/').last; + } else { + fileName = 'document_${++_fileId}'; + docId = await _docManager.loadDocumentFromBytes(fileName, await fileData.loadData()); + } + final doc = _docManager.getDocument(docId); if (doc != null) { for (var i = 0; i < doc.pages.length; i++) { _docManager.addReference(docId); - _pages.add(PageItem(documentId: docId, documentName: file.name, pageIndex: i, page: doc.pages[i])); + _pages.add(PageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); } } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading "${file.name}": $e'))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF": $e'))); } } - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); - } - } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } - } - - int _droppedCount = 0; - - Future _loadDropFiles(DropSession session) async { - setState(() { - _isLoading = true; - }); - - try { - for (final item in session.items) { - try { - final file = await _loadDataFromSessionItem(item, Formats.pdf); - if (file != null) { - ++_droppedCount; - final fileName = file.fileName ?? 'dropped_file_$_droppedCount.pdf'; - final docId = await _docManager.loadDocumentFromBytes(fileName, file.data); - final doc = _docManager.getDocument(docId); - if (doc != null) { - for (var i = 0; i < doc.pages.length; i++) { - _docManager.addReference(docId); - _pages.add(PageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); - } - } - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading dropped file: $e'))); - } - } - } + }); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); @@ -240,26 +227,6 @@ class _PdfCombinePageState extends State { } } - Future<({String? fileName, Uint8List data})?> _loadDataFromSessionItem(DropItem item, FileFormat format) async { - final reader = item.dataReader!; - if (!reader.canProvide(format)) { - return null; - } - var fileName = await reader.getSuggestedName(); - final completer = Completer(); - try { - final result = reader.getFile(format, (file) async { - completer.complete(await file.readAll()); - }); - if (result == null) { - completer.completeError(Exception('Not supported format: $format')); - } - } catch (e) { - completer.completeError(e); - } - return (fileName: fileName, data: await completer.future); - } - void _removePage(int index) { setState(() { final pageItem = _pages[index]; @@ -334,12 +301,7 @@ class _PdfCombinePageState extends State { _isDraggingOver = false; }); }, - onPerformDrop: (event) async { - await _loadDropFiles(event.session); - setState(() { - _isDraggingOver = false; - }); - }, + onPerformDrop: (event) => _processFiles(FileIterator.fromDropSession(event.session)), child: Stack( children: [ _isLoading @@ -404,8 +366,8 @@ class _PdfCombinePageState extends State { ); }, sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), - insertDuration: const Duration(milliseconds: 300), - removeDuration: const Duration(milliseconds: 300), + insertDuration: const Duration(milliseconds: 100), + removeDuration: const Duration(milliseconds: 100), dragStartDelay: _isTouchDevice || _disableDragging ? const Duration(milliseconds: 200) : Duration.zero, diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 343dd9b6..4f1a0692 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -24,7 +24,8 @@ dependencies: path_provider: ^2.1.5 animated_reorderable_list: ^1.3.0 web: ^1.1.1 - super_drag_and_drop: ^0.9.0 + super_drag_and_drop: ^0.9.1 + super_clipboard: ^0.9.1 dev_dependencies: flutter_test: diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index f9573b55..deab68ba 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -249,4 +249,33 @@ abstract class PdfImage { /// Dispose the image. void dispose(); + + /// Create [PdfImage] from BGRA pixel data. + /// + /// [bgraPixels] is the raw pixel data in BGRA8888 format. + /// [width] and [height] specify the dimensions of the image. + /// + /// The size of [bgraPixels] must be equal to `width * height * 4`. + /// Returns the created [PdfImage]. + static PdfImage createFromBgraData(Uint8List bgraPixels, {required int width, required int height}) { + return _PdfImageSimple(width, height, bgraPixels); + } +} + +class _PdfImageSimple implements PdfImage { + _PdfImageSimple(this.width, this.height, this.pixels); + + @override + final int width; + + @override + final int height; + + @override + final Uint8List pixels; + + @override + void dispose() { + // No resources to dispose. + } } From 393b853e92cf8be62aadb17fbcb3527a1fbae5d6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 3 Nov 2025 03:19:12 +0900 Subject: [PATCH 506/663] Now pdfrx supports imports of images --- packages/pdfrx/assets/pdfium_worker.js | 61 +++++----- .../example/pdf_combine/lib/file_data.dart | 92 --------------- .../{save_helper_io.dart => helper_io.dart} | 8 +- .../{save_helper_web.dart => helper_web.dart} | 7 +- .../pdfrx/example/pdf_combine/lib/main.dart | 111 +++++++++++------- .../flutter/generated_plugin_registrant.cc | 12 +- .../linux/flutter/generated_plugins.cmake | 3 +- .../Flutter/GeneratedPluginRegistrant.swift | 8 +- .../pdfrx/example/pdf_combine/pubspec.yaml | 3 +- .../flutter/generated_plugin_registrant.cc | 9 +- .../windows/flutter/generated_plugins.cmake | 3 +- packages/pdfrx/lib/src/pdfrx_flutter.dart | 13 ++ .../lib/src/native/pdfrx_pdfium.dart | 17 +-- packages/pdfrx_engine/lib/src/pdfrx_dart.dart | 22 ++++ 14 files changed, 152 insertions(+), 217 deletions(-) delete mode 100644 packages/pdfrx/example/pdf_combine/lib/file_data.dart rename packages/pdfrx/example/pdf_combine/lib/{save_helper_io.dart => helper_io.dart} (79%) rename packages/pdfrx/example/pdf_combine/lib/{save_helper_web.dart => helper_web.dart} (74%) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 3491179b..bd487f5c 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1869,16 +1869,17 @@ function createNewDocument() { /** * Set pixel data for an image object + * @param {number} pageHandle Page handle * @param {number} imageObj Image object handle * @param {ArrayBuffer} pixels BGRA8888 pixel data * @param {number} pixelWidth Image width in pixels * @param {number} pixelHeight Image height in pixels * @returns {PdfDocument|PdfError} */ -function _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight) { - const pixelDataPtr = Pdfium.wasmExports._malloc(pixels.byteLength); +function _setImageObjPixels(pageHandle, imageObj, pixels, pixelWidth, pixelHeight) { + const pixelDataPtr = Pdfium.wasmExports.malloc(pixels.byteLength); if (!pixelDataPtr) throw new Error('Failed to allocate memory for image pixels'); - HEAP8.set(new Uint8Array(pixels), pixelDataPtr); + new Uint8Array(Pdfium.memory.buffer, pixelDataPtr, pixels.byteLength).set(new Uint8Array(pixels)); const FPDFBitmap_BGRA = 4; const bitmapHandle = Pdfium.wasmExports.FPDFBitmap_CreateEx( pixelWidth, @@ -1888,14 +1889,14 @@ function _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight) { pixelWidth * 4 ); if (!bitmapHandle) { - Pdfium.wasmExports._free(pixelDataPtr); + Pdfium.wasmExports.free(pixelDataPtr); throw new Error('Failed to create bitmap for image object'); } - const pageArrayPtr = Pdfium.wasmExports._malloc(4); // Allocate space for one pointer - HEAP32[pageArrayPtr >> 2] = pageHandle; + const pageArrayPtr = Pdfium.wasmExports.malloc(4); // Allocate space for one pointer + new Int32Array(Pdfium.memory.buffer, pageArrayPtr, 1)[0] = pageHandle; const result = Pdfium.wasmExports.FPDFImageObj_SetBitmap(pageArrayPtr, 1, imageObj, bitmapHandle); - Pdfium.wasmExports._free(pageArrayPtr); - Pdfium.wasmExports._free(pixelDataPtr); + Pdfium.wasmExports.free(pageArrayPtr); + Pdfium.wasmExports.free(pixelDataPtr); Pdfium.wasmExports.FPDFBitmap_Destroy(bitmapHandle); if (!result) { throw new Error('Failed to set bitmap for image object'); @@ -1904,38 +1905,30 @@ function _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight) { /** * Create a PDF document from an image - * @param {ArrayBuffer} pixels BGRA8888 pixel data - * @param {number} pixelWidth Image width in pixels - * @param {number} pixelHeight Image height in pixels - * @param {number} width PDF page width in points - * @param {number} height PDF page height in points + * @param {{ + * pixels: ArrayBuffer, + * pixelWidth: number, + * pixelHeight: number, + * width: number, + * height: number + * }} params * @returns {PdfDocument|PdfError} */ -function createDocumentFromImage(pixels, pixelWidth, pixelHeight, width, height) { - let docHandle = 0, pageHandle = 0, imageObj = 0; +function createDocumentFromImage(params) { + let docHandle = 0, newPage = 0, imageObj = 0; try { + const { pixels, pixelWidth, pixelHeight, width, height } = params; docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); if (!docHandle) throw new Error('Failed to create new PDF document'); - pageHandle = Pdfium.wasmExports.FPDFPage_New(docHandle, 0, width, height); - if (!pageHandle) throw new Error('Failed to create new PDF page'); + newPage = Pdfium.wasmExports.FPDFPage_New(docHandle, 0, width, height); + if (!newPage) throw new Error('Failed to create new PDF page'); imageObj = Pdfium.wasmExports.FPDFPageObj_NewImageObj(docHandle); if (!imageObj) throw new Error('Failed to create new image object'); - _setImageObjPixels(imageObj, pixels, pixelWidth, pixelHeight); - - const scaleX = width / pixelWidth; - const scaleY = height / pixelHeight; - Pdfium.wasmExports.FPDFImageObj_SetMatrix( - imageObj, - scaleX, // a: horizontal scaling - 0, // b: vertical skewing - 0, // c: horizontal skewing - -scaleY, // d: vertical scaling (negative to flip Y-axis) - 0, // e: horizontal translation - height // f: vertical translation (move to top after flip) - ); - Pdfium.wasmExports.FPDFPage_InsertObject(pageHandle, imageObj); - imageObj = 0; // ownership transferred to pageHandle - Pdfium.wasmExports.FPDFPage_GenerateContent(pageHandle); + _setImageObjPixels(newPage, imageObj, pixels, pixelWidth, pixelHeight); + Pdfium.wasmExports.FPDFImageObj_SetMatrix(imageObj, width, 0, 0, height, 0, 0); + Pdfium.wasmExports.FPDFPage_InsertObject(newPage, imageObj); + imageObj = 0; // ownership transferred to newPage + Pdfium.wasmExports.FPDFPage_GenerateContent(newPage); const docHandleReturn = docHandle; docHandle = 0; // prevent cleanup in finally return _loadDocument(docHandleReturn, false, () => {}); @@ -1943,7 +1936,7 @@ function createDocumentFromImage(pixels, pixelWidth, pixelHeight, width, height) return { errorCode: -1, errorCodeStr: 'Exception: ' + e.toString() }; } finally { Pdfium.wasmExports.FPDFPageObj_Destroy(imageObj); - Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_ClosePage(newPage); Pdfium.wasmExports.FPDF_CloseDocument(docHandle); } } diff --git a/packages/pdfrx/example/pdf_combine/lib/file_data.dart b/packages/pdfrx/example/pdf_combine/lib/file_data.dart deleted file mode 100644 index 82d997de..00000000 --- a/packages/pdfrx/example/pdf_combine/lib/file_data.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; - -import 'package:file_selector/file_selector.dart'; -import 'package:flutter/foundation.dart'; -import 'package:super_clipboard/super_clipboard.dart'; -import 'package:super_drag_and_drop/super_drag_and_drop.dart'; - -/// Provide access to files without exposing platform-specific APIs. -abstract class FileIterator { - /// Iterates over the files, invoking [action] for each file. - Future iterateFiles(FutureOr Function(FileData fileData) action); - - /// Creates a [FileIterator] from a [DropSession]. - static FileIterator fromDropSession(DropSession session) { - return _FileIteratorFromDropSession(session); - } - - /// Creates a [FileIterator] from a list of [XFile]s. - static FileIterator fromXFileList(List xFileList) { - return _FileIteratorFromXFileList(xFileList); - } -} - -class _FileIteratorFromDropSession implements FileIterator { - _FileIteratorFromDropSession(this.session); - final DropSession session; - - @override - Future iterateFiles(FutureOr Function(FileData fileData) action) async { - for (final item in session.items) { - try { - final reader = item.dataReader; - if (reader != null) { - final fileUri = await _getValue(reader, Formats.fileUri); - await action(_FileDataFromDataFunction(fileUri?.toFilePath(), () => _getFile(reader))); - } - } catch (e) { - debugPrint('Error reading dropped file item: $e'); - } - } - } - - Future _getValue(DataReader reader, SimpleValueFormat format) async { - if (!reader.canProvide(format)) return null; - final completer = Completer(); - reader.getValue(format, (value) => completer.complete(value), onError: (error) => completer.completeError(error)); - return completer.future; - } - - Future _getFile(DataReader reader, [FileFormat? format]) async { - final completer = Completer(); - reader.getFile( - format, - (dataReaderFile) => completer.complete(dataReaderFile.readAll()), - onError: (error) => completer.completeError(error), - ); - return completer.future; - } -} - -class _FileIteratorFromXFileList implements FileIterator { - _FileIteratorFromXFileList(this.xFileList); - final List xFileList; - - @override - Future iterateFiles(FutureOr Function(FileData fileData) action) async { - for (final xFile in xFileList) { - final fileData = _FileDataFromDataFunction(xFile.path, () => xFile.readAsBytes()); - await action(fileData); - } - } -} - -/// Abstraction for file data access. -abstract class FileData { - /// The file path, if available. - String? get filePath; - - /// Loads the file data as bytes. - Future loadData(); -} - -/// FileData implementation that loads data using a provided function. -class _FileDataFromDataFunction implements FileData { - _FileDataFromDataFunction(this.filePath, this.loadDataFunction); - @override - final String? filePath; - final Future Function() loadDataFunction; - - @override - Future loadData() => loadDataFunction(); -} diff --git a/packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart similarity index 79% rename from packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart rename to packages/pdfrx/example/pdf_combine/lib/helper_io.dart index b72d65e7..dba3b0a0 100644 --- a/packages/pdfrx/example/pdf_combine/lib/save_helper_io.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart @@ -6,11 +6,7 @@ import 'package:share_plus/share_plus.dart'; Future savePdf(Uint8List bytes, {String? suggestedName}) async { if (Platform.isIOS || Platform.isAndroid) { - final xFile = XFile.fromData( - bytes, - name: suggestedName ?? 'document.pdf', - mimeType: 'application/pdf', - ); + final xFile = XFile.fromData(bytes, name: suggestedName ?? 'document.pdf', mimeType: 'application/pdf'); await Share.shareXFiles([xFile]); return; } @@ -26,3 +22,5 @@ Future savePdf(Uint8List bytes, {String? suggestedName}) async { await file.writeAsBytes(bytes); } } + +final isWindowsDesktop = Platform.isWindows; diff --git a/packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart similarity index 74% rename from packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart rename to packages/pdfrx/example/pdf_combine/lib/helper_web.dart index eac17f37..e6845ede 100644 --- a/packages/pdfrx/example/pdf_combine/lib/save_helper_web.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart @@ -4,10 +4,7 @@ import 'dart:typed_data'; import 'package:web/web.dart' as web; Future savePdf(Uint8List bytes, {String? suggestedName}) async { - final blob = web.Blob( - [bytes].jsify() as JSArray, - web.BlobPropertyBag(type: 'application/pdf'), - ); + final blob = web.Blob([bytes].jsify() as JSArray, web.BlobPropertyBag(type: 'application/pdf')); final url = web.URL.createObjectURL(blob); final anchor = web.HTMLAnchorElement(); @@ -19,3 +16,5 @@ Future savePdf(Uint8List bytes, {String? suggestedName}) async { web.URL.revokeObjectURL(url); anchor.remove(); } + +const bool isWindowsDesktop = false; diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index c3d29ee1..f6955c7f 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -1,15 +1,14 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:ui' as ui; import 'package:animated_reorderable_list/animated_reorderable_list.dart'; +import 'package:desktop_drop/desktop_drop.dart'; import 'package:file_selector/file_selector.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'package:super_drag_and_drop/super_drag_and_drop.dart'; -import 'file_data.dart'; -import 'save_helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; +import 'helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; void main() { pdfrxFlutterInitialize(); @@ -93,24 +92,55 @@ class DocumentManager { return docId; } + /// Load image bytes as a PDF document + /// + /// [assumedDpi] is the assumed DPI of the image for size calculation. + /// Typically, JPEG files may have DPI information in their metadata, but it's almost always 96-dpi or such + /// and thus results in very large PDF pages. Therefore, we use an assumed DPI of 300 by default. + Future _loadImageAsPdf(Uint8List bytes, String name, {double assumedDpi = 300}) async { + ui.Image? imageOpened; + PdfImage? pdfImage; + try { + /// NOTE: we should firstly try to open as image, because PDFium on Windows could not determine whether + /// the input bytes are PDF or not correctly in some cases. + final image = imageOpened = await decodeImageFromList(bytes); + final width = image.width * 72 / assumedDpi; + final height = image.height * 72 / assumedDpi; + pdfImage = await image.toPdfImage(); + imageOpened.dispose(); + imageOpened = null; + return await PdfDocument.createFromImage(pdfImage, width: width, height: height, sourceName: name); + } finally { + imageOpened?.dispose(); + pdfImage?.dispose(); + } + } + + Future _loadPdf(Uint8List bytes, String name) async { + return await PdfDocument.openData( + bytes, + passwordProvider: passwordProvider != null ? () => passwordProvider!(name) : null, + ); + } + Future loadDocumentFromBytes(String name, Uint8List bytes) async { PdfDocument doc; - try { - doc = await PdfDocument.openData( - bytes, - passwordProvider: passwordProvider != null ? () => passwordProvider!(name) : null, - ); - } catch (e) { - final image = await decodeImageFromList(bytes); - final width = image.width * 300 / 72; - final height = image.height * 300 / 72; - final pdfImage = PdfImage.createFromBgraData( - (await image.toByteData(format: ImageByteFormat.rawRgba))!.buffer.asUint8List(), - width: image.width, - height: image.height, - ); - doc = await PdfDocument.createFromImage(pdfImage, width: width, height: height, sourceName: name); + if (isWindowsDesktop) { + try { + /// NOTE: we should firstly try to open as image, because PDFium on Windows could not determine whether + /// the input bytes are PDF or not correctly in some cases. + doc = await _loadImageAsPdf(bytes, name); + } catch (e) { + doc = await _loadPdf(bytes, name); + } + } else { + try { + doc = await _loadPdf(bytes, name); + } catch (e) { + doc = await _loadImageAsPdf(bytes, name); + } } + final docId = _nextDocId++; _documents[docId] = doc; _pageRefCounts[docId] = 0; @@ -174,31 +204,32 @@ class _PdfCombinePageState extends State { final files = await openFiles( acceptedTypeGroups: [ XTypeGroup(label: 'PDFs', extensions: ['pdf']), + XTypeGroup(label: 'Images', extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff', 'webp']), ], ); if (files.isEmpty) return; - await _processFiles(FileIterator.fromXFileList(files)); + await _processFiles(files); } int _fileId = 0; - Future _processFiles(FileIterator provider) async { + Future _processFiles(List files) async { setState(() { _isLoading = true; }); try { - await provider.iterateFiles((fileData) async { + for (final file in files) { try { - final filePath = fileData.filePath; + final filePath = file.path; final int docId; final String fileName; - if (filePath != null && filePath.toLowerCase().endsWith('.pdf')) { + if (filePath.toLowerCase().endsWith('.pdf')) { docId = await _docManager.loadDocument(filePath, filePath); fileName = filePath.split('/').last; } else { fileName = 'document_${++_fileId}'; - docId = await _docManager.loadDocumentFromBytes(fileName, await fileData.loadData()); + docId = await _docManager.loadDocumentFromBytes(fileName, await file.readAsBytes()); } final doc = _docManager.getDocument(docId); @@ -213,7 +244,7 @@ class _PdfCombinePageState extends State { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF": $e'))); } } - }); + } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading PDF: $e'))); @@ -284,24 +315,18 @@ class _PdfCombinePageState extends State { const SizedBox(width: 16), ], ), - body: DropRegion( - formats: Formats.standardFormats, - hitTestBehavior: HitTestBehavior.opaque, - onDropOver: (event) { - if (event.session.items.any((item) => item.canProvide(Formats.fileUri))) { - setState(() { - _isDraggingOver = true; - }); - return DropOperation.copy; - } - return DropOperation.none; + body: DropTarget( + onDragEntered: (event) { + setState(() { + _isDraggingOver = true; + }); }, - onDropLeave: (event) { + onDragExited: (event) { setState(() { _isDraggingOver = false; }); }, - onPerformDrop: (event) => _processFiles(FileIterator.fromDropSession(event.session)), + onDragDone: (event) => _processFiles(event.files), child: Stack( children: [ _isLoading @@ -342,12 +367,12 @@ class _PdfCombinePageState extends State { return Listener( onPointerMove: (event) { setState(() { - _isTouchDevice = event.kind == PointerDeviceKind.touch; + _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; }); }, onPointerHover: (event) { setState(() { - _isTouchDevice = event.kind == PointerDeviceKind.touch; + _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; }); }, child: AnimatedReorderableGridView( @@ -367,7 +392,7 @@ class _PdfCombinePageState extends State { }, sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), insertDuration: const Duration(milliseconds: 100), - removeDuration: const Duration(milliseconds: 100), + removeDuration: const Duration(milliseconds: 300), dragStartDelay: _isTouchDevice || _disableDragging ? const Duration(milliseconds: 200) : Duration.zero, diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc index 9876374e..54c79483 100644 --- a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugin_registrant.cc @@ -6,21 +6,17 @@ #include "generated_plugin_registrant.h" +#include #include -#include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_drop_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); + desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); - irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); - g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); - super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake index cf30839a..198caa95 100644 --- a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake @@ -3,9 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop file_selector_linux - irondash_engine_context - super_native_extensions url_launcher_linux ) diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift index c530339c..1be94b67 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,22 +5,18 @@ import FlutterMacOS import Foundation -import device_info_plus +import desktop_drop import file_selector_macos -import irondash_engine_context import path_provider_foundation import pdfrx import share_plus -import super_native_extensions import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) - IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) - SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 4f1a0692..150f2525 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -24,8 +24,7 @@ dependencies: path_provider: ^2.1.5 animated_reorderable_list: ^1.3.0 web: ^1.1.1 - super_drag_and_drop: ^0.9.1 - super_clipboard: ^0.9.1 + desktop_drop: ^0.7.0 dev_dependencies: flutter_test: diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc index 0edb4238..2db4d5af 100644 --- a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugin_registrant.cc @@ -6,21 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include -#include #include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DesktopDropPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopDropPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - IrondashEngineContextPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); - SuperNativeExtensionsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake index 56b35bbb..4c198f09 100644 --- a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake @@ -3,10 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop file_selector_windows - irondash_engine_context share_plus - super_native_extensions url_launcher_windows ) diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index 52a75517..cbcd310b 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -83,6 +83,19 @@ extension PdfImageExt on PdfImage { } } +extension ImageExt on Image { + /// Convert [Image] to [PdfImage]. + Future toPdfImage() async { + final rgba = (await toByteData(format: ImageByteFormat.rawRgba))!.buffer.asUint8List(); + for (var i = 0; i < rgba.length; i += 4) { + final r = rgba[i]; + rgba[i] = rgba[i + 2]; + rgba[i + 2] = r; + } + return PdfImage.createFromBgraData(rgba, width: width, height: height); + } +} + extension PdfRectExt on PdfRect { /// Convert to [Rect] in Flutter coordinate. /// [page] is the page to convert the rectangle. diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index a6b3368b..daa76dda 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -463,6 +463,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { final bufferList = memBuffer.asTypedList(params.pixels.length); bufferList.setAll(0, params.pixels); + final imageObj = pdfium.FPDFPageObj_NewImageObj(document); final bitmap = pdfium.FPDFBitmap_CreateEx( params.pixelWidth, params.pixelHeight, @@ -470,24 +471,14 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { memBuffer.cast(), params.pixelWidth * 4, ); - final imageObj = pdfium.FPDFPageObj_NewImageObj(document); pdfium.FPDFImageObj_SetBitmap(newPages, 1, imageObj, bitmap); + pdfium.FPDFBitmap_Destroy(bitmap); - final scaleX = params.width / params.pixelWidth; - final scaleY = -params.height / params.pixelHeight; - pdfium.FPDFImageObj_SetMatrix( - imageObj, - scaleX, // a: horizontal scaling - 0, // b: vertical skewing - 0, // c: horizontal skewing - -scaleY, // d: vertical scaling (negative to flip Y-axis) - 0, // e: horizontal translation - params.height, // f: vertical translation (move to top after flip - ); + pdfium.FPDFImageObj_SetMatrix(imageObj, params.width, 0, 0, params.height, 0, 0); pdfium.FPDFPage_InsertObject(newPage, imageObj); // imageObj is now owned by the page + pdfium.FPDFPage_GenerateContent(newPage); pdfium.FPDF_ClosePage(newPage); - pdfium.FPDFBitmap_Destroy(bitmap); return document.address; }, ( diff --git a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart index 05ae93e6..25f774a8 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart @@ -16,3 +16,25 @@ extension PdfImageDartExt on PdfImage { ); } } + +extension ImageDartExt on Image { + /// Create [PdfImage] from the rendered image. + /// + /// **NF**: This method does not require Flutter and can be used in pure Dart applications. + /// + /// By default, the function assumes that the image data is in RGBA format and performs conversion to BGRA. + /// - If the image data is already in BGRA format, set [order] to [ChannelOrder.bgra]. + /// - If [bgraConversionInPlace] is set to true and conversion is needed, the conversion will be done in place + /// modifying the original image data. This can save memory but will alter the original image + PdfImage toPdfImageNF({ChannelOrder order = ChannelOrder.rgba, bool bgraConversionInPlace = false}) { + if (data == null) { + throw StateError('The image has no pixel data.'); + } + final needsConversion = order != ChannelOrder.bgra; + return PdfImage.createFromBgraData( + needsConversion ? data!.getBytes(order: ChannelOrder.bgra, inPlace: bgraConversionInPlace) : getBytes(), + width: width, + height: height, + ); + } +} From 26e94209feae17deec4fcccc42a7c744f54e1476 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 3 Nov 2025 03:52:58 +0900 Subject: [PATCH 507/663] Doc updates --- README.md | 4 +- doc/Importing-Images-to-PDF.md | 311 ++++++++++++++++++++++++++++++++ doc/PDF-Page-Manipulation.md | 41 +++++ doc/README.md | 1 + packages/pdfrx/README.md | 4 +- packages/pdfrx_engine/README.md | 4 +- 6 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 doc/Importing-Images-to-PDF.md diff --git a/README.md b/README.md index b5b83692..ccb91f6a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A platform-agnostic PDF rendering and manipulation API built on top of PDFium. - Pure Dart package (no Flutter dependencies) - Provides low-level PDF document API for viewing and editing -- Supports page re-arrangement, PDF combining, and document manipulation +- Supports page re-arrangement, PDF combining, image import, and document manipulation - Can be used in CLI applications or non-Flutter Dart projects - Supports all platforms: Android, iOS, Windows, macOS, Linux @@ -22,7 +22,7 @@ A cross-platform PDF viewer and manipulation plugin for Flutter. - Built on top of pdfrx_engine - Provides high-level viewer widgets and overlays - Includes text selection, search, zoom controls, and more -- Supports PDF editing features like page manipulation and document combining +- Supports PDF editing features like page manipulation, document combining, and image import ### [pdfrx_coregraphics](packages/pdfrx_coregraphics/) diff --git a/doc/Importing-Images-to-PDF.md b/doc/Importing-Images-to-PDF.md new file mode 100644 index 00000000..fe0a0a4a --- /dev/null +++ b/doc/Importing-Images-to-PDF.md @@ -0,0 +1,311 @@ +# Importing Images to PDF + +pdfrx provides powerful APIs for importing images into PDF documents. This feature enables you to convert images to PDF format, insert images as new pages into existing PDFs, and create PDFs from scanned documents or photos. + +## Overview + +The image import feature allows you to: + +- Convert images (JPEG, PNG, etc.) to PDF format +- Create PDF documents from images +- Insert images as new pages into existing PDFs +- Build PDFs from scanned documents or photos +- Control image dimensions and placement in the PDF + +## Creating PDF Documents from Images + +### PdfDocument.createFromImage + +[PdfDocument.createFromImage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromImage.html) creates a new PDF document containing a single page with the specified image: + +```dart +import 'dart:ui' as ui; +import 'package:pdfrx/pdfrx.dart'; + +// Load and decode an image +final imageBytes = await File('photo.jpg').readAsBytes(); +final ui.Image image = await decodeImageFromList(imageBytes); + +// Convert to PdfImage +final pdfImage = await image.toPdfImage(); + +// Create PDF with image (width and height in PDF units: 1/72 inch) +final imageDoc = await PdfDocument.createFromImage( + pdfImage, + width: 595, // A4 width in points (8.27 inches) + height: 842, // A4 height in points (11.69 inches) + sourceName: 'photo.pdf', +); + +// Encode to PDF bytes +final pdfBytes = await imageDoc.encodePdf(); +await File('output.pdf').writeAsBytes(pdfBytes); + +// Clean up +pdfImage.dispose(); +image.dispose(); +imageDoc.dispose(); +``` + +**Important Note:** The `width` and `height` parameters only control the visible page size in the PDF document. They do NOT resize or compress the actual image data. The full-resolution image is embedded in the PDF regardless of these dimensions. If you need to reduce the PDF file size, you must resize the image before passing it to `createFromImage`. + +### Understanding Image Dimensions + +PDF uses points as its unit of measurement, where 1 point = 1/72 inch. When creating a PDF from an image, you need to specify the page dimensions in points. + +#### Common Page Sizes (in points) + +```dart +// A4 (210mm × 297mm) +width: 595, height: 842 + +// Letter (8.5" × 11") +width: 612, height: 792 + +// Legal (8.5" × 14") +width: 612, height: 1008 + +// A3 (297mm × 420mm) +width: 842, height: 1191 +``` + +#### Calculating Dimensions from Image Size + +If you want to preserve the image's aspect ratio and size based on DPI: + +```dart +// Assume image DPI (common values: 72, 96, 150, 300) +const double assumedDpi = 300; + +// Calculate PDF page size from image dimensions +final width = image.width * 72 / assumedDpi; +final height = image.height * 72 / assumedDpi; + +final imageDoc = await PdfDocument.createFromImage( + pdfImage, + width: width, + height: height, + sourceName: 'image.pdf', +); +``` + +**Note:** Many JPEG files contain DPI information in their metadata, but it's often unreliable (typically 96 DPI or 72 DPI), which can result in very large PDF pages. It's usually better to use an assumed DPI (like 300) for better results. + +## Inserting Images into Existing PDFs + +You can combine `PdfDocument.createFromImage` with [page manipulation](PDF-Page-Manipulation.md) to insert images into existing PDF documents: + +```dart +// Load existing PDF +final doc = await PdfDocument.openFile('document.pdf'); + +// Load and convert image to PDF +final imageBytes = await File('photo.jpg').readAsBytes(); +final ui.Image image = await decodeImageFromList(imageBytes); +final pdfImage = await image.toPdfImage(); + +final imageDoc = await PdfDocument.createFromImage( + pdfImage, + width: 595, + height: 842, + sourceName: 'temp-image.pdf', +); + +// Insert image page at position 2 (after first page) +doc.pages = [ + doc.pages[0], // Page 1 (original) + imageDoc.pages[0], // Page 2 (new image) + ...doc.pages.sublist(1), // Remaining pages +]; + +// Save the result +final pdfBytes = await doc.encodePdf(); +await File('output.pdf').writeAsBytes(pdfBytes); + +// Clean up +pdfImage.dispose(); +image.dispose(); +doc.dispose(); +imageDoc.dispose(); +``` + +## Converting Multiple Images to a Single PDF + +You can create a PDF document containing multiple images by combining pages from multiple image-based PDFs: + +```dart +// Create a new PDF document +final combinedDoc = await PdfDocument.createNew(sourceName: 'multi-image.pdf'); + +// List to store image documents +final List imageDocs = []; +final List pdfImages = []; +final List images = []; + +try { + // Load and convert multiple images + for (final imagePath in ['image1.jpg', 'image2.jpg', 'image3.jpg']) { + final imageBytes = await File(imagePath).readAsBytes(); + final ui.Image image = await decodeImageFromList(imageBytes); + images.add(image); + + final pdfImage = await image.toPdfImage(); + pdfImages.add(pdfImage); + + // Calculate dimensions (assuming 300 DPI) + final width = image.width * 72 / 300; + final height = image.height * 72 / 300; + + final imageDoc = await PdfDocument.createFromImage( + pdfImage, + width: width, + height: height, + sourceName: imagePath, + ); + imageDocs.add(imageDoc); + } + + // Combine all image pages + combinedDoc.pages = imageDocs.map((doc) => doc.pages[0]).toList(); + + // Encode to PDF + final pdfBytes = await combinedDoc.encodePdf(); + await File('output.pdf').writeAsBytes(pdfBytes); + +} finally { + // Clean up resources + for (final img in images) { + img.dispose(); + } + for (final pdfImg in pdfImages) { + pdfImg.dispose(); + } + for (final doc in imageDocs) { + doc.dispose(); + } + combinedDoc.dispose(); +} +``` + +## Controlling PDF File Size + +### Image Resolution and File Size + +The `width` and `height` parameters in `createFromImage()` **only control the page dimensions**, not the image resolution. The full-resolution image data is embedded in the PDF file regardless of these values. + +**Example:** + +```dart +// This creates a small page, but the PDF file will still contain +// the full 4000x3000 pixel image data +final largeImage = await decodeImageFromList(bytes); // 4000x3000 pixels +final pdfImage = await largeImage.toPdfImage(); + +final doc = await PdfDocument.createFromImage( + pdfImage, + width: 200, // Small page width + height: 150, // Small page height + sourceName: 'small-page-large-file.pdf', +); +// Result: Small visible page, but LARGE file size! +``` + +### How to Reduce PDF File Size + +To reduce the PDF file size, you must resize the image **before** passing it to pdfrx: + +```dart +import 'dart:ui' as ui; +import 'dart:math'; + +// Load original image +final originalImage = await decodeImageFromList(imageBytes); + +// Target page size at desired DPI (e.g., A4 at 150 DPI) +const targetDpi = 150.0; +const pageWidthInches = 8.27; // A4 width in inches +const pageHeightInches = 11.69; // A4 height in inches +const pageWidth = pageWidthInches * targetDpi; // 1240 pixels +const pageHeight = pageHeightInches * targetDpi; // 1754 pixels + +// Calculate aspect ratios +final imageAspect = originalImage.width / originalImage.height; +final pageAspect = pageWidth / pageHeight; + +// Calculate target dimensions that fit within the page while preserving aspect ratio +int targetWidth, targetHeight; +if (imageAspect > pageAspect) { + // Image is wider than page - fit to width + targetWidth = pageWidth.round(); + targetHeight = (pageWidth / imageAspect).round(); +} else { + // Image is taller than page - fit to height + targetHeight = pageHeight.round(); + targetWidth = (pageHeight * imageAspect).round(); +} + +// Create a canvas with full page dimensions and center the resized image on it +final pageWidthPixels = pageWidth.round(); +final pageHeightPixels = pageHeight.round(); + +final recorder = ui.PictureRecorder(); +final canvas = Canvas(recorder); + +// Fill background with white +canvas.drawRect( + Rect.fromLTWH(0, 0, pageWidthPixels.toDouble(), pageHeightPixels.toDouble()), + Paint()..color = const Color(0xFFFFFFFF), +); + +// Calculate offset to center the image +final offsetX = (pageWidthPixels - targetWidth) / 2; +final offsetY = (pageHeightPixels - targetHeight) / 2; + +// Draw the resized image centered on the page +canvas.drawImageRect( + originalImage, + Rect.fromLTWH(0, 0, originalImage.width.toDouble(), originalImage.height.toDouble()), + Rect.fromLTWH(offsetX, offsetY, targetWidth.toDouble(), targetHeight.toDouble()), + Paint(), +); + +final picture = recorder.endRecording(); +final finalImage = await picture.toImage(pageWidthPixels, pageHeightPixels); +originalImage.dispose(); + +// Now convert to PDF - this will have a much smaller file size +final pdfImage = await finalImage.toPdfImage(); + +// Calculate PDF page dimensions +final pdfPageWidth = pageWidthInches * 72; // A4 width in points (595) +final pdfPageHeight = pageHeightInches * 72; // A4 height in points (842) + +final doc = await PdfDocument.createFromImage( + pdfImage, + width: pdfPageWidth, + height: pdfPageHeight, + sourceName: 'optimized.pdf', +); + +// Clean up +picture.dispose(); +finalImage.dispose(); +``` + +### Recommended Image Resolutions + +Choose your image resolution based on the intended use: + +| Use Case | DPI | A4 Page Size (pixels) | Description | +|----------|-----|----------------------|-------------| +| Screen viewing | 72-96 | 595×842 to 794×1123 | Smallest file size | +| Standard printing | 150 | 1240×1754 | Good balance | +| High-quality printing | 300 | 2480×3508 | Professional quality | +| Archival/Photo printing | 600 | 4960×7016 | Maximum quality | + +**Rule of thumb:** Match the image pixel dimensions to your target DPI and page size to avoid unnecessarily large files. + +## Related Documentation + +- [PDF Page Manipulation](PDF-Page-Manipulation.md) - Learn how to combine and rearrange pages +- [pdf_combine example app](../packages/pdfrx/example/pdf_combine/) - Visual interface for combining PDFs and images diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index 54e8a4bc..1cdf5617 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -15,6 +15,47 @@ The page manipulation feature allows you to: ## Key Concepts +### Creating PDF Documents for Page Manipulation + +There are multiple ways to create a [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) for page manipulation: + +#### Using Existing PDF Files + +When you open an existing PDF file using methods like [PdfDocument.openFile](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) or [PdfDocument.openData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html), the document inherits all settings from the original PDF, including: + +- Security settings and encryption +- Document metadata +- PDF version and features +- Form fields and annotations + +```dart +// Open existing PDF - inherits all original settings +final doc = await PdfDocument.openFile('document.pdf'); +``` + +This is useful when you want to preserve the original PDF's properties while manipulating its pages. + +#### Creating New PDF Documents with PdfDocument.createNew + +[PdfDocument.createNew](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createNew.html) creates a completely new, empty PDF document from scratch without inheriting any settings: + +```dart +// Create a brand new empty PDF +final newDoc = await PdfDocument.createNew(sourceName: 'combined.pdf'); + +// Add pages from other documents +newDoc.pages = [doc1.pages[0], doc2.pages[1], doc3.pages[2]]; + +// Encode to PDF bytes +final pdfBytes = await newDoc.encodePdf(); +``` + +**Use cases for PdfDocument.createNew:** + +- Combining pages from multiple PDFs without inheriting any security or metadata settings +- Creating a clean PDF document without any legacy properties +- Building PDFs programmatically where you want full control over document properties + ### PdfDocument.pages Property The [PdfDocument.pages](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/pages.html) property is both readable and writable: diff --git a/doc/README.md b/doc/README.md index a1c19631..9d3d03c8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -39,6 +39,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ### PDF Editing - [PDF Page Manipulation](PDF-Page-Manipulation.md) - Re-arrange, combine, and extract PDF pages +- [Importing Images to PDF](Importing-Images-to-PDF.md) - Convert images to PDF and insert images into PDFs ## Platform-Specific diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 5d08fa46..d9a47fd2 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -2,11 +2,11 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer and manipulation plugin for Flutter. It provides ready-to-use widgets for displaying and editing PDF documents in your Flutter applications. +[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer and manipulation plugin for Flutter. It provides ready-to-use widgets for displaying and editing PDF documents, including page manipulation, document combining, and image import in your Flutter applications. This plugin is built on top of [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine), which handles the low-level PDF rendering and manipulation using [PDFium](https://pdfium.googlesource.com/pdfium/). The separation allows for a clean architecture where: -- **pdfrx** (this package) - Provides Flutter widgets, UI components, platform integration, and PDF editing features +- **pdfrx** (this package) - Provides Flutter widgets, UI components, platform integration, and PDF editing features (page manipulation, combining, image import) - **pdfrx_engine** - Handles PDF parsing, rendering, and manipulation without Flutter dependencies The plugin supports Android, iOS, Windows, macOS, Linux, and Web. diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 8071e8ac..231532d2 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -2,7 +2,7 @@ [![Build Test](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml/badge.svg)](https://github.com/espresso3389/pdfrx/actions/workflows/build-test.yml) -[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering and manipulation engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs for viewing, editing, and combining PDF documents without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side PDF processing. +[pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering and manipulation engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs for viewing, editing, combining PDF documents, and importing images without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side PDF processing. This package is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. @@ -50,6 +50,8 @@ You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure t - [PdfDocument.openData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory (Uint8List) - [PdfDocument.openUri](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openUri.html) - Open PDF from stream (advanced use case) - [PdfDocument.openAsset](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openAsset.html) - Open PDF from Flutter asset + - [PdfDocument.createNew](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createNew.html) - Create new empty PDF document + - [PdfDocument.createFromImage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromImage.html) - Create PDF from image - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation and rendering - [PdfPage.render](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap - [PdfPage.loadText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content from page From 3f27e82981a719a31985ca37f15413f21bd026c1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 3 Nov 2025 03:57:49 +0900 Subject: [PATCH 508/663] WIP --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ccb91f6a..7da797d6 100644 --- a/README.md +++ b/README.md @@ -71,15 +71,31 @@ Comprehensive documentation is available in the [doc/](doc/) directory, includin This is a monorepo managed with pub workspaces. Just do `dart pub get` on some directory inside the repo to obtain all the dependencies. -## Example Application +## Example Applications -The example viewer application is located in `packages/pdfrx/example/viewer/`. It demonstrates the full capabilities of the pdfrx Flutter plugin. +### PDF Viewer + +The example viewer application is located in [packages/pdfrx/example/viewer/](packages/pdfrx/example/viewer/). It demonstrates the full capabilities of the pdfrx Flutter plugin. ```bash cd packages/pdfrx/example/viewer flutter run ``` +### PDF Combine + +The [packages/pdfrx/example/pdf_combine/](packages/pdfrx/example/pdf_combine/) application demonstrates PDF page manipulation and combining features: + +- Drag-and-drop interface for page re-arrangement +- Visual thumbnails of PDF pages +- Support for combining multiple PDF documents +- Platform file drag-and-drop support + +```bash +cd packages/pdfrx/example/pdf_combine +flutter run +``` + ## Contributing Contributions are welcome! Please read the individual package READMEs for specific development guidelines. From 31ec28b80957ddbc1faed2d73e4d752459ec23b6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 3 Nov 2025 04:02:36 +0900 Subject: [PATCH 509/663] WIP --- packages/pdfrx/example/pdf_combine/README.md | 85 ++++++-------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/README.md b/packages/pdfrx/example/pdf_combine/README.md index 79a24b1d..637a5076 100644 --- a/packages/pdfrx/example/pdf_combine/README.md +++ b/packages/pdfrx/example/pdf_combine/README.md @@ -1,60 +1,41 @@ # PDF Combine - Flutter Example -A Flutter desktop application that demonstrates how to combine multiple PDF files using the pdfrx package with cross-document page import support. +A Flutter desktop application that demonstrates how to combine multiple PDF files and images using the pdfrx package with cross-document page import support. ## Features -- **Multi-file PDF picker**: Select multiple PDF files to combine -- **Page selection UI**: Visual grid view to select specific pages from each PDF -- **Input PDF preview**: Thumbnail previews of pages for easy selection -- **Live output preview**: See the combined PDF in real-time using PdfViewer -- **Cross-document import**: Import pages from different PDF documents +- **Multi-file picker**: Select multiple PDF files and images to combine +- **Image import**: Import images (JPG, JPEG, PNG, BMP, GIF, TIFF, WebP) as PDF pages +- **Drag & drop support**: Drag and drop PDF files and images into the app +- **Page selection UI**: Visual grid view to select and reorder pages +- **Page rotation**: Rotate individual pages before combining +- **Thumbnail previews**: Preview of all pages for easy selection +- **Live output preview**: See the combined PDF in real-time using `PdfViewer` +- **Cross-document import**: Import pages from different PDF documents and images - **Save combined PDF**: Export the merged PDF to local storage ## How It Works -The app uses the new `assemble()` and `encodePdf()` APIs from pdfrx_engine: +The app uses the [`PdfDocument.createFromImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromImage.html) and [`encodePdf()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/encodePdf.html) APIs from pdfrx_engine: -1. **Open PDFs**: Load multiple PDF documents using `PdfDocument.openFile()` -2. **Select Pages**: Choose which pages to include from each document -3. **Combine**: Merge pages by setting `document.pages` with pages from all documents -4. **Encode**: Call `document.encodePdf()` to generate the combined PDF bytes -5. **Preview & Save**: Display the result and save to disk - -## Implementation Highlights - -### Cross-Document Page Import - -```dart -// Combine pages from different documents -final doc1 = await PdfDocument.openFile('file1.pdf'); -final doc2 = await PdfDocument.openFile('file2.pdf'); - -doc1.pages = [ - doc1.pages[0], // Page 1 from doc1 - doc2.pages[0], // Page 1 from doc2 (imported!) - doc1.pages[1], // Page 2 from doc1 -]; - -// Encode the combined PDF -final bytes = await doc1.encodePdf(); -``` +### PDF Files -### Page Manipulation +1. **Open PDFs**: Load PDF documents using [`PdfDocument.openFile()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/openFile.html) or [`PdfDocument.openData()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/openData.html) +2. **Extract Pages**: Access pages from each document -The app demonstrates: -- **Page reordering**: Change page sequence -- **Page deletion**: Exclude unwanted pages -- **Page duplication**: Include same page multiple times -- **Cross-document import**: Merge pages from different PDFs +### Image Files -## UI Layout +1. **Load Images**: Read image bytes and decode using Flutter's [`decodeImageFromList()`](https://api.flutter.dev/flutter/dart-ui/decodeImageFromList.html) +2. **Convert to PDF**: Use [`image.toPdfImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfImageGeneratorOnImage/toPdfImage.html) and [`PdfDocument.createFromImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromImage.html) to convert images to single-page PDF documents +3. **DPI Handling**: Images are converted with an assumed DPI of 300 for optimal page sizing -The app uses a three-panel layout: +### Combining -1. **Left Panel**: List of input PDF files with page counts -2. **Middle Panel**: Grid view for selecting pages from the currently selected PDF -3. **Right Panel**: Live preview of the combined PDF output +1. **Select & Reorder**: Choose pages and arrange them in desired order +2. **Apply Transformations**: Rotate pages as needed +3. **Combine**: Create a new PDF document and set [`document.pages`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/pages.html) with all selected pages +4. **Encode**: Call [`document.encodePdf()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/encodePdf.html) to generate the combined PDF bytes +5. **Preview & Save**: Display the result and save to disk ## Running the App @@ -63,23 +44,3 @@ cd packages/pdfrx/example/pdfcombine flutter run -d linux # or macos, windows flutter run -d chrome # for Web ``` - -## Requirements - -- Flutter SDK -- Desktop platform (Linux, macOS, Windows) or Web browser -- Local pdfrx_engine with assemble/encodePdf support - -## Dependencies - -- `pdfrx`: PDF rendering and manipulation -- `file_selector`: File selection dialog (better Linux support than file_picker, works on Web) -- `share_plus`: File downloading on Web platform - -## Notes - -This example requires the latest pdfrx_engine with: -- `PdfDocument.pages` setter for page manipulation -- `PdfDocument.assemble()` for applying page changes -- `PdfDocument.encodePdf()` for encoding to bytes -- Cross-document page import support (both native and WASM backends) From 32d7af4e1d208fe0c9578707b635ecb28c3109fb Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 4 Nov 2025 01:28:32 +0900 Subject: [PATCH 510/663] chromium/7390 ->chromium/7506 --- packages/pdfrx/android/CMakeLists.txt | 2 +- packages/pdfrx/assets/pdfium.wasm | Bin 3981008 -> 3982871 bytes packages/pdfrx/linux/CMakeLists.txt | 2 +- packages/pdfrx/windows/CMakeLists.txt | 2 +- .../lib/src/pdfrx_initialize_dart.dart | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/android/CMakeLists.txt b/packages/pdfrx/android/CMakeLists.txt index c91ddfba..8e555c39 100644 --- a/packages/pdfrx/android/CMakeLists.txt +++ b/packages/pdfrx/android/CMakeLists.txt @@ -12,7 +12,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7390) +set(PDFIUM_RELEASE chromium%2F7506) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfrx/assets/pdfium.wasm b/packages/pdfrx/assets/pdfium.wasm index 7a6ed81a0eae77f0da1e5857208285fcf0f78942..f79da4df2a51d53ba978e6b590389b6e3719ec94 100644 GIT binary patch delta 452635 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
HbC#My-NiH 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�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

pdfrx - Flutter PDF Library

+

A powerful and flexible PDF viewer and manipulation library for Flutter.

+ +
+ + + + + EOF + + # Add version info to index.html + COMMIT_SHORT=${GITHUB_SHA:0:7} + sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" deploy_temp/index.html + sed -i "s/__PDFRX_VERSION__/${PDFRX_VERSION}/g" deploy_temp/index.html + sed -i "s/__COMMIT__/${GITHUB_SHA}/g" deploy_temp/index.html + sed -i "s/__COMMIT_SHORT__/${COMMIT_SHORT}/g" deploy_temp/index.html + sed -i "s/__FLUTTER_VERSION__/${FLUTTER_VERSION}/g" deploy_temp/index.html - name: Configure Git for deployment run: | git config user.email "action@github.com" @@ -51,7 +196,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.TOKEN_FOR_GHPAGE_DEPLOYMENT }}@github.com/${{ github.repository }}.git - name: Deploy to GitHub Pages using subtree push run: | - git add -f packages/pdfrx/example/viewer/build/web + git add -f deploy_temp git commit -m "$PDFRX_VERSION $GITHUB_SHA" - git subtree split --prefix packages/pdfrx/example/viewer/build/web -b tmp + git subtree split --prefix deploy_temp -b tmp git push -f origin tmp:gh-pages diff --git a/packages/pdfrx/example/pdf_combine/web/index.html b/packages/pdfrx/example/pdf_combine/web/index.html index a733a855..d2c959b3 100644 --- a/packages/pdfrx/example/pdf_combine/web/index.html +++ b/packages/pdfrx/example/pdf_combine/web/index.html @@ -18,7 +18,8 @@ - + + @@ -29,7 +30,7 @@ - pdfcombine + pdf_combine diff --git a/packages/pdfrx/example/viewer/web/index.html b/packages/pdfrx/example/viewer/web/index.html index 64196cf0..aa18d18f 100644 --- a/packages/pdfrx/example/viewer/web/index.html +++ b/packages/pdfrx/example/viewer/web/index.html @@ -8,14 +8,17 @@ The path provided below has to start and end with a slash "/" in order for it to work correctly. - Fore more details: + For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + - + From cfd828c902bc951c06557c6456a8ab408273e707 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 4 Nov 2025 09:11:41 +0900 Subject: [PATCH 514/663] pdf_combine show generated PDF on another tab --- .../example/pdf_combine/lib/helper_web.dart | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart index e6845ede..dab038a4 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart @@ -3,18 +3,29 @@ import 'dart:typed_data'; import 'package:web/web.dart' as web; -Future savePdf(Uint8List bytes, {String? suggestedName}) async { +Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab = true}) async { final blob = web.Blob([bytes].jsify() as JSArray, web.BlobPropertyBag(type: 'application/pdf')); final url = web.URL.createObjectURL(blob); - final anchor = web.HTMLAnchorElement(); - anchor.href = url; - anchor.download = suggestedName ?? 'document.pdf'; - web.document.body?.append(anchor); - anchor.click(); - web.URL.revokeObjectURL(url); - anchor.remove(); + if (openInNewTab) { + // Open in a new tab + web.window.open(url, '_blank'); + + Future.delayed(const Duration(seconds: 1), () { + web.URL.revokeObjectURL(url); + }); + } else { + // Download the file + final anchor = web.HTMLAnchorElement(); + anchor.href = url; + anchor.download = suggestedName ?? 'document.pdf'; + web.document.body?.append(anchor); + anchor.click(); + + web.URL.revokeObjectURL(url); + anchor.remove(); + } } const bool isWindowsDesktop = false; From 0cb4a285b41706b266c89a3dd1bfcac49e326245 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 4 Nov 2025 14:42:52 +0900 Subject: [PATCH 515/663] WIP --- packages/pdfrx/example/pdf_combine/lib/main.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index f6955c7f..0d1d0f02 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -200,11 +200,11 @@ class _PdfCombinePageState extends State { super.dispose(); } - Future _pickPdfFiles() async { + Future _pickFiles() async { final files = await openFiles( acceptedTypeGroups: [ XTypeGroup(label: 'PDFs', extensions: ['pdf']), - XTypeGroup(label: 'Images', extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff', 'webp']), + XTypeGroup(label: 'Images', extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp']), ], ); if (files.isEmpty) return; @@ -305,7 +305,7 @@ class _PdfCombinePageState extends State { title: const Text('PDF Combine'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ - IconButton(icon: const Icon(Icons.add), onPressed: _pickPdfFiles, tooltip: 'Add PDF files'), + IconButton(icon: const Icon(Icons.add), onPressed: _pickFiles, tooltip: 'Add PDF files'), const SizedBox(width: 8), FilledButton.icon( onPressed: _pages.isEmpty ? null : _navigateToPreview, @@ -336,12 +336,10 @@ class _PdfCombinePageState extends State { child: Text.rich( TextSpan( children: [ - TextSpan( - text: 'Tap the following button to add PDF files or drag & drop PDF files here!\n\n', - ), + TextSpan(text: 'Add/Drag-and-Drop PDF files here!\n\n'), WidgetSpan( alignment: PlaceholderAlignment.middle, - child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickPdfFiles()), + child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickFiles()), ), ], ), From cd9de0b9f7d46818a3da613f948846e28a5594b1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 02:35:10 +0900 Subject: [PATCH 516/663] WIP --- .../example/pdf_combine/lib/helper_web.dart | 2 +- .../pdfrx/example/pdf_combine/lib/main.dart | 113 +++++++++++++++--- packages/pdfrx/lib/src/pdfrx_flutter.dart | 29 ++++- packages/pdfrx_engine/lib/pdfrx_engine.dart | 1 + .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 1 + .../pdfrx_engine/lib/src/pdf_document.dart | 5 +- packages/pdfrx_engine/lib/src/pdf_image.dart | 48 ++++++++ packages/pdfrx_engine/lib/src/pdf_page.dart | 49 +------- .../lib/src/pdf_page_proxies.dart | 1 + packages/pdfrx_engine/lib/src/pdfrx_dart.dart | 30 ++++- .../lib/src/pdfrx_entry_functions.dart | 2 +- 12 files changed, 205 insertions(+), 78 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/pdf_image.dart diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart index dab038a4..05705e2a 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:web/web.dart' as web; -Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab = true}) async { +Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab = false}) async { final blob = web.Blob([bytes].jsify() as JSArray, web.BlobPropertyBag(type: 'application/pdf')); final url = web.URL.createObjectURL(blob); diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 0d1d0f02..01f45dac 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -28,8 +28,17 @@ class PdfCombineApp extends StatelessWidget { } } -class PageItem { - PageItem({ +abstract class PageItem { + String get id; +} + +class PageItemAdd extends PageItem { + @override + String get id => '##add_item##'; +} + +class PdfPageItem extends PageItem { + PdfPageItem({ required this.documentId, required this.documentName, required this.pageIndex, @@ -52,10 +61,11 @@ class PageItem { /// Rotation override for the page final PdfPageRotation? rotationOverride; + @override String get id => '${documentId}_$pageIndex'; - PageItem copyWith({PdfPage? page, PdfPageRotation? rotationOverride}) { - return PageItem( + PdfPageItem copyWith({PdfPage? page, PdfPageRotation? rotationOverride}) { + return PdfPageItem( documentId: documentId, documentName: documentName, pageIndex: pageIndex, @@ -94,18 +104,32 @@ class DocumentManager { /// Load image bytes as a PDF document /// - /// [assumedDpi] is the assumed DPI of the image for size calculation. - /// Typically, JPEG files may have DPI information in their metadata, but it's almost always 96-dpi or such - /// and thus results in very large PDF pages. Therefore, we use an assumed DPI of 300 by default. - Future _loadImageAsPdf(Uint8List bytes, String name, {double assumedDpi = 300}) async { + /// The image will be placed on a PDF page sized to fit within [fitWidth] x [fitHeight] points, + /// maintaining the aspect ratio. The default page size is A4 (595 x 842 points). + Future _loadImageAsPdf( + Uint8List bytes, + String name, { + double fitWidth = 595, + double fitHeight = 842, + int pixelSizeThreshold = 2000, + }) async { ui.Image? imageOpened; PdfImage? pdfImage; try { - /// NOTE: we should firstly try to open as image, because PDFium on Windows could not determine whether - /// the input bytes are PDF or not correctly in some cases. - final image = imageOpened = await decodeImageFromList(bytes); - final width = image.width * 72 / assumedDpi; - final height = image.height * 72 / assumedDpi; + final (:image, :origWidth, :origHeight) = await _decodeImage(bytes, pixelSizeThreshold: pixelSizeThreshold); + imageOpened = image; + final double width, height; + final aspectRatio = origWidth / origHeight; + if (origWidth <= fitWidth && origHeight <= fitHeight) { + width = origWidth.toDouble(); + height = origHeight.toDouble(); + } else if (aspectRatio >= fitWidth / fitHeight) { + width = fitWidth; + height = fitWidth / aspectRatio; + } else { + height = fitHeight; + width = fitHeight * aspectRatio; + } pdfImage = await image.toPdfImage(); imageOpened.dispose(); imageOpened = null; @@ -116,6 +140,40 @@ class DocumentManager { } } + Future<({ui.Image image, int origWidth, int origHeight})> _decodeImage( + Uint8List bytes, { + int pixelSizeThreshold = 2000, + }) async { + final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + final wh = []; + final ui.Codec codec = await PaintingBinding.instance.instantiateImageCodecWithSize( + buffer, + getTargetSize: (w, h) { + wh.addAll([w, h]); + if (w > pixelSizeThreshold || h > pixelSizeThreshold) { + final aspectRatio = w / h; + if (w >= h) { + final targetWidth = pixelSizeThreshold; + final targetHeight = (pixelSizeThreshold / aspectRatio).round(); + return ui.TargetImageSize(width: targetWidth, height: targetHeight); + } else { + final targetHeight = pixelSizeThreshold; + final targetWidth = (pixelSizeThreshold * aspectRatio).round(); + return ui.TargetImageSize(width: targetWidth, height: targetHeight); + } + } + return ui.TargetImageSize(width: w, height: h); + }, + ); + final ui.FrameInfo frameInfo; + try { + frameInfo = await codec.getNextFrame(); + } finally { + codec.dispose(); + } + return (image: frameInfo.image, origWidth: wh[0], origHeight: wh[1]); + } + Future _loadPdf(Uint8List bytes, String name) async { return await PdfDocument.openData( bytes, @@ -236,7 +294,7 @@ class _PdfCombinePageState extends State { if (doc != null) { for (var i = 0; i < doc.pages.length; i++) { _docManager.addReference(docId); - _pages.add(PageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); + _pages.add(PdfPageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); } } } catch (e) { @@ -260,15 +318,17 @@ class _PdfCombinePageState extends State { void _removePage(int index) { setState(() { - final pageItem = _pages[index]; + final page = _pages[index]; + if (page is! PdfPageItem) return; _pages.removeAt(index); - _docManager.removeReference(pageItem.documentId); + _docManager.removeReference(page.documentId); }); } void _rotatePageLeft(int index) { setState(() { final page = _pages[index]; + if (page is! PdfPageItem) return; _pages[index] = page.copyWith(rotationOverride: (page.rotationOverride ?? page.page.rotation).rotateCCW90); }); } @@ -279,7 +339,12 @@ class _PdfCombinePageState extends State { return; } - await Navigator.push(context, MaterialPageRoute(builder: (context) => OutputPreviewPage(pages: _pages))); + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => OutputPreviewPage(pages: _pages.whereType().cast().toList()), + ), + ); } Widget _disableDraggingOnChild(Widget child) { @@ -378,6 +443,18 @@ class _PdfCombinePageState extends State { isSameItem: (a, b) => a.id == b.id, itemBuilder: (context, index) { final pageItem = _pages[index]; + if (pageItem is! PdfPageItem) { + return Card( + color: Theme.of(context).colorScheme.primaryContainer, + child: Center( + child: IconButton.filled( + icon: const Icon(Icons.add), + onPressed: _pickFiles, + tooltip: 'Add PDF files', + ), + ), + ); + } return _PageThumbnail( key: ValueKey(pageItem.id), page: pageItem.page, @@ -523,7 +600,7 @@ class _PageThumbnail extends StatelessWidget { class OutputPreviewPage extends StatefulWidget { const OutputPreviewPage({required this.pages, super.key}); - final List pages; + final List pages; @override State createState() => _OutputPreviewPageState(); diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index cbcd310b..b00928b1 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -75,10 +75,35 @@ extension PdfPageExt on PdfPage { extension PdfImageExt on PdfImage { /// Create [Image] from the rendered image. /// + /// [pixelSizeThreshold] specifies the maximum allowed pixel size (width or height). + /// If the image exceeds this size, it will be downscaled to fit within the threshold + /// while maintaining the aspect ratio. + /// /// The returned [Image] must be disposed of when no longer needed. - Future createImage() { + Future createImage({int? pixelSizeThreshold}) { + int? targetWidth; + int? targetHeight; + if (pixelSizeThreshold != null && (width > pixelSizeThreshold || height > pixelSizeThreshold)) { + final aspectRatio = width / height; + if (width >= height) { + targetWidth = pixelSizeThreshold; + targetHeight = (pixelSizeThreshold / aspectRatio).round(); + } else { + targetHeight = pixelSizeThreshold; + targetWidth = (pixelSizeThreshold * aspectRatio).round(); + } + } + final comp = Completer(); - decodeImageFromPixels(pixels, width, height, PixelFormat.bgra8888, (image) => comp.complete(image)); + decodeImageFromPixels( + pixels, + width, + height, + PixelFormat.bgra8888, + (image) => comp.complete(image), + targetWidth: targetWidth, + targetHeight: targetHeight, + ); return comp.future; } } diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 11557e2d..f6dfa66a 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -6,6 +6,7 @@ export 'src/pdf_document.dart'; export 'src/pdf_document_event.dart'; export 'src/pdf_exception.dart'; export 'src/pdf_font_query.dart'; +export 'src/pdf_image.dart'; export 'src/pdf_link.dart'; export 'src/pdf_outline_node.dart'; export 'src/pdf_page.dart'; diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 7eb562c7..462064b8 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:typed_data'; import '../pdf_document.dart'; -import '../pdf_page.dart'; +import '../pdf_image.dart'; import '../pdfrx_entry_functions.dart'; /// This is an empty implementation of [PdfrxEntryFunctions] that just throws [UnimplementedError]. diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index daa76dda..e0972b9d 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -16,6 +16,7 @@ import '../pdf_document.dart'; import '../pdf_document_event.dart'; import '../pdf_exception.dart'; import '../pdf_font_query.dart'; +import '../pdf_image.dart'; import '../pdf_link.dart'; import '../pdf_outline_node.dart'; import '../pdf_page.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdf_document.dart b/packages/pdfrx_engine/lib/src/pdf_document.dart index 7e88b885..8c89867e 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'pdf_document_event.dart'; +import 'pdf_image.dart'; import 'pdf_outline_node.dart'; import 'pdf_page.dart'; import 'pdf_permissions.dart'; @@ -118,10 +119,10 @@ abstract class PdfDocument { /// Creating a PDF document from an image. /// + /// [image] is the PDF image to create the document from. + /// [width] and [height] are the dimensions of the image in PDF units (1/72 inch). /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. - /// [width] and [height] are the dimensions of the image in PDF units (1/72 inch). - /// [image] is the PDF image to create the document from. static Future createFromImage( PdfImage image, { required double width, diff --git a/packages/pdfrx_engine/lib/src/pdf_image.dart b/packages/pdfrx_engine/lib/src/pdf_image.dart new file mode 100644 index 00000000..86a80070 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_image.dart @@ -0,0 +1,48 @@ +import 'dart:typed_data'; + +import 'pdf_page.dart'; + +/// Image rendered from PDF page. +/// +/// Please note that the image created must be disposed after use by calling [dispose]. +/// See [PdfPage.render]. +abstract class PdfImage { + /// Number of pixels in horizontal direction. + int get width; + + /// Number of pixels in vertical direction. + int get height; + + /// BGRA8888 Raw pixel data. + Uint8List get pixels; + + /// Dispose the image. + void dispose(); + + /// Create [PdfImage] from BGRA pixel data. + /// + /// [bgraPixels] is the raw pixel data in BGRA8888 format. + /// [width] and [height] specify the dimensions of the image. + /// + /// The size of [bgraPixels] must be equal to `width * height * 4`. + /// Returns the created [PdfImage]. + static PdfImage createFromBgraData(Uint8List bgraPixels, {required int width, required int height}) { + return _PdfImageBgraRaw(width, height, bgraPixels); + } +} + +class _PdfImageBgraRaw implements PdfImage { + _PdfImageBgraRaw(this.width, this.height, this.pixels); + + @override + final int width; + + @override + final int height; + + @override + final Uint8List pixels; + + @override + void dispose() {} +} diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index deab68ba..d38b2708 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -1,6 +1,5 @@ -import 'dart:typed_data'; - import 'pdf_document.dart'; +import 'pdf_image.dart'; import 'pdf_link.dart'; import 'pdf_page_proxies.dart'; import 'pdf_text.dart'; @@ -233,49 +232,3 @@ abstract class PdfPageRenderCancellationToken { /// Determine whether the rendering process is canceled or not. bool get isCanceled; } - -/// Image rendered from PDF page. -/// -/// See [PdfPage.render]. -abstract class PdfImage { - /// Number of pixels in horizontal direction. - int get width; - - /// Number of pixels in vertical direction. - int get height; - - /// BGRA8888 Raw pixel data. - Uint8List get pixels; - - /// Dispose the image. - void dispose(); - - /// Create [PdfImage] from BGRA pixel data. - /// - /// [bgraPixels] is the raw pixel data in BGRA8888 format. - /// [width] and [height] specify the dimensions of the image. - /// - /// The size of [bgraPixels] must be equal to `width * height * 4`. - /// Returns the created [PdfImage]. - static PdfImage createFromBgraData(Uint8List bgraPixels, {required int width, required int height}) { - return _PdfImageSimple(width, height, bgraPixels); - } -} - -class _PdfImageSimple implements PdfImage { - _PdfImageSimple(this.width, this.height, this.pixels); - - @override - final int width; - - @override - final int height; - - @override - final Uint8List pixels; - - @override - void dispose() { - // No resources to dispose. - } -} diff --git a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart index f974ed98..cd462521 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart @@ -1,4 +1,5 @@ import 'pdf_document.dart'; +import 'pdf_image.dart'; import 'pdf_link.dart'; import 'pdf_page.dart'; import 'pdf_text.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart index 25f774a8..f6455c33 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_dart.dart @@ -1,19 +1,39 @@ import 'package:image/image.dart'; -import 'pdf_page.dart'; +import 'pdf_image.dart'; extension PdfImageDartExt on PdfImage { /// Create [Image] (of [image package](https://pub.dev/packages/image)) from the rendered image. /// + /// [pixelSizeThreshold] specifies the maximum allowed pixel size (width or height). + /// If the image exceeds this size, it will be downscaled to fit within the threshold + /// while maintaining the aspect ratio. + /// [interpolation] specifies the interpolation method to use when resizing images. + /// /// **NF**: This method does not require Flutter and can be used in pure Dart applications. - Image createImageNF() { - return Image.fromBytes( + Image createImageNF({int? pixelSizeThreshold, Interpolation interpolation = Interpolation.linear}) { + final image = Image.fromBytes( width: width, height: height, bytes: pixels.buffer, numChannels: 4, order: ChannelOrder.bgra, ); + + if (pixelSizeThreshold != null && (width > pixelSizeThreshold || height > pixelSizeThreshold)) { + final aspectRatio = width / height; + int targetWidth; + int targetHeight; + if (width >= height) { + targetWidth = pixelSizeThreshold; + targetHeight = (pixelSizeThreshold / aspectRatio).round(); + } else { + targetHeight = pixelSizeThreshold; + targetWidth = (pixelSizeThreshold * aspectRatio).round(); + } + return copyResize(image, width: targetWidth, height: targetHeight, interpolation: interpolation); + } + return image; } } @@ -23,9 +43,9 @@ extension ImageDartExt on Image { /// **NF**: This method does not require Flutter and can be used in pure Dart applications. /// /// By default, the function assumes that the image data is in RGBA format and performs conversion to BGRA. - /// - If the image data is already in BGRA format, set [order] to [ChannelOrder.bgra]. + /// - If the image data is already in BGRA format, set [order] to [ChannelOrder.bgra] to prevent unnecessary conversion. /// - If [bgraConversionInPlace] is set to true and conversion is needed, the conversion will be done in place - /// modifying the original image data. This can save memory but will alter the original image + /// modifying the original image data. This can save memory but will alter the original image. PdfImage toPdfImageNF({ChannelOrder order = ChannelOrder.rgba, bool bgraConversionInPlace = false}) { if (data == null) { throw StateError('The image has no pixel data.'); diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 8fcfc6f9..4e4c77c9 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; import 'pdf_document.dart'; -import 'pdf_page.dart'; +import 'pdf_image.dart'; import 'pdfrx.dart'; /// The class is used to implement Pdfrx's backend functions. From e909af0810a81ab5c442ddd2daa57ab160813cd8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 04:34:46 +0900 Subject: [PATCH 517/663] WIP: PdfDocument.createFromImage is superseded by PdfDocument.createFromJpegData --- packages/pdfrx/assets/pdfium_worker.js | 167 ++++++++++++++---- .../pdfrx/example/pdf_combine/lib/main.dart | 22 +-- .../pdfrx/example/pdf_combine/pubspec.yaml | 1 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 20 +-- .../Sources/PdfrxCoregraphicsPlugin.swift | 145 +++++++++------ .../lib/pdfrx_coregraphics.dart | 24 +-- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 5 +- .../lib/src/native/pdfrx_pdfium.dart | 138 ++++++++++----- .../pdfrx_engine/lib/src/pdf_document.dart | 11 +- .../lib/src/pdfrx_entry_functions.dart | 7 +- 10 files changed, 352 insertions(+), 188 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index bd487f5c..c4a9e95c 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1867,6 +1867,133 @@ function createNewDocument() { return _loadDocument(docHandle, false, () => {}); } +/** + * Create a PDF document from JPEG data + * @param {Object} params Parameters object + * @param {ArrayBuffer} params.jpegData JPEG image data + * @param {number} params.width Page width in PDF units + * @param {number} params.height Page height in PDF units + * @returns {PdfDocument|PdfError} + */ +function createDocumentFromJpegData(params) { + const { jpegData, width, height } = params; + + if (!jpegData || !(jpegData instanceof ArrayBuffer)) { + return { errorCode: -1, errorCodeStr: 'Invalid JPEG data' }; + } + if (typeof width !== 'number' || width <= 0) { + return { errorCode: -1, errorCodeStr: 'Invalid width' }; + } + if (typeof height !== 'number' || height <= 0) { + return { errorCode: -1, errorCodeStr: 'Invalid height' }; + } + + // Create a new PDF document + const docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); + if (!docHandle) { + return { errorCode: -1, errorCodeStr: 'Failed to create PDF document' }; + } + + // Create a new page + const pageHandle = Pdfium.wasmExports.FPDFPage_New(docHandle, 0, width, height); + if (!pageHandle) { + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to create PDF page' }; + } + + // Create an image object + const imageObj = Pdfium.wasmExports.FPDFPageObj_NewImageObj(docHandle); + if (!imageObj) { + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to create image object' }; + } + + // Create a FPDF_FILEACCESS structure in WASM memory + const fileAccessSize = 12; // sizeof(FPDF_FILEACCESS) - 3 pointers (each 4 bytes in wasm32) + const fileAccessPtr = Pdfium.wasmExports.malloc(fileAccessSize); + if (!fileAccessPtr) { + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to allocate file access structure' }; + } + + // Set up file access structure + const fa = new Uint32Array(Pdfium.memory.buffer, fileAccessPtr, fileAccessSize >> 2); + fa[0] = jpegData.byteLength; // m_FileLen + const getBlockCallback = (param, position, pBuf, size) => { + const toCopy = Math.min(size, jpegData.byteLength - position); + const src = new Uint8Array(jpegData, position, toCopy); + const dst = new Uint8Array(Pdfium.memory.buffer, pBuf, toCopy); + dst.set(src); + return toCopy; + }; + const callbackIndex = Pdfium.addFunction(getBlockCallback, 'iiiii'); + fa[1] = callbackIndex; // m_GetBlock function pointer + + // Allocate page array (pointer to single page handle) + const pageArrayPtr = Pdfium.wasmExports.malloc(4); + if (!pageArrayPtr) { + Pdfium.removeFunction(callbackIndex); + Pdfium.wasmExports.free(fileAccessPtr); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to allocate page array' }; + } + new Int32Array(Pdfium.memory.buffer, pageArrayPtr, 1)[0] = pageHandle; + + // Load JPEG data into the image object + const loadResult = Pdfium.wasmExports.FPDFImageObj_LoadJpegFileInline( + pageArrayPtr, + 1, + imageObj, + fileAccessPtr + ); + Pdfium.wasmExports.free(pageArrayPtr); + Pdfium.removeFunction(callbackIndex); + Pdfium.wasmExports.free(fileAccessPtr); + + if (!loadResult) { + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to load JPEG data into image object' }; + } + + // Set image transformation matrix to fill the page + const setMatrixResult = Pdfium.wasmExports.FPDFImageObj_SetMatrix( + imageObj, + width, // a (horizontal scaling) + 0, // b (horizontal skewing) + 0, // c (vertical skewing) + height, // d (vertical scaling) + 0, // e (horizontal translation) + 0 // f (vertical translation) + ); + + if (!setMatrixResult) { + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to set image matrix' }; + } + + // Insert the image object into the page + Pdfium.wasmExports.FPDFPage_InsertObject(pageHandle, imageObj); + + // Generate page content + const generateResult = Pdfium.wasmExports.FPDFPage_GenerateContent(pageHandle); + if (!generateResult) { + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + Pdfium.wasmExports.FPDF_CloseDocument(docHandle); + return { errorCode: -1, errorCodeStr: 'Failed to generate page content' }; + } + + // Close the page (transfers ownership to document) + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + + // Load and return the document + return _loadDocument(docHandle, false, () => {}); +} + /** * Set pixel data for an image object * @param {number} pageHandle Page handle @@ -1903,44 +2030,6 @@ function _setImageObjPixels(pageHandle, imageObj, pixels, pixelWidth, pixelHeigh } } -/** - * Create a PDF document from an image - * @param {{ - * pixels: ArrayBuffer, - * pixelWidth: number, - * pixelHeight: number, - * width: number, - * height: number - * }} params - * @returns {PdfDocument|PdfError} - */ -function createDocumentFromImage(params) { - let docHandle = 0, newPage = 0, imageObj = 0; - try { - const { pixels, pixelWidth, pixelHeight, width, height } = params; - docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); - if (!docHandle) throw new Error('Failed to create new PDF document'); - newPage = Pdfium.wasmExports.FPDFPage_New(docHandle, 0, width, height); - if (!newPage) throw new Error('Failed to create new PDF page'); - imageObj = Pdfium.wasmExports.FPDFPageObj_NewImageObj(docHandle); - if (!imageObj) throw new Error('Failed to create new image object'); - _setImageObjPixels(newPage, imageObj, pixels, pixelWidth, pixelHeight); - Pdfium.wasmExports.FPDFImageObj_SetMatrix(imageObj, width, 0, 0, height, 0, 0); - Pdfium.wasmExports.FPDFPage_InsertObject(newPage, imageObj); - imageObj = 0; // ownership transferred to newPage - Pdfium.wasmExports.FPDFPage_GenerateContent(newPage); - const docHandleReturn = docHandle; - docHandle = 0; // prevent cleanup in finally - return _loadDocument(docHandleReturn, false, () => {}); - } catch (e) { - return { errorCode: -1, errorCodeStr: 'Exception: ' + e.toString() }; - } finally { - Pdfium.wasmExports.FPDFPageObj_Destroy(imageObj); - Pdfium.wasmExports.FPDF_ClosePage(newPage); - Pdfium.wasmExports.FPDF_CloseDocument(docHandle); - } -} - /** * Functions that can be called from the main thread */ @@ -1948,7 +2037,7 @@ const functions = { loadDocumentFromUrl, loadDocumentFromData, createNewDocument, - createDocumentFromImage, + createDocumentFromJpegData, loadPagesProgressively, closeDocument, loadOutline, diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 01f45dac..cdfb5e2b 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -6,9 +6,10 @@ import 'package:desktop_drop/desktop_drop.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:jpeg_encode/jpeg_encode.dart'; import 'package:pdfrx/pdfrx.dart'; -import 'helper_web.dart' if (dart.library.io) 'save_helper_io.dart'; +import 'helper_web.dart' if (dart.library.io) 'helper_io.dart'; void main() { pdfrxFlutterInitialize(); @@ -112,12 +113,10 @@ class DocumentManager { double fitWidth = 595, double fitHeight = 842, int pixelSizeThreshold = 2000, + int jpegQuality = 90, }) async { - ui.Image? imageOpened; - PdfImage? pdfImage; + final (:image, :origWidth, :origHeight) = await _decodeImage(bytes, pixelSizeThreshold: pixelSizeThreshold); try { - final (:image, :origWidth, :origHeight) = await _decodeImage(bytes, pixelSizeThreshold: pixelSizeThreshold); - imageOpened = image; final double width, height; final aspectRatio = origWidth / origHeight; if (origWidth <= fitWidth && origHeight <= fitHeight) { @@ -130,13 +129,14 @@ class DocumentManager { height = fitHeight; width = fitHeight * aspectRatio; } - pdfImage = await image.toPdfImage(); - imageOpened.dispose(); - imageOpened = null; - return await PdfDocument.createFromImage(pdfImage, width: width, height: height, sourceName: name); + final rawRgba = await image.toByteData(format: ui.ImageByteFormat.rawRgba); + final jpegData = JpegEncoder().compress(rawRgba!.buffer.asUint8List(), image.width, image.height, jpegQuality); + return await PdfDocument.createFromJpegData(jpegData, width: width, height: height, sourceName: name); + } catch (e) { + print('Error creating PDF from JPEG: $e'); + rethrow; } finally { - imageOpened?.dispose(); - pdfImage?.dispose(); + image.dispose(); } } diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 150f2525..351ede7b 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: animated_reorderable_list: ^1.3.0 web: ^1.1.1 desktop_drop: ^0.7.0 + jpeg_encode: ^1.0.1 dev_dependencies: flutter_test: diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index e943cf82..066502f8 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -314,28 +314,22 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { } @override - Future createFromImage( - PdfImage image, { + Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, }) async { await init(); - final jsPixels = image.pixels.buffer.toJS; + final jsData = jpegData.buffer.toJS; final result = await _sendCommand( - 'createDocumentFromImage', - parameters: { - 'pixels': jsPixels, - 'pixelWidth': image.width, - 'pixelHeight': image.height, - 'width': width, - 'height': height, - }, - transfer: [jsPixels].toJS, + 'createDocumentFromJpegData', + parameters: {'jpegData': jsData, 'width': width, 'height': height}, + transfer: [jsData].toJS, ); final errorCode = (result['errorCode'] as num?)?.toInt(); if (errorCode != null) { - throw StateError('Failed to create document from image: ${result['errorCodeStr']} ($errorCode)'); + throw StateError('Failed to create document from JPEG data: ${result['errorCodeStr']} ($errorCode)'); } return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); } diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index 2a164074..5163fe6c 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -30,8 +30,8 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { openDocument(arguments: call.arguments, result: result) case "createNewDocument": createNewDocument(arguments: call.arguments, result: result) - case "createDocumentFromImage": - createDocumentFromImage(arguments: call.arguments, result: result) + case "createDocumentFromJpegData": + createDocumentFromJpegData(arguments: call.arguments, result: result) case "renderPage": renderPage(arguments: call.arguments, result: result) case "loadPageText": @@ -164,88 +164,125 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { ]) } - /// Creates a PDF document from BGRA8888 image data - private func createDocumentFromImage(arguments: Any?, result: @escaping FlutterResult) { + /// Creates a PDF document from JPEG data + private func createDocumentFromJpegData(arguments: Any?, result: @escaping FlutterResult) { guard let args = arguments as? [String: Any] else { result( FlutterError( - code: "bad-arguments", message: "Invalid arguments for createDocumentFromImage.", details: nil + code: "bad-arguments", message: "Invalid arguments for createDocumentFromJpegData.", details: nil )) return } - guard - let pixels = args["pixels"] as? FlutterStandardTypedData, - let pixelWidth = args["pixelWidth"] as? Int, - let pixelHeight = args["pixelHeight"] as? Int, - let width = args["width"] as? Double, - let height = args["height"] as? Double - else { + guard let jpegData = args["jpegData"] as? FlutterStandardTypedData else { result( FlutterError( - code: "bad-arguments", message: "Missing required parameters for createDocumentFromImage.", details: nil + code: "missing-jpeg-data", message: "JPEG data is required.", details: nil )) return } - // Create a CGContext from BGRA pixel data - let pixelData = pixels.data - let bytesPerPixel = 4 - let bytesPerRow = pixelWidth * bytesPerPixel - let expectedDataSize = bytesPerRow * pixelHeight + guard let width = args["width"] as? Double, width > 0 else { + result( + FlutterError( + code: "invalid-width", message: "Valid width is required.", details: nil + )) + return + } - guard pixelData.count == expectedDataSize else { + guard let height = args["height"] as? Double, height > 0 else { result( FlutterError( - code: "invalid-data", message: "Pixel data size mismatch. Expected \(expectedDataSize) bytes, got \(pixelData.count).", details: nil + code: "invalid-height", message: "Valid height is required.", details: nil )) return } - // Create a mutable copy of pixel data since CGContext requires mutable data - var mutablePixelData = pixelData + // Create an empty PDF document + guard let pdfDocument = PDFDocument() else { + result( + FlutterError( + code: "pdf-document-failure", message: "Failed to create PDFDocument.", details: nil + )) + return + } - guard - let context = mutablePixelData.withUnsafeMutableBytes({ (ptr: UnsafeMutableRawBufferPointer) -> CGContext? in - CGContext( - data: ptr.baseAddress, - width: pixelWidth, - height: pixelHeight, - bitsPerComponent: 8, - bytesPerRow: bytesPerRow, - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue - ) - }) - else { + // Create a PDF page with the specified dimensions + let pageRect = CGRect(x: 0, y: 0, width: width, height: height) + guard let pdfPage = PDFPage() else { + result( + FlutterError( + code: "pdf-page-failure", message: "Failed to create PDF page.", details: nil + )) + return + } + pdfPage.setBounds(pageRect, for: .mediaBox) + + // Create image from JPEG data + #if os(iOS) + guard let image = UIImage(data: jpegData.data) else { result( FlutterError( - code: "context-failure", message: "Failed to create bitmap context from image data.", details: nil + code: "invalid-jpeg", message: "Failed to decode JPEG data.", details: nil )) return } + guard let cgImage = image.cgImage else { + result( + FlutterError( + code: "invalid-image", message: "Failed to get CGImage from JPEG.", details: nil + )) + return + } + #elseif os(macOS) + guard let image = NSImage(data: jpegData.data) else { + result( + FlutterError( + code: "invalid-jpeg", message: "Failed to decode JPEG data.", details: nil + )) + return + } + guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { + result( + FlutterError( + code: "invalid-image", message: "Failed to get CGImage from JPEG.", details: nil + )) + return + } + #endif + + // Draw the image on the page using CoreGraphics + let context = CGContext( + data: nil, + width: Int(width), + height: Int(height), + bitsPerComponent: 8, + bytesPerRow: 0, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) - guard let cgImage = context.makeImage() else { + guard let ctx = context else { result( FlutterError( - code: "image-failure", message: "Failed to create CGImage from context.", details: nil + code: "context-failure", message: "Failed to create graphics context.", details: nil )) return } - // Create a PDF document using NSMutableData to capture the output + // Create a mutable data to hold the PDF content let pdfData = NSMutableData() - var mediaBox = CGRect(x: 0, y: 0, width: width, height: height) - - guard let dataConsumer = CGDataConsumer(data: pdfData) else { + guard let pdfConsumer = CGDataConsumer(data: pdfData) else { result( FlutterError( - code: "data-consumer-failure", message: "Failed to create PDF data consumer.", details: nil + code: "consumer-failure", message: "Failed to create PDF data consumer.", details: nil )) return } - guard let pdfContext = CGContext(consumer: dataConsumer, mediaBox: &mediaBox, nil) else { + // Create a PDF context to generate the page + var mediaBox = pageRect + guard let pdfContext = CGContext(consumer: pdfConsumer, mediaBox: &mediaBox, nil) else { result( FlutterError( code: "pdf-context-failure", message: "Failed to create PDF context.", details: nil @@ -253,23 +290,16 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { return } - // Begin PDF page pdfContext.beginPDFPage(nil) - - // Draw the image to fill the entire page - pdfContext.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) - - // End PDF page + pdfContext.draw(cgImage, in: pageRect) pdfContext.endPDFPage() - - // Close the PDF context pdfContext.closePDF() - // Create PDFDocument from data - guard let pdfDocument = PDFDocument(data: pdfData) else { + // Create PDFDocument from generated data + guard let resultDocument = PDFDocument(data: pdfData as Data) else { result( FlutterError( - code: "pdf-document-failure", message: "Failed to create PDFDocument from generated PDF data.", details: nil + code: "pdf-creation-failure", message: "Failed to create PDF document from image.", details: nil )) return } @@ -277,10 +307,11 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { // Register the document let handle = nextHandle nextHandle += 1 - documents[handle] = pdfDocument + documents[handle] = resultDocument + // Build page info var pageInfos: [[String: Any]] = [] - if let page = pdfDocument.page(at: 0) { + if let page = resultDocument.page(at: 0) { let bounds = page.bounds(for: .mediaBox) pageInfos.append([ "width": Double(bounds.width), diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index aa791051..f4bd2f03 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -202,24 +202,24 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { } @override - Future createFromImage( - PdfImage image, { + Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, }) async { await init(); - final result = await _channel - .invokeMapMethod('createDocumentFromImage', { - 'pixels': image.pixels, - 'pixelWidth': image.width, - 'pixelHeight': image.height, - 'width': width, - 'height': height, - 'sourceName': sourceName, - }); + final result = await _channel.invokeMapMethod( + 'createDocumentFromJpegData', + { + 'jpegData': jpegData, + 'width': width, + 'height': height, + 'sourceName': sourceName, + }, + ); if (result == null) { - throw const PdfException('Failed to create PDF document from image.'); + throw const PdfException('Failed to create PDF document from JPEG data.'); } return _CoreGraphicsPdfDocument.fromPlatformMap( channel: _channel, diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 462064b8..68e8558d 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:typed_data'; import '../pdf_document.dart'; -import '../pdf_image.dart'; import '../pdfrx_entry_functions.dart'; /// This is an empty implementation of [PdfrxEntryFunctions] that just throws [UnimplementedError]. @@ -83,8 +82,8 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { Future createNew({required String sourceName}) => unimplemented(); @override - Future createFromImage( - PdfImage image, { + Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index e0972b9d..51353ee8 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -446,57 +446,53 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } @override - Future createFromImage( - PdfImage image, { + Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, }) async { await _init(); - final doc = await (await backgroundWorker).computeWithArena( - (arena, params) { - final document = pdfium.FPDF_CreateNewDocument(); - final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); - final newPages = arena.allocate(sizeOf>()); - newPages.value = newPage; - - final memBuffer = arena.allocate(params.pixels.length); - final bufferList = memBuffer.asTypedList(params.pixels.length); - bufferList.setAll(0, params.pixels); - - final imageObj = pdfium.FPDFPageObj_NewImageObj(document); - final bitmap = pdfium.FPDFBitmap_CreateEx( - params.pixelWidth, - params.pixelHeight, - pdfium_bindings.FPDFBitmap_BGRA, - memBuffer.cast(), - params.pixelWidth * 4, - ); - pdfium.FPDFImageObj_SetBitmap(newPages, 1, imageObj, bitmap); - pdfium.FPDFBitmap_Destroy(bitmap); + final dataBuffer = malloc(jpegData.length); + try { + dataBuffer.asTypedList(jpegData.length).setAll(0, jpegData); + final doc = await (await backgroundWorker).computeWithArena( + (arena, params) { + final document = pdfium.FPDF_CreateNewDocument(); + final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); + final newPages = arena.allocate(sizeOf>()); + newPages.value = newPage; - pdfium.FPDFImageObj_SetMatrix(imageObj, params.width, 0, 0, params.height, 0, 0); - pdfium.FPDFPage_InsertObject(newPage, imageObj); // imageObj is now owned by the page + final imageObj = pdfium.FPDFPageObj_NewImageObj(document); - pdfium.FPDFPage_GenerateContent(newPage); - pdfium.FPDF_ClosePage(newPage); - return document.address; - }, - ( - pixels: image.pixels, - pixelWidth: image.width, - pixelHeight: image.height, - width: width, - height: height, + final fa = _FileAccess.fromDataBuffer(Pointer.fromAddress(dataBuffer.address), jpegData.length); + pdfium.FPDFImageObj_LoadJpegFileInline(newPages, 1, imageObj, fa.fileAccess); + fa.dispose(); + + pdfium.FPDFImageObj_SetMatrix(imageObj, params.width, 0, 0, params.height, 0, 0); + pdfium.FPDFPage_InsertObject(newPage, imageObj); // image is now owned by the page + + pdfium.FPDFPage_GenerateContent(newPage); + pdfium.FPDF_ClosePage(newPage); + return document.address; + }, + ( + dataBuffer: dataBuffer.address, + dataLength: jpegData.length, + width: width, + height: height, + sourceName: sourceName, + ), + ); + return _PdfDocumentPdfium.fromPdfDocument( + pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), sourceName: sourceName, - ), - ); - return _PdfDocumentPdfium.fromPdfDocument( - pdfium_bindings.FPDF_DOCUMENT.fromAddress(doc), - sourceName: sourceName, - useProgressiveLoading: false, - disposeCallback: null, - ); + useProgressiveLoading: false, + disposeCallback: null, + ); + } finally { + malloc.free(dataBuffer); + } } static String _getPdfiumErrorString([int? error]) { @@ -1459,3 +1455,59 @@ PdfDest? _pdfDestFromDest(pdfium_bindings.FPDF_DEST dest, pdfium_bindings.FPDF_D } return null; } + +/// Native callable type for `FPDF_FILEACCESS.m_GetBlock` +typedef _NativeFileReadCallable = + NativeCallable< + Int Function(Pointer param, UnsignedLong position, Pointer pBuf, UnsignedLong size) + >; + +/// Manages `FPDF_FILEACCESS` structure and its associated native callable. +class _FileAccess { + _FileAccess._(this.fileAccess, this._nativeReadCallable); + + final Pointer fileAccess; + final _NativeFileReadCallable? _nativeReadCallable; + + static _FileAccess fromDataBuffer(Pointer bufferPtr, int length) { + _NativeFileReadCallable? nativeReadCallable; + Pointer? fileAccessToRelease; + try { + final fileAccess = fileAccessToRelease = malloc(); + fileAccess.ref.m_FileLen = length; + + nativeReadCallable = _NativeFileReadCallable.isolateLocal(( + Pointer param, + int position, + Pointer pBuf, + int size, + ) { + final dataPtr = bufferPtr.offset(position); + final toCopy = min(size, length - position); + if (toCopy <= 0) { + return 0; + } + pBuf.cast().asTypedList(toCopy).setAll(0, dataPtr.cast().asTypedList(toCopy)); + return toCopy; + }, exceptionalReturn: 0); + + fileAccess.ref.m_GetBlock = nativeReadCallable.nativeFunction; + final result = _FileAccess._(fileAccess, nativeReadCallable); + nativeReadCallable = null; + fileAccessToRelease = null; + return result; + } catch (e) { + rethrow; + } finally { + nativeReadCallable?.close(); + if (fileAccessToRelease != null) { + malloc.free(fileAccessToRelease); + } + } + } + + void dispose() { + malloc.free(fileAccess); + _nativeReadCallable?.close(); + } +} diff --git a/packages/pdfrx_engine/lib/src/pdf_document.dart b/packages/pdfrx_engine/lib/src/pdf_document.dart index 8c89867e..a7cc9b9f 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:typed_data'; import 'pdf_document_event.dart'; -import 'pdf_image.dart'; import 'pdf_outline_node.dart'; import 'pdf_page.dart'; import 'pdf_permissions.dart'; @@ -117,18 +116,18 @@ abstract class PdfDocument { static Future createNew({required String sourceName}) => PdfrxEntryFunctions.instance.createNew(sourceName: sourceName); - /// Creating a PDF document from an image. + /// Creating a PDF document from JPEG data. /// - /// [image] is the PDF image to create the document from. + /// [jpegData] is the JPEG encoded image data. /// [width] and [height] are the dimensions of the image in PDF units (1/72 inch). /// [sourceName] must be some ID, e.g., file name or URL, to identify the source of the PDF. If [sourceName] is not /// unique for each source, the viewer may not work correctly. - static Future createFromImage( - PdfImage image, { + static Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, - }) => PdfrxEntryFunctions.instance.createFromImage(image, width: width, height: height, sourceName: sourceName); + }) => PdfrxEntryFunctions.instance.createFromJpegData(jpegData, width: width, height: height, sourceName: sourceName); /// Opening the PDF from custom source. /// diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 4e4c77c9..5f69138e 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -3,7 +3,6 @@ import 'dart:typed_data'; import './mock/pdfrx_mock.dart' if (dart.library.io) './native/pdfrx_pdfium.dart'; import 'pdf_document.dart'; -import 'pdf_image.dart'; import 'pdfrx.dart'; /// The class is used to implement Pdfrx's backend functions. @@ -85,9 +84,9 @@ abstract class PdfrxEntryFunctions { /// See [PdfDocument.createNew]. Future createNew({required String sourceName}); - /// See [PdfDocument.createFromImage]. - Future createFromImage( - PdfImage image, { + /// See [PdfDocument.createFromJpegData]. + Future createFromJpegData( + Uint8List jpegData, { required double width, required double height, required String sourceName, From 8d6375b19a17aa4bca252e7daa278154637eea09 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 04:35:11 +0900 Subject: [PATCH 518/663] WIP --- packages/pdfrx/example/pdf_combine/lib/main.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index cdfb5e2b..59362e83 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -132,9 +132,6 @@ class DocumentManager { final rawRgba = await image.toByteData(format: ui.ImageByteFormat.rawRgba); final jpegData = JpegEncoder().compress(rawRgba!.buffer.asUint8List(), image.width, image.height, jpegQuality); return await PdfDocument.createFromJpegData(jpegData, width: width, height: height, sourceName: name); - } catch (e) { - print('Error creating PDF from JPEG: $e'); - rethrow; } finally { image.dispose(); } From f738a92b8f5680ff07e068726a422ef03050526e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 05:35:28 +0900 Subject: [PATCH 519/663] WIP --- .../example/pdf_combine/lib/helper_io.dart | 33 ++++++++ .../example/pdf_combine/lib/helper_web.dart | 35 ++++++++ .../pdfrx/example/pdf_combine/lib/main.dart | 82 +++++++------------ 3 files changed, 99 insertions(+), 51 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart index dba3b0a0..0a3463d8 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart @@ -1,7 +1,10 @@ import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:jpeg_encode/jpeg_encode.dart'; import 'package:share_plus/share_plus.dart'; Future savePdf(Uint8List bytes, {String? suggestedName}) async { @@ -23,4 +26,34 @@ Future savePdf(Uint8List bytes, {String? suggestedName}) async { } } +typedef CalculateTargetSize = ({int width, int height}) Function(int originalWidth, int originalHeight); + +class JpegData { + const JpegData(this.data, this.width, this.height); + final Uint8List data; + final int width; + final int height; +} + +Future compressImageToJpeg( + Uint8List imageData, { + CalculateTargetSize? calculateTargetSize, + int quality = 90, +}) async { + calculateTargetSize ??= (w, h) => (width: w, height: h); + final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(imageData); + final codec = await PaintingBinding.instance.instantiateImageCodecWithSize( + buffer, + getTargetSize: (w, h) { + final size = calculateTargetSize!(w, h); + return ui.TargetImageSize(width: size.width, height: size.height); + }, + ); + final frameInfo = await codec.getNextFrame(); + final rgba = (await frameInfo.image.toByteData(format: ui.ImageByteFormat.rawRgba))!.buffer.asUint8List(); + final byteData = JpegEncoder().compress(rgba, frameInfo.image.width, frameInfo.image.height, quality); + codec.dispose(); + return JpegData(byteData.buffer.asUint8List(), frameInfo.image.width, frameInfo.image.height); +} + final isWindowsDesktop = Platform.isWindows; diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart index 05705e2a..e9c8b3cc 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart @@ -1,6 +1,7 @@ import 'dart:js_interop'; import 'dart:typed_data'; +import 'package:web/helpers.dart'; import 'package:web/web.dart' as web; Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab = false}) async { @@ -28,4 +29,38 @@ Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab } } +typedef CalculateTargetSize = ({int width, int height}) Function(int originalWidth, int originalHeight); + +class JpegData { + const JpegData(this.data, this.width, this.height); + final Uint8List data; + final int width; + final int height; +} + +Future compressImageToJpeg( + Uint8List imageData, { + CalculateTargetSize? calculateTargetSize, + int quality = 90, +}) async { + calculateTargetSize ??= (w, h) => (width: w, height: h); + final blob = web.Blob([imageData].jsify() as JSArray); + final webCodec = await web.window.createImageBitmap(blob).toDart; + + final size = calculateTargetSize(webCodec.width, webCodec.height); + + final offScreenCanvas = web.OffscreenCanvas(size.width, size.height); + final context = offScreenCanvas.getContext('2d') as web.CanvasRenderingContext2D; + context.drawImage(webCodec, 0, 0, size.width, size.height); + final encodedBlob = await offScreenCanvas + .convertToBlob( + ImageEncodeOptions() + ..type = 'image/jpeg' + ..quality = quality / 100, + ) + .toDart; + final arrayBuffer = await encodedBlob.arrayBuffer().toDart; + return JpegData(arrayBuffer.toDart.asUint8List(), size.width, size.height); +} + const bool isWindowsDesktop = false; diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 59362e83..d205a79b 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -6,7 +6,6 @@ import 'package:desktop_drop/desktop_drop.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:jpeg_encode/jpeg_encode.dart'; import 'package:pdfrx/pdfrx.dart'; import 'helper_web.dart' if (dart.library.io) 'helper_io.dart'; @@ -107,6 +106,9 @@ class DocumentManager { /// /// The image will be placed on a PDF page sized to fit within [fitWidth] x [fitHeight] points, /// maintaining the aspect ratio. The default page size is A4 (595 x 842 points). + /// [pixelSizeThreshold] specifies the maximum allowed pixel size (width or height) for the image; + /// if the image exceeds this size, it will be downscaled to fit within the threshold while maintaining the aspect ratio. + /// [jpegQuality] specifies the JPEG compression quality (1-100). Future _loadImageAsPdf( Uint8List bytes, String name, { @@ -115,60 +117,38 @@ class DocumentManager { int pixelSizeThreshold = 2000, int jpegQuality = 90, }) async { - final (:image, :origWidth, :origHeight) = await _decodeImage(bytes, pixelSizeThreshold: pixelSizeThreshold); - try { - final double width, height; - final aspectRatio = origWidth / origHeight; - if (origWidth <= fitWidth && origHeight <= fitHeight) { - width = origWidth.toDouble(); - height = origHeight.toDouble(); - } else if (aspectRatio >= fitWidth / fitHeight) { - width = fitWidth; - height = fitWidth / aspectRatio; - } else { - height = fitHeight; - width = fitHeight * aspectRatio; - } - final rawRgba = await image.toByteData(format: ui.ImageByteFormat.rawRgba); - final jpegData = JpegEncoder().compress(rawRgba!.buffer.asUint8List(), image.width, image.height, jpegQuality); - return await PdfDocument.createFromJpegData(jpegData, width: width, height: height, sourceName: name); - } finally { - image.dispose(); - } - } - - Future<({ui.Image image, int origWidth, int origHeight})> _decodeImage( - Uint8List bytes, { - int pixelSizeThreshold = 2000, - }) async { - final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); - final wh = []; - final ui.Codec codec = await PaintingBinding.instance.instantiateImageCodecWithSize( - buffer, - getTargetSize: (w, h) { - wh.addAll([w, h]); - if (w > pixelSizeThreshold || h > pixelSizeThreshold) { - final aspectRatio = w / h; - if (w >= h) { - final targetWidth = pixelSizeThreshold; - final targetHeight = (pixelSizeThreshold / aspectRatio).round(); - return ui.TargetImageSize(width: targetWidth, height: targetHeight); - } else { - final targetHeight = pixelSizeThreshold; - final targetWidth = (pixelSizeThreshold * aspectRatio).round(); - return ui.TargetImageSize(width: targetWidth, height: targetHeight); - } + final jpegData = await compressImageToJpeg( + bytes, + calculateTargetSize: (origWidth, origHeight) { + final aspectRatio = origWidth / origHeight; + if (origWidth <= pixelSizeThreshold && origHeight <= pixelSizeThreshold) { + return (width: origWidth, height: origHeight); + } else if (aspectRatio >= 1) { + final targetWidth = pixelSizeThreshold; + final targetHeight = (pixelSizeThreshold / aspectRatio).round(); + return (width: targetWidth, height: targetHeight); + } else { + final targetHeight = pixelSizeThreshold; + final targetWidth = (pixelSizeThreshold * aspectRatio).round(); + return (width: targetWidth, height: targetHeight); } - return ui.TargetImageSize(width: w, height: h); }, + quality: jpegQuality, ); - final ui.FrameInfo frameInfo; - try { - frameInfo = await codec.getNextFrame(); - } finally { - codec.dispose(); + + final double width, height; + final aspectRatio = jpegData.width / jpegData.height; + if (jpegData.width <= fitWidth && jpegData.height <= fitHeight) { + width = jpegData.width.toDouble(); + height = jpegData.height.toDouble(); + } else if (aspectRatio >= fitWidth / fitHeight) { + width = fitWidth; + height = fitWidth / aspectRatio; + } else { + height = fitHeight; + width = fitHeight * aspectRatio; } - return (image: frameInfo.image, origWidth: wh[0], origHeight: wh[1]); + return await PdfDocument.createFromJpegData(jpegData.data, width: width, height: height, sourceName: name); } Future _loadPdf(Uint8List bytes, String name) async { From 6638b5cc52d4c53cad14001839879cd0fa039767 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 05:44:46 +0900 Subject: [PATCH 520/663] Add _jpegEncodeAsync --- packages/pdfrx/example/pdf_combine/lib/helper_io.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart index 0a3463d8..1000731e 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:file_selector/file_selector.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:jpeg_encode/jpeg_encode.dart'; import 'package:share_plus/share_plus.dart'; @@ -51,9 +51,16 @@ Future compressImageToJpeg( ); final frameInfo = await codec.getNextFrame(); final rgba = (await frameInfo.image.toByteData(format: ui.ImageByteFormat.rawRgba))!.buffer.asUint8List(); - final byteData = JpegEncoder().compress(rgba, frameInfo.image.width, frameInfo.image.height, quality); + final byteData = await _jpegEncodeAsync(rgba, frameInfo.image.width, frameInfo.image.height, quality); codec.dispose(); return JpegData(byteData.buffer.asUint8List(), frameInfo.image.width, frameInfo.image.height); } +Future _jpegEncodeAsync(Uint8List rgba, int width, int height, int quality) async { + return await compute((jpegParams) { + final (rgba, width, height, quality) = jpegParams; + return JpegEncoder().compress(rgba, width, height, quality); + }, (rgba, width, height, quality)); +} + final isWindowsDesktop = Platform.isWindows; From 7f3289a7ff7ae39407f3cc5588c71a8ff546293d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 05:50:01 +0900 Subject: [PATCH 521/663] WIP --- packages/pdfrx/example/pdf_combine/lib/helper_web.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart index e9c8b3cc..840df744 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_web.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_web.dart @@ -1,7 +1,6 @@ import 'dart:js_interop'; import 'dart:typed_data'; -import 'package:web/helpers.dart'; import 'package:web/web.dart' as web; Future savePdf(Uint8List bytes, {String? suggestedName, bool openInNewTab = false}) async { @@ -54,7 +53,7 @@ Future compressImageToJpeg( context.drawImage(webCodec, 0, 0, size.width, size.height); final encodedBlob = await offScreenCanvas .convertToBlob( - ImageEncodeOptions() + web.ImageEncodeOptions() ..type = 'image/jpeg' ..quality = quality / 100, ) From 2a6de679d5b7eb3c35e79a41c84561560c0b3f89 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 06:22:54 +0900 Subject: [PATCH 522/663] WIP --- .../pdfrx/example/pdf_combine/lib/main.dart | 203 ++++++++++-------- 1 file changed, 115 insertions(+), 88 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index d205a79b..747869ee 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -32,11 +32,13 @@ abstract class PageItem { String get id; } -class PageItemAdd extends PageItem { +class PageItemsTrailer extends PageItem { @override - String get id => '##add_item##'; + String get id => '##trailer##'; } +final _pageItemsTrailer = PageItemsTrailer(); + class PdfPageItem extends PageItem { PdfPageItem({ required this.documentId, @@ -221,7 +223,7 @@ class PdfCombinePage extends StatefulWidget { class _PdfCombinePageState extends State { late final _docManager = DocumentManager((name) => passwordDialog(name, context)); - final _pages = []; + final _pages = [_pageItemsTrailer]; final _scrollController = ScrollController(); bool _isLoading = false; bool _disableDragging = false; @@ -271,7 +273,13 @@ class _PdfCombinePageState extends State { if (doc != null) { for (var i = 0; i < doc.pages.length; i++) { _docManager.addReference(docId); - _pages.add(PdfPageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i])); + _pages.insert( + _pages.length - 1, + PdfPageItem(documentId: docId, documentName: fileName, pageIndex: i, page: doc.pages[i]), + ); + if (mounted) { + setState(() {}); + } } } } catch (e) { @@ -347,7 +355,6 @@ class _PdfCombinePageState extends State { title: const Text('PDF Combine'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ - IconButton(icon: const Icon(Icons.add), onPressed: _pickFiles, tooltip: 'Add PDF files'), const SizedBox(width: 8), FilledButton.icon( onPressed: _pages.isEmpty ? null : _navigateToPreview, @@ -371,93 +378,113 @@ class _PdfCombinePageState extends State { onDragDone: (event) => _processFiles(event.files), child: Stack( children: [ - _isLoading - ? const Center(child: CircularProgressIndicator()) - : _pages.isEmpty - ? Center( - child: Text.rich( - TextSpan( - children: [ - TextSpan(text: 'Add/Drag-and-Drop PDF files here!\n\n'), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: IconButton.filled(icon: Icon(Icons.add), onPressed: () => _pickFiles()), - ), - ], + if (!_isDraggingOver && _pages.length == 1) + Container( + color: Colors.blue.withValues(alpha: 0.1), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.file_open, size: 64, color: Theme.of(context).colorScheme.primary), + const SizedBox(height: 16), + Text( + 'Or Drop PDF files here', + style: Theme.of( + context, + ).textTheme.headlineSmall?.copyWith(color: Theme.of(context).colorScheme.primary), ), - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ), - ) - : LayoutBuilder( - builder: (context, constraints) { - final w = constraints.maxWidth; - final int crossAxisCount; - if (w < 120) { - crossAxisCount = 1; - } else if (w < 400) { - crossAxisCount = 2; - } else if (w < 800) { - crossAxisCount = 3; - } else if (w < 1200) { - crossAxisCount = 4; - } else { - crossAxisCount = w ~/ 300; - } - return Listener( - onPointerMove: (event) { - setState(() { - _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; - }); - }, - onPointerHover: (event) { - setState(() { - _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; - }); - }, - child: AnimatedReorderableGridView( - items: _pages, - isSameItem: (a, b) => a.id == b.id, - itemBuilder: (context, index) { - final pageItem = _pages[index]; - if (pageItem is! PdfPageItem) { - return Card( - color: Theme.of(context).colorScheme.primaryContainer, - child: Center( - child: IconButton.filled( - icon: const Icon(Icons.add), - onPressed: _pickFiles, - tooltip: 'Add PDF files', + ], + ), + ), + ), + LayoutBuilder( + builder: (context, constraints) { + final w = constraints.maxWidth; + final int crossAxisCount; + if (w < 120) { + crossAxisCount = 1; + } else if (w < 400) { + crossAxisCount = 2; + } else if (w < 800) { + crossAxisCount = 3; + } else if (w < 1200) { + crossAxisCount = 4; + } else { + crossAxisCount = w ~/ 300; + } + return Listener( + onPointerMove: (event) { + setState(() { + _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; + }); + }, + onPointerHover: (event) { + setState(() { + _isTouchDevice = event.kind == ui.PointerDeviceKind.touch; + }); + }, + child: AnimatedReorderableGridView( + items: _pages, + nonDraggableItems: [_pageItemsTrailer], + lockedItems: [_pageItemsTrailer], + isSameItem: (a, b) => a.id == b.id, + itemBuilder: (context, index) { + final pageItem = _pages[index]; + if (pageItem is! PdfPageItem) { + return Card( + key: ValueKey(pageItem.id), + child: Center( + child: _isLoading + ? const CircularProgressIndicator() + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.file_open, + size: 64, + color: Theme.of(context).colorScheme.primary, + ), + onPressed: () => _pickFiles(), + ), + const SizedBox(height: 16), + Text( + 'Open PDF files', + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], ), - ), - ); - } - return _PageThumbnail( - key: ValueKey(pageItem.id), - page: pageItem.page, - rotationOverride: pageItem.rotationOverride, - onRemove: () => _removePage(index), - onRotateLeft: () => _rotatePageLeft(index), - currentIndex: index, - dragDisabler: _disableDraggingOnChild, - ); - }, - sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), - insertDuration: const Duration(milliseconds: 100), - removeDuration: const Duration(milliseconds: 300), - dragStartDelay: _isTouchDevice || _disableDragging - ? const Duration(milliseconds: 200) - : Duration.zero, - onReorder: (oldIndex, newIndex) { - setState(() { - final removed = _pages.removeAt(oldIndex); - _pages.insert(newIndex, removed); - }); - }, - ), + ), + ); + } + return _PageThumbnail( + key: ValueKey(pageItem.id), + page: pageItem.page, + rotationOverride: pageItem.rotationOverride, + onRemove: () => _removePage(index), + onRotateLeft: () => _rotatePageLeft(index), + currentIndex: index, + dragDisabler: _disableDraggingOnChild, ); }, + sliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), + insertDuration: const Duration(milliseconds: 100), + removeDuration: const Duration(milliseconds: 300), + dragStartDelay: _isTouchDevice || _disableDragging + ? const Duration(milliseconds: 200) + : Duration.zero, + onReorder: (oldIndex, newIndex) { + setState(() { + final removed = _pages.removeAt(oldIndex); + _pages.insert(newIndex, removed); + }); + }, ), + ); + }, + ), if (_isDraggingOver) Container( color: Colors.blue.withValues(alpha: 0.1), @@ -476,7 +503,7 @@ class _PdfCombinePageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.file_upload, size: 64, color: Theme.of(context).colorScheme.primary), + Icon(Icons.file_download, size: 64, color: Theme.of(context).colorScheme.primary), const SizedBox(height: 16), Text( 'Drop PDF files here', From b9b3a14bec5933d0c0fe651cfd2b03373d02272f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 5 Nov 2025 07:10:25 +0900 Subject: [PATCH 523/663] WIP --- packages/pdfrx/example/pdf_combine/lib/main.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 747869ee..20f9448b 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -442,17 +442,17 @@ class _PdfCombinePageState extends State { IconButton( icon: Icon( Icons.file_open, - size: 64, + size: 56, color: Theme.of(context).colorScheme.primary, ), onPressed: () => _pickFiles(), ), const SizedBox(height: 16), Text( - 'Open PDF files', - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), + 'Add PDF files...', + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.primary), ), ], ), @@ -660,6 +660,9 @@ class _OutputPreviewPageState extends State { try { final timestamp = DateTime.now().millisecondsSinceEpoch; await savePdf(_outputPdfBytes!, suggestedName: 'output_$timestamp.pdf'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('File downloaded successfully.'))); + } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error saving PDF: $e'))); From e2474900791d828b2486a5172fb7806477022f6a Mon Sep 17 00:00:00 2001 From: james Date: Wed, 5 Nov 2025 11:18:18 +1030 Subject: [PATCH 524/663] Add handleOffset to PdfTextSelectionParams --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 26 ++++++++++--------- .../lib/src/widgets/pdf_viewer_params.dart | 20 ++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c47bcaab..ef06bef2 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2186,17 +2186,18 @@ class _PdfViewerState extends State final builder = widget.params.textSelectionParams?.buildSelectionHandle ?? _buildDefaultSelectionHandle; if (_textSelA != null) { + final offset = widget.params.textSelectionParams?.handleOffset?.call(_textSelA!) ?? Offset.zero; switch (_textSelA!.direction) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: - aRight = viewSize.width - rectA.left; - aBottom = viewSize.height - rectA.top; + aRight = viewSize.width - rectA.left - offset.dx; + aBottom = viewSize.height - rectA.top - offset.dy; case PdfTextDirection.rtl: - aLeft = rectA.right; - aBottom = viewSize.height - rectA.top; + aLeft = rectA.right + offset.dx; + aBottom = viewSize.height - rectA.top - offset.dy; case PdfTextDirection.vrtl: - aLeft = rectA.right; - aBottom = viewSize.height - rectA.top; + aLeft = rectA.right + offset.dx; + aBottom = viewSize.height - rectA.top - offset.dy; } anchorA = builder( context, @@ -2209,17 +2210,18 @@ class _PdfViewerState extends State ); } if (_textSelB != null) { + final offset = widget.params.textSelectionParams?.handleOffset?.call(_textSelB!) ?? Offset.zero; switch (_textSelB!.direction) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: - bLeft = rectB.right; - bTop = rectB.bottom; + bLeft = rectB.right + offset.dx; + bTop = rectB.bottom + offset.dy; case PdfTextDirection.rtl: - bRight = viewSize.width - rectB.left; - bTop = rectB.bottom; + bRight = viewSize.width - rectB.left - offset.dx; + bTop = rectB.bottom + offset.dy; case PdfTextDirection.vrtl: - bRight = viewSize.width - rectB.left; - bTop = rectB.bottom; + bRight = viewSize.width - rectB.left - offset.dx; + bTop = rectB.bottom + offset.dy; } anchorB = builder( context, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2615e516..f6fd6982 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -741,6 +741,7 @@ class PdfTextSelectionParams { this.enableSelectionHandles, this.showContextMenuAutomatically, this.buildSelectionHandle, + this.handleOffset, this.onTextSelectionChange, this.magnifier, }); @@ -767,6 +768,12 @@ class PdfTextSelectionParams { /// - If the function is null, the default anchor handle will be used. final PdfViewerTextSelectionAnchorHandleBuilder? buildSelectionHandle; + /// Optional callback to calculate the offset for the anchor handles. + /// + /// This callback is called for each anchor handle to determine the offset + /// to apply to the handle's default position. If null, defaults to [Offset.zero]. + final PdfViewerHandleOffsetCallback? handleOffset; + /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; @@ -778,6 +785,7 @@ class PdfTextSelectionParams { if (identical(this, other)) return true; return other is PdfTextSelectionParams && other.buildSelectionHandle == buildSelectionHandle && + other.handleOffset == handleOffset && other.onTextSelectionChange == onTextSelectionChange && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && @@ -787,6 +795,7 @@ class PdfTextSelectionParams { @override int get hashCode => buildSelectionHandle.hashCode ^ + handleOffset.hashCode ^ onTextSelectionChange.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ @@ -914,6 +923,17 @@ typedef PdfViewerTextSelectionAnchorHandleBuilder = PdfViewerTextSelectionAnchorHandleState state, ); +/// Function to calculate the offset for an anchor handle. +/// +/// This callback is called for each anchor handle to determine the offset +/// to apply to the handle's default position. +/// +/// The callback receives the [PdfTextSelectionAnchor] and should return an [Offset] +/// that positions the handle widget relative to the anchor point: +/// - For anchor A (LTR): default anchor point is text's top-left, widget's bottom-right +/// - For anchor B (LTR): default anchor point is text's bottom-right, widget's top-left +typedef PdfViewerHandleOffsetCallback = Offset Function(PdfTextSelectionAnchor anchor); + /// Function to be notified when the text selection is changed. /// /// [textSelection] contains the selected text range on each page. From 383ca827153cbe32669d386eeedb65ceaeeeec6f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 6 Nov 2025 00:05:01 +0900 Subject: [PATCH 525/663] #517: PdfTextSelectionParams: handleOffset -> calcSelectionHandleOffset --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 38 +++++++++---------- .../lib/src/widgets/pdf_viewer_params.dart | 13 ++++--- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index ef06bef2..8ab117bf 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2186,7 +2186,14 @@ class _PdfViewerState extends State final builder = widget.params.textSelectionParams?.buildSelectionHandle ?? _buildDefaultSelectionHandle; if (_textSelA != null) { - final offset = widget.params.textSelectionParams?.handleOffset?.call(_textSelA!) ?? Offset.zero; + final state = _selPartMoving == _TextSelectionPart.a + ? PdfViewerTextSelectionAnchorHandleState.dragging + : _hoverOn == _TextSelectionPart.a + ? PdfViewerTextSelectionAnchorHandleState.hover + : PdfViewerTextSelectionAnchorHandleState.normal; + final offset = + widget.params.textSelectionParams?.calcSelectionHandleOffset?.call(context, _textSelA!, state) ?? + Offset.zero; switch (_textSelA!.direction) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: @@ -2199,18 +2206,17 @@ class _PdfViewerState extends State aLeft = rectA.right + offset.dx; aBottom = viewSize.height - rectA.top - offset.dy; } - anchorA = builder( - context, - _textSelA!, - _selPartMoving == _TextSelectionPart.a - ? PdfViewerTextSelectionAnchorHandleState.dragging - : _hoverOn == _TextSelectionPart.a - ? PdfViewerTextSelectionAnchorHandleState.hover - : PdfViewerTextSelectionAnchorHandleState.normal, - ); + anchorA = builder(context, _textSelA!, state); } if (_textSelB != null) { - final offset = widget.params.textSelectionParams?.handleOffset?.call(_textSelB!) ?? Offset.zero; + final state = _selPartMoving == _TextSelectionPart.b + ? PdfViewerTextSelectionAnchorHandleState.dragging + : _hoverOn == _TextSelectionPart.b + ? PdfViewerTextSelectionAnchorHandleState.hover + : PdfViewerTextSelectionAnchorHandleState.normal; + final offset = + widget.params.textSelectionParams?.calcSelectionHandleOffset?.call(context, _textSelB!, state) ?? + Offset.zero; switch (_textSelB!.direction) { case PdfTextDirection.ltr: case PdfTextDirection.unknown: @@ -2223,15 +2229,7 @@ class _PdfViewerState extends State bRight = viewSize.width - rectB.left - offset.dx; bTop = rectB.bottom + offset.dy; } - anchorB = builder( - context, - _textSelB!, - _selPartMoving == _TextSelectionPart.b - ? PdfViewerTextSelectionAnchorHandleState.dragging - : _hoverOn == _TextSelectionPart.b - ? PdfViewerTextSelectionAnchorHandleState.hover - : PdfViewerTextSelectionAnchorHandleState.normal, - ); + anchorB = builder(context, _textSelB!, state); } } else { _anchorARect = _anchorBRect = null; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index f6fd6982..4181257c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -741,7 +741,7 @@ class PdfTextSelectionParams { this.enableSelectionHandles, this.showContextMenuAutomatically, this.buildSelectionHandle, - this.handleOffset, + this.calcSelectionHandleOffset, this.onTextSelectionChange, this.magnifier, }); @@ -772,7 +772,7 @@ class PdfTextSelectionParams { /// /// This callback is called for each anchor handle to determine the offset /// to apply to the handle's default position. If null, defaults to [Offset.zero]. - final PdfViewerHandleOffsetCallback? handleOffset; + final PdfViewerCalcSelectionAnchorHandleOffsetFunction? calcSelectionHandleOffset; /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; @@ -785,7 +785,7 @@ class PdfTextSelectionParams { if (identical(this, other)) return true; return other is PdfTextSelectionParams && other.buildSelectionHandle == buildSelectionHandle && - other.handleOffset == handleOffset && + other.calcSelectionHandleOffset == calcSelectionHandleOffset && other.onTextSelectionChange == onTextSelectionChange && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && @@ -795,7 +795,7 @@ class PdfTextSelectionParams { @override int get hashCode => buildSelectionHandle.hashCode ^ - handleOffset.hashCode ^ + calcSelectionHandleOffset.hashCode ^ onTextSelectionChange.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ @@ -915,7 +915,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, @@ -932,7 +932,8 @@ typedef PdfViewerTextSelectionAnchorHandleBuilder = /// that positions the handle widget relative to the anchor point: /// - For anchor A (LTR): default anchor point is text's top-left, widget's bottom-right /// - For anchor B (LTR): default anchor point is text's bottom-right, widget's top-left -typedef PdfViewerHandleOffsetCallback = Offset Function(PdfTextSelectionAnchor anchor); +typedef PdfViewerCalcSelectionAnchorHandleOffsetFunction = + Offset Function(BuildContext context, PdfTextSelectionAnchor anchor, PdfViewerTextSelectionAnchorHandleState state); /// Function to be notified when the text selection is changed. /// From 0a6e9c86d60472037ede441cc1fcf6cec98e50e8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 6 Nov 2025 00:20:20 +0900 Subject: [PATCH 526/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8ab117bf..bca8aad0 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2177,7 +2177,7 @@ class _PdfViewerState extends State return contextMenuIfNeeded(); } - double? aLeft, aTop, aRight, aBottom; + double? aLeft, aRight, aBottom; double? bLeft, bTop, bRight; Widget? anchorA, anchorB; @@ -2478,7 +2478,6 @@ class _PdfViewerState extends State if (anchorA != null) Positioned( left: aLeft, - top: aTop, right: aRight, bottom: aBottom, child: MouseRegion( From 3b8660bb2cc65a4e3a75eb995297d1a35c897bf4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 6 Nov 2025 03:50:03 +0900 Subject: [PATCH 527/663] WIP --- packages/pdfrx/example/pdf_combine/lib/helper_io.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart index 1000731e..72846ab0 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart @@ -41,7 +41,7 @@ Future compressImageToJpeg( int quality = 90, }) async { calculateTargetSize ??= (w, h) => (width: w, height: h); - final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(imageData); + final buffer = await ui.ImmutableBuffer.fromUint8List(imageData); final codec = await PaintingBinding.instance.instantiateImageCodecWithSize( buffer, getTargetSize: (w, h) { From 54aa5fb7d33852895db2ee9c6e2a8c25e6ca25a2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 7 Nov 2025 06:41:22 +0900 Subject: [PATCH 528/663] Implement PdfiumFileAccess, PdfiumFileWrite --- .gitignore | 2 + .vscode/settings.json | 1 + packages/pdfrx/android/CMakeLists.txt | 15 -- .../darwin/pdfrx/Sources/interop/pdfrx.cpp | 21 +- .../pdfrx/example/viewer/ios/Podfile.lock | 26 +++ .../ios/Runner.xcodeproj/project.pbxproj | 18 ++ packages/pdfrx/linux/CMakeLists.txt | 10 - packages/pdfrx/src/CMakeLists.txt | 16 -- packages/pdfrx/src/pdfium_interop.cpp | 67 ------- packages/pdfrx/windows/CMakeLists.txt | 9 - .../lib/src/native/apple_direct_lookup.dart | 2 - .../pdfrx_engine/lib/src/native/pdfium.dart | 3 +- .../lib/src/native/pdfium_file_access.dart | 92 +++++++++ .../lib/src/native/pdfium_file_write.dart | 65 ++++++ .../lib/src/native/pdfium_interop.dart | 81 -------- .../lib/src/native/pdfrx_pdfium.dart | 50 +++-- .../lib/src/native/pthread/file_access.dart | 89 +++++++++ .../lib/src/native/pthread/file_write.dart | 87 ++++++++ .../lib/src/native/pthread/pthread.dart | 46 +++++ .../lib/src/native/win32/file_access.dart | 93 +++++++++ .../lib/src/native/win32/file_write.dart | 88 +++++++++ .../lib/src/native/win32/kernel32.dart | 34 ++++ .../pdfrx_engine/lib/src/native/worker.dart | 3 + .../pdfrx_engine/test/pdf_document_test.dart | 187 ++++++++++++++++++ 24 files changed, 866 insertions(+), 239 deletions(-) delete mode 100644 packages/pdfrx/src/CMakeLists.txt delete mode 100644 packages/pdfrx/src/pdfium_interop.cpp create mode 100644 packages/pdfrx_engine/lib/src/native/pdfium_file_access.dart create mode 100644 packages/pdfrx_engine/lib/src/native/pdfium_file_write.dart delete mode 100644 packages/pdfrx_engine/lib/src/native/pdfium_interop.dart create mode 100644 packages/pdfrx_engine/lib/src/native/pthread/file_access.dart create mode 100644 packages/pdfrx_engine/lib/src/native/pthread/file_write.dart create mode 100644 packages/pdfrx_engine/lib/src/native/pthread/pthread.dart create mode 100644 packages/pdfrx_engine/lib/src/native/win32/file_access.dart create mode 100644 packages/pdfrx_engine/lib/src/native/win32/file_write.dart create mode 100644 packages/pdfrx_engine/lib/src/native/win32/kernel32.dart diff --git a/.gitignore b/.gitignore index a8d8da46..6ed509df 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ pubspec_overrides.yaml **/macos/Podfile.lock **/ios/**/*.xcodeproj/project.pbxproj **/macos/**/*.xcodeproj/project.pbxproj + +build/ios/ diff --git a/.vscode/settings.json b/.vscode/settings.json index a69df4b1..6ba644f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -157,6 +157,7 @@ "POLYLINE", "PRINTERMARK", "PRINTMODE", + "Pthread", "pubspec", "QUADPOINTSF", "RADIOBUTTON", diff --git a/packages/pdfrx/android/CMakeLists.txt b/packages/pdfrx/android/CMakeLists.txt index 8e555c39..de093356 100644 --- a/packages/pdfrx/android/CMakeLists.txt +++ b/packages/pdfrx/android/CMakeLists.txt @@ -4,10 +4,6 @@ cmake_minimum_required(VERSION 3.18.1) set(PROJECT_NAME "pdfrx") project(${PROJECT_NAME} LANGUAGES CXX) -# Invoke the build for native code shared with the other target platforms. -# This can be changed to accommodate different builds. -add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") - # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(VERSION 3.14...3.25) @@ -68,17 +64,6 @@ file(REMOVE ${PDFIUM_LATEST_DIR}) file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) set(pdfrx_bundled_libraries - # Defined in ../src/CMakeLists.txt. - # This can be changed to accommodate different builds. - $ ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) - -target_include_directories(pdfrx PRIVATE ${PDFIUM_LATEST_DIR}/include) - -# Support 16KB page size (#365) -# Consistent build IDs for identical binaries (#467) -target_link_options(pdfrx PRIVATE "-Wl,-z,max-page-size=16384,--build-id=none") - -target_link_libraries(pdfrx PRIVATE ${PDFIUM_LATEST_LIB_FILENAME}) diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp index bc9003df..8eab5ef5 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp +++ b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp @@ -1,7 +1,4 @@ -// Relative import to be able to reuse the C sources. -// See the comment in ../{projectName}}.podspec for more information. -#include "../../../../src/pdfium_interop.cpp" - +// PDFium bindings for Darwin (iOS/macOS) #include #include #include @@ -9,20 +6,22 @@ #include #include #include +#include + +#if defined(__APPLE__) +#define PDFRX_EXPORT __attribute__((visibility("default"))) __attribute__((used)) +#else +#define PDFRX_EXPORT +#endif // This function is used to keep the linker from stripping out the PDFium // functions that are not directly referenced in this file. This is necessary -// because we are dynamically loading the functions at runtime. -extern "C" PDFRX_EXPORT void const *const *PDFRX_INTEROP_API pdfrx_binding() +// because we are dynamically loading the functions at runtime via DynamicLibrary. +extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() { #define KEEP_FUNC(func) reinterpret_cast(#func), reinterpret_cast(func) static void const * const bindings[] = { - // File access functions - KEEP_FUNC(pdfrx_file_access_create), - KEEP_FUNC(pdfrx_file_access_destroy), - KEEP_FUNC(pdfrx_file_access_set_value), - // PDFium functions KEEP_FUNC(FPDF_InitLibraryWithConfig), KEEP_FUNC(FPDF_InitLibrary), diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index c3dcb254..fd3b2951 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_ios (0.0.1): + - Flutter - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfrx (0.0.12): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: + - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: + file_selector_ios: + :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + pdfrx: + :path: ".symlinks/plugins/pdfrx/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + pdfrx: af3a9815e0f9b28ad40c6bdc8ee56f3d77dc5574 + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 96fca8b5..371bc57d 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -201,6 +201,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 167CB5D42EFFE1024C6A90CF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -278,6 +279,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 167CB5D42EFFE1024C6A90CF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/packages/pdfrx/linux/CMakeLists.txt b/packages/pdfrx/linux/CMakeLists.txt index eb65447b..6c5c80a2 100644 --- a/packages/pdfrx/linux/CMakeLists.txt +++ b/packages/pdfrx/linux/CMakeLists.txt @@ -21,10 +21,6 @@ ELSE() ENDIF() message( STATUS "Target CPU Name: ${CPU_NAME}" ) -# Invoke the build for native code shared with the other target platforms. -# This can be changed to accommodate different builds. -add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") - # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(VERSION 3.14...3.25) @@ -85,12 +81,6 @@ file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. set(pdfrx_bundled_libraries - # Defined in ../src/CMakeLists.txt. - # This can be changed to accommodate different builds. - $ ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) - -target_include_directories(pdfrx INTERFACE PRIVATE ${PDFIUM_LATEST_DIR}/include) -target_link_libraries(pdfrx PRIVATE ${PDFIUM_LATEST_LIB_FILENAME}) diff --git a/packages/pdfrx/src/CMakeLists.txt b/packages/pdfrx/src/CMakeLists.txt deleted file mode 100644 index 5385169e..00000000 --- a/packages/pdfrx/src/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -# The Flutter tooling requires that developers have CMake 3.10 or later -# installed. You should not increase this version, as doing so will cause -# the plugin to fail to compile for some customers of the plugin. -cmake_minimum_required(VERSION 3.10) - -project(pdfrx_library VERSION 0.0.1 LANGUAGES CXX) - -add_library(pdfrx SHARED - "pdfium_interop.cpp" -) - -set_target_properties(pdfrx PROPERTIES - OUTPUT_NAME "pdfrx" -) - -target_compile_definitions(pdfrx PUBLIC DART_SHARED_LIB) diff --git a/packages/pdfrx/src/pdfium_interop.cpp b/packages/pdfrx/src/pdfium_interop.cpp deleted file mode 100644 index 57cbdbae..00000000 --- a/packages/pdfrx/src/pdfium_interop.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include - -#include -#include - -#if defined(_WIN32) -#define PDFRX_EXPORT __declspec(dllexport) -#define PDFRX_INTEROP_API __stdcall -#else -#define PDFRX_EXPORT __attribute__((visibility("default"))) __attribute__((used)) -#define PDFRX_INTEROP_API -#endif - -struct pdfrx_file_access; - -typedef void(PDFRX_INTEROP_API *pdfrx_read_function)(void *param, - size_t position, - unsigned char *pBuf, - size_t size); - -struct pdfrx_file_access -{ - FPDF_FILEACCESS fileAccess; - int retValue; - pdfrx_read_function readBlock; - void *param; - std::mutex mutex; - std::condition_variable cond; -}; - -static int PDFRX_INTEROP_API read(void *param, - unsigned long position, - unsigned char *pBuf, - unsigned long size) -{ - auto fileAccess = reinterpret_cast(param); - std::unique_lock lock(fileAccess->mutex); - fileAccess->readBlock(fileAccess->param, position, pBuf, size); - fileAccess->cond.wait(lock); - return fileAccess->retValue; -} - -extern "C" PDFRX_EXPORT pdfrx_file_access *PDFRX_INTEROP_API pdfrx_file_access_create(unsigned long fileSize, pdfrx_read_function readBlock, void *param) -{ - auto fileAccess = new pdfrx_file_access(); - fileAccess->fileAccess.m_FileLen = fileSize; - fileAccess->fileAccess.m_GetBlock = read; - fileAccess->fileAccess.m_Param = fileAccess; - fileAccess->retValue = 0; - fileAccess->readBlock = readBlock; - fileAccess->param = param; - return fileAccess; -} - -extern "C" PDFRX_EXPORT void PDFRX_INTEROP_API pdfrx_file_access_destroy(pdfrx_file_access *fileAccess) -{ - delete fileAccess; -} - -extern "C" PDFRX_EXPORT void PDFRX_INTEROP_API pdfrx_file_access_set_value(pdfrx_file_access *fileAccess, int retValue) -{ - std::unique_lock lock(fileAccess->mutex); - fileAccess->retValue = retValue; - fileAccess->cond.notify_one(); -} diff --git a/packages/pdfrx/windows/CMakeLists.txt b/packages/pdfrx/windows/CMakeLists.txt index 8bee5521..f5961f99 100644 --- a/packages/pdfrx/windows/CMakeLists.txt +++ b/packages/pdfrx/windows/CMakeLists.txt @@ -8,10 +8,6 @@ cmake_minimum_required(VERSION 3.14) set(PROJECT_NAME "pdfrx") project(${PROJECT_NAME} LANGUAGES CXX) -# Invoke the build for native code shared with the other target platforms. -# This can be changed to accommodate different builds. -add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") - # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(VERSION 3.14...3.25) @@ -113,11 +109,6 @@ endif() # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. set(pdfrx_bundled_libraries - # Defined in ../src/CMakeLists.txt. - # This can be changed to accommodate different builds. - $ ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) - -target_include_directories(pdfrx PRIVATE ${PDFIUM_LATEST_DIR}/include) diff --git a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart index a5e85fae..3ed49645 100644 --- a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart +++ b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart @@ -3,7 +3,6 @@ import 'dart:ffi' as ffi; import '../pdfrx.dart'; import 'pdfium.dart' as pdfium_native; import 'pdfium_bindings.dart' as pdfium_bindings; -import 'pdfium_interop.dart' as file_access_helpers; /// Sets up direct lookup for Apple platforms if applicable. /// @@ -22,6 +21,5 @@ void setupAppleDirectLookupIfApplicable() { //print('Loading PDFium bindings via direct interop...'); pdfium_native.pdfium = pdfium_bindings.pdfium.fromLookup(lookup); - file_access_helpers.interop = file_access_helpers.PdfrxFileAccessHelpers.fromLookup(lookup); } } diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index 8c15d443..8b1f6257 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -10,7 +10,6 @@ import 'pdfium_bindings.dart' as pdfium_bindings; String _getModuleFileName() { if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; if (Platform.isAndroid) return 'libpdfium.so'; - if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; if (Platform.isWindows) return 'pdfium.dll'; if (Platform.isLinux) { return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfium.so'; @@ -20,7 +19,7 @@ String _getModuleFileName() { DynamicLibrary _getModule() { if (Platform.isIOS || Platform.isMacOS) { - // NOTE: with SwiftPM, the library is embedded in the app bundle (iOS/macOS) + // For iOS and macOS, we assume pdfium is already loaded (or statically linked) in the process. return DynamicLibrary.process(); } return DynamicLibrary.open(_getModuleFileName()); diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_file_access.dart b/packages/pdfrx_engine/lib/src/native/pdfium_file_access.dart new file mode 100644 index 00000000..8e6eb769 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pdfium_file_access.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'pthread/file_access.dart'; +import 'win32/file_access.dart'; + +class PdfiumFileAccess { + PdfiumFileAccess._(); + + /// Creates a file access structure for PDFium with the provided read function. + static Future create( + int fileSize, + FutureOr Function(Uint8List buffer, int position, int size) read, + ) async { + final fa = PdfiumFileAccess._(); + void readAndSignal(int position, Pointer buffer, int size) async { + try { + final readSize = await read(buffer.asTypedList(size), position, size); + PdfiumFileAccessHelper.instance.setValue(fa.fileAccess, readSize); + } catch (e) { + PdfiumFileAccessHelper.instance.setValue(fa.fileAccess, -1); + } + } + + fa._nativeCallable = _NativeFileReadCallable.listener(readAndSignal); + fa.fileAccess = await PdfiumFileAccessHelper.instance.create(fileSize, fa._nativeCallable.nativeFunction.address); + return fa; + } + + /// Disposes the file access structure and associated resources. + void dispose() { + PdfiumFileAccessHelper.instance.destroy(fileAccess); + _nativeCallable.close(); + } + + /// Address of `FPDF_FILEACCESS` structure. + late final int fileAccess; + late final _NativeFileReadCallable _nativeCallable; +} + +typedef _NativeFileReadCallable = NativeCallable, IntPtr)>; + +/// Abstract interface for platform-specific file access implementations. +/// +/// This provides a bridge between Dart and native code for PDF file access operations, +/// using platform-specific synchronization primitives (pthread on Unix-like systems, +/// Windows synchronization objects on Windows). +abstract class PdfiumFileAccessHelper { + /// Creates a file access structure for PDFium. + /// + /// Parameters: + /// - [fileSize]: Total size of the file in bytes + /// - [readBlock]: Function pointer to the read callback + /// + /// Returns the address of the allocated structure. + Future create(int fileSize, int readBlock); + + /// Destroys a file access structure and frees associated resources. + /// + /// Parameters: + /// - [faAddress]: Address of the file access structure to destroy + void destroy(int faAddress); + + /// Sets the return value and signals the waiting thread. + /// + /// This is called from Dart after completing an async read operation + /// to unblock the native thread waiting for data. + /// + /// Parameters: + /// - [faAddress]: Address of the file access structure + /// - [value]: Return value to set (typically 1 for success, 0 for failure) + void setValue(int faAddress, int value); + + /// Gets the singleton instance for the current platform. + static PdfiumFileAccessHelper get instance { + // ignore: prefer_conditional_expression + if (_instance == null) { + if (Platform.isWindows) { + _instance = PdfiumFileAccessHelperWin32(); + } else if (Platform.isAndroid || Platform.isLinux || Platform.isIOS || Platform.isMacOS) { + _instance = PdfiumFileAccessHelperPthread(); + } else { + throw UnsupportedError('PdfiumFileAccessHelper is not implemented for this platform.'); + } + } + return _instance!; + } + + static PdfiumFileAccessHelper? _instance; +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_file_write.dart b/packages/pdfrx_engine/lib/src/native/pdfium_file_write.dart new file mode 100644 index 00000000..9f12321a --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pdfium_file_write.dart @@ -0,0 +1,65 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'pthread/file_write.dart'; +import 'win32/file_write.dart'; + +class PdfiumFileWrite { + PdfiumFileWrite._(); + + /// Creates a file write structure for PDFium with the provided write function. + static Future create(FutureOr Function(Uint8List buffer, int position, int size) write) async { + final fw = PdfiumFileWrite._(); + void writeAndSignal(Pointer buffer, int position, int size) async { + try { + final writtenSize = await write(buffer.asTypedList(size), position, size); + PdfiumFileWriteHelper.instance.setValue(fw.fileWrite, writtenSize); + } catch (e) { + PdfiumFileWriteHelper.instance.setValue(fw.fileWrite, -1); + } + } + + fw._nativeCallable = _NativeFileWriteCallable.listener(writeAndSignal); + fw.fileWrite = await PdfiumFileWriteHelper.instance.create(fw._nativeCallable.nativeFunction.address); + return fw; + } + + /// Disposes the file write structure and associated resources. + void dispose() { + PdfiumFileWriteHelper.instance.destroy(fileWrite); + _nativeCallable.close(); + } + + /// Address of `FPDF_FILEWRITE` structure. + late final int fileWrite; + late final _NativeFileWriteCallable _nativeCallable; +} + +typedef _NativeFileWriteCallable = NativeCallable, IntPtr, IntPtr)>; + +/// Abstract interface for platform-specific file write implementations. +abstract class PdfiumFileWriteHelper { + Future create(int writeBlock); + + void destroy(int fwAddress); + + void setValue(int fwAddress, int value); + + static PdfiumFileWriteHelper get instance { + // ignore: prefer_conditional_expression + if (_instance == null) { + if (Platform.isWindows) { + _instance = PdfiumFileWriteHelperWin32(); + } else if (Platform.isAndroid || Platform.isLinux || Platform.isIOS || Platform.isMacOS) { + _instance = PdfiumFileWriteHelperPthread(); + } else { + throw UnsupportedError('PdfiumFileWriteHelper is not implemented for this platform.'); + } + } + return _instance!; + } + + static PdfiumFileWriteHelper? _instance; +} diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart b/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart deleted file mode 100644 index 45245ff3..00000000 --- a/packages/pdfrx_engine/lib/src/native/pdfium_interop.dart +++ /dev/null @@ -1,81 +0,0 @@ -// ignore_for_file: non_constant_identifier_names - -import 'dart:async'; -import 'dart:ffi'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'pdfium_bindings.dart'; - -typedef InteropLookupFunction = Pointer Function(String symbolName); - -final class PdfrxFileAccessHelpers { - PdfrxFileAccessHelpers() : lookup = PdfrxFileAccessHelpers._lookupDefault; - PdfrxFileAccessHelpers.fromLookup(this.lookup); - - final InteropLookupFunction lookup; - - static String _getModuleFileName() { - if (Platform.isAndroid) return 'libpdfrx.so'; - if (Platform.isIOS || Platform.isMacOS) return 'pdfrx.framework/pdfrx'; - if (Platform.isWindows) return 'pdfrx.dll'; - if (Platform.isLinux) { - return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfrx.so'; - } - throw UnsupportedError('Unsupported platform'); - } - - static DynamicLibrary _getModule() { - if (Platform.isIOS || Platform.isMacOS) { - return DynamicLibrary.process(); - } - return DynamicLibrary.open(_getModuleFileName()); - } - - static final _interopLib = _getModule(); - - static Pointer _lookupDefault(String symbolName) => _interopLib.lookup(symbolName); - - late final _pdfrx_file_access_create = - Pointer>.fromAddress( - lookup>('pdfrx_file_access_create').address, - ).asFunction(); - - late final _pdfrx_file_access_destroy = Pointer>.fromAddress( - lookup>('pdfrx_file_access_destroy').address, - ).asFunction(); - - late final _pdfrx_file_access_set_value = Pointer>.fromAddress( - lookup>('pdfrx_file_access_set_value').address, - ).asFunction(); -} - -PdfrxFileAccessHelpers interop = PdfrxFileAccessHelpers(); - -typedef _NativeFileReadCallable = NativeCallable, IntPtr)>; - -class FileAccess { - FileAccess(int fileSize, FutureOr Function(Uint8List buffer, int position, int size) read) { - void readNative(int param, int position, Pointer buffer, int size) async { - try { - final readSize = await read(buffer.asTypedList(size), position, size); - interop._pdfrx_file_access_set_value(_fileAccess, readSize); - } catch (e) { - interop._pdfrx_file_access_set_value(_fileAccess, -1); - } - } - - _nativeCallable = _NativeFileReadCallable.listener(readNative); - _fileAccess = interop._pdfrx_file_access_create(fileSize, _nativeCallable.nativeFunction.address, 0); - } - - void dispose() { - interop._pdfrx_file_access_destroy(_fileAccess); - _nativeCallable.close(); - } - - Pointer get fileAccess => Pointer.fromAddress(_fileAccess); - - late final int _fileAccess; - late final _NativeFileReadCallable _nativeCallable; -} diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 51353ee8..6dc3f4df 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -32,7 +32,7 @@ import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium.dart'; import 'pdfium_bindings.dart' as pdfium_bindings; -import 'pdfium_interop.dart' as file_access_helpers; +import 'pdfium_file_access.dart'; import 'worker.dart'; Directory? _appLocalFontPath; @@ -48,7 +48,7 @@ Future _init() async { _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); - (await backgroundWorker).computeWithArena((arena, params) { + (await BackgroundWorker.instance).computeWithArena((arena, params) { final config = arena.allocate(sizeOf()); config.ref.version = 2; @@ -77,14 +77,12 @@ Future _init() async { await _initializeFontEnvironment(); } -final backgroundWorker = BackgroundWorker.create(); - /// Stores the fonts that were not found during mapping. -/// NOTE: This is used by [backgroundWorker] and should not be used directly; use [_getAndClearMissingFonts] instead. +/// NOTE: This is used by [BackgroundWorker] and should not be used directly; use [_getAndClearMissingFonts] instead. final _lastMissingFonts = {}; /// MapFont function used by PDFium to map font requests to system fonts. -/// NOTE: This is used by [backgroundWorker] and should not be used directly. +/// NOTE: This is used by [BackgroundWorker] and should not be used directly. NativeCallable< Pointer Function( Pointer, @@ -100,7 +98,7 @@ _mapFont; /// Setup the system font info in PDFium. Future _initializeFontEnvironment() async { - await (await backgroundWorker).computeWithArena((arena, params) { + await (await BackgroundWorker.instance).computeWithArena((arena, params) { // kBase14FontNames const fontNamesToIgnore = { 'Courier': true, @@ -180,7 +178,7 @@ Future _initializeFontEnvironment() async { /// Retrieve and clear the last missing fonts from [_lastMissingFonts] in a thread-safe manner. Future> _getAndClearMissingFonts() async { - return await (await backgroundWorker).compute((params) { + return await (await BackgroundWorker.instance).compute((params) { final fonts = _lastMissingFonts.values.toList(); _lastMissingFonts.clear(); return fonts; @@ -195,7 +193,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { - return await (await backgroundWorker).suspendDuringAction(action); + return await (await BackgroundWorker.instance).suspendDuringAction(action); } @override @@ -255,7 +253,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { }) async { await _init(); return _openByFunc( - (password) async => (await backgroundWorker).computeWithArena((arena, params) { + (password) async => (await BackgroundWorker.instance).computeWithArena((arena, params) { final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); return doc.address; }, (filePath: filePath, password: password)), @@ -317,7 +315,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { try { await read(buffer.asTypedList(fileSize), 0, fileSize); return _openByFunc( - (password) async => (await backgroundWorker).computeWithArena( + (password) async => (await BackgroundWorker.instance).computeWithArena( (arena, params) => pdfium.FPDF_LoadMemDocument( Pointer.fromAddress(params.buffer), params.fileSize, @@ -344,15 +342,15 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } // Otherwise, load the file on demand - final fa = file_access_helpers.FileAccess(fileSize, read); + final fa = await PdfiumFileAccess.create(fileSize, read); try { return _openByFunc( - (password) async => (await backgroundWorker).computeWithArena( + (password) async => (await BackgroundWorker.instance).computeWithArena( (arena, params) => pdfium.FPDF_LoadCustomDocument( Pointer.fromAddress(params.fileAccess), params.password?.toUtf8(arena) ?? nullptr, ).address, - (fileAccess: fa.fileAccess.address, password: password), + (fileAccess: fa.fileAccess, password: password), ), sourceName: sourceName, passwordProvider: passwordProvider, @@ -434,7 +432,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future createNew({required String sourceName}) async { await _init(); - final doc = await (await backgroundWorker).compute((params) { + final doc = await (await BackgroundWorker.instance).compute((params) { return pdfium.FPDF_CreateNewDocument().address; }, null); return _PdfDocumentPdfium.fromPdfDocument( @@ -456,7 +454,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { final dataBuffer = malloc(jpegData.length); try { dataBuffer.asTypedList(jpegData.length).setAll(0, jpegData); - final doc = await (await backgroundWorker).computeWithArena( + final doc = await (await BackgroundWorker.instance).computeWithArena( (arena, params) { final document = pdfium.FPDF_CreateNewDocument(); final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); @@ -585,7 +583,7 @@ class _PdfDocumentPdfium extends PdfDocument { } _PdfDocumentPdfium? pdfDoc; try { - final result = await (await backgroundWorker).computeWithArena((arena, docAddress) { + final result = await (await BackgroundWorker.instance).computeWithArena((arena, docAddress) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(docAddress); Pointer formInfo = nullptr; pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; @@ -690,7 +688,7 @@ class _PdfDocumentPdfium extends PdfDocument { Duration? timeout, }) async { try { - final results = await (await backgroundWorker).computeWithArena( + final results = await (await BackgroundWorker.instance).computeWithArena( (arena, params) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); final pageCount = pdfium.FPDF_GetPageCount(doc); @@ -814,7 +812,7 @@ class _PdfDocumentPdfium extends PdfDocument { if (!isDisposed) { isDisposed = true; subject.close(); - await (await backgroundWorker).compute((params) { + await (await BackgroundWorker.instance).compute((params) { final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle); final formInfo = Pointer.fromAddress(params.formInfo); pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); @@ -831,7 +829,7 @@ class _PdfDocumentPdfium extends PdfDocument { @override Future> loadOutline() async => isDisposed ? [] - : await (await backgroundWorker).computeWithArena((arena, params) { + : await (await BackgroundWorker.instance).computeWithArena((arena, params) { final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); }, (document: document.address)); @@ -865,7 +863,7 @@ class _PdfDocumentPdfium extends PdfDocument { Future encodePdf({bool incremental = false, bool removeSecurity = false}) async { await assemble(); final byteBuffer = BytesBuilder(); - return await (await backgroundWorker).computeWithArena((arena, params) { + return await (await BackgroundWorker.instance).computeWithArena((arena, params) { int write(Pointer pThis, Pointer pData, int size) { byteBuffer.add(Pointer.fromAddress(pData.address).asTypedList(size)); return size; @@ -931,7 +929,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { return false; } - await (await backgroundWorker).computeWithArena( + await (await BackgroundWorker.instance).computeWithArena( (arena, params) { final arranger = _DocumentPageArranger._( pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document), @@ -1075,7 +1073,7 @@ class _PdfPagePdfium extends PdfPage { ct?.attach(cancelFlag); if (cancelFlag.value || document.isDisposed) return false; - return await (await backgroundWorker).compute( + return await (await BackgroundWorker.instance).compute( (params) { final cancelFlag = Pointer.fromAddress(params.cancelFlag); if (cancelFlag.value) return false; @@ -1190,7 +1188,7 @@ class _PdfPagePdfium extends PdfPage { @override Future loadText() async { if (document.isDisposed || !isLoaded) return null; - return await (await backgroundWorker).computeWithArena((arena, params) { + return await (await BackgroundWorker.instance).computeWithArena((arena, params) { final doubleSize = sizeOf(); final rectBuffer = arena.allocate(4 * sizeOf()); final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); @@ -1237,7 +1235,7 @@ class _PdfPagePdfium extends PdfPage { Future> _loadWebLinks() async => document.isDisposed ? [] - : await (await backgroundWorker).computeWithArena((arena, params) { + : await (await BackgroundWorker.instance).computeWithArena((arena, params) { pdfium_bindings.FPDF_PAGE page = nullptr; pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; @@ -1304,7 +1302,7 @@ class _PdfPagePdfium extends PdfPage { Future> _loadAnnotLinks() async => document.isDisposed ? [] - : await (await backgroundWorker).computeWithArena((arena, params) { + : await (await BackgroundWorker.instance).computeWithArena((arena, params) { final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); try { diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart new file mode 100644 index 00000000..0f3625d6 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart @@ -0,0 +1,89 @@ +// ignore_for_file: non_constant_identifier_names, camel_case_types, constant_identifier_names + +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import '../pdfium_bindings.dart' as pdfium_bindings; +import '../pdfium_file_access.dart'; +import '../worker.dart'; +import 'pthread.dart'; + +class PdfiumFileAccessHelperPthread implements PdfiumFileAccessHelper { + @override + Future create(int fileSize, int readBlock) async { + final buffer = malloc.allocate( + sizeOf() + sizeOfPthreadMutex + sizeOfPthreadCond + sizeOf() * 2, + ); + final fa = buffer.cast(); + fa.ref.m_FileLen = fileSize; + fa.ref.m_Param = Pointer.fromAddress(buffer.address); + fa.ref.m_GetBlock = Pointer.fromAddress(await _getReadFuncOnBackgroundWorker()); + + final readFuncPtr = Pointer.fromAddress(buffer.address + _readFuncOffset); + readFuncPtr.value = readBlock; + + pthread_mutex_init(buffer.address + _mutexOffset, 0); + pthread_cond_init(buffer.address + _condOffset, 0); + + return buffer.address; + } + + @override + void destroy(int faAddress) { + pthread_mutex_destroy(faAddress + _mutexOffset); + pthread_cond_destroy(faAddress + _condOffset); + malloc.free(Pointer.fromAddress(faAddress)); + } + + @override + void setValue(int faAddress, int value) { + pthread_mutex_lock(faAddress + _mutexOffset); + final returnValue = Pointer.fromAddress(faAddress + _retValueOffset); + returnValue.value = value; + pthread_cond_signal(faAddress + _condOffset); + pthread_mutex_unlock(faAddress + _mutexOffset); + } +} + +typedef _NativeFileReadCallable = + NativeCallable, UnsignedLong, Pointer, UnsignedLong)>; + +/// NOTE: Don't read the value of this variable directly, use [_getReadFuncOnBackgroundWorker] instead. +final _readFuncPtr = _NativeFileReadCallable.isolateLocal(_read, exceptionalReturn: 0).nativeFunction; + +/// Gets the read function pointer address on the background worker isolate. +Future _getReadFuncOnBackgroundWorker() async { + return await (await BackgroundWorker.instance).compute((m) => _readFuncPtr.address, {}); +} + +int _read(Pointer param, int position, Pointer buffer, int size) { + final faAddress = param.address; + final cs = faAddress + _mutexOffset; + final cv = faAddress + _condOffset; + final readFuncPtr = Pointer.fromAddress(faAddress + _readFuncOffset); + final readFunc = Pointer, IntPtr)>>.fromAddress( + readFuncPtr.value, + ).asFunction, int)>(); + + pthread_mutex_lock(cs); + // Call Dart side read function. The call is returned immediately (it runs asynchronously) + readFunc(position, buffer, size); + // So, we should wait for Dart to signal completion + pthread_cond_wait(cv, cs); + final returnValue = Pointer.fromAddress(faAddress + _retValueOffset).value; + pthread_mutex_unlock(cs); + return returnValue; +} + +/// pthread_mutex_t offset within FPDF_FILEACCESS +final _mutexOffset = sizeOf(); + +/// pthread_cond_t offset within FPDF_FILEACCESS +final _condOffset = _mutexOffset + sizeOfPthreadMutex; + +/// read function pointer offset within FPDF_FILEACCESS +final _readFuncOffset = _condOffset + sizeOfPthreadCond; + +/// return-value offset within FPDF_FILEACCESS +final _retValueOffset = _readFuncOffset + sizeOf(); diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart new file mode 100644 index 00000000..c7ba1dd5 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart @@ -0,0 +1,87 @@ +// ignore_for_file: non_constant_identifier_names, camel_case_types, constant_identifier_names + +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import '../pdfium_bindings.dart' as pdfium_bindings; +import '../pdfium_file_write.dart'; +import '../worker.dart'; +import 'pthread.dart'; + +class PdfiumFileWriteHelperPthread implements PdfiumFileWriteHelper { + @override + Future create(int writeBlock) async { + final buffer = malloc.allocate( + sizeOf() + sizeOfPthreadMutex + sizeOfPthreadCond + sizeOf() * 2, + ); + final fw = buffer.cast(); + fw.ref.version = 1; + fw.ref.WriteBlock = Pointer.fromAddress(await _getWriteFuncOnBackgroundWorker()); + + final writeFuncPtr = Pointer.fromAddress(buffer.address + _writeFuncOffset); + writeFuncPtr.value = writeBlock; + + pthread_mutex_init(buffer.address + _mutexOffsetWrite, 0); + pthread_cond_init(buffer.address + _condOffsetWrite, 0); + + return buffer.address; + } + + @override + void destroy(int fwAddress) { + pthread_mutex_destroy(fwAddress + _mutexOffsetWrite); + pthread_cond_destroy(fwAddress + _condOffsetWrite); + malloc.free(Pointer.fromAddress(fwAddress)); + } + + @override + void setValue(int fwAddress, int value) { + pthread_mutex_lock(fwAddress + _mutexOffsetWrite); + final returnValue = Pointer.fromAddress(fwAddress + _retValueOffsetWrite); + returnValue.value = value; + pthread_cond_signal(fwAddress + _condOffsetWrite); + pthread_mutex_unlock(fwAddress + _mutexOffsetWrite); + } +} + +typedef _NativeFileWriteCallable = NativeCallable, Pointer, UnsignedLong)>; + +/// NOTE: Don't read the value of this variable directly, use [_getWriteFuncOnBackgroundWorker] instead. +final _writeFuncPtr = _NativeFileWriteCallable.isolateLocal(_write, exceptionalReturn: 0).nativeFunction; + +/// Gets the write function pointer address on the background worker isolate. +Future _getWriteFuncOnBackgroundWorker() async { + return await (await BackgroundWorker.instance).compute((m) => _writeFuncPtr.address, {}); +} + +int _write(Pointer pThis, Pointer pData, int size) { + final fwAddress = pThis.address; + final cs = fwAddress + _mutexOffsetWrite; + final cv = fwAddress + _condOffsetWrite; + final writeFuncPtr = Pointer.fromAddress(fwAddress + _writeFuncOffset); + final writeFunc = Pointer, IntPtr)>>.fromAddress( + writeFuncPtr.value, + ).asFunction, int)>(); + + pthread_mutex_lock(cs); + // Call Dart side write function. The call is returned immediately (it runs asynchronously) + writeFunc(pData, size); + // So, we should wait for Dart to signal completion + pthread_cond_wait(cv, cs); + final returnValue = Pointer.fromAddress(fwAddress + _retValueOffsetWrite).value; + pthread_mutex_unlock(cs); + return returnValue; +} + +/// pthread_mutex_t offset within FPDF_FILEWRITE +final _mutexOffsetWrite = sizeOf(); + +/// pthread_cond_t offset within FPDF_FILEWRITE +final _condOffsetWrite = _mutexOffsetWrite + sizeOfPthreadMutex; + +/// write function pointer offset within FPDF_FILEWRITE +final _writeFuncOffset = _condOffsetWrite + sizeOfPthreadCond; + +/// return-value offset within FPDF_FILEWRITE +final _retValueOffsetWrite = _writeFuncOffset + sizeOf(); diff --git a/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart b/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart new file mode 100644 index 00000000..0c01abe2 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart @@ -0,0 +1,46 @@ +import 'dart:ffi'; +import 'dart:io'; + +/// We hope pthread is always available in the process +final DynamicLibrary _pthread = DynamicLibrary.process(); + +final pthread_mutex_init = _pthread.lookupFunction( + 'pthread_mutex_init', +); +final pthread_mutex_destroy = _pthread.lookupFunction( + 'pthread_mutex_destroy', +); +final pthread_mutex_lock = _pthread.lookupFunction('pthread_mutex_lock'); +final pthread_mutex_unlock = _pthread.lookupFunction('pthread_mutex_unlock'); +final pthread_cond_init = _pthread.lookupFunction( + 'pthread_cond_init', +); +final pthread_cond_wait = _pthread.lookupFunction( + 'pthread_cond_wait', +); +final pthread_cond_signal = _pthread.lookupFunction('pthread_cond_signal'); +final pthread_cond_destroy = _pthread.lookupFunction('pthread_cond_destroy'); + +/// Size of pthread_mutex_t varies by platform +int get sizeOfPthreadMutex { + if (Platform.isAndroid) { + return 40; // Android uses 40 bytes for pthread_mutex_t on 64-bit + } else if (Platform.isLinux) { + return 40; // Linux uses 40 bytes for pthread_mutex_t on 64-bit + } else if (Platform.isIOS || Platform.isMacOS) { + return 64; // Darwin (iOS/macOS) uses 64 bytes for pthread_mutex_t on 64-bit + } + throw UnsupportedError('Unsupported platform for pthread mutex size'); +} + +/// Size of pthread_cond_t varies by platform +int get sizeOfPthreadCond { + if (Platform.isAndroid) { + return 48; // Android uses 48 bytes for pthread_cond_t on 64-bit + } else if (Platform.isLinux) { + return 48; // Linux uses 48 bytes for pthread_cond_t on 64-bit + } else if (Platform.isIOS || Platform.isMacOS) { + return 48; // Darwin (iOS/macOS) uses 48 bytes for pthread_cond_t on 64-bit + } + throw UnsupportedError('Unsupported platform for pthread cond size'); +} diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart new file mode 100644 index 00000000..20887727 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart @@ -0,0 +1,93 @@ +// ignore_for_file: non_constant_identifier_names, camel_case_types, constant_identifier_names + +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import '../pdfium_bindings.dart' as pdfium_bindings; +import '../pdfium_file_access.dart'; +import '../worker.dart'; +import 'kernel32.dart'; + +class PdfiumFileAccessHelperWin32 implements PdfiumFileAccessHelper { + @override + Future create(int fileSize, int readBlock) async { + final buffer = malloc.allocate( + sizeOf() + + sizeOfCriticalSection + + sizeOfConditionVariable + + sizeOf() * 2, + ); + final fa = buffer.cast(); + fa.ref.m_FileLen = fileSize; + fa.ref.m_Param = Pointer.fromAddress(buffer.address); + fa.ref.m_GetBlock = Pointer.fromAddress(await _getReadFuncOnBackgroundWorker()); + + final readFuncPtr = Pointer.fromAddress(buffer.address + _readFuncOffset); + readFuncPtr.value = readBlock; + + InitializeCriticalSection(buffer.address + _csOffset); + InitializeConditionVariable(buffer.address + _cvOffset); + + return buffer.address; + } + + @override + void destroy(int faAddress) { + DeleteCriticalSection(faAddress + _csOffset); + malloc.free(Pointer.fromAddress(faAddress)); + } + + @override + void setValue(int faAddress, int value) { + final returnValue = Pointer.fromAddress(faAddress + _retValueOffset); + returnValue.value = value; + WakeConditionVariable(faAddress + _cvOffset); + } +} + +typedef _NativeFileReadCallable = + NativeCallable, UnsignedLong, Pointer, UnsignedLong)>; + +/// NOTE: Don't read the value of this variable directly, use [_getReadFuncOnBackgroundWorker] instead. +/// +/// The value will be leaked, but it's acceptable since the value is singleton per isolate. +final _readFuncPtr = _NativeFileReadCallable.isolateLocal(_read, exceptionalReturn: 0).nativeFunction; + +/// Gets the read function pointer address on the background worker isolate. +Future _getReadFuncOnBackgroundWorker() async { + return await (await BackgroundWorker.instance).compute((m) => _readFuncPtr.address, {}); +} + +int _read(Pointer param, int position, Pointer buffer, int size) { + final faAddress = param.address; + final cs = faAddress + _csOffset; + final cv = faAddress + _cvOffset; + final readFuncPtr = Pointer.fromAddress(faAddress + _readFuncOffset); + final readFunc = Pointer, IntPtr)>>.fromAddress( + readFuncPtr.value, + ).asFunction, int)>(); + + EnterCriticalSection(cs); + + // Call Dart side read function. The call is returned immediately (it runs asynchronously) + readFunc(position, buffer, size); + + // So, we should wait for Dart to signal completion + SleepConditionVariableCS(cv, cs, INFINITE); + final returnValue = Pointer.fromAddress(faAddress + _retValueOffset).value; + LeaveCriticalSection(cs); + return returnValue; +} + +/// CRITICAL_SECTION offset within FPDF_FILEACCESS +final _csOffset = sizeOf(); + +/// CONDITION_VARIABLE offset within FPDF_FILEACCESS +final _cvOffset = _csOffset + sizeOfCriticalSection; + +/// read function pointer offset within FPDF_FILEACCESS +final _readFuncOffset = _cvOffset + sizeOfConditionVariable; + +/// return-value offset within FPDF_FILEACCESS +final _retValueOffset = _readFuncOffset + sizeOf(); diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart new file mode 100644 index 00000000..96732947 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart @@ -0,0 +1,88 @@ +// ignore_for_file: non_constant_identifier_names, camel_case_types, constant_identifier_names + +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import '../pdfium_bindings.dart' as pdfium_bindings; +import '../pdfium_file_write.dart'; +import '../worker.dart'; +import 'kernel32.dart'; + +class PdfiumFileWriteHelperWin32 implements PdfiumFileWriteHelper { + @override + Future create(int writeBlock) async { + final buffer = malloc.allocate( + sizeOf() + sizeOfCriticalSection + sizeOfConditionVariable + sizeOf() * 2, + ); + final fw = buffer.cast(); + fw.ref.version = 1; + fw.ref.WriteBlock = Pointer.fromAddress(await _getWriteFuncOnBackgroundWorker()); + + final writeFuncPtr = Pointer.fromAddress(buffer.address + _writeFuncOffset); + writeFuncPtr.value = writeBlock; + + InitializeCriticalSection(buffer.address + _csOffsetWrite); + InitializeConditionVariable(buffer.address + _cvOffsetWrite); + + return buffer.address; + } + + @override + void destroy(int fwAddress) { + DeleteCriticalSection(fwAddress + _csOffsetWrite); + malloc.free(Pointer.fromAddress(fwAddress)); + } + + @override + void setValue(int fwAddress, int value) { + final returnValue = Pointer.fromAddress(fwAddress + _retValueOffsetWrite); + returnValue.value = value; + WakeConditionVariable(fwAddress + _cvOffsetWrite); + } +} + +typedef _NativeFileWriteCallable = NativeCallable, Pointer, UnsignedLong)>; + +/// NOTE: Don't read the value of this variable directly, use [_getWriteFuncOnBackgroundWorker] instead. +/// +/// The value will be leaked, but it's acceptable since the value is singleton per isolate. +final _writeFuncPtr = _NativeFileWriteCallable.isolateLocal(_write, exceptionalReturn: 0).nativeFunction; + +/// Gets the write function pointer address on the background worker isolate. +Future _getWriteFuncOnBackgroundWorker() async { + return await (await BackgroundWorker.instance).compute((m) => _writeFuncPtr.address, {}); +} + +int _write(Pointer pThis, Pointer pData, int size) { + final fwAddress = pThis.address; + final cs = fwAddress + _csOffsetWrite; + final cv = fwAddress + _cvOffsetWrite; + final writeFuncPtr = Pointer.fromAddress(fwAddress + _writeFuncOffset); + final writeFunc = Pointer, IntPtr)>>.fromAddress( + writeFuncPtr.value, + ).asFunction, int)>(); + + EnterCriticalSection(cs); + + // Call Dart side write function. The call is returned immediately (it runs asynchronously) + writeFunc(pData, size); + + // So, we should wait for Dart to signal completion + SleepConditionVariableCS(cv, cs, INFINITE); + final returnValue = Pointer.fromAddress(fwAddress + _retValueOffsetWrite).value; + LeaveCriticalSection(cs); + return returnValue; +} + +/// CRITICAL_SECTION offset within FPDF_FILEWRITE +final _csOffsetWrite = sizeOf(); + +/// CONDITION_VARIABLE offset within FPDF_FILEWRITE +final _cvOffsetWrite = _csOffsetWrite + sizeOfCriticalSection; + +/// write function pointer offset within FPDF_FILEWRITE +final _writeFuncOffset = _cvOffsetWrite + sizeOfConditionVariable; + +/// return-value offset within FPDF_FILEWRITE +final _retValueOffsetWrite = _writeFuncOffset + sizeOf(); diff --git a/packages/pdfrx_engine/lib/src/native/win32/kernel32.dart b/packages/pdfrx_engine/lib/src/native/win32/kernel32.dart new file mode 100644 index 00000000..fe6a95da --- /dev/null +++ b/packages/pdfrx_engine/lib/src/native/win32/kernel32.dart @@ -0,0 +1,34 @@ +// ignore_for_file: non_constant_identifier_names, constant_identifier_names + +import 'dart:ffi'; + +final _kernel32 = DynamicLibrary.open('kernel32.dll'); + +final InitializeCriticalSection = _kernel32.lookupFunction( + 'InitializeCriticalSection', +); +final InitializeConditionVariable = _kernel32.lookupFunction( + 'InitializeConditionVariable', +); +final DeleteCriticalSection = _kernel32.lookupFunction( + 'DeleteCriticalSection', +); +final EnterCriticalSection = _kernel32.lookupFunction( + 'EnterCriticalSection', +); +final SleepConditionVariableCS = _kernel32 + .lookupFunction('SleepConditionVariableCS'); +final LeaveCriticalSection = _kernel32.lookupFunction( + 'LeaveCriticalSection', +); +final WakeConditionVariable = _kernel32.lookupFunction( + 'WakeConditionVariable', +); + +const int INFINITE = 0xFFFFFFFF; + +/// CRITICAL_SECTION size is 40 bytes on Windows x64 +const int sizeOfCriticalSection = 40; + +/// CONDITION_VARIABLE size is 8 bytes on Windows x64 +const int sizeOfConditionVariable = 8; diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 133a77f4..5f62046a 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -13,6 +13,9 @@ typedef PdfrxComputeCallback = FutureOr Function(M message); /// Background worker based on Dart [Isolate]. class BackgroundWorker { BackgroundWorker._(this._receivePort, this._sendPort); + + static final instance = create(debugName: 'PdfrxEngineWorker'); + final ReceivePort _receivePort; final SendPort _sendPort; bool _isDisposed = false; diff --git a/packages/pdfrx_engine/test/pdf_document_test.dart b/packages/pdfrx_engine/test/pdf_document_test.dart index 287e5c05..ab017b62 100644 --- a/packages/pdfrx_engine/test/pdf_document_test.dart +++ b/packages/pdfrx_engine/test/pdf_document_test.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; @@ -22,4 +23,190 @@ void main() { MockClient((request) async => http.Response.bytes(await testPdfFile.readAsBytes(), 200)); await testDocument(await PdfDocument.openUri(Uri.parse('https://example.com/hello.pdf'))); }); + + group('PdfDocument.openCustom with maxSizeToCacheOnMemory=0', () { + test('opens PDF with custom read function', () async { + final data = await testPdfFile.readAsBytes(); + + // Custom read function that reads from the data buffer + int readFunc(Uint8List buffer, int position, int size) { + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: data.length, + sourceName: 'custom:test.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + }); + + test('handles multiple concurrent reads', () async { + final data = await testPdfFile.readAsBytes(); + var readCount = 0; + + int readFunc(Uint8List buffer, int position, int size) { + readCount++; + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: data.length, + sourceName: 'custom:concurrent.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + expect(readCount, greaterThan(0), reason: 'Read function should be called at least once'); + }); + + test('handles async read function', () async { + final data = await testPdfFile.readAsBytes(); + + Future asyncReadFunc(Uint8List buffer, int position, int size) async { + // Simulate async delay + await Future.delayed(Duration(milliseconds: 1)); + + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: asyncReadFunc, + fileSize: data.length, + sourceName: 'custom:async.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + }); + + test('handles read at various positions', () async { + final data = await testPdfFile.readAsBytes(); + final readPositions = []; + + int readFunc(Uint8List buffer, int position, int size) { + readPositions.add(position); + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: data.length, + sourceName: 'custom:positions.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + + // Verify that reads occurred at different positions (random access) + expect(readPositions.isNotEmpty, true, reason: 'Should have read positions recorded'); + // PDFium typically reads from multiple positions for PDF structure + expect(readPositions.toSet().length, greaterThan(1), reason: 'Should read from multiple positions'); + }); + + test('handles read errors gracefully', () async { + int readFunc(Uint8List buffer, int position, int size) { + // Return 0 to indicate EOF/error - no valid PDF data + return 0; + } + + // This should fail because we're not providing valid PDF data + expect( + () async => await PdfDocument.openCustom( + read: readFunc, + fileSize: 1000, + sourceName: 'custom:error.pdf', + maxSizeToCacheOnMemory: 0, + ), + throwsA(isA()), + ); + }); + + test('calls onDispose callback when document is disposed', () async { + final data = await testPdfFile.readAsBytes(); + var disposeCalled = false; + + int readFunc(Uint8List buffer, int position, int size) { + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: data.length, + sourceName: 'custom:dispose.pdf', + maxSizeToCacheOnMemory: 0, + onDispose: () { + disposeCalled = true; + }, + ); + + expect(disposeCalled, false, reason: 'onDispose should not be called yet'); + await doc.dispose(); + expect(disposeCalled, true, reason: 'onDispose should be called after dispose'); + }); + + test('handles large file sizes correctly', () async { + final data = await testPdfFile.readAsBytes(); + final largeFileSize = data.length; + + int readFunc(Uint8List buffer, int position, int size) { + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: largeFileSize, + sourceName: 'custom:large.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + }); + + test('handles partial reads correctly', () async { + final data = await testPdfFile.readAsBytes(); + final readSizes = []; + + int readFunc(Uint8List buffer, int position, int size) { + readSizes.add(size); + + if (position >= data.length) return 0; + final actualSize = (position + size > data.length) ? data.length - position : size; + buffer.setRange(0, actualSize, data, position); + return actualSize; + } + + final doc = await PdfDocument.openCustom( + read: readFunc, + fileSize: data.length, + sourceName: 'custom:partial.pdf', + maxSizeToCacheOnMemory: 0, + ); + + await testDocument(doc); + // Verify that reads occurred with various sizes + expect(readSizes.isNotEmpty, true, reason: 'Should have read sizes recorded'); + }); + }); } From 657f7a244414128e1ad17486d7bb3e00bf1da8ab Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 7 Nov 2025 06:41:42 +0900 Subject: [PATCH 529/663] Update pdf_combine to run correctly on iOS/macOS --- packages/pdfrx/example/pdf_combine/.metadata | 15 ------- .../pdf_combine/ios/Flutter/Debug.xcconfig | 1 + .../pdf_combine/ios/Flutter/Release.xcconfig | 1 + .../pdfrx/example/pdf_combine/ios/Podfile | 43 +++++++++++++++++++ .../contents.xcworkspacedata | 3 ++ .../example/pdf_combine/lib/helper_io.dart | 6 ++- .../pdfrx/example/pdf_combine/lib/main.dart | 14 +++++- .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../pdfrx/example/pdf_combine/macos/Podfile | 42 ++++++++++++++++++ .../contents.xcworkspacedata | 3 ++ .../macos/Runner/DebugProfile.entitlements | 2 + .../macos/Runner/Release.entitlements | 2 + .../pdfrx/example/pdf_combine/pubspec.yaml | 2 +- 14 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 packages/pdfrx/example/pdf_combine/ios/Podfile create mode 100644 packages/pdfrx/example/pdf_combine/macos/Podfile diff --git a/packages/pdfrx/example/pdf_combine/.metadata b/packages/pdfrx/example/pdf_combine/.metadata index 5f4336f9..fca9f99c 100644 --- a/packages/pdfrx/example/pdf_combine/.metadata +++ b/packages/pdfrx/example/pdf_combine/.metadata @@ -15,24 +15,9 @@ migration: - platform: root create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: android - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: ios - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: linux - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: macos create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: web - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: windows - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 # User provided section diff --git a/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig b/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 100644 --- a/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig +++ b/packages/pdfrx/example/pdf_combine/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig b/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig index 592ceee8..c4855bfe 100644 --- a/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig +++ b/packages/pdfrx/example/pdf_combine/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/ios/Podfile b/packages/pdfrx/example/pdf_combine/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/packages/pdfrx/example/pdf_combine/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart index 72846ab0..94ef4714 100644 --- a/packages/pdfrx/example/pdf_combine/lib/helper_io.dart +++ b/packages/pdfrx/example/pdf_combine/lib/helper_io.dart @@ -9,8 +9,10 @@ import 'package:share_plus/share_plus.dart'; Future savePdf(Uint8List bytes, {String? suggestedName}) async { if (Platform.isIOS || Platform.isAndroid) { - final xFile = XFile.fromData(bytes, name: suggestedName ?? 'document.pdf', mimeType: 'application/pdf'); - await Share.shareXFiles([xFile]); + final params = ShareParams( + files: [XFile.fromData(bytes, name: suggestedName ?? 'document.pdf', mimeType: 'application/pdf')], + ); + await SharePlus.instance.share(params); return; } final savePath = await getSaveLocation( diff --git a/packages/pdfrx/example/pdf_combine/lib/main.dart b/packages/pdfrx/example/pdf_combine/lib/main.dart index 20f9448b..b334ad6e 100644 --- a/packages/pdfrx/example/pdf_combine/lib/main.dart +++ b/packages/pdfrx/example/pdf_combine/lib/main.dart @@ -240,8 +240,18 @@ class _PdfCombinePageState extends State { Future _pickFiles() async { final files = await openFiles( acceptedTypeGroups: [ - XTypeGroup(label: 'PDFs', extensions: ['pdf']), - XTypeGroup(label: 'Images', extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp']), + XTypeGroup(label: 'PDFs', extensions: ['pdf'], uniformTypeIdentifiers: ['com.adobe.pdf']), + XTypeGroup( + label: 'Images', + extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp'], + uniformTypeIdentifiers: [ + 'public.jpeg', + 'public.png', + 'com.microsoft.bmp', + 'com.compuserve.gif', + 'org.webmproject.webp', + ], + ), ], ); if (files.isEmpty) return; diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b6..4b81f9b2 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig index c2efd0b6..5caa9d15 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/pdfrx/example/pdf_combine/macos/Podfile b/packages/pdfrx/example/pdf_combine/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/packages/pdfrx/example/pdf_combine/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/packages/pdfrx/example/pdf_combine/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements b/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements index dddb8a30..d138bd5b 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.files.user-selected.read-write + diff --git a/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements b/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements index 852fa1a4..19afff14 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements +++ b/packages/pdfrx/example/pdf_combine/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.app-sandbox + com.apple.security.files.user-selected.read-write + diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 351ede7b..60efcfb0 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: path: ../../ file_selector: ^1.0.3 - share_plus: ^10.0.3 + share_plus: ^12.0.1 path_provider: ^2.1.5 animated_reorderable_list: ^1.3.0 web: ^1.1.1 From 15e7a5b3f106828436bd7ed9735dd58bef0216c4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 7 Nov 2025 14:33:17 +0900 Subject: [PATCH 530/663] #518: introduces PdfViewerKeyHandlerParams.enabled --- .../src/widgets/internals/pdf_viewer_key_handler.dart | 11 ++++++++--- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index 0a096e7b..848fb35c 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -25,6 +25,13 @@ class PdfViewerKeyHandler extends StatelessWidget { @override Widget build(BuildContext context) { + final childBuilder = Builder( + builder: (context) => ListenableBuilder(listenable: Focus.of(context), builder: (context, _) => child), + ); + if (!params.enabled) { + return childBuilder; + } + return Focus( focusNode: params.focusNode, parentNode: params.parentNode, @@ -41,9 +48,7 @@ class PdfViewerKeyHandler extends StatelessWidget { } return KeyEventResult.ignored; }, - child: Builder( - builder: (context) => ListenableBuilder(listenable: Focus.of(context), builder: (context, _) => child), - ), + child: childBuilder, ); } } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 4181257c..2ade36a7 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1423,12 +1423,14 @@ typedef PdfViewerOnKeyCallback = /// please refer to the documentation of [Focus] widget. class PdfViewerKeyHandlerParams { const PdfViewerKeyHandlerParams({ + this.enabled = true, this.autofocus = false, this.canRequestFocus = true, this.focusNode, this.parentNode, }); + final bool enabled; final bool autofocus; final bool canRequestFocus; final FocusNode? focusNode; @@ -1438,14 +1440,16 @@ class PdfViewerKeyHandlerParams { bool operator ==(covariant PdfViewerKeyHandlerParams other) { if (identical(this, other)) return true; - return other.autofocus == autofocus && + return other.enabled == enabled && + other.autofocus == autofocus && other.canRequestFocus == canRequestFocus && other.focusNode == focusNode && other.parentNode == parentNode; } @override - int get hashCode => autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; + int get hashCode => + enabled.hashCode ^ autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; } enum PdfViewerGeneralTapType { From c49d4c38b15c56a8d25a410a75a7378457d66b56 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 7 Nov 2025 15:24:05 +0900 Subject: [PATCH 531/663] FIXED: command line/test codes on macOS should load dylib from the specified path. --- packages/pdfrx_engine/lib/src/native/pdfium.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index 8b1f6257..cd50b23b 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -8,7 +8,6 @@ import 'pdfium_bindings.dart' as pdfium_bindings; /// Get the module file name for pdfium. String _getModuleFileName() { - if (Pdfrx.pdfiumModulePath != null) return Pdfrx.pdfiumModulePath!; if (Platform.isAndroid) return 'libpdfium.so'; if (Platform.isWindows) return 'pdfium.dll'; if (Platform.isLinux) { @@ -18,8 +17,12 @@ String _getModuleFileName() { } DynamicLibrary _getModule() { + // If the module path is explicitly specified, use it. + if (Pdfrx.pdfiumModulePath != null) { + return DynamicLibrary.open(Pdfrx.pdfiumModulePath!); + } + // For iOS/macOS, we assume pdfium is already loaded (or statically linked) in the process. if (Platform.isIOS || Platform.isMacOS) { - // For iOS and macOS, we assume pdfium is already loaded (or statically linked) in the process. return DynamicLibrary.process(); } return DynamicLibrary.open(_getModuleFileName()); From abe6f7d5b0585274e3b34d7305d5b1bacb60a4b5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 7 Nov 2025 15:44:27 +0900 Subject: [PATCH 532/663] WIP --- packages/pdfrx/assets/pdfium_worker.js | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index c4a9e95c..6828893a 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1808,13 +1808,26 @@ function _insertImportedPage(destDocHandle, srcDocHandle, srcPageIndex, destInde function encodePdf(params) { const { docHandle, incremental = false, removeSecurity = false } = params; - const chunks = []; + let buffer = new Uint8Array(1024 * 1024); // Start with 1MB buffer + let totalSize = 0; // Create a callback function that will be called by PDFium to write data const writeCallback = Pdfium.addFunction((pThis, pData, size) => { void pThis; // Suppress unused parameter warning + + // Grow buffer if needed + if (totalSize + size > buffer.length) { + const newSize = Math.max(buffer.length * 2, totalSize + size); + const newBuffer = new Uint8Array(newSize); + newBuffer.set(buffer.subarray(0, totalSize)); + buffer = newBuffer; + } + + // Copy data directly into buffer const chunk = new Uint8Array(Pdfium.memory.buffer, pData, size); - chunks.push(new Uint8Array(chunk)); // Copy the data + buffer.set(chunk, totalSize); + totalSize += size; + return size; }, 'iiii'); @@ -1840,14 +1853,8 @@ function encodePdf(params) { throw new Error('FPDF_SaveAsCopy failed'); } - // Combine all chunks into a single ArrayBuffer - const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0); - const combined = new Uint8Array(totalSize); - let offset = 0; - for (const chunk of chunks) { - combined.set(chunk, offset); - offset += chunk.length; - } + // Trim buffer to actual size + const combined = buffer.subarray(0, totalSize); return { result: { data: combined.buffer }, From 2f28f1838747791d27e74284f631c20626117f67 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 9 Nov 2025 02:20:56 +0900 Subject: [PATCH 533/663] pdfrx_engine example: main.dart -> pdf2image.dart --- packages/pdfrx_engine/example/{main.dart => pdf2image.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/pdfrx_engine/example/{main.dart => pdf2image.dart} (100%) diff --git a/packages/pdfrx_engine/example/main.dart b/packages/pdfrx_engine/example/pdf2image.dart similarity index 100% rename from packages/pdfrx_engine/example/main.dart rename to packages/pdfrx_engine/example/pdf2image.dart From a4cb97f8e80543b94453b9781eb2eccfb6e93d4b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 9 Nov 2025 02:42:19 +0900 Subject: [PATCH 534/663] WIP --- packages/pdfrx_engine/lib/src/native/worker.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 5f62046a..2cd20cbb 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -12,18 +12,17 @@ typedef PdfrxComputeCallback = FutureOr Function(M message); /// Background worker based on Dart [Isolate]. class BackgroundWorker { - BackgroundWorker._(this._receivePort, this._sendPort); + BackgroundWorker._(this._sendPort); static final instance = create(debugName: 'PdfrxEngineWorker'); - final ReceivePort _receivePort; final SendPort _sendPort; bool _isDisposed = false; static Future create({String? debugName}) async { final receivePort = ReceivePort(); await Isolate.spawn(_workerEntry, receivePort.sendPort, debugName: debugName); - final worker = BackgroundWorker._(receivePort, await receivePort.first as SendPort); + final worker = BackgroundWorker._(await receivePort.first as SendPort); // propagate the pdfium module path to the worker worker.compute((params) { @@ -99,7 +98,6 @@ class BackgroundWorker { try { _isDisposed = true; _sendPort.send(null); - _receivePort.close(); } catch (e) { developer.log('Failed to dispose worker (possible double-dispose?): $e'); } From eb0367310d152be056df32967474d8cc5de89a0e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sun, 9 Nov 2025 14:24:22 +0900 Subject: [PATCH 535/663] #520 Now pdfrx_binding imports all of available PDFium functions --- .vscode/settings.json | 1 + .../darwin/pdfrx/Sources/interop/pdfrx.cpp | 413 +- .../lib/src/native/pdfium_bindings.dart | 16960 +++++++++------- packages/pdfrx_engine/pubspec.yaml | 23 +- 4 files changed, 10298 insertions(+), 7099 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ba644f2..c17f4a37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -86,6 +86,7 @@ "FPDFPAGE", "fpdfview", "FREETEXT", + "FSDK", "FXCT", "getdents", "glyphpath", diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp index 8eab5ef5..051ea8d9 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp +++ b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp @@ -1,12 +1,26 @@ // PDFium bindings for Darwin (iOS/macOS) -#include -#include -#include -#include -#include -#include -#include -#include +#include "fpdf_signature.h" +#include "fpdf_sysfontinfo.h" +#include "fpdf_javascript.h" +#include "fpdf_text.h" +#include "fpdf_searchex.h" +#include "fpdf_progressive.h" +#include "fpdfview.h" +#include "fpdf_edit.h" +#include "fpdf_attachment.h" +#include "fpdf_annot.h" +#include "fpdf_catalog.h" +#include "fpdf_ppo.h" +#include "fpdf_formfill.h" +#include "fpdf_save.h" +#include "fpdf_doc.h" +#include "fpdf_structtree.h" +#include "fpdf_dataavail.h" +#include "fpdf_fwlevent.h" +#include "fpdf_ext.h" +#include "fpdf_transformpage.h" +#include "fpdf_flatten.h" +#include "fpdf_thumbnail.h" #if defined(__APPLE__) #define PDFRX_EXPORT __attribute__((visibility("default"))) __attribute__((used)) @@ -75,113 +89,26 @@ extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() KEEP_FUNC(FPDF_GetXFAPacketCount), KEEP_FUNC(FPDF_GetXFAPacketName), KEEP_FUNC(FPDF_GetXFAPacketContent), - KEEP_FUNC(FPDFDOC_InitFormFillEnvironment), - KEEP_FUNC(FPDFDOC_ExitFormFillEnvironment), - KEEP_FUNC(FORM_OnAfterLoadPage), - KEEP_FUNC(FORM_OnBeforeClosePage), - KEEP_FUNC(FORM_DoDocumentJSAction), - KEEP_FUNC(FORM_DoDocumentOpenAction), - KEEP_FUNC(FORM_DoDocumentAAction), - KEEP_FUNC(FORM_DoPageAAction), - KEEP_FUNC(FORM_OnMouseMove), - KEEP_FUNC(FORM_OnMouseWheel), - KEEP_FUNC(FORM_OnFocus), - KEEP_FUNC(FORM_OnLButtonDown), - KEEP_FUNC(FORM_OnRButtonDown), - KEEP_FUNC(FORM_OnLButtonUp), - KEEP_FUNC(FORM_OnRButtonUp), - KEEP_FUNC(FORM_OnLButtonDoubleClick), - KEEP_FUNC(FORM_OnKeyDown), - KEEP_FUNC(FORM_OnKeyUp), - KEEP_FUNC(FORM_OnChar), - KEEP_FUNC(FORM_GetFocusedText), - KEEP_FUNC(FORM_GetSelectedText), - KEEP_FUNC(FORM_ReplaceAndKeepSelection), - KEEP_FUNC(FORM_ReplaceSelection), - KEEP_FUNC(FORM_SelectAllText), - KEEP_FUNC(FORM_CanUndo), - KEEP_FUNC(FORM_CanRedo), - KEEP_FUNC(FORM_Undo), - KEEP_FUNC(FORM_Redo), - KEEP_FUNC(FORM_ForceToKillFocus), - KEEP_FUNC(FORM_GetFocusedAnnot), - KEEP_FUNC(FORM_SetFocusedAnnot), - KEEP_FUNC(FPDFPage_HasFormFieldAtPoint), - KEEP_FUNC(FPDFPage_FormFieldZOrderAtPoint), - KEEP_FUNC(FPDF_SetFormFieldHighlightColor), - KEEP_FUNC(FPDF_SetFormFieldHighlightAlpha), - KEEP_FUNC(FPDF_RemoveFormFieldHighlight), - KEEP_FUNC(FPDF_FFLDraw), - KEEP_FUNC(FPDF_GetFormType), - KEEP_FUNC(FORM_SetIndexSelected), - KEEP_FUNC(FORM_IsIndexSelected), - KEEP_FUNC(FPDF_LoadXFA), - KEEP_FUNC(FPDFAnnot_IsSupportedSubtype), - KEEP_FUNC(FPDFPage_CreateAnnot), - KEEP_FUNC(FPDFPage_GetAnnotCount), - KEEP_FUNC(FPDFPage_GetAnnot), - KEEP_FUNC(FPDFPage_GetAnnotIndex), - KEEP_FUNC(FPDFPage_CloseAnnot), - KEEP_FUNC(FPDFPage_RemoveAnnot), - KEEP_FUNC(FPDFAnnot_GetSubtype), - KEEP_FUNC(FPDFAnnot_IsObjectSupportedSubtype), - KEEP_FUNC(FPDFAnnot_UpdateObject), - KEEP_FUNC(FPDFAnnot_AddInkStroke), - KEEP_FUNC(FPDFAnnot_RemoveInkList), - KEEP_FUNC(FPDFAnnot_AppendObject), - KEEP_FUNC(FPDFAnnot_GetObjectCount), - KEEP_FUNC(FPDFAnnot_GetObject), - KEEP_FUNC(FPDFAnnot_RemoveObject), - KEEP_FUNC(FPDFAnnot_SetColor), - KEEP_FUNC(FPDFAnnot_GetColor), - KEEP_FUNC(FPDFAnnot_HasAttachmentPoints), - KEEP_FUNC(FPDFAnnot_SetAttachmentPoints), - KEEP_FUNC(FPDFAnnot_AppendAttachmentPoints), - KEEP_FUNC(FPDFAnnot_CountAttachmentPoints), - KEEP_FUNC(FPDFAnnot_GetAttachmentPoints), - KEEP_FUNC(FPDFAnnot_SetRect), - KEEP_FUNC(FPDFAnnot_GetRect), - KEEP_FUNC(FPDFAnnot_GetVertices), - KEEP_FUNC(FPDFAnnot_GetInkListCount), - KEEP_FUNC(FPDFAnnot_GetInkListPath), - KEEP_FUNC(FPDFAnnot_GetLine), - KEEP_FUNC(FPDFAnnot_SetBorder), - KEEP_FUNC(FPDFAnnot_GetBorder), - KEEP_FUNC(FPDFAnnot_GetFormAdditionalActionJavaScript), - KEEP_FUNC(FPDFAnnot_HasKey), - KEEP_FUNC(FPDFAnnot_GetValueType), - KEEP_FUNC(FPDFAnnot_SetStringValue), - KEEP_FUNC(FPDFAnnot_GetStringValue), - KEEP_FUNC(FPDFAnnot_GetNumberValue), - KEEP_FUNC(FPDFAnnot_SetAP), - KEEP_FUNC(FPDFAnnot_GetAP), - KEEP_FUNC(FPDFAnnot_GetLinkedAnnot), - KEEP_FUNC(FPDFAnnot_GetFlags), - KEEP_FUNC(FPDFAnnot_SetFlags), - KEEP_FUNC(FPDFAnnot_GetFormFieldFlags), - KEEP_FUNC(FPDFAnnot_SetFormFieldFlags), - KEEP_FUNC(FPDFAnnot_GetFormFieldAtPoint), - KEEP_FUNC(FPDFAnnot_GetFormFieldName), - KEEP_FUNC(FPDFAnnot_GetFormFieldAlternateName), - KEEP_FUNC(FPDFAnnot_GetFormFieldType), - KEEP_FUNC(FPDFAnnot_GetFormFieldValue), - KEEP_FUNC(FPDFAnnot_GetOptionCount), - KEEP_FUNC(FPDFAnnot_GetOptionLabel), - KEEP_FUNC(FPDFAnnot_IsOptionSelected), - KEEP_FUNC(FPDFAnnot_GetFontSize), - KEEP_FUNC(FPDFAnnot_SetFontColor), - KEEP_FUNC(FPDFAnnot_GetFontColor), - KEEP_FUNC(FPDFAnnot_IsChecked), - KEEP_FUNC(FPDFAnnot_SetFocusableSubtypes), - KEEP_FUNC(FPDFAnnot_GetFocusableSubtypesCount), - KEEP_FUNC(FPDFAnnot_GetFocusableSubtypes), - KEEP_FUNC(FPDFAnnot_GetLink), - KEEP_FUNC(FPDFAnnot_GetFormControlCount), - KEEP_FUNC(FPDFAnnot_GetFormControlIndex), - KEEP_FUNC(FPDFAnnot_GetFormFieldExportValue), - KEEP_FUNC(FPDFAnnot_SetURI), - KEEP_FUNC(FPDFAnnot_GetFileAttachment), - KEEP_FUNC(FPDFAnnot_AddFileAttachment), + KEEP_FUNC(FPDF_GetSignatureCount), + KEEP_FUNC(FPDF_GetSignatureObject), + KEEP_FUNC(FPDFSignatureObj_GetContents), + KEEP_FUNC(FPDFSignatureObj_GetByteRange), + KEEP_FUNC(FPDFSignatureObj_GetSubFilter), + KEEP_FUNC(FPDFSignatureObj_GetReason), + KEEP_FUNC(FPDFSignatureObj_GetTime), + KEEP_FUNC(FPDFSignatureObj_GetDocMDPPermission), + KEEP_FUNC(FPDF_GetDefaultTTFMap), + KEEP_FUNC(FPDF_GetDefaultTTFMapCount), + KEEP_FUNC(FPDF_GetDefaultTTFMapEntry), + KEEP_FUNC(FPDF_AddInstalledFont), + KEEP_FUNC(FPDF_SetSystemFontInfo), + KEEP_FUNC(FPDF_GetDefaultSystemFontInfo), + KEEP_FUNC(FPDF_FreeDefaultSystemFontInfo), + KEEP_FUNC(FPDFDoc_GetJavaScriptActionCount), + KEEP_FUNC(FPDFDoc_GetJavaScriptAction), + KEEP_FUNC(FPDFDoc_CloseJavaScriptAction), + KEEP_FUNC(FPDFJavaScriptAction_GetName), + KEEP_FUNC(FPDFJavaScriptAction_GetScript), KEEP_FUNC(FPDFText_LoadPage), KEEP_FUNC(FPDFText_ClosePage), KEEP_FUNC(FPDFText_CountChars), @@ -218,33 +145,12 @@ extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() KEEP_FUNC(FPDFLink_GetRect), KEEP_FUNC(FPDFLink_GetTextRange), KEEP_FUNC(FPDFLink_CloseWebLinks), - KEEP_FUNC(FPDFBookmark_GetFirstChild), - KEEP_FUNC(FPDFBookmark_GetNextSibling), - KEEP_FUNC(FPDFBookmark_GetTitle), - KEEP_FUNC(FPDFBookmark_GetCount), - KEEP_FUNC(FPDFBookmark_Find), - KEEP_FUNC(FPDFBookmark_GetDest), - KEEP_FUNC(FPDFBookmark_GetAction), - KEEP_FUNC(FPDFAction_GetType), - KEEP_FUNC(FPDFAction_GetDest), - KEEP_FUNC(FPDFAction_GetFilePath), - KEEP_FUNC(FPDFAction_GetURIPath), - KEEP_FUNC(FPDFDest_GetDestPageIndex), - KEEP_FUNC(FPDFDest_GetView), - KEEP_FUNC(FPDFDest_GetLocationInPage), - KEEP_FUNC(FPDFLink_GetLinkAtPoint), - KEEP_FUNC(FPDFLink_GetLinkZOrderAtPoint), - KEEP_FUNC(FPDFLink_GetDest), - KEEP_FUNC(FPDFLink_GetAction), - KEEP_FUNC(FPDFLink_Enumerate), - KEEP_FUNC(FPDFLink_GetAnnot), - KEEP_FUNC(FPDFLink_GetAnnotRect), - KEEP_FUNC(FPDFLink_CountQuadPoints), - KEEP_FUNC(FPDFLink_GetQuadPoints), - KEEP_FUNC(FPDF_GetPageAAction), - KEEP_FUNC(FPDF_GetFileIdentifier), - KEEP_FUNC(FPDF_GetMetaText), - KEEP_FUNC(FPDF_GetPageLabel), + KEEP_FUNC(FPDFText_GetCharIndexFromTextIndex), + KEEP_FUNC(FPDFText_GetTextIndexFromCharIndex), + KEEP_FUNC(FPDF_RenderPageBitmapWithColorScheme_Start), + KEEP_FUNC(FPDF_RenderPageBitmap_Start), + KEEP_FUNC(FPDF_RenderPage_Continue), + KEEP_FUNC(FPDF_RenderPage_Close), KEEP_FUNC(FPDF_CreateNewDocument), KEEP_FUNC(FPDFPage_New), KEEP_FUNC(FPDFPage_Delete), @@ -279,9 +185,11 @@ extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() KEEP_FUNC(FPDFPageObjMark_GetParamKey), KEEP_FUNC(FPDFPageObjMark_GetParamValueType), KEEP_FUNC(FPDFPageObjMark_GetParamIntValue), + //KEEP_FUNC(FPDFPageObjMark_GetParamFloatValue), KEEP_FUNC(FPDFPageObjMark_GetParamStringValue), KEEP_FUNC(FPDFPageObjMark_GetParamBlobValue), KEEP_FUNC(FPDFPageObjMark_SetIntParam), + //KEEP_FUNC(FPDFPageObjMark_SetFloatParam), KEEP_FUNC(FPDFPageObjMark_SetStringParam), KEEP_FUNC(FPDFPageObjMark_SetBlobParam), KEEP_FUNC(FPDFPageObjMark_RemoveParam), @@ -359,6 +267,127 @@ extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() KEEP_FUNC(FPDFFormObj_CountObjects), KEEP_FUNC(FPDFFormObj_GetObject), KEEP_FUNC(FPDFFormObj_RemoveObject), + KEEP_FUNC(FPDFDoc_GetAttachmentCount), + KEEP_FUNC(FPDFDoc_AddAttachment), + KEEP_FUNC(FPDFDoc_GetAttachment), + KEEP_FUNC(FPDFDoc_DeleteAttachment), + KEEP_FUNC(FPDFAttachment_GetName), + KEEP_FUNC(FPDFAttachment_HasKey), + KEEP_FUNC(FPDFAttachment_GetValueType), + KEEP_FUNC(FPDFAttachment_SetStringValue), + KEEP_FUNC(FPDFAttachment_GetStringValue), + KEEP_FUNC(FPDFAttachment_SetFile), + KEEP_FUNC(FPDFAttachment_GetFile), + KEEP_FUNC(FPDFAttachment_GetSubtype), + KEEP_FUNC(FPDFDOC_InitFormFillEnvironment), + KEEP_FUNC(FPDFDOC_ExitFormFillEnvironment), + KEEP_FUNC(FORM_OnAfterLoadPage), + KEEP_FUNC(FORM_OnBeforeClosePage), + KEEP_FUNC(FORM_DoDocumentJSAction), + KEEP_FUNC(FORM_DoDocumentOpenAction), + KEEP_FUNC(FORM_DoDocumentAAction), + KEEP_FUNC(FORM_DoPageAAction), + KEEP_FUNC(FORM_OnMouseMove), + KEEP_FUNC(FORM_OnMouseWheel), + KEEP_FUNC(FORM_OnFocus), + KEEP_FUNC(FORM_OnLButtonDown), + KEEP_FUNC(FORM_OnRButtonDown), + KEEP_FUNC(FORM_OnLButtonUp), + KEEP_FUNC(FORM_OnRButtonUp), + KEEP_FUNC(FORM_OnLButtonDoubleClick), + KEEP_FUNC(FORM_OnKeyDown), + KEEP_FUNC(FORM_OnKeyUp), + KEEP_FUNC(FORM_OnChar), + KEEP_FUNC(FORM_GetFocusedText), + KEEP_FUNC(FORM_GetSelectedText), + KEEP_FUNC(FORM_ReplaceAndKeepSelection), + KEEP_FUNC(FORM_ReplaceSelection), + KEEP_FUNC(FORM_SelectAllText), + KEEP_FUNC(FORM_CanUndo), + KEEP_FUNC(FORM_CanRedo), + KEEP_FUNC(FORM_Undo), + KEEP_FUNC(FORM_Redo), + KEEP_FUNC(FORM_ForceToKillFocus), + KEEP_FUNC(FORM_GetFocusedAnnot), + KEEP_FUNC(FORM_SetFocusedAnnot), + KEEP_FUNC(FPDFPage_HasFormFieldAtPoint), + KEEP_FUNC(FPDFPage_FormFieldZOrderAtPoint), + KEEP_FUNC(FPDF_SetFormFieldHighlightColor), + KEEP_FUNC(FPDF_SetFormFieldHighlightAlpha), + KEEP_FUNC(FPDF_RemoveFormFieldHighlight), + KEEP_FUNC(FPDF_FFLDraw), + KEEP_FUNC(FPDF_GetFormType), + KEEP_FUNC(FORM_SetIndexSelected), + KEEP_FUNC(FORM_IsIndexSelected), + KEEP_FUNC(FPDF_LoadXFA), + KEEP_FUNC(FPDFAnnot_IsSupportedSubtype), + KEEP_FUNC(FPDFPage_CreateAnnot), + KEEP_FUNC(FPDFPage_GetAnnotCount), + KEEP_FUNC(FPDFPage_GetAnnot), + KEEP_FUNC(FPDFPage_GetAnnotIndex), + KEEP_FUNC(FPDFPage_CloseAnnot), + KEEP_FUNC(FPDFPage_RemoveAnnot), + KEEP_FUNC(FPDFAnnot_GetSubtype), + KEEP_FUNC(FPDFAnnot_IsObjectSupportedSubtype), + KEEP_FUNC(FPDFAnnot_UpdateObject), + KEEP_FUNC(FPDFAnnot_AddInkStroke), + KEEP_FUNC(FPDFAnnot_RemoveInkList), + KEEP_FUNC(FPDFAnnot_AppendObject), + KEEP_FUNC(FPDFAnnot_GetObjectCount), + KEEP_FUNC(FPDFAnnot_GetObject), + KEEP_FUNC(FPDFAnnot_RemoveObject), + KEEP_FUNC(FPDFAnnot_SetColor), + KEEP_FUNC(FPDFAnnot_GetColor), + KEEP_FUNC(FPDFAnnot_HasAttachmentPoints), + KEEP_FUNC(FPDFAnnot_SetAttachmentPoints), + KEEP_FUNC(FPDFAnnot_AppendAttachmentPoints), + KEEP_FUNC(FPDFAnnot_CountAttachmentPoints), + KEEP_FUNC(FPDFAnnot_GetAttachmentPoints), + KEEP_FUNC(FPDFAnnot_SetRect), + KEEP_FUNC(FPDFAnnot_GetRect), + KEEP_FUNC(FPDFAnnot_GetVertices), + KEEP_FUNC(FPDFAnnot_GetInkListCount), + KEEP_FUNC(FPDFAnnot_GetInkListPath), + KEEP_FUNC(FPDFAnnot_GetLine), + KEEP_FUNC(FPDFAnnot_SetBorder), + KEEP_FUNC(FPDFAnnot_GetBorder), + KEEP_FUNC(FPDFAnnot_GetFormAdditionalActionJavaScript), + KEEP_FUNC(FPDFAnnot_HasKey), + KEEP_FUNC(FPDFAnnot_GetValueType), + KEEP_FUNC(FPDFAnnot_SetStringValue), + KEEP_FUNC(FPDFAnnot_GetStringValue), + KEEP_FUNC(FPDFAnnot_GetNumberValue), + KEEP_FUNC(FPDFAnnot_SetAP), + KEEP_FUNC(FPDFAnnot_GetAP), + KEEP_FUNC(FPDFAnnot_GetLinkedAnnot), + KEEP_FUNC(FPDFAnnot_GetFlags), + KEEP_FUNC(FPDFAnnot_SetFlags), + KEEP_FUNC(FPDFAnnot_GetFormFieldFlags), + KEEP_FUNC(FPDFAnnot_SetFormFieldFlags), + KEEP_FUNC(FPDFAnnot_GetFormFieldAtPoint), + KEEP_FUNC(FPDFAnnot_GetFormFieldName), + KEEP_FUNC(FPDFAnnot_GetFormFieldAlternateName), + KEEP_FUNC(FPDFAnnot_GetFormFieldType), + KEEP_FUNC(FPDFAnnot_GetFormFieldValue), + KEEP_FUNC(FPDFAnnot_GetOptionCount), + KEEP_FUNC(FPDFAnnot_GetOptionLabel), + KEEP_FUNC(FPDFAnnot_IsOptionSelected), + KEEP_FUNC(FPDFAnnot_GetFontSize), + KEEP_FUNC(FPDFAnnot_SetFontColor), + KEEP_FUNC(FPDFAnnot_GetFontColor), + KEEP_FUNC(FPDFAnnot_IsChecked), + KEEP_FUNC(FPDFAnnot_SetFocusableSubtypes), + KEEP_FUNC(FPDFAnnot_GetFocusableSubtypesCount), + KEEP_FUNC(FPDFAnnot_GetFocusableSubtypes), + KEEP_FUNC(FPDFAnnot_GetLink), + KEEP_FUNC(FPDFAnnot_GetFormControlCount), + KEEP_FUNC(FPDFAnnot_GetFormControlIndex), + KEEP_FUNC(FPDFAnnot_GetFormFieldExportValue), + KEEP_FUNC(FPDFAnnot_SetURI), + KEEP_FUNC(FPDFAnnot_GetFileAttachment), + KEEP_FUNC(FPDFAnnot_AddFileAttachment), + KEEP_FUNC(FPDFCatalog_IsTagged), + KEEP_FUNC(FPDFCatalog_SetLanguage), KEEP_FUNC(FPDF_ImportPagesByIndex), KEEP_FUNC(FPDF_ImportPages), KEEP_FUNC(FPDF_ImportNPagesToOne), @@ -368,13 +397,99 @@ extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() KEEP_FUNC(FPDF_CopyViewerPreferences), KEEP_FUNC(FPDF_SaveAsCopy), KEEP_FUNC(FPDF_SaveWithVersion), - KEEP_FUNC(FPDF_GetDefaultTTFMap), - KEEP_FUNC(FPDF_GetDefaultTTFMapCount), - KEEP_FUNC(FPDF_GetDefaultTTFMapEntry), - KEEP_FUNC(FPDF_AddInstalledFont), - KEEP_FUNC(FPDF_SetSystemFontInfo), - KEEP_FUNC(FPDF_GetDefaultSystemFontInfo), - KEEP_FUNC(FPDF_FreeDefaultSystemFontInfo), + KEEP_FUNC(FPDFBookmark_GetFirstChild), + KEEP_FUNC(FPDFBookmark_GetNextSibling), + KEEP_FUNC(FPDFBookmark_GetTitle), + KEEP_FUNC(FPDFBookmark_GetCount), + KEEP_FUNC(FPDFBookmark_Find), + KEEP_FUNC(FPDFBookmark_GetDest), + KEEP_FUNC(FPDFBookmark_GetAction), + KEEP_FUNC(FPDFAction_GetType), + KEEP_FUNC(FPDFAction_GetDest), + KEEP_FUNC(FPDFAction_GetFilePath), + KEEP_FUNC(FPDFAction_GetURIPath), + KEEP_FUNC(FPDFDest_GetDestPageIndex), + KEEP_FUNC(FPDFDest_GetView), + KEEP_FUNC(FPDFDest_GetLocationInPage), + KEEP_FUNC(FPDFLink_GetLinkAtPoint), + KEEP_FUNC(FPDFLink_GetLinkZOrderAtPoint), + KEEP_FUNC(FPDFLink_GetDest), + KEEP_FUNC(FPDFLink_GetAction), + KEEP_FUNC(FPDFLink_Enumerate), + KEEP_FUNC(FPDFLink_GetAnnot), + KEEP_FUNC(FPDFLink_GetAnnotRect), + KEEP_FUNC(FPDFLink_CountQuadPoints), + KEEP_FUNC(FPDFLink_GetQuadPoints), + KEEP_FUNC(FPDF_GetPageAAction), + KEEP_FUNC(FPDF_GetFileIdentifier), + KEEP_FUNC(FPDF_GetMetaText), + KEEP_FUNC(FPDF_GetPageLabel), + KEEP_FUNC(FPDF_StructTree_GetForPage), + KEEP_FUNC(FPDF_StructTree_Close), + KEEP_FUNC(FPDF_StructTree_CountChildren), + KEEP_FUNC(FPDF_StructTree_GetChildAtIndex), + KEEP_FUNC(FPDF_StructElement_GetAltText), + KEEP_FUNC(FPDF_StructElement_GetActualText), + KEEP_FUNC(FPDF_StructElement_GetID), + KEEP_FUNC(FPDF_StructElement_GetLang), + KEEP_FUNC(FPDF_StructElement_GetStringAttribute), + KEEP_FUNC(FPDF_StructElement_GetMarkedContentID), + KEEP_FUNC(FPDF_StructElement_GetType), + KEEP_FUNC(FPDF_StructElement_GetObjType), + KEEP_FUNC(FPDF_StructElement_GetTitle), + KEEP_FUNC(FPDF_StructElement_CountChildren), + KEEP_FUNC(FPDF_StructElement_GetChildAtIndex), + KEEP_FUNC(FPDF_StructElement_GetChildMarkedContentID), + KEEP_FUNC(FPDF_StructElement_GetParent), + KEEP_FUNC(FPDF_StructElement_GetAttributeCount), + KEEP_FUNC(FPDF_StructElement_GetAttributeAtIndex), + KEEP_FUNC(FPDF_StructElement_Attr_GetCount), + KEEP_FUNC(FPDF_StructElement_Attr_GetName), + KEEP_FUNC(FPDF_StructElement_Attr_GetValue), + KEEP_FUNC(FPDF_StructElement_Attr_GetType), + KEEP_FUNC(FPDF_StructElement_Attr_GetBooleanValue), + KEEP_FUNC(FPDF_StructElement_Attr_GetNumberValue), + KEEP_FUNC(FPDF_StructElement_Attr_GetStringValue), + KEEP_FUNC(FPDF_StructElement_Attr_GetBlobValue), + KEEP_FUNC(FPDF_StructElement_Attr_CountChildren), + KEEP_FUNC(FPDF_StructElement_Attr_GetChildAtIndex), + KEEP_FUNC(FPDF_StructElement_GetMarkedContentIdCount), + KEEP_FUNC(FPDF_StructElement_GetMarkedContentIdAtIndex), + KEEP_FUNC(FPDFAvail_Create), + KEEP_FUNC(FPDFAvail_Destroy), + KEEP_FUNC(FPDFAvail_IsDocAvail), + KEEP_FUNC(FPDFAvail_GetDocument), + KEEP_FUNC(FPDFAvail_GetFirstPageNum), + KEEP_FUNC(FPDFAvail_IsPageAvail), + KEEP_FUNC(FPDFAvail_IsFormAvail), + KEEP_FUNC(FPDFAvail_IsLinearized), + KEEP_FUNC(FSDK_SetUnSpObjProcessHandler), + KEEP_FUNC(FSDK_SetTimeFunction), + KEEP_FUNC(FSDK_SetLocaltimeFunction), + KEEP_FUNC(FPDFDoc_GetPageMode), + KEEP_FUNC(FPDFPage_SetMediaBox), + KEEP_FUNC(FPDFPage_SetCropBox), + KEEP_FUNC(FPDFPage_SetBleedBox), + KEEP_FUNC(FPDFPage_SetTrimBox), + KEEP_FUNC(FPDFPage_SetArtBox), + KEEP_FUNC(FPDFPage_GetMediaBox), + KEEP_FUNC(FPDFPage_GetCropBox), + KEEP_FUNC(FPDFPage_GetBleedBox), + KEEP_FUNC(FPDFPage_GetTrimBox), + KEEP_FUNC(FPDFPage_GetArtBox), + KEEP_FUNC(FPDFPage_TransFormWithClip), + KEEP_FUNC(FPDFPageObj_TransformClipPath), + KEEP_FUNC(FPDFPageObj_GetClipPath), + KEEP_FUNC(FPDFClipPath_CountPaths), + KEEP_FUNC(FPDFClipPath_CountPathSegments), + KEEP_FUNC(FPDFClipPath_GetPathSegment), + KEEP_FUNC(FPDF_CreateClipPath), + KEEP_FUNC(FPDF_DestroyClipPath), + KEEP_FUNC(FPDFPage_InsertClipPath), + KEEP_FUNC(FPDFPage_Flatten), + KEEP_FUNC(FPDFPage_GetDecodedThumbnailData), + KEEP_FUNC(FPDFPage_GetRawThumbnailData), + KEEP_FUNC(FPDFPage_GetThumbnailAsBitmap), nullptr, nullptr // End marker }; diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart index d9218c80..9a0ede1e 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart @@ -1368,2945 +1368,3075 @@ late final _FPDF_GetXFAPacketContentPtr = _lookup< ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_GetXFAPacketContent'); late final _FPDF_GetXFAPacketContent = _FPDF_GetXFAPacketContentPtr.asFunction , int , ffi.Pointer )>(); -/// Function: FPDFDOC_InitFormFillEnvironment -/// Initialize form fill environment. +/// Experimental API. +/// Function: FPDF_GetSignatureCount +/// Get total number of signatures in the document. /// Parameters: -/// document - Handle to document from FPDF_LoadDocument(). -/// formInfo - Pointer to a FPDF_FORMFILLINFO structure. -/// Return Value: -/// Handle to the form fill module, or NULL on failure. -/// Comments: -/// This function should be called before any form fill operation. -/// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until -/// the returned FPDF_FORMHANDLE is closed. -FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, -ffi.Pointer formInfo, +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// Return value: +/// Total number of signatures in the document on success, -1 on error. +int FPDF_GetSignatureCount(FPDF_DOCUMENT document, ) { - return _FPDFDOC_InitFormFillEnvironment(document, -formInfo, + return _FPDF_GetSignatureCount(document, ); } -late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< - ffi.NativeFunction )>>('FPDFDOC_InitFormFillEnvironment'); -late final _FPDFDOC_InitFormFillEnvironment = _FPDFDOC_InitFormFillEnvironmentPtr.asFunction )>(); +late final _FPDF_GetSignatureCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSignatureCount'); +late final _FPDF_GetSignatureCount = _FPDF_GetSignatureCountPtr.asFunction(); -/// Function: FPDFDOC_ExitFormFillEnvironment -/// Take ownership of |hHandle| and exit form fill environment. +/// Experimental API. +/// Function: FPDF_GetSignatureObject +/// Get the Nth signature of the document. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// This function is a no-op when |hHandle| is null. -void FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle, +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// index - Index into the array of signatures of the document. +/// Return value: +/// Returns the handle to the signature, or NULL on failure. The caller +/// does not take ownership of the returned FPDF_SIGNATURE. Instead, it +/// remains valid until FPDF_CloseDocument() is called for the document. +FPDF_SIGNATURE FPDF_GetSignatureObject(FPDF_DOCUMENT document, +int index, ) { - return _FPDFDOC_ExitFormFillEnvironment(hHandle, + return _FPDF_GetSignatureObject(document, +index, ); } -late final _FPDFDOC_ExitFormFillEnvironmentPtr = _lookup< - ffi.NativeFunction>('FPDFDOC_ExitFormFillEnvironment'); -late final _FPDFDOC_ExitFormFillEnvironment = _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction(); +late final _FPDF_GetSignatureObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSignatureObject'); +late final _FPDF_GetSignatureObject = _FPDF_GetSignatureObjectPtr.asFunction(); -/// Function: FORM_OnAfterLoadPage -/// This method is required for implementing all the form related -/// functions. Should be invoked after user successfully loaded a -/// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. +/// Experimental API. +/// Function: FPDFSignatureObj_GetContents +/// Get the contents of a signature object. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -void FORM_OnAfterLoadPage(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the contents. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the contents on success, 0 on error. +/// +/// For public-key signatures, |buffer| is either a DER-encoded PKCS#1 binary or +/// a DER-encoded PKCS#7 binary. If |length| is less than the returned length, or +/// |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetContents(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, ) { - return _FORM_OnAfterLoadPage(page, -hHandle, + return _FPDFSignatureObj_GetContents(signature, +buffer, +length, ); } -late final _FORM_OnAfterLoadPagePtr = _lookup< - ffi.NativeFunction>('FORM_OnAfterLoadPage'); -late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction(); +late final _FPDFSignatureObj_GetContentsPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetContents'); +late final _FPDFSignatureObj_GetContents = _FPDFSignatureObj_GetContentsPtr.asFunction , int )>(); -/// Function: FORM_OnBeforeClosePage -/// This method is required for implementing all the form related -/// functions. Should be invoked before user closes the PDF page. +/// Experimental API. +/// Function: FPDFSignatureObj_GetByteRange +/// Get the byte range of a signature object. /// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -void FORM_OnBeforeClosePage(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the +/// byte range. +/// length - The size, in ints, of |buffer|. +/// Return value: +/// Returns the number of ints in the byte range on +/// success, 0 on error. +/// +/// |buffer| is an array of pairs of integers (starting byte offset, +/// length in bytes) that describes the exact byte range for the digest +/// calculation. If |length| is less than the returned length, or +/// |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetByteRange(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, ) { - return _FORM_OnBeforeClosePage(page, -hHandle, + return _FPDFSignatureObj_GetByteRange(signature, +buffer, +length, ); } -late final _FORM_OnBeforeClosePagePtr = _lookup< - ffi.NativeFunction>('FORM_OnBeforeClosePage'); -late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction(); +late final _FPDFSignatureObj_GetByteRangePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetByteRange'); +late final _FPDFSignatureObj_GetByteRange = _FPDFSignatureObj_GetByteRangePtr.asFunction , int )>(); -/// Function: FORM_DoDocumentJSAction -/// This method is required for performing document-level JavaScript -/// actions. It should be invoked after the PDF document has been loaded. +/// Experimental API. +/// Function: FPDFSignatureObj_GetSubFilter +/// Get the encoding of the value of a signature object. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// If there is document-level JavaScript action embedded in the -/// document, this method will execute the JavaScript action. Otherwise, -/// the method will do nothing. -void FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the encoding. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the encoding name (including the +/// trailing NUL character) on success, 0 on error. +/// +/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetSubFilter(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, ) { - return _FORM_DoDocumentJSAction(hHandle, + return _FPDFSignatureObj_GetSubFilter(signature, +buffer, +length, ); } -late final _FORM_DoDocumentJSActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentJSAction'); -late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); +late final _FPDFSignatureObj_GetSubFilterPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetSubFilter'); +late final _FPDFSignatureObj_GetSubFilter = _FPDFSignatureObj_GetSubFilterPtr.asFunction , int )>(); -/// Function: FORM_DoDocumentOpenAction -/// This method is required for performing open-action when the document -/// is opened. +/// Experimental API. +/// Function: FPDFSignatureObj_GetReason +/// Get the reason (comment) of the signature object. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if there are no open-actions embedded -/// in the document. -void FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the reason. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the reason on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The +/// string is terminated by a UTF16 NUL character. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, ) { - return _FORM_DoDocumentOpenAction(hHandle, + return _FPDFSignatureObj_GetReason(signature, +buffer, +length, ); } -late final _FORM_DoDocumentOpenActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentOpenAction'); -late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr.asFunction(); +late final _FPDFSignatureObj_GetReasonPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetReason'); +late final _FPDFSignatureObj_GetReason = _FPDFSignatureObj_GetReasonPtr.asFunction , int )>(); -/// Function: FORM_DoDocumentAAction -/// This method is required for performing the document's -/// additional-action. +/// Experimental API. +/// Function: FPDFSignatureObj_GetTime +/// Get the time of signing of a signature object. /// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// aaType - The type of the additional-actions which defined -/// above. -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if there is no document -/// additional-action corresponding to the specified |aaType|. -void FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, -int aaType, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the time. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the encoding name (including the +/// trailing NUL character) on success, 0 on error. +/// +/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +/// +/// The format of time is expected to be D:YYYYMMDDHHMMSS+XX'YY', i.e. it's +/// percision is seconds, with timezone information. This value should be used +/// only when the time of signing is not available in the (PKCS#7 binary) +/// signature. +int FPDFSignatureObj_GetTime(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, ) { - return _FORM_DoDocumentAAction(hHandle, -aaType, + return _FPDFSignatureObj_GetTime(signature, +buffer, +length, ); } -late final _FORM_DoDocumentAActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentAAction'); -late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction(); +late final _FPDFSignatureObj_GetTimePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetTime'); +late final _FPDFSignatureObj_GetTime = _FPDFSignatureObj_GetTimePtr.asFunction , int )>(); -/// Function: FORM_DoPageAAction -/// This method is required for performing the page object's -/// additional-action when opened or closed. +/// Experimental API. +/// Function: FPDFSignatureObj_GetDocMDPPermission +/// Get the DocMDP permission of a signature object. /// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// aaType - The type of the page object's additional-actions -/// which defined above. -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if no additional-action corresponding -/// to the specified |aaType| exists. -void FORM_DoPageAAction(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, -int aaType, +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// Return value: +/// Returns the permission (1, 2 or 3) on success, 0 on error. +int FPDFSignatureObj_GetDocMDPPermission(FPDF_SIGNATURE signature, ) { - return _FORM_DoPageAAction(page, -hHandle, -aaType, + return _FPDFSignatureObj_GetDocMDPPermission(signature, ); } -late final _FORM_DoPageAActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoPageAAction'); -late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction(); +late final _FPDFSignatureObj_GetDocMDPPermissionPtr = _lookup< + ffi.NativeFunction>('FPDFSignatureObj_GetDocMDPPermission'); +late final _FPDFSignatureObj_GetDocMDPPermission = _FPDFSignatureObj_GetDocMDPPermissionPtr.asFunction(); -/// Function: FORM_OnMouseMove -/// Call this member function when the mouse cursor moves. +/// Function: FPDF_GetDefaultTTFMap +/// Returns a pointer to the default character set to TT Font name map. The +/// map is an array of FPDF_CharsetFontMap structs, with its end indicated +/// by a { -1, NULL } entry. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. +/// None. /// Return Value: -/// True indicates success; otherwise false. -int FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnMouseMove(hHandle, -page, -modifier, -page_x, -page_y, -); +/// Pointer to the Charset Font Map. +/// Note: +/// Once FPDF_GetDefaultTTFMapCount() and FPDF_GetDefaultTTFMapEntry() are no +/// longer experimental, this API will be marked as deprecated. +/// See https://crbug.com/348468114 +ffi.Pointer FPDF_GetDefaultTTFMap() { + return _FPDF_GetDefaultTTFMap(); } -late final _FORM_OnMouseMovePtr = _lookup< - ffi.NativeFunction>('FORM_OnMouseMove'); -late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction(); +late final _FPDF_GetDefaultTTFMapPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultTTFMap'); +late final _FPDF_GetDefaultTTFMap = _FPDF_GetDefaultTTFMapPtr.asFunction Function()>(); -/// Experimental API -/// Function: FORM_OnMouseWheel -/// Call this member function when the user scrolls the mouse wheel. +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapCount +/// Returns the number of entries in the default character set to TT Font name +/// map. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_coord - Specifies the coordinates of the cursor in PDF user -/// space. -/// delta_x - Specifies the amount of wheel movement on the x-axis, -/// in units of platform-agnostic wheel deltas. Negative -/// values mean left. -/// delta_y - Specifies the amount of wheel movement on the y-axis, -/// in units of platform-agnostic wheel deltas. Negative -/// values mean down. +/// None. /// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// For |delta_x| and |delta_y|, the caller must normalize -/// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 -/// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines -/// WHEEL_DELTA as 120. -int FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -ffi.Pointer page_coord, -int delta_x, -int delta_y, -) { - return _FORM_OnMouseWheel(hHandle, -page, -modifier, -page_coord, -delta_x, -delta_y, -); +/// The number of entries in the map. +int FPDF_GetDefaultTTFMapCount() { + return _FPDF_GetDefaultTTFMapCount(); } -late final _FORM_OnMouseWheelPtr = _lookup< - ffi.NativeFunction , ffi.Int , ffi.Int )>>('FORM_OnMouseWheel'); -late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction , int , int )>(); +late final _FPDF_GetDefaultTTFMapCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDefaultTTFMapCount'); +late final _FPDF_GetDefaultTTFMapCount = _FPDF_GetDefaultTTFMapCountPtr.asFunction(); -/// Function: FORM_OnFocus -/// This function focuses the form annotation at a given point. If the -/// annotation at the point already has focus, nothing happens. If there -/// is no annotation at the point, removes form focus. +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapEntry +/// Returns an entry in the default character set to TT Font name map. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. +/// index - The index to the entry in the map to retrieve. /// Return Value: -/// True if there is an annotation at the given point and it has focus. -int FORM_OnFocus(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, +/// A pointer to the entry, if it is in the map, or NULL if the index is out +/// of bounds. +ffi.Pointer FPDF_GetDefaultTTFMapEntry(int index, ) { - return _FORM_OnFocus(hHandle, -page, -modifier, -page_x, -page_y, + return _FPDF_GetDefaultTTFMapEntry(index, ); } -late final _FORM_OnFocusPtr = _lookup< - ffi.NativeFunction>('FORM_OnFocus'); -late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction(); +late final _FPDF_GetDefaultTTFMapEntryPtr = _lookup< + ffi.NativeFunction Function(ffi.Size )>>('FPDF_GetDefaultTTFMapEntry'); +late final _FPDF_GetDefaultTTFMapEntry = _FPDF_GetDefaultTTFMapEntryPtr.asFunction Function(int )>(); -/// Function: FORM_OnLButtonDown -/// Call this member function when the user presses the left -/// mouse button. +/// Function: FPDF_AddInstalledFont +/// Add a system font to the list in PDFium. +/// Comments: +/// This function is only called during the system font list building +/// process. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. +/// mapper - Opaque pointer to Foxit font mapper +/// face - The font face name +/// charset - Font character set. See above defined constants. /// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, +/// None. +void FPDF_AddInstalledFont(ffi.Pointer mapper, +ffi.Pointer face, +int charset, ) { - return _FORM_OnLButtonDown(hHandle, -page, -modifier, -page_x, -page_y, + return _FPDF_AddInstalledFont(mapper, +face, +charset, ); } -late final _FORM_OnLButtonDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonDown'); -late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction(); +late final _FPDF_AddInstalledFontPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_AddInstalledFont'); +late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction , ffi.Pointer , int )>(); -/// Function: FORM_OnRButtonDown -/// Same as above, execpt for the right mouse button. +/// Function: FPDF_SetSystemFontInfo +/// Set the system font info interface into PDFium +/// Parameters: +/// font_info - Pointer to a FPDF_SYSFONTINFO structure +/// Return Value: +/// None /// Comments: -/// At the present time, has no effect except in XFA builds, but is -/// included for the sake of symmetry. -int FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, +/// Platform support implementation should implement required methods of +/// FFDF_SYSFONTINFO interface, then call this function during PDFium +/// initialization process. +/// +/// Call this with NULL to tell PDFium to stop using a previously set +/// |FPDF_SYSFONTINFO|. +void FPDF_SetSystemFontInfo(ffi.Pointer font_info, ) { - return _FORM_OnRButtonDown(hHandle, -page, -modifier, -page_x, -page_y, + return _FPDF_SetSystemFontInfo(font_info, ); } -late final _FORM_OnRButtonDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnRButtonDown'); -late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction(); +late final _FPDF_SetSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_SetSystemFontInfo'); +late final _FPDF_SetSystemFontInfo = _FPDF_SetSystemFontInfoPtr.asFunction )>(); -/// Function: FORM_OnLButtonUp -/// Call this member function when the user releases the left -/// mouse button. +/// Function: FPDF_GetDefaultSystemFontInfo +/// Get default system font info interface for current platform /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in device. -/// page_y - Specifies the y-coordinate of the cursor in device. +/// None /// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnLButtonUp(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnLButtonUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonUp'); -late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction(); - -/// Function: FORM_OnRButtonUp -/// Same as above, execpt for the right mouse button. +/// Pointer to a FPDF_SYSFONTINFO structure describing the default +/// interface, or NULL if the platform doesn't have a default interface. +/// Application should call FPDF_FreeDefaultSystemFontInfo to free the +/// returned pointer. /// Comments: -/// At the present time, has no effect except in XFA builds, but is -/// included for the sake of symmetry. -int FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnRButtonUp(hHandle, -page, -modifier, -page_x, -page_y, -); +/// For some platforms, PDFium implements a default version of system +/// font info interface. The default implementation can be passed to +/// FPDF_SetSystemFontInfo(). +ffi.Pointer FPDF_GetDefaultSystemFontInfo() { + return _FPDF_GetDefaultSystemFontInfo(); } -late final _FORM_OnRButtonUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnRButtonUp'); -late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction(); +late final _FPDF_GetDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultSystemFontInfo'); +late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.asFunction Function()>(); -/// Function: FORM_OnLButtonDoubleClick -/// Call this member function when the user double clicks the -/// left mouse button. +/// Function: FPDF_FreeDefaultSystemFontInfo +/// Free a default system font info interface /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. +/// font_info - Pointer to a FPDF_SYSFONTINFO structure /// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonDoubleClick(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, +/// None +/// Comments: +/// This function should be called on the output from +/// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. +void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer font_info, ) { - return _FORM_OnLButtonDoubleClick(hHandle, -page, -modifier, -page_x, -page_y, + return _FPDF_FreeDefaultSystemFontInfo(font_info, ); } -late final _FORM_OnLButtonDoubleClickPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonDoubleClick'); -late final _FORM_OnLButtonDoubleClick = _FORM_OnLButtonDoubleClickPtr.asFunction(); +late final _FPDF_FreeDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_FreeDefaultSystemFontInfo'); +late final _FPDF_FreeDefaultSystemFontInfo = _FPDF_FreeDefaultSystemFontInfoPtr.asFunction )>(); -/// Function: FORM_OnKeyDown -/// Call this member function when a nonsystem key is pressed. -/// Parameters: -/// hHandle - Handle to the form fill module, aseturned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nKeyCode - The virtual-key code of the given key (see -/// fpdf_fwlevent.h for virtual key codes). -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nKeyCode, -int modifier, +/// Experimental API. +/// Get the number of JavaScript actions in |document|. +/// +/// document - handle to a document. +/// +/// Returns the number of JavaScript actions in |document| or -1 on error. +int FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document, ) { - return _FORM_OnKeyDown(hHandle, -page, -nKeyCode, -modifier, + return _FPDFDoc_GetJavaScriptActionCount(document, ); } -late final _FORM_OnKeyDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnKeyDown'); -late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction(); +late final _FPDFDoc_GetJavaScriptActionCountPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetJavaScriptActionCount'); +late final _FPDFDoc_GetJavaScriptActionCount = _FPDFDoc_GetJavaScriptActionCountPtr.asFunction(); -/// Function: FORM_OnKeyUp -/// Call this member function when a nonsystem key is released. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nKeyCode - The virtual-key code of the given key (see -/// fpdf_fwlevent.h for virtual key codes). -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// Currently unimplemented and always returns false. PDFium reserves this -/// API and may implement it in the future on an as-needed basis. -int FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nKeyCode, -int modifier, +/// Experimental API. +/// Get the JavaScript action at |index| in |document|. +/// +/// document - handle to a document. +/// index - the index of the requested JavaScript action. +/// +/// Returns the handle to the JavaScript action, or NULL on failure. +/// Caller owns the returned handle and must close it with +/// FPDFDoc_CloseJavaScriptAction(). +FPDF_JAVASCRIPT_ACTION FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, +int index, ) { - return _FORM_OnKeyUp(hHandle, -page, -nKeyCode, -modifier, + return _FPDFDoc_GetJavaScriptAction(document, +index, ); } -late final _FORM_OnKeyUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnKeyUp'); -late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction(); +late final _FPDFDoc_GetJavaScriptActionPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetJavaScriptAction'); +late final _FPDFDoc_GetJavaScriptAction = _FPDFDoc_GetJavaScriptActionPtr.asFunction(); -/// Function: FORM_OnChar -/// Call this member function when a keystroke translates to a -/// nonsystem character. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nChar - The character code value itself. -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnChar(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nChar, -int modifier, +/// javascript - Handle to a JavaScript action. +void FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript, ) { - return _FORM_OnChar(hHandle, -page, -nChar, -modifier, + return _FPDFDoc_CloseJavaScriptAction(javascript, ); } -late final _FORM_OnCharPtr = _lookup< - ffi.NativeFunction>('FORM_OnChar'); -late final _FORM_OnChar = _FORM_OnCharPtr.asFunction(); +late final _FPDFDoc_CloseJavaScriptActionPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_CloseJavaScriptAction'); +late final _FPDFDoc_CloseJavaScriptAction = _FPDFDoc_CloseJavaScriptActionPtr.asFunction(); -/// Experimental API -/// Function: FORM_GetFocusedText -/// Call this function to obtain the text within the current focused -/// field, if any. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// buffer - Buffer for holding the form text, encoded in -/// UTF-16LE. If NULL, |buffer| is not modified. -/// buflen - Length of |buffer| in bytes. If |buflen| is less -/// than the length of the form text string, |buffer| is -/// not modified. -/// Return Value: -/// Length in bytes for the text in the focused field. -int FORM_GetFocusedText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer buffer, +/// Experimental API. +/// Get the name from the |javascript| handle. |buffer| is only modified if +/// |buflen| is longer than the length of the name. On errors, |buffer| is +/// unmodified and the returned length is 0. +/// +/// javascript - handle to an JavaScript action. +/// buffer - buffer for holding the name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the JavaScript action name in bytes. +int FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript, +ffi.Pointer buffer, int buflen, ) { - return _FORM_GetFocusedText(hHandle, -page, + return _FPDFJavaScriptAction_GetName(javascript, buffer, buflen, ); } -late final _FORM_GetFocusedTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetFocusedText'); -late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction , int )>(); +late final _FPDFJavaScriptAction_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetName'); +late final _FPDFJavaScriptAction_GetName = _FPDFJavaScriptAction_GetNamePtr.asFunction , int )>(); -/// Function: FORM_GetSelectedText -/// Call this function to obtain selected text within a form text -/// field or form combobox text field. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// buffer - Buffer for holding the selected text, encoded in -/// UTF-16LE. If NULL, |buffer| is not modified. -/// buflen - Length of |buffer| in bytes. If |buflen| is less -/// than the length of the selected text string, -/// |buffer| is not modified. -/// Return Value: -/// Length in bytes of selected text in form text field or form combobox -/// text field. -int FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer buffer, +/// Experimental API. +/// Get the script from the |javascript| handle. |buffer| is only modified if +/// |buflen| is longer than the length of the script. On errors, |buffer| is +/// unmodified and the returned length is 0. +/// +/// javascript - handle to an JavaScript action. +/// buffer - buffer for holding the name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the JavaScript action name in bytes. +int FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript, +ffi.Pointer buffer, int buflen, ) { - return _FORM_GetSelectedText(hHandle, -page, + return _FPDFJavaScriptAction_GetScript(javascript, buffer, buflen, ); } -late final _FORM_GetSelectedTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetSelectedText'); -late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction , int )>(); +late final _FPDFJavaScriptAction_GetScriptPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetScript'); +late final _FPDFJavaScriptAction_GetScript = _FPDFJavaScriptAction_GetScriptPtr.asFunction , int )>(); -/// Experimental API -/// Function: FORM_ReplaceAndKeepSelection -/// Call this function to replace the selected text in a form -/// text field or user-editable form combobox text field with another -/// text string (which can be empty or non-empty). If there is no -/// selected text, this function will append the replacement text after -/// the current caret position. After the insertion, the inserted text -/// will be selected. +/// Function: FPDFText_LoadPage +/// Prepare information about all characters in a page. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as Returned by FPDF_LoadPage(). -/// wsText - The text to be inserted, in UTF-16LE format. -/// Return Value: -/// None. -void FORM_ReplaceAndKeepSelection(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -FPDF_WIDESTRING wsText, +/// page - Handle to the page. Returned by FPDF_LoadPage function +/// (in FPDFVIEW module). +/// Return value: +/// A handle to the text page information structure. +/// NULL if something goes wrong. +/// Comments: +/// Application must call FPDFText_ClosePage to release the text page +/// information. +FPDF_TEXTPAGE FPDFText_LoadPage(FPDF_PAGE page, ) { - return _FORM_ReplaceAndKeepSelection(hHandle, -page, -wsText, + return _FPDFText_LoadPage(page, ); } -late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< - ffi.NativeFunction>('FORM_ReplaceAndKeepSelection'); -late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr.asFunction(); +late final _FPDFText_LoadPagePtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadPage'); +late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); -/// Function: FORM_ReplaceSelection -/// Call this function to replace the selected text in a form -/// text field or user-editable form combobox text field with another -/// text string (which can be empty or non-empty). If there is no -/// selected text, this function will append the replacement text after -/// the current caret position. After the insertion, the selection range -/// will be set to empty. +/// Function: FPDFText_ClosePage +/// Release all resources allocated for a text page information +/// structure. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as Returned by FPDF_LoadPage(). -/// wsText - The text to be inserted, in UTF-16LE format. +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. /// Return Value: /// None. -void FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -FPDF_WIDESTRING wsText, +void FPDFText_ClosePage(FPDF_TEXTPAGE text_page, ) { - return _FORM_ReplaceSelection(hHandle, -page, -wsText, + return _FPDFText_ClosePage(text_page, ); } -late final _FORM_ReplaceSelectionPtr = _lookup< - ffi.NativeFunction>('FORM_ReplaceSelection'); -late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction(); +late final _FPDFText_ClosePagePtr = _lookup< + ffi.NativeFunction>('FPDFText_ClosePage'); +late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); -/// Experimental API -/// Function: FORM_SelectAllText -/// Call this function to select all the text within the currently focused -/// form text field or form combobox text field. +/// Function: FPDFText_CountChars +/// Get number of characters in a page. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// Whether the operation succeeded or not. -int FORM_SelectAllText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return value: +/// Number of characters in the page. Return -1 for error. +/// Generated characters, like additional space characters, new line +/// characters, are also counted. +/// Comments: +/// Characters in a page form a "stream", inside the stream, each +/// character has an index. +/// We will use the index parameters in many of FPDFTEXT functions. The +/// first character in the page +/// has an index value of zero. +int FPDFText_CountChars(FPDF_TEXTPAGE text_page, ) { - return _FORM_SelectAllText(hHandle, -page, + return _FPDFText_CountChars(text_page, ); } -late final _FORM_SelectAllTextPtr = _lookup< - ffi.NativeFunction>('FORM_SelectAllText'); -late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction(); +late final _FPDFText_CountCharsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountChars'); +late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); -/// Function: FORM_CanUndo -/// Find out if it is possible for the current focused widget in a given -/// form to perform an undo operation. +/// Function: FPDFText_GetUnicode +/// Get Unicode of a character in a page. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if it is possible to undo. -int FORM_CanUndo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The Unicode of the particular character. +/// If a character is not encoded in Unicode and Foxit engine can't +/// convert to Unicode, +/// the return value will be zero. +int FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_CanUndo(hHandle, -page, + return _FPDFText_GetUnicode(text_page, +index, ); } -late final _FORM_CanUndoPtr = _lookup< - ffi.NativeFunction>('FORM_CanUndo'); -late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); +late final _FPDFText_GetUnicodePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetUnicode'); +late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); -/// Function: FORM_CanRedo -/// Find out if it is possible for the current focused widget in a given -/// form to perform a redo operation. +/// Experimental API. +/// Function: FPDFText_GetTextObject +/// Get the FPDF_PAGEOBJECT associated with a given character. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if it is possible to redo. -int FORM_CanRedo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The associated text object for the character at |index|, or NULL on +/// error. The returned text object, if non-null, is of type +/// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. +FPDF_PAGEOBJECT FPDFText_GetTextObject(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_CanRedo(hHandle, -page, + return _FPDFText_GetTextObject(text_page, +index, ); } -late final _FORM_CanRedoPtr = _lookup< - ffi.NativeFunction>('FORM_CanRedo'); -late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); +late final _FPDFText_GetTextObjectPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetTextObject'); +late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction(); -/// Function: FORM_Undo -/// Make the current focused widget perform an undo operation. +/// Experimental API. +/// Function: FPDFText_IsGenerated +/// Get if a character in a page is generated by PDFium. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if the undo operation succeeded. -int FORM_Undo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is generated by PDFium. +/// 0 if the character is not generated by PDFium. +/// -1 if there was an error. +int FPDFText_IsGenerated(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_Undo(hHandle, -page, + return _FPDFText_IsGenerated(text_page, +index, ); } -late final _FORM_UndoPtr = _lookup< - ffi.NativeFunction>('FORM_Undo'); -late final _FORM_Undo = _FORM_UndoPtr.asFunction(); +late final _FPDFText_IsGeneratedPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsGenerated'); +late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); -/// Function: FORM_Redo -/// Make the current focused widget perform a redo operation. +/// Experimental API. +/// Function: FPDFText_IsHyphen +/// Get if a character in a page is a hyphen. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if the redo operation succeeded. -int FORM_Redo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is a hyphen. +/// 0 if the character is not a hyphen. +/// -1 if there was an error. +int FPDFText_IsHyphen(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_Redo(hHandle, -page, + return _FPDFText_IsHyphen(text_page, +index, ); } -late final _FORM_RedoPtr = _lookup< - ffi.NativeFunction>('FORM_Redo'); -late final _FORM_Redo = _FORM_RedoPtr.asFunction(); +late final _FPDFText_IsHyphenPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsHyphen'); +late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); -/// Function: FORM_ForceToKillFocus. -/// Call this member function to force to kill the focus of the form -/// field which has focus. If it would kill the focus of a form field, -/// save the value of form field if was changed by theuser. +/// Experimental API. +/// Function: FPDFText_HasUnicodeMapError +/// Get if a character in a page has an invalid unicode mapping. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character has an invalid unicode mapping. +/// 0 if the character has no known unicode mapping issues. +/// -1 if there was an error. +int FPDFText_HasUnicodeMapError(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_ForceToKillFocus(hHandle, + return _FPDFText_HasUnicodeMapError(text_page, +index, ); } -late final _FORM_ForceToKillFocusPtr = _lookup< - ffi.NativeFunction>('FORM_ForceToKillFocus'); -late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); +late final _FPDFText_HasUnicodeMapErrorPtr = _lookup< + ffi.NativeFunction>('FPDFText_HasUnicodeMapError'); +late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr.asFunction(); -/// Experimental API. -/// Function: FORM_GetFocusedAnnot. -/// Call this member function to get the currently focused annotation. +/// Function: FPDFText_GetFontSize +/// Get the font size of a particular character. /// Parameters: -/// handle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page_index - Buffer to hold the index number of the page which -/// contains the focused annotation. 0 for the first page. -/// Can't be NULL. -/// annot - Buffer to hold the focused annotation. Can't be NULL. -/// Return Value: -/// On success, return true and write to the out parameters. Otherwise -/// return false and leave the out parameters unmodified. -/// Comments: -/// Not currently supported for XFA forms - will report no focused -/// annotation. -/// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| -/// by this function is no longer needed. -/// This will return true and set |page_index| to -1 and |annot| to NULL, -/// if there is no focused annotation. -int FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle, -ffi.Pointer page_index, -ffi.Pointer annot, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The font size of the particular character, measured in points (about +/// 1/72 inch). This is the typographic size of the font (so called +/// "em size"). +double FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, +int index, ) { - return _FORM_GetFocusedAnnot(handle, -page_index, -annot, + return _FPDFText_GetFontSize(text_page, +index, ); } -late final _FORM_GetFocusedAnnotPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FORM_GetFocusedAnnot'); -late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction , ffi.Pointer )>(); +late final _FPDFText_GetFontSizePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontSize'); +late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction(); /// Experimental API. -/// Function: FORM_SetFocusedAnnot. -/// Call this member function to set the currently focused annotation. +/// Function: FPDFText_GetFontInfo +/// Get the font name and flags of a particular character. /// Parameters: -/// handle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - Handle to an annotation. -/// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() -/// instead. -int FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// buffer - A buffer receiving the font name. +/// buflen - The length of |buffer| in bytes. +/// flags - Optional pointer to an int receiving the font flags. +/// These flags should be interpreted per PDF spec 1.7 +/// Section 5.7.1 Font Descriptor Flags. +/// Return value: +/// On success, return the length of the font name, including the +/// trailing NUL character, in bytes. If this length is less than or +/// equal to |length|, |buffer| is set to the font name, |flags| is +/// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on +/// failure. +int FPDFText_GetFontInfo(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer flags, ) { - return _FORM_SetFocusedAnnot(handle, -annot, + return _FPDFText_GetFontInfo(text_page, +index, +buffer, +buflen, +flags, ); } -late final _FORM_SetFocusedAnnotPtr = _lookup< - ffi.NativeFunction>('FORM_SetFocusedAnnot'); -late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction(); +late final _FPDFText_GetFontInfoPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFText_GetFontInfo'); +late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction , int , ffi.Pointer )>(); -/// Function: FPDFPage_HasFormFieldAtPoint -/// Get the form field type by point. +/// Experimental API. +/// Function: FPDFText_GetFontWeight +/// Get the font weight of a particular character. /// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// page_x - X position in PDF "user space". -/// page_y - Y position in PDF "user space". -/// Return Value: -/// Return the type of the form field; -1 indicates no field. -/// See field types above. -int FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -double page_x, -double page_y, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// On success, return the font weight of the particular character. If +/// |text_page| is invalid, if |index| is out of bounds, or if the +/// character's text object is undefined, return -1. +int FPDFText_GetFontWeight(FPDF_TEXTPAGE text_page, +int index, ) { - return _FPDFPage_HasFormFieldAtPoint(hHandle, -page, -page_x, -page_y, + return _FPDFText_GetFontWeight(text_page, +index, ); } -late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFPage_HasFormFieldAtPoint'); -late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr.asFunction(); +late final _FPDFText_GetFontWeightPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontWeight'); +late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); -/// Function: FPDFPage_FormFieldZOrderAtPoint -/// Get the form field z-order by point. +/// Experimental API. +/// Function: FPDFText_GetFillColor +/// Get the fill color of a particular character. /// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// page_x - X position in PDF "user space". -/// page_y - Y position in PDF "user space". -/// Return Value: -/// Return the z-order of the form field; -1 indicates no field. -/// Higher numbers are closer to the front. -int FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -double page_x, -double page_y, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the fill color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the fill color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the fill color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the fill color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetFillColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, ) { - return _FPDFPage_FormFieldZOrderAtPoint(hHandle, -page, -page_x, -page_y, + return _FPDFText_GetFillColor(text_page, +index, +R, +G, +B, +A, ); } -late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFPage_FormFieldZOrderAtPoint'); -late final _FPDFPage_FormFieldZOrderAtPoint = _FPDFPage_FormFieldZOrderAtPointPtr.asFunction(); +late final _FPDFText_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetFillColor'); +late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Function: FPDF_SetFormFieldHighlightColor -/// Set the highlight color of the specified (or all) form fields -/// in the document. +/// Experimental API. +/// Function: FPDFText_GetStrokeColor +/// Get the stroke color of a particular character. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// doc - Handle to the document, as returned by -/// FPDF_LoadDocument(). -/// fieldType - A 32-bit integer indicating the type of a form -/// field (defined above). -/// color - The highlight color of the form field. Constructed by -/// 0xxxrrggbb. -/// Return Value: -/// None. -/// Comments: -/// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the -/// highlight color will be applied to all the form fields in the -/// document. -/// Please refresh the client window to show the highlight immediately -/// if necessary. -void FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, -int fieldType, -int color, +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the stroke color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the stroke color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the stroke color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the stroke color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetStrokeColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, ) { - return _FPDF_SetFormFieldHighlightColor(hHandle, -fieldType, -color, + return _FPDFText_GetStrokeColor(text_page, +index, +R, +G, +B, +A, ); } -late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< - ffi.NativeFunction>('FPDF_SetFormFieldHighlightColor'); -late final _FPDF_SetFormFieldHighlightColor = _FPDF_SetFormFieldHighlightColorPtr.asFunction(); +late final _FPDFText_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetStrokeColor'); +late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Function: FPDF_SetFormFieldHighlightAlpha -/// Set the transparency of the form field highlight color in the -/// document. +/// Experimental API. +/// Function: FPDFText_GetCharAngle +/// Get character rotation angle. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// doc - Handle to the document, as returaned by -/// FPDF_LoadDocument(). -/// alpha - The transparency of the form field highlight color, -/// between 0-255. +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. /// Return Value: -/// None. -void FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, -int alpha, +/// On success, return the angle value in radian. Value will always be +/// greater or equal to 0. If |text_page| is invalid, or if |index| is +/// out of bounds, then return -1. +double FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page, +int index, ) { - return _FPDF_SetFormFieldHighlightAlpha(hHandle, -alpha, + return _FPDFText_GetCharAngle(text_page, +index, ); } -late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< - ffi.NativeFunction>('FPDF_SetFormFieldHighlightAlpha'); -late final _FPDF_SetFormFieldHighlightAlpha = _FPDF_SetFormFieldHighlightAlphaPtr.asFunction(); +late final _FPDFText_GetCharAnglePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharAngle'); +late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction(); -/// Function: FPDF_RemoveFormFieldHighlight -/// Remove the form field highlight color in the document. +/// Function: FPDFText_GetCharBox +/// Get bounding box of a particular character. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// left - Pointer to a double number receiving left position +/// of the character box. +/// right - Pointer to a double number receiving right position +/// of the character box. +/// bottom - Pointer to a double number receiving bottom position +/// of the character box. +/// top - Pointer to a double number receiving top position of +/// the character box. /// Return Value: -/// None. +/// On success, return TRUE and fill in |left|, |right|, |bottom|, and +/// |top|. If |text_page| is invalid, or if |index| is out of bounds, +/// then return FALSE, and the out parameters remain unmodified. /// Comments: -/// Please refresh the client window to remove the highlight immediately -/// if necessary. -void FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle, +/// All positions are measured in PDF "user space". +int FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer left, +ffi.Pointer right, +ffi.Pointer bottom, +ffi.Pointer top, ) { - return _FPDF_RemoveFormFieldHighlight(hHandle, + return _FPDFText_GetCharBox(text_page, +index, +left, +right, +bottom, +top, ); } -late final _FPDF_RemoveFormFieldHighlightPtr = _lookup< - ffi.NativeFunction>('FPDF_RemoveFormFieldHighlight'); -late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr.asFunction(); +late final _FPDFText_GetCharBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetCharBox'); +late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Function: FPDF_FFLDraw -/// Render FormFields and popup window on a page to a device independent -/// bitmap. +/// Experimental API. +/// Function: FPDFText_GetLooseCharBox +/// Get a "loose" bounding box of a particular character, i.e., covering +/// the entire glyph bounds, without taking the actual glyph shape into +/// account. /// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). Bitmap handles can be created by -/// FPDFBitmap_Create(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// start_x - Left pixel position of the display area in the -/// device coordinates. -/// start_y - Top pixel position of the display area in the device -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees -/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 -/// degrees counter-clockwise). -/// flags - 0 for normal display, or combination of flags -/// defined above. +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// rect - Pointer to a FS_RECTF receiving the character box. /// Return Value: -/// None. +/// On success, return TRUE and fill in |rect|. If |text_page| is +/// invalid, or if |index| is out of bounds, then return FALSE, and the +/// |rect| out parameter remains unmodified. /// Comments: -/// This function is designed to render annotations that are -/// user-interactive, which are widget annotations (for FormFields) and -/// popup annotations. -/// With the FPDF_ANNOT flag, this function will render a popup annotation -/// when users mouse-hover on a non-widget annotation. Regardless of -/// FPDF_ANNOT flag, this function will always render widget annotations -/// for FormFields. -/// In order to implement the FormFill functions, implementation should -/// call this function after rendering functions, such as -/// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have -/// finished rendering the page contents. -void FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, -FPDF_BITMAP bitmap, -FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int flags, +/// All positions are measured in PDF "user space". +int FPDFText_GetLooseCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer rect, ) { - return _FPDF_FFLDraw(hHandle, -bitmap, -page, -start_x, -start_y, -size_x, -size_y, -rotate, -flags, + return _FPDFText_GetLooseCharBox(text_page, +index, +rect, ); } -late final _FPDF_FFLDrawPtr = _lookup< - ffi.NativeFunction>('FPDF_FFLDraw'); -late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction(); +late final _FPDFText_GetLooseCharBoxPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetLooseCharBox'); +late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr.asFunction )>(); -/// Experimental API -/// Function: FPDF_GetFormType -/// Returns the type of form contained in the PDF document. +/// Experimental API. +/// Function: FPDFText_GetMatrix +/// Get the effective transformation matrix for a particular character. /// Parameters: -/// document - Handle to document. +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage(). +/// index - Zero-based index of the character. +/// matrix - Pointer to a FS_MATRIX receiving the transformation +/// matrix. /// Return Value: -/// Integer value representing one of the FORMTYPE_ values. -/// Comments: -/// If |document| is NULL, then the return value is FORMTYPE_NONE. -int FPDF_GetFormType(FPDF_DOCUMENT document, +/// On success, return TRUE and fill in |matrix|. If |text_page| is +/// invalid, or if |index| is out of bounds, or if |matrix| is NULL, +/// then return FALSE, and |matrix| remains unmodified. +int FPDFText_GetMatrix(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer matrix, ) { - return _FPDF_GetFormType(document, + return _FPDFText_GetMatrix(text_page, +index, +matrix, ); } -late final _FPDF_GetFormTypePtr = _lookup< - ffi.NativeFunction>('FPDF_GetFormType'); -late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); +late final _FPDFText_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetMatrix'); +late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction )>(); -/// Experimental API -/// Function: FORM_SetIndexSelected -/// Selects/deselects the value at the given |index| of the focused -/// annotation. +/// Function: FPDFText_GetCharOrigin +/// Get origin of a particular character. /// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// page - Handle to the page. Returned by FPDF_LoadPage -/// index - 0-based index of value to be set as -/// selected/unselected -/// selected - true to select, false to deselect +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// x - Pointer to a double number receiving x coordinate of +/// the character origin. +/// y - Pointer to a double number receiving y coordinate of +/// the character origin. /// Return Value: -/// TRUE if the operation succeeded. -/// FALSE if the operation failed or widget is not a supported type. +/// Whether the call succeeded. If false, x and y are unchanged. /// Comments: -/// Intended for use with listbox/combobox widget types. Comboboxes -/// have at most a single value selected at a time which cannot be -/// deselected. Deselect on a combobox is a no-op that returns false. -/// Default implementation is a no-op that will return false for -/// other types. -/// Not currently supported for XFA forms - will return false. -int FORM_SetIndexSelected(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, +/// All positions are measured in PDF "user space". +int FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, int index, -int selected, +ffi.Pointer x, +ffi.Pointer y, ) { - return _FORM_SetIndexSelected(hHandle, -page, + return _FPDFText_GetCharOrigin(text_page, index, -selected, +x, +y, ); } -late final _FORM_SetIndexSelectedPtr = _lookup< - ffi.NativeFunction>('FORM_SetIndexSelected'); -late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction(); +late final _FPDFText_GetCharOriginPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFText_GetCharOrigin'); +late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction , ffi.Pointer )>(); -/// Experimental API -/// Function: FORM_IsIndexSelected -/// Returns whether or not the value at |index| of the focused -/// annotation is currently selected. +/// Function: FPDFText_GetCharIndexAtPos +/// Get the index of a character at or nearby a certain position on the +/// page. /// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// page - Handle to the page. Returned by FPDF_LoadPage -/// index - 0-based Index of value to check +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// x - X position in PDF "user space". +/// y - Y position in PDF "user space". +/// xTolerance - An x-axis tolerance value for character hit +/// detection, in point units. +/// yTolerance - A y-axis tolerance value for character hit +/// detection, in point units. /// Return Value: -/// TRUE if value at |index| is currently selected. -/// FALSE if value at |index| is not selected or widget is not a -/// supported type. -/// Comments: -/// Intended for use with listbox/combobox widget types. Default -/// implementation is a no-op that will return false for other types. -/// Not currently supported for XFA forms - will return false. -int FORM_IsIndexSelected(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int index, +/// The zero-based index of the character at, or nearby the point (x,y). +/// If there is no character at or nearby the point, return value will +/// be -1. If an error occurs, -3 will be returned. +int FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, +double x, +double y, +double xTolerance, +double yTolerance, ) { - return _FORM_IsIndexSelected(hHandle, -page, -index, + return _FPDFText_GetCharIndexAtPos(text_page, +x, +y, +xTolerance, +yTolerance, ); } -late final _FORM_IsIndexSelectedPtr = _lookup< - ffi.NativeFunction>('FORM_IsIndexSelected'); -late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction(); +late final _FPDFText_GetCharIndexAtPosPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharIndexAtPos'); +late final _FPDFText_GetCharIndexAtPos = _FPDFText_GetCharIndexAtPosPtr.asFunction(); -/// Function: FPDF_LoadXFA -/// If the document consists of XFA fields, call this method to -/// attempt to load XFA fields. +/// Function: FPDFText_GetText +/// Extract unicode text string from the page. /// Parameters: -/// document - Handle to document from FPDF_LoadDocument(). +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start characters. +/// count - Number of UCS-2 values to be extracted. +/// result - A buffer (allocated by application) receiving the +/// extracted UCS-2 values. The buffer must be able to +/// hold `count` UCS-2 values plus a terminator. /// Return Value: -/// TRUE upon success, otherwise FALSE. If XFA support is not built -/// into PDFium, performs no action and always returns FALSE. -int FPDF_LoadXFA(FPDF_DOCUMENT document, +/// Number of characters written into the result buffer, including the +/// trailing terminator. +/// Comments: +/// This function ignores characters without UCS-2 representations. +/// It considers all characters on the page, even those that are not +/// visible when the page has a cropbox. To filter out the characters +/// outside of the cropbox, use FPDF_GetPageBoundingBox() and +/// FPDFText_GetCharBox(). +int FPDFText_GetText(FPDF_TEXTPAGE text_page, +int start_index, +int count, +ffi.Pointer result, ) { - return _FPDF_LoadXFA(document, + return _FPDFText_GetText(text_page, +start_index, +count, +result, ); } -late final _FPDF_LoadXFAPtr = _lookup< - ffi.NativeFunction>('FPDF_LoadXFA'); -late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); +late final _FPDFText_GetTextPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetText'); +late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction )>(); -/// Experimental API. -/// Check if an annotation subtype is currently supported for creation. -/// Currently supported subtypes: -/// - circle -/// - fileattachment -/// - freetext -/// - highlight -/// - ink -/// - link -/// - popup -/// - square, -/// - squiggly -/// - stamp -/// - strikeout -/// - text -/// - underline -/// -/// subtype - the subtype to be checked. -/// -/// Returns true if this subtype supported. -int FPDFAnnot_IsSupportedSubtype(int subtype, +/// Function: FPDFText_CountRects +/// Counts number of rectangular areas occupied by a segment of text, +/// and caches the result for subsequent FPDFText_GetRect() calls. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start character. +/// count - Number of characters, or -1 for all remaining. +/// Return value: +/// Number of rectangles, 0 if text_page is null, or -1 on bad +/// start_index. +/// Comments: +/// This function, along with FPDFText_GetRect can be used by +/// applications to detect the position on the page for a text segment, +/// so proper areas can be highlighted. The FPDFText_* functions will +/// automatically merge small character boxes into bigger one if those +/// characters are on the same line and use same font settings. +int FPDFText_CountRects(FPDF_TEXTPAGE text_page, +int start_index, +int count, ) { - return _FPDFAnnot_IsSupportedSubtype(subtype, + return _FPDFText_CountRects(text_page, +start_index, +count, ); } -late final _FPDFAnnot_IsSupportedSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsSupportedSubtype'); -late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); +late final _FPDFText_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountRects'); +late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction(); -/// Experimental API. -/// Create an annotation in |page| of the subtype |subtype|. If the specified -/// subtype is illegal or unsupported, then a new annotation will not be created. -/// Must call FPDFPage_CloseAnnot() when the annotation returned by this -/// function is no longer needed. -/// -/// page - handle to a page. -/// subtype - the subtype of the new annotation. -/// -/// Returns a handle to the new annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFPage_CreateAnnot(FPDF_PAGE page, -int subtype, +/// Function: FPDFText_GetRect +/// Get a rectangular area from the result generated by +/// FPDFText_CountRects. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// rect_index - Zero-based index for the rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |text_page| is invalid then return FALSE, and the out +/// parameters remain unmodified. If |text_page| is valid but +/// |rect_index| is out of bounds, then return FALSE and set the out +/// parameters to 0. +int FPDFText_GetRect(FPDF_TEXTPAGE text_page, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, ) { - return _FPDFPage_CreateAnnot(page, -subtype, -); + return _FPDFText_GetRect(text_page, +rect_index, +left, +top, +right, +bottom, +); } -late final _FPDFPage_CreateAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CreateAnnot'); -late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction(); +late final _FPDFText_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetRect'); +late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Experimental API. -/// Get the number of annotations in |page|. -/// -/// page - handle to a page. -/// -/// Returns the number of annotations in |page|. -int FPDFPage_GetAnnotCount(FPDF_PAGE page, +/// Function: FPDFText_GetBoundedText +/// Extract unicode text within a rectangular boundary on the page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// left - Left boundary. +/// top - Top boundary. +/// right - Right boundary. +/// bottom - Bottom boundary. +/// buffer - Caller-allocated buffer to receive UTF-16 values. +/// buflen - Number of UTF-16 values (not bytes) that `buffer` +/// is capable of holding. +/// Return Value: +/// If buffer is NULL or buflen is zero, return number of UTF-16 +/// values (not bytes) of text present within the rectangle, excluding +/// a terminating NUL. Generally you should pass a buffer at least one +/// larger than this if you want a terminating NUL, which will be +/// provided if space is available. Otherwise, return number of UTF-16 +/// values copied into the buffer, including the terminating NUL when +/// space for it is available. +/// Comment: +/// If the buffer is too small, as much text as will fit is copied into +/// it. May return a split surrogate in that case. +int FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, +double left, +double top, +double right, +double bottom, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPage_GetAnnotCount(page, + return _FPDFText_GetBoundedText(text_page, +left, +top, +right, +bottom, +buffer, +buflen, ); } -late final _FPDFPage_GetAnnotCountPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnotCount'); -late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); +late final _FPDFText_GetBoundedTextPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFText_GetBoundedText'); +late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction , int )>(); -/// Experimental API. -/// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the -/// annotation returned by this function is no longer needed. -/// -/// page - handle to a page. -/// index - the index of the annotation. -/// -/// Returns a handle to the annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFPage_GetAnnot(FPDF_PAGE page, -int index, +/// Function: FPDFText_FindStart +/// Start a search. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// findwhat - A unicode match pattern. +/// flags - Option flags. +/// start_index - Start from this character. -1 for end of the page. +/// Return Value: +/// A handle for the search context. FPDFText_FindClose must be called +/// to release this handle. +FPDF_SCHHANDLE FPDFText_FindStart(FPDF_TEXTPAGE text_page, +FPDF_WIDESTRING findwhat, +int flags, +int start_index, ) { - return _FPDFPage_GetAnnot(page, -index, + return _FPDFText_FindStart(text_page, +findwhat, +flags, +start_index, ); } -late final _FPDFPage_GetAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnot'); -late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction(); +late final _FPDFText_FindStartPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindStart'); +late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction(); -/// Experimental API. -/// Get the index of |annot| in |page|. This is the opposite of -/// FPDFPage_GetAnnot(). -/// -/// page - handle to the page that the annotation is on. -/// annot - handle to an annotation. -/// -/// Returns the index of |annot|, or -1 on failure. -int FPDFPage_GetAnnotIndex(FPDF_PAGE page, -FPDF_ANNOTATION annot, +/// Function: FPDFText_FindNext +/// Search in the direction from page start to end. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindNext(FPDF_SCHHANDLE handle, ) { - return _FPDFPage_GetAnnotIndex(page, -annot, + return _FPDFText_FindNext(handle, ); } -late final _FPDFPage_GetAnnotIndexPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnotIndex'); -late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction(); +late final _FPDFText_FindNextPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindNext'); +late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); -/// Experimental API. -/// Close an annotation. Must be called when the annotation returned by -/// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This -/// function does not remove the annotation from the document. -/// -/// annot - handle to an annotation. -void FPDFPage_CloseAnnot(FPDF_ANNOTATION annot, +/// Function: FPDFText_FindPrev +/// Search in the direction from page end to start. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindPrev(FPDF_SCHHANDLE handle, ) { - return _FPDFPage_CloseAnnot(annot, + return _FPDFText_FindPrev(handle, ); } -late final _FPDFPage_CloseAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CloseAnnot'); -late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); +late final _FPDFText_FindPrevPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindPrev'); +late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); -/// Experimental API. -/// Remove the annotation in |page| at |index|. -/// -/// page - handle to a page. -/// index - the index of the annotation. -/// -/// Returns true if successful. -int FPDFPage_RemoveAnnot(FPDF_PAGE page, -int index, +/// Function: FPDFText_GetSchResultIndex +/// Get the starting character index of the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Index for the starting character. +int FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle, ) { - return _FPDFPage_RemoveAnnot(page, -index, + return _FPDFText_GetSchResultIndex(handle, ); } -late final _FPDFPage_RemoveAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_RemoveAnnot'); -late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); +late final _FPDFText_GetSchResultIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchResultIndex'); +late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); -/// Experimental API. -/// Get the subtype of an annotation. -/// -/// annot - handle to an annotation. -/// -/// Returns the annotation subtype. -int FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot, +/// Function: FPDFText_GetSchCount +/// Get the number of matched characters in the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Number of matched characters. +int FPDFText_GetSchCount(FPDF_SCHHANDLE handle, ) { - return _FPDFAnnot_GetSubtype(annot, + return _FPDFText_GetSchCount(handle, ); } -late final _FPDFAnnot_GetSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetSubtype'); -late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); +late final _FPDFText_GetSchCountPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchCount'); +late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); -/// Experimental API. -/// Check if an annotation subtype is currently supported for object extraction, -/// update, and removal. -/// Currently supported subtypes: ink and stamp. -/// -/// subtype - the subtype to be checked. -/// -/// Returns true if this subtype supported. -int FPDFAnnot_IsObjectSupportedSubtype(int subtype, +/// Function: FPDFText_FindClose +/// Release a search context. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// None. +void FPDFText_FindClose(FPDF_SCHHANDLE handle, ) { - return _FPDFAnnot_IsObjectSupportedSubtype(subtype, + return _FPDFText_FindClose(handle, ); } -late final _FPDFAnnot_IsObjectSupportedSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsObjectSupportedSubtype'); -late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); +late final _FPDFText_FindClosePtr = _lookup< + ffi.NativeFunction>('FPDFText_FindClose'); +late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); -/// Experimental API. -/// Update |obj| in |annot|. |obj| must be in |annot| already and must have -/// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp -/// annotations are supported by this API. Also note that only path, image, and -/// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and -/// FPDFImageObj_*(). -/// -/// annot - handle to an annotation. -/// obj - handle to the object that |annot| needs to update. +/// Function: FPDFLink_LoadWebLinks +/// Prepare information about weblinks in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return Value: +/// A handle to the page's links information structure, or +/// NULL if something goes wrong. +/// Comments: +/// Weblinks are those links implicitly embedded in PDF pages. PDF also +/// has a type of annotation called "link" (FPDFTEXT doesn't deal with +/// that kind of link). FPDFTEXT weblink feature is useful for +/// automatically detecting links in the page contents. For example, +/// things like "https://www.example.com" will be detected, so +/// applications can allow user to click on those characters to activate +/// the link, even the PDF doesn't come with link annotations. /// -/// Return true if successful. -int FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, -FPDF_PAGEOBJECT obj, +/// FPDFLink_CloseWebLinks must be called to release resources. +FPDF_PAGELINK FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page, ) { - return _FPDFAnnot_UpdateObject(annot, -obj, + return _FPDFLink_LoadWebLinks(text_page, ); } -late final _FPDFAnnot_UpdateObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_UpdateObject'); -late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction(); +late final _FPDFLink_LoadWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_LoadWebLinks'); +late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction(); -/// Experimental API. -/// Add a new InkStroke, represented by an array of points, to the InkList of -/// |annot|. The API creates an InkList if one doesn't already exist in |annot|. -/// This API works only for ink annotations. Please refer to ISO 32000-1:2008 -/// spec, section 12.5.6.13. -/// -/// annot - handle to an annotation. -/// points - pointer to a FS_POINTF array representing input points. -/// point_count - number of elements in |points| array. This should not exceed -/// the maximum value that can be represented by an int32_t). -/// -/// Returns the 0-based index at which the new InkStroke is added in the InkList -/// of the |annot|. Returns -1 on failure. -int FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot, -ffi.Pointer points, -int point_count, +/// Function: FPDFLink_CountWebLinks +/// Count number of detected web links. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// Number of detected web links. +int FPDFLink_CountWebLinks(FPDF_PAGELINK link_page, ) { - return _FPDFAnnot_AddInkStroke(annot, -points, -point_count, + return _FPDFLink_CountWebLinks(link_page, ); } -late final _FPDFAnnot_AddInkStrokePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_AddInkStroke'); -late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction , int )>(); +late final _FPDFLink_CountWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountWebLinks'); +late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); -/// Experimental API. -/// Removes an InkList in |annot|. -/// This API works only for ink annotations. -/// -/// annot - handle to an annotation. -/// -/// Return true on successful removal of /InkList entry from context of the -/// non-null ink |annot|. Returns false on failure. -int FPDFAnnot_RemoveInkList(FPDF_ANNOTATION annot, +/// Function: FPDFLink_GetURL +/// Fetch the URL information for a detected web link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// buffer - A unicode buffer for the result. +/// buflen - Number of 16-bit code units (not bytes) for the +/// buffer, including an additional terminator. +/// Return Value: +/// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit +/// code units (not bytes) needed to buffer the result (an additional +/// terminator is included in this count). +/// Otherwise, copy the result into |buffer|, truncating at |buflen| if +/// the result is too large to fit, and return the number of 16-bit code +/// units actually copied into the buffer (the additional terminator is +/// also included in this count). +/// If |link_index| does not correspond to a valid link, then the result +/// is an empty string. +int FPDFLink_GetURL(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFAnnot_RemoveInkList(annot, + return _FPDFLink_GetURL(link_page, +link_index, +buffer, +buflen, ); } -late final _FPDFAnnot_RemoveInkListPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_RemoveInkList'); -late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); +late final _FPDFLink_GetURLPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFLink_GetURL'); +late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction , int )>(); -/// Experimental API. -/// Add |obj| to |annot|. |obj| must have been created by -/// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and -/// will be owned by |annot|. Note that an |obj| cannot belong to more than one -/// |annot|. Currently, only ink and stamp annotations are supported by this API. -/// Also note that only path, image, and text objects have APIs for creation. -/// -/// annot - handle to an annotation. -/// obj - handle to the object that is to be added to |annot|. -/// -/// Return true if successful. -int FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, -FPDF_PAGEOBJECT obj, +/// Function: FPDFLink_CountRects +/// Count number of rectangular areas for the link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// Return Value: +/// Number of rectangular areas for the link. If |link_index| does +/// not correspond to a valid link, then 0 is returned. +int FPDFLink_CountRects(FPDF_PAGELINK link_page, +int link_index, ) { - return _FPDFAnnot_AppendObject(annot, -obj, + return _FPDFLink_CountRects(link_page, +link_index, ); } -late final _FPDFAnnot_AppendObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_AppendObject'); -late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction(); +late final _FPDFLink_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountRects'); +late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); -/// Experimental API. -/// Get the total number of objects in |annot|, including path objects, text -/// objects, external objects, image objects, and shading objects. -/// -/// annot - handle to an annotation. -/// -/// Returns the number of objects in |annot|. -int FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot, +/// Function: FPDFLink_GetRect +/// Fetch the boundaries of a rectangle for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// rect_index - Zero-based index for a rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |link_page| is invalid or if |link_index| does not +/// correspond to a valid link, then return FALSE, and the out +/// parameters remain unmodified. +int FPDFLink_GetRect(FPDF_PAGELINK link_page, +int link_index, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, ) { - return _FPDFAnnot_GetObjectCount(annot, + return _FPDFLink_GetRect(link_page, +link_index, +rect_index, +left, +top, +right, +bottom, ); } -late final _FPDFAnnot_GetObjectCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetObjectCount'); -late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); +late final _FPDFLink_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFLink_GetRect'); +late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); /// Experimental API. -/// Get the object in |annot| at |index|. -/// -/// annot - handle to an annotation. -/// index - the index of the object. -/// -/// Return a handle to the object, or NULL on failure. -FPDF_PAGEOBJECT FPDFAnnot_GetObject(FPDF_ANNOTATION annot, -int index, +/// Function: FPDFLink_GetTextRange +/// Fetch the start char index and char count for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// start_char_index - pointer to int receiving the start char index +/// char_count - pointer to int receiving the char count +/// Return Value: +/// On success, return TRUE and fill in |start_char_index| and +/// |char_count|. if |link_page| is invalid or if |link_index| does +/// not correspond to a valid link, then return FALSE and the out +/// parameters remain unmodified. +int FPDFLink_GetTextRange(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer start_char_index, +ffi.Pointer char_count, ) { - return _FPDFAnnot_GetObject(annot, -index, + return _FPDFLink_GetTextRange(link_page, +link_index, +start_char_index, +char_count, ); } -late final _FPDFAnnot_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetObject'); -late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction(); +late final _FPDFLink_GetTextRangePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_GetTextRange'); +late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction , ffi.Pointer )>(); -/// Experimental API. -/// Remove the object in |annot| at |index|. +/// Function: FPDFLink_CloseWebLinks +/// Release resources used by weblink feature. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// None. +void FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page, +) { + return _FPDFLink_CloseWebLinks(link_page, +); +} + +late final _FPDFLink_CloseWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CloseWebLinks'); +late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); + +/// Get the character index in |text_page| internal character list. /// -/// annot - handle to an annotation. -/// index - the index of the object to be removed. +/// text_page - a text page information structure. +/// nTextIndex - index of the text returned from FPDFText_GetText(). /// -/// Return true if successful. -int FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, -int index, +/// Returns the index of the character in internal character list. -1 for error. +int FPDFText_GetCharIndexFromTextIndex(FPDF_TEXTPAGE text_page, +int nTextIndex, ) { - return _FPDFAnnot_RemoveObject(annot, -index, + return _FPDFText_GetCharIndexFromTextIndex(text_page, +nTextIndex, ); } -late final _FPDFAnnot_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_RemoveObject'); -late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction(); +late final _FPDFText_GetCharIndexFromTextIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharIndexFromTextIndex'); +late final _FPDFText_GetCharIndexFromTextIndex = _FPDFText_GetCharIndexFromTextIndexPtr.asFunction(); -/// Experimental API. -/// Set the color of an annotation. Fails when called on annotations with -/// appearance streams already defined; instead use -/// FPDFPageObj_Set{Stroke|Fill}Color(). +/// Get the text index in |text_page| internal character list. /// -/// annot - handle to an annotation. -/// type - type of the color to be set. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. -/// A - buffer to hold the opacity. Ranges from 0 to 255. +/// text_page - a text page information structure. +/// nCharIndex - index of the character in internal character list. /// -/// Returns true if successful. -DartFPDF_BOOL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, -FPDFANNOT_COLORTYPE type, -int R, -int G, -int B, -int A, +/// Returns the index of the text returned from FPDFText_GetText(). -1 for error. +int FPDFText_GetTextIndexFromCharIndex(FPDF_TEXTPAGE text_page, +int nCharIndex, ) { - return _FPDFAnnot_SetColor(annot, -type.value, -R, -G, -B, -A, + return _FPDFText_GetTextIndexFromCharIndex(text_page, +nCharIndex, ); } -late final _FPDFAnnot_SetColorPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetColor'); -late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction(); +late final _FPDFText_GetTextIndexFromCharIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetTextIndexFromCharIndex'); +late final _FPDFText_GetTextIndexFromCharIndex = _FPDFText_GetTextIndexFromCharIndexPtr.asFunction(); /// Experimental API. -/// Get the color of an annotation. If no color is specified, default to yellow -/// for highlight annotation, black for all else. Fails when called on -/// annotations with appearance streams already defined; instead use -/// FPDFPageObj_Get{Stroke|Fill}Color(). -/// -/// annot - handle to an annotation. -/// type - type of the color requested. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. -/// A - buffer to hold the opacity. Ranges from 0 to 255. -/// -/// Returns true if successful. -DartFPDF_BOOL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, -FPDFANNOT_COLORTYPE type, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, +/// Function: FPDF_RenderPageBitmapWithColorScheme_Start +/// Start to render page contents to a device independent bitmap +/// progressively with a specified color scheme for the content. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handle can be created by +/// FPDFBitmap_Create function. +/// page - Handle to the page as returned by FPDF_LoadPage +/// function. +/// start_x - Left pixel position of the display area in the +/// bitmap coordinate. +/// start_y - Top pixel position of the display area in the +/// bitmap coordinate. +/// size_x - Horizontal size (in pixels) for displaying the +/// page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 +/// degrees clockwise), 2 (rotated 180 degrees), +/// 3 (rotated 90 degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined in fpdfview.h. With FPDF_ANNOT flag, it +/// renders all annotations that does not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// color_scheme - Color scheme to be used in rendering the |page|. +/// If null, this function will work similar to +/// FPDF_RenderPageBitmap_Start(). +/// pause - The IFSDK_PAUSE interface. A callback mechanism +/// allowing the page rendering process. +/// Return value: +/// Rendering Status. See flags for progressive process status for the +/// details. +int FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +ffi.Pointer color_scheme, +ffi.Pointer pause, ) { - return _FPDFAnnot_GetColor(annot, -type.value, -R, -G, -B, -A, + return _FPDF_RenderPageBitmapWithColorScheme_Start(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +color_scheme, +pause, ); } -late final _FPDFAnnot_GetColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetColor'); -late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDF_RenderPageBitmapWithColorScheme_StartPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_RenderPageBitmapWithColorScheme_Start'); +late final _FPDF_RenderPageBitmapWithColorScheme_Start = _FPDF_RenderPageBitmapWithColorScheme_StartPtr.asFunction , ffi.Pointer )>(); -/// Experimental API. -/// Check if the annotation is of a type that has attachment points -/// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that -/// encompasses the texts affected by the annotation. They provide the -/// coordinates in the page where the annotation is attached. Only text markup -/// annotations (i.e. highlight, strikeout, squiggly, and underline) and link -/// annotations have quadpoints. -/// -/// annot - handle to an annotation. -/// -/// Returns true if the annotation is of a type that has quadpoints, false -/// otherwise. -int FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot, +/// Function: FPDF_RenderPageBitmap_Start +/// Start to render page contents to a device independent bitmap +/// progressively. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handle can be created by +/// FPDFBitmap_Create(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// start_x - Left pixel position of the display area in the +/// bitmap coordinates. +/// start_y - Top pixel position of the display area in the bitmap +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees +/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 +/// degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined in fpdfview.h. With FPDF_ANNOT flag, it +/// renders all annotations that does not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// pause - The IFSDK_PAUSE interface.A callback mechanism +/// allowing the page rendering process +/// Return value: +/// Rendering Status. See flags for progressive process status for the +/// details. +int FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +ffi.Pointer pause, ) { - return _FPDFAnnot_HasAttachmentPoints(annot, + return _FPDF_RenderPageBitmap_Start(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +pause, ); } -late final _FPDFAnnot_HasAttachmentPointsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_HasAttachmentPoints'); -late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr.asFunction(); +late final _FPDF_RenderPageBitmap_StartPtr = _lookup< + ffi.NativeFunction )>>('FPDF_RenderPageBitmap_Start'); +late final _FPDF_RenderPageBitmap_Start = _FPDF_RenderPageBitmap_StartPtr.asFunction )>(); -/// Experimental API. -/// Replace the attachment points (i.e. quadpoints) set of an annotation at -/// |quad_index|. This index needs to be within the result of -/// FPDFAnnot_CountAttachmentPoints(). -/// If the annotation's appearance stream is defined and this annotation is of a -/// type with quadpoints, then update the bounding box too if the new quadpoints -/// define a bigger one. -/// -/// annot - handle to an annotation. -/// quad_index - index of the set of quadpoints. -/// quad_points - the quadpoints to be set. -/// -/// Returns true if successful. -int FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, -int quad_index, -ffi.Pointer quad_points, +/// Function: FPDF_RenderPage_Continue +/// Continue rendering a PDF page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// pause - The IFSDK_PAUSE interface (a callback mechanism +/// allowing the page rendering process to be paused +/// before it's finished). This can be NULL if you +/// don't want to pause. +/// Return value: +/// The rendering status. See flags for progressive process status for +/// the details. +int FPDF_RenderPage_Continue(FPDF_PAGE page, +ffi.Pointer pause, ) { - return _FPDFAnnot_SetAttachmentPoints(annot, -quad_index, -quad_points, + return _FPDF_RenderPage_Continue(page, +pause, ); } -late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetAttachmentPoints'); -late final _FPDFAnnot_SetAttachmentPoints = _FPDFAnnot_SetAttachmentPointsPtr.asFunction )>(); +late final _FPDF_RenderPage_ContinuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_RenderPage_Continue'); +late final _FPDF_RenderPage_Continue = _FPDF_RenderPage_ContinuePtr.asFunction )>(); -/// Experimental API. -/// Append to the list of attachment points (i.e. quadpoints) of an annotation. -/// If the annotation's appearance stream is defined and this annotation is of a -/// type with quadpoints, then update the bounding box too if the new quadpoints -/// define a bigger one. -/// -/// annot - handle to an annotation. -/// quad_points - the quadpoints to be set. -/// -/// Returns true if successful. -int FPDFAnnot_AppendAttachmentPoints(FPDF_ANNOTATION annot, -ffi.Pointer quad_points, +/// Function: FPDF_RenderPage_Close +/// Release the resource allocate during page rendering. Need to be +/// called after finishing rendering or +/// cancel the rendering. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return value: +/// None. +void FPDF_RenderPage_Close(FPDF_PAGE page, ) { - return _FPDFAnnot_AppendAttachmentPoints(annot, -quad_points, + return _FPDF_RenderPage_Close(page, ); } -late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_AppendAttachmentPoints'); -late final _FPDFAnnot_AppendAttachmentPoints = _FPDFAnnot_AppendAttachmentPointsPtr.asFunction )>(); +late final _FPDF_RenderPage_ClosePtr = _lookup< + ffi.NativeFunction>('FPDF_RenderPage_Close'); +late final _FPDF_RenderPage_Close = _FPDF_RenderPage_ClosePtr.asFunction(); -/// Experimental API. -/// Get the number of sets of quadpoints of an annotation. -/// -/// annot - handle to an annotation. +/// Create a new PDF document. /// -/// Returns the number of sets of quadpoints, or 0 on failure. -int FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_CountAttachmentPoints(annot, -); +/// Returns a handle to a new document, or NULL on failure. +FPDF_DOCUMENT FPDF_CreateNewDocument() { + return _FPDF_CreateNewDocument(); } -late final _FPDFAnnot_CountAttachmentPointsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_CountAttachmentPoints'); -late final _FPDFAnnot_CountAttachmentPoints = _FPDFAnnot_CountAttachmentPointsPtr.asFunction(); +late final _FPDF_CreateNewDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_CreateNewDocument'); +late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); -/// Experimental API. -/// Get the attachment points (i.e. quadpoints) of an annotation. +/// Create a new PDF page. /// -/// annot - handle to an annotation. -/// quad_index - index of the set of quadpoints. -/// quad_points - receives the quadpoints; must not be NULL. +/// document - handle to document. +/// page_index - suggested 0-based index of the page to create. If it is larger +/// than document's current last index(L), the created page index +/// is the next available index -- L+1. +/// width - the page width in points. +/// height - the page height in points. /// -/// Returns true if successful. -int FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, -int quad_index, -ffi.Pointer quad_points, +/// Returns the handle to the new page or NULL on failure. +/// +/// The page should be closed with FPDF_ClosePage() when finished as +/// with any other page in the document. +FPDF_PAGE FPDFPage_New(FPDF_DOCUMENT document, +int page_index, +double width, +double height, ) { - return _FPDFAnnot_GetAttachmentPoints(annot, -quad_index, -quad_points, + return _FPDFPage_New(document, +page_index, +width, +height, ); } -late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetAttachmentPoints'); -late final _FPDFAnnot_GetAttachmentPoints = _FPDFAnnot_GetAttachmentPointsPtr.asFunction )>(); +late final _FPDFPage_NewPtr = _lookup< + ffi.NativeFunction>('FPDFPage_New'); +late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction(); -/// Experimental API. -/// Set the annotation rectangle defining the location of the annotation. If the -/// annotation's appearance stream is defined and this annotation is of a type -/// without quadpoints, then update the bounding box too if the new rectangle -/// defines a bigger one. -/// -/// annot - handle to an annotation. -/// rect - the annotation rectangle to be set. +/// Delete the page at |page_index|. /// -/// Returns true if successful. -int FPDFAnnot_SetRect(FPDF_ANNOTATION annot, -ffi.Pointer rect, +/// document - handle to document. +/// page_index - the index of the page to delete. +void FPDFPage_Delete(FPDF_DOCUMENT document, +int page_index, ) { - return _FPDFAnnot_SetRect(annot, -rect, + return _FPDFPage_Delete(document, +page_index, ); } -late final _FPDFAnnot_SetRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetRect'); -late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction )>(); +late final _FPDFPage_DeletePtr = _lookup< + ffi.NativeFunction>('FPDFPage_Delete'); +late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); /// Experimental API. -/// Get the annotation rectangle defining the location of the annotation. +/// Move the given pages to a new index position. /// -/// annot - handle to an annotation. -/// rect - receives the rectangle; must not be NULL. +/// page_indices - the ordered list of pages to move. No duplicates allowed. +/// page_indices_len - the number of elements in |page_indices| +/// dest_page_index - the new index position to which the pages in +/// |page_indices| are moved. /// -/// Returns true if successful. -int FPDFAnnot_GetRect(FPDF_ANNOTATION annot, -ffi.Pointer rect, +/// Returns TRUE on success. If it returns FALSE, the document may be left in an +/// indeterminate state. +/// +/// Example: The PDF document starts out with pages [A, B, C, D], with indices +/// [0, 1, 2, 3]. +/// +/// > Move(doc, [3, 2], 2, 1); // returns true +/// > // The document has pages [A, D, C, B]. +/// > +/// > Move(doc, [0, 4, 3], 3, 1); // returns false +/// > // Returned false because index 4 is out of range. +/// > +/// > Move(doc, [0, 3, 1], 3, 2); // returns false +/// > // Returned false because index 2 is out of range for 3 page indices. +/// > +/// > Move(doc, [2, 2], 2, 0); // returns false +/// > // Returned false because [2, 2] contains duplicates. +int FPDF_MovePages(FPDF_DOCUMENT document, +ffi.Pointer page_indices, +int page_indices_len, +int dest_page_index, ) { - return _FPDFAnnot_GetRect(annot, -rect, + return _FPDF_MovePages(document, +page_indices, +page_indices_len, +dest_page_index, ); } -late final _FPDFAnnot_GetRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetRect'); -late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction )>(); +late final _FPDF_MovePagesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_MovePages'); +late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction , int , int )>(); -/// Experimental API. -/// Get the vertices of a polygon or polyline annotation. |buffer| is an array of -/// points of the annotation. If |length| is less than the returned length, or -/// |annot| or |buffer| is NULL, |buffer| will not be modified. +/// Get the rotation of |page|. /// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// buffer - buffer for holding the points. -/// length - length of the buffer in points. +/// page - handle to a page /// -/// Returns the number of points if the annotation is of type polygon or -/// polyline, 0 otherwise. -int FPDFAnnot_GetVertices(FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int length, +/// Returns one of the following indicating the page rotation: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +int FPDFPage_GetRotation(FPDF_PAGE page, ) { - return _FPDFAnnot_GetVertices(annot, -buffer, -length, + return _FPDFPage_GetRotation(page, ); } -late final _FPDFAnnot_GetVerticesPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetVertices'); -late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction , int )>(); +late final _FPDFPage_GetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetRotation'); +late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); -/// Experimental API. -/// Get the number of paths in the ink list of an ink annotation. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// Set rotation for |page|. /// -/// Returns the number of paths in the ink list if the annotation is of type ink, -/// 0 otherwise. -int FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot, +/// page - handle to a page. +/// rotate - the rotation value, one of: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +void FPDFPage_SetRotation(FPDF_PAGE page, +int rotate, ) { - return _FPDFAnnot_GetInkListCount(annot, + return _FPDFPage_SetRotation(page, +rotate, ); } -late final _FPDFAnnot_GetInkListCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetInkListCount'); -late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); +late final _FPDFPage_SetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetRotation'); +late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); -/// Experimental API. -/// Get a path in the ink list of an ink annotation. |buffer| is an array of -/// points of the path. If |length| is less than the returned length, or |annot| -/// or |buffer| is NULL, |buffer| will not be modified. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// path_index - index of the path -/// buffer - buffer for holding the points. -/// length - length of the buffer in points. +/// Insert |page_object| into |page|. /// -/// Returns the number of points of the path if the annotation is of type ink, 0 -/// otherwise. -int FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot, -int path_index, -ffi.Pointer buffer, -int length, +/// page - handle to a page +/// page_object - handle to a page object. The |page_object| will be +/// automatically freed. +void FPDFPage_InsertObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_GetInkListPath(annot, -path_index, -buffer, -length, + return _FPDFPage_InsertObject(page, +page_object, ); } -late final _FPDFAnnot_GetInkListPathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetInkListPath'); -late final _FPDFAnnot_GetInkListPath = _FPDFAnnot_GetInkListPathPtr.asFunction , int )>(); +late final _FPDFPage_InsertObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObject'); +late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); -/// Experimental API. -/// Get the starting and ending coordinates of a line annotation. +/// Insert |page_object| into |page| at the specified |index|. /// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// start - starting point -/// end - ending point +/// page - handle to a page +/// page_object - handle to a page object as previously obtained by +/// FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). Ownership of the object +/// is transferred back to PDFium. +/// index - the index position to insert the object at. If index equals +/// the current object count, the object will be appended to the +/// end. If index is greater than the object count, the function +/// will fail and return false. /// -/// Returns true if the annotation is of type line, |start| and |end| are not -/// NULL, false otherwise. -int FPDFAnnot_GetLine(FPDF_ANNOTATION annot, -ffi.Pointer start, -ffi.Pointer end, +/// Returns true if successful. +int FPDFPage_InsertObjectAtIndex(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +int index, ) { - return _FPDFAnnot_GetLine(annot, -start, -end, + return _FPDFPage_InsertObjectAtIndex(page, +page_object, +index, ); } -late final _FPDFAnnot_GetLinePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFAnnot_GetLine'); -late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction , ffi.Pointer )>(); +late final _FPDFPage_InsertObjectAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObjectAtIndex'); +late final _FPDFPage_InsertObjectAtIndex = _FPDFPage_InsertObjectAtIndexPtr.asFunction(); /// Experimental API. -/// Set the characteristics of the annotation's border (rounded rectangle). +/// Remove |page_object| from |page|. /// -/// annot - handle to an annotation -/// horizontal_radius - horizontal corner radius, in default user space units -/// vertical_radius - vertical corner radius, in default user space units -/// border_width - border width, in default user space units +/// page - handle to a page +/// page_object - handle to a page object to be removed. /// -/// Returns true if setting the border for |annot| succeeds, false otherwise. +/// Returns TRUE on success. /// -/// If |annot| contains an appearance stream that overrides the border values, -/// then the appearance stream will be removed on success. -int FPDFAnnot_SetBorder(FPDF_ANNOTATION annot, -double horizontal_radius, -double vertical_radius, -double border_width, +/// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free +/// it. +/// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all +/// FPDF_TEXTPAGE handles for |page| are no longer valid. +int FPDFPage_RemoveObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_SetBorder(annot, -horizontal_radius, -vertical_radius, -border_width, + return _FPDFPage_RemoveObject(page, +page_object, ); } -late final _FPDFAnnot_SetBorderPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetBorder'); -late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction(); +late final _FPDFPage_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveObject'); +late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction(); -/// Experimental API. -/// Get the characteristics of the annotation's border (rounded rectangle). +/// Get number of page objects inside |page|. /// -/// annot - handle to an annotation -/// horizontal_radius - horizontal corner radius, in default user space units -/// vertical_radius - vertical corner radius, in default user space units -/// border_width - border width, in default user space units +/// page - handle to a page. /// -/// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are -/// not NULL, false otherwise. -int FPDFAnnot_GetBorder(FPDF_ANNOTATION annot, -ffi.Pointer horizontal_radius, -ffi.Pointer vertical_radius, -ffi.Pointer border_width, +/// Returns the number of objects in |page|. +int FPDFPage_CountObjects(FPDF_PAGE page, ) { - return _FPDFAnnot_GetBorder(annot, -horizontal_radius, -vertical_radius, -border_width, + return _FPDFPage_CountObjects(page, ); } -late final _FPDFAnnot_GetBorderPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetBorder'); -late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPage_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CountObjects'); +late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); -/// Experimental API. -/// Get the JavaScript of an event of the annotation's additional actions. -/// |buffer| is only modified if |buflen| is large enough to hold the whole -/// JavaScript string. If |buflen| is smaller, the total size of the JavaScript -/// is still returned, but nothing is copied. If there is no JavaScript for -/// |event| in |annot|, an empty string is written to |buf| and 2 is returned, -/// denoting the size of the null terminator in the buffer. On other errors, -/// nothing is written to |buffer| and 0 is returned. +/// Get object in |page| at |index|. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// event - event type, one of the FPDF_ANNOT_AACTION_* values. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page - handle to a page. +/// index - the index of a page object. /// -/// Returns the length of the string value in bytes, including the 2-byte -/// null terminator. -int FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -int event, -ffi.Pointer buffer, -int buflen, +/// Returns the handle to the page object, or NULL on failed. +FPDF_PAGEOBJECT FPDFPage_GetObject(FPDF_PAGE page, +int index, ) { - return _FPDFAnnot_GetFormAdditionalActionJavaScript(hHandle, -annot, -event, -buffer, -buflen, + return _FPDFPage_GetObject(page, +index, ); } -late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormAdditionalActionJavaScript'); -late final _FPDFAnnot_GetFormAdditionalActionJavaScript = _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction , int )>(); +late final _FPDFPage_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetObject'); +late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction(); -/// Experimental API. -/// Check if |annot|'s dictionary has |key| as a key. +/// Checks if |page| contains transparency. /// -/// annot - handle to an annotation. -/// key - the key to look for, encoded in UTF-8. +/// page - handle to a page. /// -/// Returns true if |key| exists. -int FPDFAnnot_HasKey(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, +/// Returns TRUE if |page| contains transparency. +int FPDFPage_HasTransparency(FPDF_PAGE page, ) { - return _FPDFAnnot_HasKey(annot, -key, + return _FPDFPage_HasTransparency(page, ); } -late final _FPDFAnnot_HasKeyPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_HasKey'); -late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction(); +late final _FPDFPage_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasTransparency'); +late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); -/// Experimental API. -/// Get the type of the value corresponding to |key| in |annot|'s dictionary. +/// Generate the content of |page|. /// -/// annot - handle to an annotation. -/// key - the key to look for, encoded in UTF-8. +/// page - handle to a page. /// -/// Returns the type of the dictionary value. -int FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, +/// Returns TRUE on success. +/// +/// Before you save the page to a file, or reload the page, you must call +/// |FPDFPage_GenerateContent| or any changes to |page| will be lost. +int FPDFPage_GenerateContent(FPDF_PAGE page, ) { - return _FPDFAnnot_GetValueType(annot, -key, + return _FPDFPage_GenerateContent(page, ); } -late final _FPDFAnnot_GetValueTypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetValueType'); -late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction(); +late final _FPDFPage_GenerateContentPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GenerateContent'); +late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); -/// Experimental API. -/// Set the string value corresponding to |key| in |annot|'s dictionary, -/// overwriting the existing value if any. The value type would be -/// FPDF_OBJECT_STRING after this function call succeeds. -/// -/// annot - handle to an annotation. -/// key - the key to the dictionary entry to be set, encoded in UTF-8. -/// value - the string value to be set, encoded in UTF-16LE. +/// Destroy |page_object| by releasing its resources. |page_object| must have +/// been created by FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). This function must be called on +/// newly-created objects if they are not added to a page through +/// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). /// -/// Returns true if successful. -int FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -FPDF_WIDESTRING value, +/// page_object - handle to a page object. +void FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_SetStringValue(annot, -key, -value, + return _FPDFPageObj_Destroy(page_object, ); } -late final _FPDFAnnot_SetStringValuePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetStringValue'); -late final _FPDFAnnot_SetStringValue = _FPDFAnnot_SetStringValuePtr.asFunction(); +late final _FPDFPageObj_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Destroy'); +late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); -/// Experimental API. -/// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| -/// is only modified if |buflen| is longer than the length of contents. Note that -/// if |key| does not exist in the dictionary or if |key|'s corresponding value -/// in the dictionary is not a string (i.e. the value is not of type -/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied -/// to |buffer| and the return value would be 2. On other errors, nothing would -/// be added to |buffer| and the return value would be 0. +/// Checks if |page_object| contains transparency. /// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page_object - handle to a page object. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, +/// Returns TRUE if |page_object| contains transparency. +int FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_GetStringValue(annot, -key, -buffer, -buflen, + return _FPDFPageObj_HasTransparency(page_object, ); } -late final _FPDFAnnot_GetStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetStringValue'); -late final _FPDFAnnot_GetStringValue = _FPDFAnnot_GetStringValuePtr.asFunction , int )>(); +late final _FPDFPageObj_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_HasTransparency'); +late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr.asFunction(); -/// Experimental API. -/// Get the float value corresponding to |key| in |annot|'s dictionary. Writes -/// value to |value| and returns True if |key| exists in the dictionary and -/// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False -/// otherwise. +/// Get type of |page_object|. /// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. -/// value - receives the value, must not be NULL. +/// page_object - handle to a page object. /// -/// Returns True if value found, False otherwise. -int FPDFAnnot_GetNumberValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -ffi.Pointer value, +/// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on +/// error. +int FPDFPageObj_GetType(FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_GetNumberValue(annot, -key, -value, + return _FPDFPageObj_GetType(page_object, ); } -late final _FPDFAnnot_GetNumberValuePtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetNumberValue'); -late final _FPDFAnnot_GetNumberValue = _FPDFAnnot_GetNumberValuePtr.asFunction )>(); +late final _FPDFPageObj_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetType'); +late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); /// Experimental API. -/// Set the AP (appearance string) in |annot|'s dictionary for a given -/// |appearanceMode|. +/// Gets active state for |page_object| within page. /// -/// annot - handle to an annotation. -/// appearanceMode - the appearance mode (normal, rollover or down) for which -/// to get the AP. -/// value - the string value to be set, encoded in UTF-16LE. If -/// nullptr is passed, the AP is cleared for that mode. If the -/// mode is Normal, APs for all modes are cleared. +/// page_object - handle to a page object. +/// active - pointer to variable that will receive if the page object is +/// active. This is a required parameter. Not filled if FALSE +/// is returned. /// -/// Returns true if successful. -int FPDFAnnot_SetAP(FPDF_ANNOTATION annot, -int appearanceMode, -FPDF_WIDESTRING value, +/// For page objects where |active| is filled with FALSE, the |page_object| is +/// treated as if it wasn't in the document even though it is still held +/// internally. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_GetIsActive(FPDF_PAGEOBJECT page_object, +ffi.Pointer active, ) { - return _FPDFAnnot_SetAP(annot, -appearanceMode, -value, + return _FPDFPageObj_GetIsActive(page_object, +active, ); } -late final _FPDFAnnot_SetAPPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetAP'); -late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction(); +late final _FPDFPageObj_GetIsActivePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetIsActive'); +late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction )>(); /// Experimental API. -/// Get the AP (appearance string) from |annot|'s dictionary for a given -/// |appearanceMode|. -/// |buffer| is only modified if |buflen| is large enough to hold the whole AP -/// string. If |buflen| is smaller, the total size of the AP is still returned, -/// but nothing is copied. -/// If there is no appearance stream for |annot| in |appearanceMode|, an empty -/// string is written to |buf| and 2 is returned. -/// On other errors, nothing is written to |buffer| and 0 is returned. +/// Sets if |page_object| is active within page. /// -/// annot - handle to an annotation. -/// appearanceMode - the appearance mode (normal, rollover or down) for which -/// to get the AP. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page_object - handle to a page object. +/// active - a boolean specifying if the object is active. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetAP(FPDF_ANNOTATION annot, -int appearanceMode, -ffi.Pointer buffer, -int buflen, +/// Returns TRUE on success. +/// +/// Page objects all start in the active state by default, and remain in that +/// state unless this function is called. +/// +/// When |active| is false, this makes the |page_object| be treated as if it +/// wasn't in the document even though it is still held internally. +int FPDFPageObj_SetIsActive(FPDF_PAGEOBJECT page_object, +int active, ) { - return _FPDFAnnot_GetAP(annot, -appearanceMode, -buffer, -buflen, + return _FPDFPageObj_SetIsActive(page_object, +active, ); } -late final _FPDFAnnot_GetAPPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetAP'); -late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction , int )>(); +late final _FPDFPageObj_SetIsActivePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetIsActive'); +late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction(); -/// Experimental API. -/// Get the annotation corresponding to |key| in |annot|'s dictionary. Common -/// keys for linking annotations include "IRT" and "Popup". Must call -/// FPDFPage_CloseAnnot() when the annotation returned by this function is no -/// longer needed. +/// Transform |page_object| by the given matrix. /// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// page_object - handle to a page object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. /// -/// Returns a handle to the linked annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page_object|. +void FPDFPageObj_Transform(FPDF_PAGEOBJECT page_object, +double a, +double b, +double c, +double d, +double e, +double f, ) { - return _FPDFAnnot_GetLinkedAnnot(annot, -key, + return _FPDFPageObj_Transform(page_object, +a, +b, +c, +d, +e, +f, ); } -late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetLinkedAnnot'); -late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr.asFunction(); +late final _FPDFPageObj_TransformPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Transform'); +late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction(); /// Experimental API. -/// Get the annotation flags of |annot|. +/// Transform |page_object| by the given matrix. /// -/// annot - handle to an annotation. +/// page_object - handle to a page object. +/// matrix - the transform matrix. /// -/// Returns the annotation flags. -int FPDFAnnot_GetFlags(FPDF_ANNOTATION annot, +/// Returns TRUE on success. +/// +/// This can be used to scale, rotate, shear and translate the |page_object|. +/// It is an improved version of FPDFPageObj_Transform() that does not do +/// unnecessary double to float conversions, and only uses 1 parameter for the +/// matrix. It also returns whether the operation succeeded or not. +int FPDFPageObj_TransformF(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, ) { - return _FPDFAnnot_GetFlags(annot, + return _FPDFPageObj_TransformF(page_object, +matrix, ); } -late final _FPDFAnnot_GetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFlags'); -late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); +late final _FPDFPageObj_TransformFPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_TransformF'); +late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction )>(); /// Experimental API. -/// Set the |annot|'s flags to be of the value |flags|. +/// Get the transform matrix of a page object. /// -/// annot - handle to an annotation. -/// flags - the flag values to be set. +/// page_object - handle to a page object. +/// matrix - pointer to struct to receive the matrix value. /// -/// Returns true if successful. -int FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, -int flags, +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and used to scale, rotate, shear and translate the page object. +/// +/// For page objects outside form objects, the matrix values are relative to the +/// page that contains it. +/// For page objects inside form objects, the matrix values are relative to the +/// form that contains it. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, ) { - return _FPDFAnnot_SetFlags(annot, -flags, + return _FPDFPageObj_GetMatrix(page_object, +matrix, ); } -late final _FPDFAnnot_SetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFlags'); -late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); +late final _FPDFPageObj_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetMatrix'); +late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction )>(); /// Experimental API. -/// Get the annotation flags of |annot|. +/// Set the transform matrix of a page object. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. +/// page_object - handle to a page object. +/// matrix - pointer to struct with the matrix value. /// -/// Returns the annotation flags specific to interactive forms. -int FPDFAnnot_GetFormFieldFlags(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the page object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, ) { - return _FPDFAnnot_GetFormFieldFlags(handle, -annot, + return _FPDFPageObj_SetMatrix(page_object, +matrix, ); } -late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormFieldFlags'); -late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr.asFunction(); +late final _FPDFPageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_SetMatrix'); +late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction )>(); -/// Experimental API. -/// Sets the form field flags for an interactive form annotation. +/// Transform all annotations in |page|. /// -/// handle - the handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// flags - the form field flags to be set. +/// page - handle to a page. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. /// -/// Returns true if successful. -int FPDFAnnot_SetFormFieldFlags(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int flags, +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page| annotations. +void FPDFPage_TransformAnnots(FPDF_PAGE page, +double a, +double b, +double c, +double d, +double e, +double f, ) { - return _FPDFAnnot_SetFormFieldFlags(handle, -annot, -flags, + return _FPDFPage_TransformAnnots(page, +a, +b, +c, +d, +e, +f, ); } -late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFormFieldFlags'); -late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr.asFunction(); +late final _FPDFPage_TransformAnnotsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_TransformAnnots'); +late final _FPDFPage_TransformAnnots = _FPDFPage_TransformAnnotsPtr.asFunction(); -/// Experimental API. -/// Retrieves an interactive form annotation whose rectangle contains a given -/// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned -/// is no longer needed. -/// +/// Create a new image object. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - handle to the page, returned by FPDF_LoadPage function. -/// point - position in PDF "user space". +/// document - handle to a document. /// -/// Returns the interactive form annotation whose rectangle contains the given -/// coordinates on the page. If there is no such annotation, return NULL. -FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer point, +/// Returns a handle to a new image object. +FPDF_PAGEOBJECT FPDFPageObj_NewImageObj(FPDF_DOCUMENT document, ) { - return _FPDFAnnot_GetFormFieldAtPoint(hHandle, -page, -point, + return _FPDFPageObj_NewImageObj(document, ); } -late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetFormFieldAtPoint'); -late final _FPDFAnnot_GetFormFieldAtPoint = _FPDFAnnot_GetFormFieldAtPointPtr.asFunction )>(); +late final _FPDFPageObj_NewImageObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewImageObj'); +late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction(); /// Experimental API. -/// Gets the name of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". +/// Get the marked content ID for the object. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the name string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page_object - handle to a page object. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldName(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, +/// Returns the page object's marked content ID, or -1 on error. +int FPDFPageObj_GetMarkedContentID(FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_GetFormFieldName(hHandle, -annot, -buffer, -buflen, + return _FPDFPageObj_GetMarkedContentID(page_object, ); } -late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldName'); -late final _FPDFAnnot_GetFormFieldName = _FPDFAnnot_GetFormFieldNamePtr.asFunction , int )>(); +late final _FPDFPageObj_GetMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMarkedContentID'); +late final _FPDFPageObj_GetMarkedContentID = _FPDFPageObj_GetMarkedContentIDPtr.asFunction(); /// Experimental API. -/// Gets the alternate name of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". +/// Get number of content marks in |page_object|. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the alternate name string, encoded in -/// UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page_object - handle to a page object. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldAlternateName(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, +/// Returns the number of content marks in |page_object|, or -1 in case of +/// failure. +int FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object, ) { - return _FPDFAnnot_GetFormFieldAlternateName(hHandle, -annot, -buffer, -buflen, + return _FPDFPageObj_CountMarks(page_object, ); } -late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldAlternateName'); -late final _FPDFAnnot_GetFormFieldAlternateName = _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction , int )>(); +late final _FPDFPageObj_CountMarksPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CountMarks'); +late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); /// Experimental API. -/// Gets the form field type of |annot|, which is an interactive form annotation. +/// Get content mark in |page_object| at |index|. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. +/// page_object - handle to a page object. +/// index - the index of a page object. /// -/// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on -/// success. Returns -1 on error. -/// See field types in fpdf_formfill.h. -int FPDFAnnot_GetFormFieldType(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, +int index, ) { - return _FPDFAnnot_GetFormFieldType(hHandle, -annot, + return _FPDFPageObj_GetMark(page_object, +index, ); } -late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormFieldType'); -late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr.asFunction(); +late final _FPDFPageObj_GetMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMark'); +late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction(); /// Experimental API. -/// Gets the value of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". +/// Add a new content mark to a |page_object|. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// page_object - handle to a page object. +/// name - the name (tag) of the mark. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING name, ) { - return _FPDFAnnot_GetFormFieldValue(hHandle, -annot, -buffer, -buflen, + return _FPDFPageObj_AddMark(page_object, +name, ); } -late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldValue'); -late final _FPDFAnnot_GetFormFieldValue = _FPDFAnnot_GetFormFieldValuePtr.asFunction , int )>(); +late final _FPDFPageObj_AddMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_AddMark'); +late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction(); /// Experimental API. -/// Get the number of options in the |annot|'s "Opt" dictionary. Intended for -/// use with listbox and combobox widget annotations. +/// Removes a content |mark| from a |page_object|. +/// The mark handle will be invalid after the removal. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. +/// page_object - handle to a page object. +/// mark - handle to a content mark in that object to remove. /// -/// Returns the number of options in "Opt" dictionary on success. Return value -/// will be -1 if annotation does not have an "Opt" dictionary or other error. -int FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_RemoveMark(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, ) { - return _FPDFAnnot_GetOptionCount(hHandle, -annot, + return _FPDFPageObj_RemoveMark(page_object, +mark, ); } -late final _FPDFAnnot_GetOptionCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetOptionCount'); -late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr.asFunction(); +late final _FPDFPageObj_RemoveMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_RemoveMark'); +late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction(); /// Experimental API. -/// Get the string value for the label of the option at |index| in |annot|'s -/// "Opt" dictionary. Intended for use with listbox and combobox widget -/// annotations. |buffer| is only modified if |buflen| is longer than the length -/// of contents. If index is out of range or in case of other error, nothing -/// will be added to |buffer| and the return value will be 0. Note that -/// return value of empty string is 2 for "\0\0". +/// Get the name of a content mark. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// index - numeric index of the option in the "Opt" array -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// mark - handle to a content mark. +/// buffer - buffer for holding the returned name in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the name. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. /// -/// Returns the length of the string value in bytes. -/// If |annot| does not have an "Opt" array, |index| is out of range or if any -/// other error occurs, returns 0. -int FPDFAnnot_GetOptionLabel(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -int index, +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, ffi.Pointer buffer, int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFAnnot_GetOptionLabel(hHandle, -annot, -index, + return _FPDFPageObjMark_GetName(mark, buffer, buflen, +out_buflen, ); } -late final _FPDFAnnot_GetOptionLabelPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetOptionLabel'); -late final _FPDFAnnot_GetOptionLabel = _FPDFAnnot_GetOptionLabelPtr.asFunction , int )>(); +late final _FPDFPageObjMark_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetName'); +late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary -/// is selected. Intended for use with listbox and combobox widget annotations. +/// Get the number of key/value pair parameters in |mark|. /// -/// handle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// index - numeric index of the option in the "Opt" array. +/// mark - handle to a content mark. /// -/// Returns true if the option at |index| in |annot|'s "Opt" dictionary is -/// selected, false otherwise. -int FPDFAnnot_IsOptionSelected(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int index, +/// Returns the number of key/value pair parameters |mark|, or -1 in case of +/// failure. +int FPDFPageObjMark_CountParams(FPDF_PAGEOBJECTMARK mark, ) { - return _FPDFAnnot_IsOptionSelected(handle, -annot, -index, + return _FPDFPageObjMark_CountParams(mark, ); } -late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsOptionSelected'); -late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr.asFunction(); +late final _FPDFPageObjMark_CountParamsPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_CountParams'); +late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr.asFunction(); /// Experimental API. -/// Get the float value of the font size for an |annot| with variable text. -/// If 0, the font is to be auto-sized: its size is computed as a function of -/// the height of the annotation rectangle. +/// Get the key of a property in a content mark. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// value - Required. Float which will be set to font size on success. +/// mark - handle to a content mark. +/// index - index of the property. +/// buffer - buffer for holding the returned key in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the key. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. /// -/// Returns true if the font size was set in |value|, false on error or if -/// |value| not provided. -int FPDFAnnot_GetFontSize(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer value, +/// Returns TRUE if the operation was successful, FALSE otherwise. +int FPDFPageObjMark_GetParamKey(FPDF_PAGEOBJECTMARK mark, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFAnnot_GetFontSize(hHandle, -annot, -value, + return _FPDFPageObjMark_GetParamKey(mark, +index, +buffer, +buflen, +out_buflen, ); } -late final _FPDFAnnot_GetFontSizePtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetFontSize'); -late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction )>(); +late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamKey'); +late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Set the text color of an annotation. -/// -/// handle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// R - the red component for the text color. -/// G - the green component for the text color. -/// B - the blue component for the text color. +/// Get the type of the value of a property in a content mark by key. /// -/// Returns true if successful. +/// mark - handle to a content mark. +/// key - string key of the property. /// -/// Currently supported subtypes: freetext. -/// The range for the color components is 0 to 255. -int FPDFAnnot_SetFontColor(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int R, -int G, -int B, +/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. +int FPDFPageObjMark_GetParamValueType(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, ) { - return _FPDFAnnot_SetFontColor(handle, -annot, -R, -G, -B, + return _FPDFPageObjMark_GetParamValueType(mark, +key, ); } -late final _FPDFAnnot_SetFontColorPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFontColor'); -late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction(); +late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_GetParamValueType'); +late final _FPDFPageObjMark_GetParamValueType = _FPDFPageObjMark_GetParamValueTypePtr.asFunction(); /// Experimental API. -/// Get the RGB value of the font color for an |annot| with variable text. +/// Get the value of a number property in a content mark by key as int. +/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER +/// for this property. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// mark - handle to a content mark. +/// key - string key of the property. +/// out_value - pointer to variable that will receive the value. Not filled if +/// false is returned. /// -/// Returns true if the font color was set, false on error or if the font -/// color was not provided. -int FPDFAnnot_GetFontColor(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, +/// Returns TRUE if the key maps to a number value, FALSE otherwise. +int FPDFPageObjMark_GetParamIntValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer out_value, ) { - return _FPDFAnnot_GetFontColor(hHandle, -annot, -R, -G, -B, + return _FPDFPageObjMark_GetParamIntValue(mark, +key, +out_value, ); } -late final _FPDFAnnot_GetFontColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetFontColor'); -late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObjMark_GetParamIntValue'); +late final _FPDFPageObjMark_GetParamIntValue = _FPDFPageObjMark_GetParamIntValuePtr.asFunction )>(); /// Experimental API. -/// Determine if |annot| is a form widget that is checked. Intended for use with -/// checkbox and radio button widgets. +/// Get the value of a number property in a content mark by key as float. +/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER +/// for this property. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. +/// mark - handle to a content mark. +/// key - string key of the property. +/// out_value - pointer to variable that will receive the value. Not filled if +/// false is returned. /// -/// Returns true if |annot| is a form widget and is checked, false otherwise. -int FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, +/// Returns TRUE if the key maps to a number value, FALSE otherwise. +int FPDFPageObjMark_GetParamFloatValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer out_value, ) { - return _FPDFAnnot_IsChecked(hHandle, -annot, + return _FPDFPageObjMark_GetParamFloatValue(mark, +key, +out_value, ); } -late final _FPDFAnnot_IsCheckedPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsChecked'); -late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction(); +late final _FPDFPageObjMark_GetParamFloatValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObjMark_GetParamFloatValue'); +late final _FPDFPageObjMark_GetParamFloatValue = _FPDFPageObjMark_GetParamFloatValuePtr.asFunction )>(); /// Experimental API. -/// Set the list of focusable annotation subtypes. Annotations of subtype -/// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API -/// will override the existing subtypes. +/// Get the value of a string property in a content mark by key. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// subtypes - list of annotation subtype which can be tabbed over. -/// count - total number of annotation subtype in list. -/// Returns true if list of annotation subtype is set successfully, false -/// otherwise. -int FPDFAnnot_SetFocusableSubtypes(FPDF_FORMHANDLE hHandle, -ffi.Pointer subtypes, -int count, +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value in UTF-16LE. This is +/// only modified if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamStringValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFAnnot_SetFocusableSubtypes(hHandle, -subtypes, -count, + return _FPDFPageObjMark_GetParamStringValue(mark, +key, +buffer, +buflen, +out_buflen, ); } -late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_SetFocusableSubtypes'); -late final _FPDFAnnot_SetFocusableSubtypes = _FPDFAnnot_SetFocusableSubtypesPtr.asFunction , int )>(); +late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamStringValue'); +late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Get the count of focusable annotation subtypes as set by host -/// for a |hHandle|. +/// Get the value of a blob property in a content mark by key. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// Returns the count of focusable annotation subtypes or -1 on error. -/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. -int FPDFAnnot_GetFocusableSubtypesCount(FPDF_FORMHANDLE hHandle, -) { - return _FPDFAnnot_GetFocusableSubtypesCount(hHandle, -); -} - -late final _FPDFAnnot_GetFocusableSubtypesCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFocusableSubtypesCount'); -late final _FPDFAnnot_GetFocusableSubtypesCount = _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction(); - -/// Experimental API. -/// Get the list of focusable annotation subtype as set by host. +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value. This is only modified +/// if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// subtypes - receives the list of annotation subtype which can be tabbed -/// over. Caller must have allocated |subtypes| more than or -/// equal to the count obtained from -/// FPDFAnnot_GetFocusableSubtypesCount() API. -/// count - size of |subtypes|. -/// Returns true on success and set list of annotation subtype to |subtypes|, -/// false otherwise. -/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. -int FPDFAnnot_GetFocusableSubtypes(FPDF_FORMHANDLE hHandle, -ffi.Pointer subtypes, -int count, +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamBlobValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFAnnot_GetFocusableSubtypes(hHandle, -subtypes, -count, + return _FPDFPageObjMark_GetParamBlobValue(mark, +key, +buffer, +buflen, +out_buflen, ); } -late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_GetFocusableSubtypes'); -late final _FPDFAnnot_GetFocusableSubtypes = _FPDFAnnot_GetFocusableSubtypesPtr.asFunction , int )>(); +late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamBlobValue'); +late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. +/// Set the value of an int property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. /// -/// annot - handle to an annotation. +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - int value to set. /// -/// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, -/// if the input annot is NULL or input annot's subtype is not link. -FPDF_LINK FPDFAnnot_GetLink(FPDF_ANNOTATION annot, +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetIntParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +int value, ) { - return _FPDFAnnot_GetLink(annot, + return _FPDFPageObjMark_SetIntParam(document, +page_object, +mark, +key, +value, ); } -late final _FPDFAnnot_GetLinkPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetLink'); -late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); +late final _FPDFPageObjMark_SetIntParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetIntParam'); +late final _FPDFPageObjMark_SetIntParam = _FPDFPageObjMark_SetIntParamPtr.asFunction(); /// Experimental API. -/// Gets the count of annotations in the |annot|'s control group. -/// A group of interactive form annotations is collectively called a form -/// control group. Here, |annot|, an interactive form annotation, should be -/// either a radio button or a checkbox. +/// Set the value of a float property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - float value to set. /// -/// Returns number of controls in its control group or -1 on error. -int FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetFloatParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +double value, ) { - return _FPDFAnnot_GetFormControlCount(hHandle, -annot, + return _FPDFPageObjMark_SetFloatParam(document, +page_object, +mark, +key, +value, ); } -late final _FPDFAnnot_GetFormControlCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormControlCount'); -late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr.asFunction(); +late final _FPDFPageObjMark_SetFloatParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetFloatParam'); +late final _FPDFPageObjMark_SetFloatParam = _FPDFPageObjMark_SetFloatParamPtr.asFunction(); /// Experimental API. -/// Gets the index of |annot| in |annot|'s control group. -/// A group of interactive form annotations is collectively called a form -/// control group. Here, |annot|, an interactive form annotation, should be -/// either a radio button or a checkbox. +/// Set the value of a string property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - string value to set. /// -/// Returns index of a given |annot| in its control group or -1 on error. -int FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetStringParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +FPDF_BYTESTRING value, ) { - return _FPDFAnnot_GetFormControlIndex(hHandle, -annot, + return _FPDFPageObjMark_SetStringParam(document, +page_object, +mark, +key, +value, ); } -late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormControlIndex'); -late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr.asFunction(); +late final _FPDFPageObjMark_SetStringParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetStringParam'); +late final _FPDFPageObjMark_SetStringParam = _FPDFPageObjMark_SetStringParamPtr.asFunction(); /// Experimental API. -/// Gets the export value of |annot| which is an interactive form annotation. -/// Intended for use with radio button and checkbox widget annotations. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value -/// will be 0. Note that return value of empty string is 2 for "\0\0". +/// Set the value of a blob property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. /// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - pointer to blob value to set. +/// value_len - size in bytes of |value|. /// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetBlobParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer value, +int value_len, ) { - return _FPDFAnnot_GetFormFieldExportValue(hHandle, -annot, -buffer, -buflen, + return _FPDFPageObjMark_SetBlobParam(document, +page_object, +mark, +key, +value, +value_len, ); } -late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldExportValue'); -late final _FPDFAnnot_GetFormFieldExportValue = _FPDFAnnot_GetFormFieldExportValuePtr.asFunction , int )>(); +late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPageObjMark_SetBlobParam'); +late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction , int )>(); /// Experimental API. -/// Add a URI action to |annot|, overwriting the existing action, if any. +/// Removes a property from a content mark by key. /// -/// annot - handle to a link annotation. -/// uri - the URI to be set, encoded in 7-bit ASCII. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. /// -/// Returns true if successful. -int FPDFAnnot_SetURI(FPDF_ANNOTATION annot, -ffi.Pointer uri, +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_RemoveParam(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, ) { - return _FPDFAnnot_SetURI(annot, -uri, + return _FPDFPageObjMark_RemoveParam(page_object, +mark, +key, ); } -late final _FPDFAnnot_SetURIPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetURI'); -late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction )>(); +late final _FPDFPageObjMark_RemoveParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_RemoveParam'); +late final _FPDFPageObjMark_RemoveParam = _FPDFPageObjMark_RemoveParamPtr.asFunction(); -/// Experimental API. -/// Get the attachment from |annot|. +/// Load an image from a JPEG image file and then set it into |image_object|. /// -/// annot - handle to a file annotation. +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. /// -/// Returns the handle to the attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFileAttachment(annot, +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. +int FPDFImageObj_LoadJpegFile(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, +) { + return _FPDFImageObj_LoadJpegFile(pages, +count, +image_object, +file_access, ); } -late final _FPDFAnnot_GetFileAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFileAttachment'); -late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr.asFunction(); +late final _FPDFImageObj_LoadJpegFilePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFile'); +late final _FPDFImageObj_LoadJpegFile = _FPDFImageObj_LoadJpegFilePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); -/// Experimental API. -/// Add an embedded file with |name| to |annot|. +/// Load an image from a JPEG image file and then set it into |image_object|. /// -/// annot - handle to a file annotation. -/// name - name of the new attachment. +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. /// -/// Returns a handle to the new attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, -FPDF_WIDESTRING name, +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. This function loads the JPEG image inline, so the image +/// content is copied to the file. This allows |file_access| and its associated +/// data to be deleted after this function returns. +int FPDFImageObj_LoadJpegFileInline(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, ) { - return _FPDFAnnot_AddFileAttachment(annot, -name, + return _FPDFImageObj_LoadJpegFileInline(pages, +count, +image_object, +file_access, ); } -late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_AddFileAttachment'); -late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr.asFunction(); +late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFileInline'); +late final _FPDFImageObj_LoadJpegFileInline = _FPDFImageObj_LoadJpegFileInlinePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); -/// Function: FPDFText_LoadPage -/// Prepare information about all characters in a page. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage function -/// (in FPDFVIEW module). -/// Return value: -/// A handle to the text page information structure. -/// NULL if something goes wrong. -/// Comments: -/// Application must call FPDFText_ClosePage to release the text page -/// information. -FPDF_TEXTPAGE FPDFText_LoadPage(FPDF_PAGE page, +/// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. +/// +/// Set the transform matrix of |image_object|. +/// +/// image_object - handle to an image object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |image_object|. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, +double a, +double b, +double c, +double d, +double e, +double f, ) { - return _FPDFText_LoadPage(page, + return _FPDFImageObj_SetMatrix(image_object, +a, +b, +c, +d, +e, +f, ); } -late final _FPDFText_LoadPagePtr = _lookup< - ffi.NativeFunction>('FPDFText_LoadPage'); -late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); +late final _FPDFImageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_SetMatrix'); +late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction(); -/// Function: FPDFText_ClosePage -/// Release all resources allocated for a text page information -/// structure. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return Value: -/// None. -void FPDFText_ClosePage(FPDF_TEXTPAGE text_page, +/// Set |bitmap| to |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// bitmap - handle of the bitmap. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetBitmap(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +FPDF_BITMAP bitmap, ) { - return _FPDFText_ClosePage(text_page, + return _FPDFImageObj_SetBitmap(pages, +count, +image_object, +bitmap, ); } -late final _FPDFText_ClosePagePtr = _lookup< - ffi.NativeFunction>('FPDFText_ClosePage'); -late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); +late final _FPDFImageObj_SetBitmapPtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , FPDF_BITMAP )>>('FPDFImageObj_SetBitmap'); +late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction , int , FPDF_PAGEOBJECT , FPDF_BITMAP )>(); -/// Function: FPDFText_CountChars -/// Get number of characters in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return value: -/// Number of characters in the page. Return -1 for error. -/// Generated characters, like additional space characters, new line -/// characters, are also counted. -/// Comments: -/// Characters in a page form a "stream", inside the stream, each -/// character has an index. -/// We will use the index parameters in many of FPDFTEXT functions. The -/// first character in the page -/// has an index value of zero. -int FPDFText_CountChars(FPDF_TEXTPAGE text_page, +/// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only +/// operates on |image_object| and does not take the associated image mask into +/// account. It also ignores the matrix for |image_object|. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// image_object - handle to an image object. +/// +/// Returns the bitmap. +FPDF_BITMAP FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object, ) { - return _FPDFText_CountChars(text_page, + return _FPDFImageObj_GetBitmap(image_object, ); } -late final _FPDFText_CountCharsPtr = _lookup< - ffi.NativeFunction>('FPDFText_CountChars'); -late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); +late final _FPDFImageObj_GetBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetBitmap'); +late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction(); -/// Function: FPDFText_GetUnicode -/// Get Unicode of a character in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The Unicode of the particular character. -/// If a character is not encoded in Unicode and Foxit engine can't -/// convert to Unicode, -/// the return value will be zero. -int FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, -int index, +/// Experimental API. +/// Get a bitmap rasterization of |image_object| that takes the image mask and +/// image matrix into account. To render correctly, the caller must provide the +/// |document| associated with |image_object|. If there is a |page| associated +/// with |image_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// document - handle to a document associated with |image_object|. +/// page - handle to an optional page associated with |image_object|. +/// image_object - handle to an image object. +/// +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT image_object, ) { - return _FPDFText_GetUnicode(text_page, -index, + return _FPDFImageObj_GetRenderedBitmap(document, +page, +image_object, ); } -late final _FPDFText_GetUnicodePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetUnicode'); -late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); +late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetRenderedBitmap'); +late final _FPDFImageObj_GetRenderedBitmap = _FPDFImageObj_GetRenderedBitmapPtr.asFunction(); -/// Experimental API. -/// Function: FPDFText_GetTextObject -/// Get the FPDF_PAGEOBJECT associated with a given character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The associated text object for the character at |index|, or NULL on -/// error. The returned text object, if non-null, is of type -/// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. -FPDF_PAGEOBJECT FPDFText_GetTextObject(FPDF_TEXTPAGE text_page, -int index, +/// Get the decoded image data of |image_object|. The decoded data is the +/// uncompressed image data, i.e. the raw image data after having all filters +/// applied. |buffer| is only modified if |buflen| is longer than the length of +/// the decoded image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the decoded image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the decoded image data. +int FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFText_GetTextObject(text_page, -index, + return _FPDFImageObj_GetImageDataDecoded(image_object, +buffer, +buflen, ); } -late final _FPDFText_GetTextObjectPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetTextObject'); -late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction(); +late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataDecoded'); +late final _FPDFImageObj_GetImageDataDecoded = _FPDFImageObj_GetImageDataDecodedPtr.asFunction , int )>(); -/// Experimental API. -/// Function: FPDFText_IsGenerated -/// Get if a character in a page is generated by PDFium. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character is generated by PDFium. -/// 0 if the character is not generated by PDFium. -/// -1 if there was an error. -int FPDFText_IsGenerated(FPDF_TEXTPAGE text_page, -int index, +/// Get the raw image data of |image_object|. The raw data is the image data as +/// stored in the PDF without applying any filters. |buffer| is only modified if +/// |buflen| is longer than the length of the raw image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the raw image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the raw image data. +int FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFText_IsGenerated(text_page, -index, + return _FPDFImageObj_GetImageDataRaw(image_object, +buffer, +buflen, ); } -late final _FPDFText_IsGeneratedPtr = _lookup< - ffi.NativeFunction>('FPDFText_IsGenerated'); -late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); +late final _FPDFImageObj_GetImageDataRawPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataRaw'); +late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr.asFunction , int )>(); -/// Experimental API. -/// Function: FPDFText_IsHyphen -/// Get if a character in a page is a hyphen. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character is a hyphen. -/// 0 if the character is not a hyphen. -/// -1 if there was an error. -int FPDFText_IsHyphen(FPDF_TEXTPAGE text_page, -int index, +/// Get the number of filters (i.e. decoders) of the image in |image_object|. +/// +/// image_object - handle to an image object. +/// +/// Returns the number of |image_object|'s filters. +int FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object, ) { - return _FPDFText_IsHyphen(text_page, -index, + return _FPDFImageObj_GetImageFilterCount(image_object, ); } -late final _FPDFText_IsHyphenPtr = _lookup< - ffi.NativeFunction>('FPDFText_IsHyphen'); -late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); +late final _FPDFImageObj_GetImageFilterCountPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetImageFilterCount'); +late final _FPDFImageObj_GetImageFilterCount = _FPDFImageObj_GetImageFilterCountPtr.asFunction(); -/// Experimental API. -/// Function: FPDFText_HasUnicodeMapError -/// Get if a character in a page has an invalid unicode mapping. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character has an invalid unicode mapping. -/// 0 if the character has no known unicode mapping issues. -/// -1 if there was an error. -int FPDFText_HasUnicodeMapError(FPDF_TEXTPAGE text_page, +/// Get the filter at |index| of |image_object|'s list of filters. Note that the +/// filters need to be applied in order, i.e. the first filter should be applied +/// first, then the second, etc. |buffer| is only modified if |buflen| is longer +/// than the length of the filter string. +/// +/// image_object - handle to an image object. +/// index - the index of the filter requested. +/// buffer - buffer for holding filter string, encoded in UTF-8. +/// buflen - length of the buffer. +/// +/// Returns the length of the filter string. +int FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, int index, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFText_HasUnicodeMapError(text_page, + return _FPDFImageObj_GetImageFilter(image_object, index, +buffer, +buflen, ); } -late final _FPDFText_HasUnicodeMapErrorPtr = _lookup< - ffi.NativeFunction>('FPDFText_HasUnicodeMapError'); -late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr.asFunction(); +late final _FPDFImageObj_GetImageFilterPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageFilter'); +late final _FPDFImageObj_GetImageFilter = _FPDFImageObj_GetImageFilterPtr.asFunction , int )>(); -/// Function: FPDFText_GetFontSize -/// Get the font size of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The font size of the particular character, measured in points (about -/// 1/72 inch). This is the typographic size of the font (so called -/// "em size"). -double FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, -int index, +/// Get the image metadata of |image_object|, including dimension, DPI, bits per +/// pixel, and colorspace. If the |image_object| is not an image object or if it +/// does not have an image, then the return value will be false. Otherwise, +/// failure to retrieve any specific parameter would result in its value being 0. +/// +/// image_object - handle to an image object. +/// page - handle to the page that |image_object| is on. Required for +/// retrieving the image's bits per pixel and colorspace. +/// metadata - receives the image metadata; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer metadata, ) { - return _FPDFText_GetFontSize(text_page, -index, + return _FPDFImageObj_GetImageMetadata(image_object, +page, +metadata, ); } -late final _FPDFText_GetFontSizePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetFontSize'); -late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction(); +late final _FPDFImageObj_GetImageMetadataPtr = _lookup< + ffi.NativeFunction )>>('FPDFImageObj_GetImageMetadata'); +late final _FPDFImageObj_GetImageMetadata = _FPDFImageObj_GetImageMetadataPtr.asFunction )>(); /// Experimental API. -/// Function: FPDFText_GetFontInfo -/// Get the font name and flags of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// buffer - A buffer receiving the font name. -/// buflen - The length of |buffer| in bytes. -/// flags - Optional pointer to an int receiving the font flags. -/// These flags should be interpreted per PDF spec 1.7 -/// Section 5.7.1 Font Descriptor Flags. -/// Return value: -/// On success, return the length of the font name, including the -/// trailing NUL character, in bytes. If this length is less than or -/// equal to |length|, |buffer| is set to the font name, |flags| is -/// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on -/// failure. -int FPDFText_GetFontInfo(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer buffer, +/// Get the image size in pixels. Faster method to get only image size. +/// +/// image_object - handle to an image object. +/// width - receives the image width in pixels; must not be NULL. +/// height - receives the image height in pixels; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, +ffi.Pointer width, +ffi.Pointer height, +) { + return _FPDFImageObj_GetImagePixelSize(image_object, +width, +height, +); +} + +late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFImageObj_GetImagePixelSize'); +late final _FPDFImageObj_GetImagePixelSize = _FPDFImageObj_GetImagePixelSizePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Get ICC profile decoded data of |image_object|. If the |image_object| is not +/// an image object or if it does not have an image, then the return value will +/// be false. It also returns false if the |image_object| has no ICC profile. +/// |buffer| is only modified if ICC profile exists and |buflen| is longer than +/// the length of the ICC profile decoded data. +/// +/// image_object - handle to an image object; must not be NULL. +/// page - handle to the page containing |image_object|; must not be +/// NULL. Required for retrieving the image's colorspace. +/// buffer - Buffer to receive ICC profile data; may be NULL if querying +/// required size via |out_buflen|. +/// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. +/// out_buflen - Pointer to receive the ICC profile data size in bytes; must +/// not be NULL. Will be set if this API returns true. +/// +/// Returns true if |out_buflen| is not null and an ICC profile exists for the +/// given |image_object|. +int FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer buffer, int buflen, -ffi.Pointer flags, +ffi.Pointer out_buflen, ) { - return _FPDFText_GetFontInfo(text_page, -index, + return _FPDFImageObj_GetIccProfileDataDecoded(image_object, +page, buffer, buflen, -flags, +out_buflen, ); } -late final _FPDFText_GetFontInfoPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFText_GetFontInfo'); -late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction , int , ffi.Pointer )>(); +late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFImageObj_GetIccProfileDataDecoded'); +late final _FPDFImageObj_GetIccProfileDataDecoded = _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction , int , ffi.Pointer )>(); -/// Experimental API. -/// Function: FPDFText_GetFontWeight -/// Get the font weight of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// On success, return the font weight of the particular character. If -/// |text_page| is invalid, if |index| is out of bounds, or if the -/// character's text object is undefined, return -1. -int FPDFText_GetFontWeight(FPDF_TEXTPAGE text_page, -int index, +/// Create a new path object at an initial position. +/// +/// x - initial horizontal position. +/// y - initial vertical position. +/// +/// Returns a handle to a new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath(double x, +double y, ) { - return _FPDFText_GetFontWeight(text_page, -index, + return _FPDFPageObj_CreateNewPath(x, +y, ); } -late final _FPDFText_GetFontWeightPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetFontWeight'); -late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); +late final _FPDFPageObj_CreateNewPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewPath'); +late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr.asFunction(); -/// Experimental API. -/// Function: FPDFText_GetFillColor -/// Get the fill color of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// R - Pointer to an unsigned int number receiving the -/// red value of the fill color. -/// G - Pointer to an unsigned int number receiving the -/// green value of the fill color. -/// B - Pointer to an unsigned int number receiving the -/// blue value of the fill color. -/// A - Pointer to an unsigned int number receiving the -/// alpha value of the fill color. -/// Return value: -/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are -/// unchanged. -int FPDFText_GetFillColor(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, +/// Create a closed path consisting of a rectangle. +/// +/// x - horizontal position for the left boundary of the rectangle. +/// y - vertical position for the bottom boundary of the rectangle. +/// w - width of the rectangle. +/// h - height of the rectangle. +/// +/// Returns a handle to the new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect(double x, +double y, +double w, +double h, ) { - return _FPDFText_GetFillColor(text_page, -index, + return _FPDFPageObj_CreateNewRect(x, +y, +w, +h, +); +} + +late final _FPDFPageObj_CreateNewRectPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewRect'); +late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr.asFunction(); + +/// Get the bounding box of |page_object|. +/// +/// page_object - handle to a page object. +/// left - pointer where the left coordinate will be stored +/// bottom - pointer where the bottom coordinate will be stored +/// right - pointer where the right coordinate will be stored +/// top - pointer where the top coordinate will be stored +/// +/// On success, returns TRUE and fills in the 4 coordinates. +int FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPageObj_GetBounds(page_object, +left, +bottom, +right, +top, +); +} + +late final _FPDFPageObj_GetBoundsPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetBounds'); +late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the quad points that bounds |page_object|. +/// +/// page_object - handle to a page object. +/// quad_points - pointer where the quadrilateral points will be stored. +/// +/// On success, returns TRUE and fills in |quad_points|. +/// +/// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page +/// object. When the object is rotated by a non-multiple of 90 degrees, this API +/// returns a tighter bound that cannot be represented with just the 4 sides of +/// a rectangle. +/// +/// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and +/// FPDF_PAGEOBJ_IMAGE. +int FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer quad_points, +) { + return _FPDFPageObj_GetRotatedBounds(page_object, +quad_points, +); +} + +late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetRotatedBounds'); +late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr.asFunction )>(); + +/// Set the blend mode of |page_object|. +/// +/// page_object - handle to a page object. +/// blend_mode - string containing the blend mode. +/// +/// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, +/// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, +/// Overlay, Saturation, Screen, SoftLight +void FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING blend_mode, +) { + return _FPDFPageObj_SetBlendMode(page_object, +blend_mode, +); +} + +late final _FPDFPageObj_SetBlendModePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetBlendMode'); +late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr.asFunction(); + +/// Set the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's stroke color. +/// G - the green component for the object's stroke color. +/// B - the blue component for the object's stroke color. +/// A - the stroke alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, +) { + return _FPDFPageObj_SetStrokeColor(page_object, R, G, B, @@ -4314,37 +4444,26 @@ A, ); } -late final _FPDFText_GetFillColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetFillColor'); -late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPageObj_SetStrokeColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeColor'); +late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr.asFunction(); -/// Experimental API. -/// Function: FPDFText_GetStrokeColor -/// Get the stroke color of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// R - Pointer to an unsigned int number receiving the -/// red value of the stroke color. -/// G - Pointer to an unsigned int number receiving the -/// green value of the stroke color. -/// B - Pointer to an unsigned int number receiving the -/// blue value of the stroke color. -/// A - Pointer to an unsigned int number receiving the -/// alpha value of the stroke color. -/// Return value: -/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are -/// unchanged. -int FPDFText_GetStrokeColor(FPDF_TEXTPAGE text_page, -int index, +/// Get the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the path stroke color. +/// G - the green component of the object's stroke color. +/// B - the blue component of the object's stroke color. +/// A - the stroke alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetStrokeColor(FPDF_PAGEOBJECT page_object, ffi.Pointer R, ffi.Pointer G, ffi.Pointer B, ffi.Pointer A, ) { - return _FPDFText_GetStrokeColor(text_page, -index, + return _FPDFPageObj_GetStrokeColor(page_object, R, G, B, @@ -4352,6034 +4471,9066 @@ A, ); } -late final _FPDFText_GetStrokeColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetStrokeColor'); -late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPageObj_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetStrokeColor'); +late final _FPDFPageObj_GetStrokeColor = _FPDFPageObj_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Experimental API. -/// Function: FPDFText_GetCharAngle -/// Get character rotation angle. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return Value: -/// On success, return the angle value in radian. Value will always be -/// greater or equal to 0. If |text_page| is invalid, or if |index| is -/// out of bounds, then return -1. -double FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page, -int index, +/// Set the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_SetStrokeWidth(FPDF_PAGEOBJECT page_object, +double width, ) { - return _FPDFText_GetCharAngle(text_page, -index, + return _FPDFPageObj_SetStrokeWidth(page_object, +width, ); } -late final _FPDFText_GetCharAnglePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetCharAngle'); -late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction(); +late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeWidth'); +late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr.asFunction(); -/// Function: FPDFText_GetCharBox -/// Get bounding box of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// left - Pointer to a double number receiving left position -/// of the character box. -/// right - Pointer to a double number receiving right position -/// of the character box. -/// bottom - Pointer to a double number receiving bottom position -/// of the character box. -/// top - Pointer to a double number receiving top position of -/// the character box. -/// Return Value: -/// On success, return TRUE and fill in |left|, |right|, |bottom|, and -/// |top|. If |text_page| is invalid, or if |index| is out of bounds, -/// then return FALSE, and the out parameters remain unmodified. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer left, -ffi.Pointer right, -ffi.Pointer bottom, -ffi.Pointer top, +/// Get the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_GetStrokeWidth(FPDF_PAGEOBJECT page_object, +ffi.Pointer width, ) { - return _FPDFText_GetCharBox(text_page, -index, -left, -right, -bottom, -top, + return _FPDFPageObj_GetStrokeWidth(page_object, +width, ); } -late final _FPDFText_GetCharBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetCharBox'); -late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetStrokeWidth'); +late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr.asFunction )>(); -/// Experimental API. -/// Function: FPDFText_GetLooseCharBox -/// Get a "loose" bounding box of a particular character, i.e., covering -/// the entire glyph bounds, without taking the actual glyph shape into -/// account. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// rect - Pointer to a FS_RECTF receiving the character box. -/// Return Value: -/// On success, return TRUE and fill in |rect|. If |text_page| is -/// invalid, or if |index| is out of bounds, then return FALSE, and the -/// |rect| out parameter remains unmodified. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetLooseCharBox(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer rect, +/// Get the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line join, or -1 on failure. +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_GetLineJoin(FPDF_PAGEOBJECT page_object, ) { - return _FPDFText_GetLooseCharBox(text_page, -index, -rect, + return _FPDFPageObj_GetLineJoin(page_object, ); } -late final _FPDFText_GetLooseCharBoxPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetLooseCharBox'); -late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr.asFunction )>(); +late final _FPDFPageObj_GetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineJoin'); +late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); -/// Experimental API. -/// Function: FPDFText_GetMatrix -/// Get the effective transformation matrix for a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage(). -/// index - Zero-based index of the character. -/// matrix - Pointer to a FS_MATRIX receiving the transformation -/// matrix. -/// Return Value: -/// On success, return TRUE and fill in |matrix|. If |text_page| is -/// invalid, or if |index| is out of bounds, or if |matrix| is NULL, -/// then return FALSE, and |matrix| remains unmodified. -int FPDFText_GetMatrix(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer matrix, +/// Set the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// line_join - line join +/// +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_SetLineJoin(FPDF_PAGEOBJECT page_object, +int line_join, ) { - return _FPDFText_GetMatrix(text_page, -index, -matrix, + return _FPDFPageObj_SetLineJoin(page_object, +line_join, ); } -late final _FPDFText_GetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetMatrix'); -late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction )>(); +late final _FPDFPageObj_SetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineJoin'); +late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction(); -/// Function: FPDFText_GetCharOrigin -/// Get origin of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// x - Pointer to a double number receiving x coordinate of -/// the character origin. -/// y - Pointer to a double number receiving y coordinate of -/// the character origin. -/// Return Value: -/// Whether the call succeeded. If false, x and y are unchanged. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer x, -ffi.Pointer y, +/// Get the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line cap, or -1 on failure. +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_GetLineCap(FPDF_PAGEOBJECT page_object, ) { - return _FPDFText_GetCharOrigin(text_page, -index, -x, -y, + return _FPDFPageObj_GetLineCap(page_object, ); } -late final _FPDFText_GetCharOriginPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFText_GetCharOrigin'); -late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction , ffi.Pointer )>(); +late final _FPDFPageObj_GetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineCap'); +late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); -/// Function: FPDFText_GetCharIndexAtPos -/// Get the index of a character at or nearby a certain position on the -/// page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// x - X position in PDF "user space". -/// y - Y position in PDF "user space". -/// xTolerance - An x-axis tolerance value for character hit -/// detection, in point units. -/// yTolerance - A y-axis tolerance value for character hit -/// detection, in point units. -/// Return Value: -/// The zero-based index of the character at, or nearby the point (x,y). -/// If there is no character at or nearby the point, return value will -/// be -1. If an error occurs, -3 will be returned. -int FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, -double x, -double y, -double xTolerance, -double yTolerance, +/// Set the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// line_cap - line cap +/// +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_SetLineCap(FPDF_PAGEOBJECT page_object, +int line_cap, ) { - return _FPDFText_GetCharIndexAtPos(text_page, -x, -y, -xTolerance, -yTolerance, + return _FPDFPageObj_SetLineCap(page_object, +line_cap, ); } -late final _FPDFText_GetCharIndexAtPosPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetCharIndexAtPos'); -late final _FPDFText_GetCharIndexAtPos = _FPDFText_GetCharIndexAtPosPtr.asFunction(); +late final _FPDFPageObj_SetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineCap'); +late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction(); -/// Function: FPDFText_GetText -/// Extract unicode text string from the page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// start_index - Index for the start characters. -/// count - Number of UCS-2 values to be extracted. -/// result - A buffer (allocated by application) receiving the -/// extracted UCS-2 values. The buffer must be able to -/// hold `count` UCS-2 values plus a terminator. -/// Return Value: -/// Number of characters written into the result buffer, including the -/// trailing terminator. -/// Comments: -/// This function ignores characters without UCS-2 representations. -/// It considers all characters on the page, even those that are not -/// visible when the page has a cropbox. To filter out the characters -/// outside of the cropbox, use FPDF_GetPageBoundingBox() and -/// FPDFText_GetCharBox(). -int FPDFText_GetText(FPDF_TEXTPAGE text_page, -int start_index, -int count, -ffi.Pointer result, +/// Set the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's fill color. +/// G - the green component for the object's fill color. +/// B - the blue component for the object's fill color. +/// A - the fill alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, ) { - return _FPDFText_GetText(text_page, -start_index, -count, -result, + return _FPDFPageObj_SetFillColor(page_object, +R, +G, +B, +A, ); } -late final _FPDFText_GetTextPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetText'); -late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction )>(); +late final _FPDFPageObj_SetFillColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetFillColor'); +late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr.asFunction(); -/// Function: FPDFText_CountRects -/// Counts number of rectangular areas occupied by a segment of text, -/// and caches the result for subsequent FPDFText_GetRect() calls. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// start_index - Index for the start character. -/// count - Number of characters, or -1 for all remaining. -/// Return value: -/// Number of rectangles, 0 if text_page is null, or -1 on bad -/// start_index. -/// Comments: -/// This function, along with FPDFText_GetRect can be used by -/// applications to detect the position on the page for a text segment, -/// so proper areas can be highlighted. The FPDFText_* functions will -/// automatically merge small character boxes into bigger one if those -/// characters are on the same line and use same font settings. -int FPDFText_CountRects(FPDF_TEXTPAGE text_page, -int start_index, -int count, +/// Get the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the object's fill color. +/// G - the green component of the object's fill color. +/// B - the blue component of the object's fill color. +/// A - the fill alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetFillColor(FPDF_PAGEOBJECT page_object, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, ) { - return _FPDFText_CountRects(text_page, -start_index, -count, + return _FPDFPageObj_GetFillColor(page_object, +R, +G, +B, +A, ); } -late final _FPDFText_CountRectsPtr = _lookup< - ffi.NativeFunction>('FPDFText_CountRects'); -late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction(); +late final _FPDFPageObj_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetFillColor'); +late final _FPDFPageObj_GetFillColor = _FPDFPageObj_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Function: FPDFText_GetRect -/// Get a rectangular area from the result generated by -/// FPDFText_CountRects. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// rect_index - Zero-based index for the rectangle. -/// left - Pointer to a double value receiving the rectangle -/// left boundary. -/// top - Pointer to a double value receiving the rectangle -/// top boundary. -/// right - Pointer to a double value receiving the rectangle -/// right boundary. -/// bottom - Pointer to a double value receiving the rectangle -/// bottom boundary. -/// Return Value: -/// On success, return TRUE and fill in |left|, |top|, |right|, and -/// |bottom|. If |text_page| is invalid then return FALSE, and the out -/// parameters remain unmodified. If |text_page| is valid but -/// |rect_index| is out of bounds, then return FALSE and set the out -/// parameters to 0. -int FPDFText_GetRect(FPDF_TEXTPAGE text_page, -int rect_index, -ffi.Pointer left, -ffi.Pointer top, -ffi.Pointer right, -ffi.Pointer bottom, +/// Experimental API. +/// Get the line dash |phase| of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - pointer where the dashing phase will be stored. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, +ffi.Pointer phase, ) { - return _FPDFText_GetRect(text_page, -rect_index, -left, -top, -right, -bottom, + return _FPDFPageObj_GetDashPhase(page_object, +phase, ); } -late final _FPDFText_GetRectPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetRect'); -late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPageObj_GetDashPhasePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetDashPhase'); +late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr.asFunction )>(); -/// Function: FPDFText_GetBoundedText -/// Extract unicode text within a rectangular boundary on the page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// left - Left boundary. -/// top - Top boundary. -/// right - Right boundary. -/// bottom - Bottom boundary. -/// buffer - Caller-allocated buffer to receive UTF-16 values. -/// buflen - Number of UTF-16 values (not bytes) that `buffer` -/// is capable of holding. -/// Return Value: -/// If buffer is NULL or buflen is zero, return number of UTF-16 -/// values (not bytes) of text present within the rectangle, excluding -/// a terminating NUL. Generally you should pass a buffer at least one -/// larger than this if you want a terminating NUL, which will be -/// provided if space is available. Otherwise, return number of UTF-16 -/// values copied into the buffer, including the terminating NUL when -/// space for it is available. -/// Comment: -/// If the buffer is too small, as much text as will fit is copied into -/// it. May return a split surrogate in that case. -int FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, -double left, -double top, -double right, -double bottom, -ffi.Pointer buffer, -int buflen, +/// Experimental API. +/// Set the line dash phase of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, +double phase, ) { - return _FPDFText_GetBoundedText(text_page, -left, -top, -right, -bottom, -buffer, -buflen, + return _FPDFPageObj_SetDashPhase(page_object, +phase, ); } -late final _FPDFText_GetBoundedTextPtr = _lookup< - ffi.NativeFunction , ffi.Int )>>('FPDFText_GetBoundedText'); -late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction , int )>(); +late final _FPDFPageObj_SetDashPhasePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetDashPhase'); +late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr.asFunction(); -/// Function: FPDFText_FindStart -/// Start a search. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// findwhat - A unicode match pattern. -/// flags - Option flags. -/// start_index - Start from this character. -1 for end of the page. -/// Return Value: -/// A handle for the search context. FPDFText_FindClose must be called -/// to release this handle. -FPDF_SCHHANDLE FPDFText_FindStart(FPDF_TEXTPAGE text_page, -FPDF_WIDESTRING findwhat, -int flags, -int start_index, +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line dash array size or -1 on failure. +int FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object, ) { - return _FPDFText_FindStart(text_page, -findwhat, -flags, -start_index, + return _FPDFPageObj_GetDashCount(page_object, ); } -late final _FPDFText_FindStartPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindStart'); -late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction(); +late final _FPDFPageObj_GetDashCountPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetDashCount'); +late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); -/// Function: FPDFText_FindNext -/// Search in the direction from page start to end. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Whether a match is found. -int FPDFText_FindNext(FPDF_SCHHANDLE handle, +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - pointer where the dashing array will be stored. +/// dash_count - number of elements in |dash_array|. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, ) { - return _FPDFText_FindNext(handle, + return _FPDFPageObj_GetDashArray(page_object, +dash_array, +dash_count, ); } -late final _FPDFText_FindNextPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindNext'); -late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); +late final _FPDFPageObj_GetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFPageObj_GetDashArray'); +late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr.asFunction , int )>(); -/// Function: FPDFText_FindPrev -/// Search in the direction from page end to start. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Whether a match is found. -int FPDFText_FindPrev(FPDF_SCHHANDLE handle, +/// Experimental API. +/// Set the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - the dash array. +/// dash_count - number of elements in |dash_array|. +/// phase - the line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, +double phase, ) { - return _FPDFText_FindPrev(handle, + return _FPDFPageObj_SetDashArray(page_object, +dash_array, +dash_count, +phase, ); } -late final _FPDFText_FindPrevPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindPrev'); -late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); +late final _FPDFPageObj_SetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Float )>>('FPDFPageObj_SetDashArray'); +late final _FPDFPageObj_SetDashArray = _FPDFPageObj_SetDashArrayPtr.asFunction , int , double )>(); -/// Function: FPDFText_GetSchResultIndex -/// Get the starting character index of the search result. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Index for the starting character. -int FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle, +/// Get number of segments inside |path|. +/// +/// path - handle to a path. +/// +/// A segment is a command, created by e.g. FPDFPath_MoveTo(), +/// FPDFPath_LineTo() or FPDFPath_BezierTo(). +/// +/// Returns the number of objects in |path| or -1 on failure. +int FPDFPath_CountSegments(FPDF_PAGEOBJECT path, ) { - return _FPDFText_GetSchResultIndex(handle, + return _FPDFPath_CountSegments(path, ); } -late final _FPDFText_GetSchResultIndexPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetSchResultIndex'); -late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); +late final _FPDFPath_CountSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFPath_CountSegments'); +late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); -/// Function: FPDFText_GetSchCount -/// Get the number of matched characters in the search result. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Number of matched characters. -int FPDFText_GetSchCount(FPDF_SCHHANDLE handle, +/// Get segment in |path| at |index|. +/// +/// path - handle to a path. +/// index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, +int index, ) { - return _FPDFText_GetSchCount(handle, + return _FPDFPath_GetPathSegment(path, +index, ); } -late final _FPDFText_GetSchCountPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetSchCount'); -late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); +late final _FPDFPath_GetPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFPath_GetPathSegment'); +late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction(); -/// Function: FPDFText_FindClose -/// Release a search context. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// None. -void FPDFText_FindClose(FPDF_SCHHANDLE handle, +/// Get coordinates of |segment|. +/// +/// segment - handle to a segment. +/// x - the horizontal position of the segment. +/// y - the vertical position of the segment. +/// +/// Returns TRUE on success, otherwise |x| and |y| is not set. +int FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, +ffi.Pointer x, +ffi.Pointer y, ) { - return _FPDFText_FindClose(handle, + return _FPDFPathSegment_GetPoint(segment, +x, +y, ); } -late final _FPDFText_FindClosePtr = _lookup< - ffi.NativeFunction>('FPDFText_FindClose'); -late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); +late final _FPDFPathSegment_GetPointPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPathSegment_GetPoint'); +late final _FPDFPathSegment_GetPoint = _FPDFPathSegment_GetPointPtr.asFunction , ffi.Pointer )>(); -/// Function: FPDFLink_LoadWebLinks -/// Prepare information about weblinks in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return Value: -/// A handle to the page's links information structure, or -/// NULL if something goes wrong. -/// Comments: -/// Weblinks are those links implicitly embedded in PDF pages. PDF also -/// has a type of annotation called "link" (FPDFTEXT doesn't deal with -/// that kind of link). FPDFTEXT weblink feature is useful for -/// automatically detecting links in the page contents. For example, -/// things like "https://www.example.com" will be detected, so -/// applications can allow user to click on those characters to activate -/// the link, even the PDF doesn't come with link annotations. +/// Get type of |segment|. /// -/// FPDFLink_CloseWebLinks must be called to release resources. -FPDF_PAGELINK FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page, +/// segment - handle to a segment. +/// +/// Returns one of the FPDF_SEGMENT_* values on success, +/// FPDF_SEGMENT_UNKNOWN on error. +int FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment, ) { - return _FPDFLink_LoadWebLinks(text_page, + return _FPDFPathSegment_GetType(segment, ); } -late final _FPDFLink_LoadWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_LoadWebLinks'); -late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction(); +late final _FPDFPathSegment_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetType'); +late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); -/// Function: FPDFLink_CountWebLinks -/// Count number of detected web links. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// Return Value: -/// Number of detected web links. -int FPDFLink_CountWebLinks(FPDF_PAGELINK link_page, +/// Gets if the |segment| closes the current subpath of a given path. +/// +/// segment - handle to a segment. +/// +/// Returns close flag for non-NULL segment, FALSE otherwise. +int FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment, ) { - return _FPDFLink_CountWebLinks(link_page, + return _FPDFPathSegment_GetClose(segment, ); } -late final _FPDFLink_CountWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountWebLinks'); -late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); +late final _FPDFPathSegment_GetClosePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetClose'); +late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); -/// Function: FPDFLink_GetURL -/// Fetch the URL information for a detected web link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// buffer - A unicode buffer for the result. -/// buflen - Number of 16-bit code units (not bytes) for the -/// buffer, including an additional terminator. -/// Return Value: -/// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit -/// code units (not bytes) needed to buffer the result (an additional -/// terminator is included in this count). -/// Otherwise, copy the result into |buffer|, truncating at |buflen| if -/// the result is too large to fit, and return the number of 16-bit code -/// units actually copied into the buffer (the additional terminator is -/// also included in this count). -/// If |link_index| does not correspond to a valid link, then the result -/// is an empty string. -int FPDFLink_GetURL(FPDF_PAGELINK link_page, -int link_index, -ffi.Pointer buffer, -int buflen, +/// Move a path's current point. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new current point. +/// y - the vertical position of the new current point. +/// +/// Note that no line will be created between the previous current point and the +/// new one. +/// +/// Returns TRUE on success +int FPDFPath_MoveTo(FPDF_PAGEOBJECT path, +double x, +double y, ) { - return _FPDFLink_GetURL(link_page, -link_index, -buffer, -buflen, + return _FPDFPath_MoveTo(path, +x, +y, ); } -late final _FPDFLink_GetURLPtr = _lookup< - ffi.NativeFunction , ffi.Int )>>('FPDFLink_GetURL'); -late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction , int )>(); +late final _FPDFPath_MoveToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_MoveTo'); +late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction(); -/// Function: FPDFLink_CountRects -/// Count number of rectangular areas for the link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// Return Value: -/// Number of rectangular areas for the link. If |link_index| does -/// not correspond to a valid link, then 0 is returned. -int FPDFLink_CountRects(FPDF_PAGELINK link_page, -int link_index, +/// Add a line between the current point and a new point in the path. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new point. +/// y - the vertical position of the new point. +/// +/// The path's current point is changed to (x, y). +/// +/// Returns TRUE on success +int FPDFPath_LineTo(FPDF_PAGEOBJECT path, +double x, +double y, ) { - return _FPDFLink_CountRects(link_page, -link_index, + return _FPDFPath_LineTo(path, +x, +y, ); } -late final _FPDFLink_CountRectsPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountRects'); -late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); +late final _FPDFPath_LineToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_LineTo'); +late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction(); -/// Function: FPDFLink_GetRect -/// Fetch the boundaries of a rectangle for a link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// rect_index - Zero-based index for a rectangle. -/// left - Pointer to a double value receiving the rectangle -/// left boundary. -/// top - Pointer to a double value receiving the rectangle -/// top boundary. -/// right - Pointer to a double value receiving the rectangle -/// right boundary. -/// bottom - Pointer to a double value receiving the rectangle -/// bottom boundary. -/// Return Value: -/// On success, return TRUE and fill in |left|, |top|, |right|, and -/// |bottom|. If |link_page| is invalid or if |link_index| does not -/// correspond to a valid link, then return FALSE, and the out -/// parameters remain unmodified. -int FPDFLink_GetRect(FPDF_PAGELINK link_page, -int link_index, -int rect_index, -ffi.Pointer left, -ffi.Pointer top, -ffi.Pointer right, -ffi.Pointer bottom, +/// Add a cubic Bezier curve to the given path, starting at the current point. +/// +/// path - the handle to the path object. +/// x1 - the horizontal position of the first Bezier control point. +/// y1 - the vertical position of the first Bezier control point. +/// x2 - the horizontal position of the second Bezier control point. +/// y2 - the vertical position of the second Bezier control point. +/// x3 - the horizontal position of the ending point of the Bezier curve. +/// y3 - the vertical position of the ending point of the Bezier curve. +/// +/// Returns TRUE on success +int FPDFPath_BezierTo(FPDF_PAGEOBJECT path, +double x1, +double y1, +double x2, +double y2, +double x3, +double y3, ) { - return _FPDFLink_GetRect(link_page, -link_index, -rect_index, -left, -top, -right, -bottom, + return _FPDFPath_BezierTo(path, +x1, +y1, +x2, +y2, +x3, +y3, ); } -late final _FPDFLink_GetRectPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFLink_GetRect'); -late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPath_BezierToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_BezierTo'); +late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction(); -/// Experimental API. -/// Function: FPDFLink_GetTextRange -/// Fetch the start char index and char count for a link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// start_char_index - pointer to int receiving the start char index -/// char_count - pointer to int receiving the char count -/// Return Value: -/// On success, return TRUE and fill in |start_char_index| and -/// |char_count|. if |link_page| is invalid or if |link_index| does -/// not correspond to a valid link, then return FALSE and the out -/// parameters remain unmodified. -int FPDFLink_GetTextRange(FPDF_PAGELINK link_page, -int link_index, -ffi.Pointer start_char_index, -ffi.Pointer char_count, +/// Close the current subpath of a given path. +/// +/// path - the handle to the path object. +/// +/// This will add a line between the current point and the initial point of the +/// subpath, thus terminating the current subpath. +/// +/// Returns TRUE on success +int FPDFPath_Close(FPDF_PAGEOBJECT path, ) { - return _FPDFLink_GetTextRange(link_page, -link_index, -start_char_index, -char_count, + return _FPDFPath_Close(path, ); } -late final _FPDFLink_GetTextRangePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_GetTextRange'); -late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction , ffi.Pointer )>(); +late final _FPDFPath_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFPath_Close'); +late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); -/// Function: FPDFLink_CloseWebLinks -/// Release resources used by weblink feature. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// Return Value: -/// None. -void FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page, -) { - return _FPDFLink_CloseWebLinks(link_page, -); -} - -late final _FPDFLink_CloseWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CloseWebLinks'); -late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); - -/// Get the first child of |bookmark|, or the first top-level bookmark item. +/// Set the drawing mode of a path. /// -/// document - handle to the document. -/// bookmark - handle to the current bookmark. Pass NULL for the first top -/// level item. +/// path - the handle to the path object. +/// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path should be stroked or not. /// -/// Returns a handle to the first child of |bookmark| or the first top-level -/// bookmark item. NULL if no child or top-level bookmark found. -/// Note that another name for the bookmarks is the document outline, as -/// described in ISO 32000-1:2008, section 12.3.3. -FPDF_BOOKMARK FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, +/// Returns TRUE on success +int FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, +int fillmode, +int stroke, ) { - return _FPDFBookmark_GetFirstChild(document, -bookmark, + return _FPDFPath_SetDrawMode(path, +fillmode, +stroke, ); } -late final _FPDFBookmark_GetFirstChildPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetFirstChild'); -late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr.asFunction(); +late final _FPDFPath_SetDrawModePtr = _lookup< + ffi.NativeFunction>('FPDFPath_SetDrawMode'); +late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction(); -/// Get the next sibling of |bookmark|. -/// -/// document - handle to the document. -/// bookmark - handle to the current bookmark. +/// Get the drawing mode of a path. /// -/// Returns a handle to the next sibling of |bookmark|, or NULL if this is the -/// last bookmark at this level. +/// path - the handle to the path object. +/// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path is stroked or not. /// -/// Note that the caller is responsible for handling circular bookmark -/// references, as may arise from malformed documents. -FPDF_BOOKMARK FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, +/// Returns TRUE on success +int FPDFPath_GetDrawMode(FPDF_PAGEOBJECT path, +ffi.Pointer fillmode, +ffi.Pointer stroke, ) { - return _FPDFBookmark_GetNextSibling(document, -bookmark, + return _FPDFPath_GetDrawMode(path, +fillmode, +stroke, ); } -late final _FPDFBookmark_GetNextSiblingPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetNextSibling'); -late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr.asFunction(); +late final _FPDFPath_GetDrawModePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPath_GetDrawMode'); +late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction , ffi.Pointer )>(); -/// Get the title of |bookmark|. -/// -/// bookmark - handle to the bookmark. -/// buffer - buffer for the title. May be NULL. -/// buflen - the length of the buffer in bytes. May be 0. +/// Create a new text object using one of the standard PDF fonts. /// -/// Returns the number of bytes in the title, including the terminating NUL -/// character. The number of bytes is returned regardless of the |buffer| and -/// |buflen| parameters. +/// document - handle to the document. +/// font - string containing the font name, without spaces. +/// font_size - the font size for the new text object. /// -/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The -/// string is terminated by a UTF16 NUL character. If |buflen| is less than the -/// required length, or |buffer| is NULL, |buffer| will not be modified. -int FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark, -ffi.Pointer buffer, -int buflen, +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, +double font_size, ) { - return _FPDFBookmark_GetTitle(bookmark, -buffer, -buflen, + return _FPDFPageObj_NewTextObj(document, +font, +font_size, ); } -late final _FPDFBookmark_GetTitlePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFBookmark_GetTitle'); -late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction , int )>(); +late final _FPDFPageObj_NewTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewTextObj'); +late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction(); -/// Experimental API. -/// Get the number of chlidren of |bookmark|. +/// Set the text for a text object. If it had text, it will be replaced. /// -/// bookmark - handle to the bookmark. +/// text_object - handle to the text object. +/// text - the UTF-16LE encoded string containing the text to be added. /// -/// Returns a signed integer that represents the number of sub-items the given -/// bookmark has. If the value is positive, child items shall be shown by default -/// (open state). If the value is negative, child items shall be hidden by -/// default (closed state). Please refer to PDF 32000-1:2008, Table 153. -/// Returns 0 if the bookmark has no children or is invalid. -int FPDFBookmark_GetCount(FPDF_BOOKMARK bookmark, +/// Returns TRUE on success +int FPDFText_SetText(FPDF_PAGEOBJECT text_object, +FPDF_WIDESTRING text, ) { - return _FPDFBookmark_GetCount(bookmark, + return _FPDFText_SetText(text_object, +text, ); } -late final _FPDFBookmark_GetCountPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetCount'); -late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); +late final _FPDFText_SetTextPtr = _lookup< + ffi.NativeFunction>('FPDFText_SetText'); +late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction(); -/// Find the bookmark with |title| in |document|. -/// -/// document - handle to the document. -/// title - the UTF-16LE encoded Unicode title for which to search. +/// Experimental API. +/// Set the text using charcodes for a text object. If it had text, it will be +/// replaced. /// -/// Returns the handle to the bookmark, or NULL if |title| can't be found. +/// text_object - handle to the text object. +/// charcodes - pointer to an array of charcodes to be added. +/// count - number of elements in |charcodes|. /// -/// FPDFBookmark_Find() will always return the first bookmark found even if -/// multiple bookmarks have the same |title|. -FPDF_BOOKMARK FPDFBookmark_Find(FPDF_DOCUMENT document, -FPDF_WIDESTRING title, +/// Returns TRUE on success +int FPDFText_SetCharcodes(FPDF_PAGEOBJECT text_object, +ffi.Pointer charcodes, +int count, ) { - return _FPDFBookmark_Find(document, -title, + return _FPDFText_SetCharcodes(text_object, +charcodes, +count, ); } -late final _FPDFBookmark_FindPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_Find'); -late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction(); +late final _FPDFText_SetCharcodesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFText_SetCharcodes'); +late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction , int )>(); -/// Get the destination associated with |bookmark|. +/// Returns a font object loaded from a stream of data. The font is loaded +/// into the document. Various font data structures, such as the ToUnicode data, +/// are auto-generated based on the inputs. /// -/// document - handle to the document. -/// bookmark - handle to the bookmark. +/// document - handle to the document. +/// data - the stream of font data, which will be copied by the font object. +/// size - the size of the font data, in bytes. +/// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. +/// cid - a boolean specifying if the font is a CID font or not. /// -/// Returns the handle to the destination data, or NULL if no destination is -/// associated with |bookmark|. -FPDF_DEST FPDFBookmark_GetDest(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure +FPDF_FONT FPDFText_LoadFont(FPDF_DOCUMENT document, +ffi.Pointer data, +int size, +int font_type, +int cid, ) { - return _FPDFBookmark_GetDest(document, -bookmark, + return _FPDFText_LoadFont(document, +data, +size, +font_type, +cid, ); } -late final _FPDFBookmark_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetDest'); -late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction(); +late final _FPDFText_LoadFontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , ffi.Int , FPDF_BOOL )>>('FPDFText_LoadFont'); +late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction , int , int , int )>(); -/// Get the action associated with |bookmark|. +/// Experimental API. +/// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred +/// way of using font style is using a dash to separate the name from the style, +/// for example 'Helvetica-BoldItalic'. /// -/// bookmark - handle to the bookmark. +/// document - handle to the document. +/// font - string containing the font name, without spaces. /// -/// Returns the handle to the action data, or NULL if no action is associated -/// with |bookmark|. -/// If this function returns a valid handle, it is valid as long as |bookmark| is -/// valid. -/// If this function returns NULL, FPDFBookmark_GetDest() should be called to get -/// the |bookmark| destination data. -FPDF_ACTION FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark, +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadStandardFont(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, ) { - return _FPDFBookmark_GetAction(bookmark, + return _FPDFText_LoadStandardFont(document, +font, ); } -late final _FPDFBookmark_GetActionPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetAction'); -late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction(); +late final _FPDFText_LoadStandardFontPtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadStandardFont'); +late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr.asFunction(); -/// Get the type of |action|. +/// Experimental API. +/// Returns a font object loaded from a stream of data for a type 2 CID font. The +/// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode +/// data and the CIDToGIDMap data are caller provided, instead of auto-generated. /// -/// action - handle to the action. +/// document - handle to the document. +/// font_data - the stream of font data, which will be copied by +/// the font object. +/// font_data_size - the size of the font data, in bytes. +/// to_unicode_cmap - the ToUnicode data. +/// cid_to_gid_map_data - the stream of CIDToGIDMap data. +/// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. /// -/// Returns one of: -/// PDFACTION_UNSUPPORTED -/// PDFACTION_GOTO -/// PDFACTION_REMOTEGOTO -/// PDFACTION_URI -/// PDFACTION_LAUNCH -int FPDFAction_GetType(FPDF_ACTION action, +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadCidType2Font(FPDF_DOCUMENT document, +ffi.Pointer font_data, +int font_data_size, +FPDF_BYTESTRING to_unicode_cmap, +ffi.Pointer cid_to_gid_map_data, +int cid_to_gid_map_data_size, ) { - return _FPDFAction_GetType(action, + return _FPDFText_LoadCidType2Font(document, +font_data, +font_data_size, +to_unicode_cmap, +cid_to_gid_map_data, +cid_to_gid_map_data_size, ); } -late final _FPDFAction_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFAction_GetType'); -late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); - -/// Get the destination of |action|. -/// -/// document - handle to the document. -/// action - handle to the action. |action| must be a |PDFACTION_GOTO| or -/// |PDFACTION_REMOTEGOTO|. +late final _FPDFText_LoadCidType2FontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , FPDF_BYTESTRING , ffi.Pointer , ffi.Uint32 )>>('FPDFText_LoadCidType2Font'); +late final _FPDFText_LoadCidType2Font = _FPDFText_LoadCidType2FontPtr.asFunction , int , FPDF_BYTESTRING , ffi.Pointer , int )>(); + +/// Get the font size of a text object. /// -/// Returns a handle to the destination data, or NULL on error, typically -/// because the arguments were bad or the action was of the wrong type. +/// text - handle to a text. +/// size - pointer to the font size of the text object, measured in points +/// (about 1/72 inch) /// -/// In the case of |PDFACTION_REMOTEGOTO|, you must first call -/// FPDFAction_GetFilePath(), then load the document at that path, then pass -/// the document handle from that document as |document| to FPDFAction_GetDest(). -FPDF_DEST FPDFAction_GetDest(FPDF_DOCUMENT document, -FPDF_ACTION action, +/// Returns TRUE on success. +int FPDFTextObj_GetFontSize(FPDF_PAGEOBJECT text, +ffi.Pointer size, ) { - return _FPDFAction_GetDest(document, -action, + return _FPDFTextObj_GetFontSize(text, +size, ); } -late final _FPDFAction_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFAction_GetDest'); -late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction(); +late final _FPDFTextObj_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFTextObj_GetFontSize'); +late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction )>(); -/// Get the file path of |action|. -/// -/// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or -/// |PDFACTION_REMOTEGOTO|. -/// buffer - a buffer for output the path string. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the file path, including the trailing NUL -/// character, or 0 on error, typically because the arguments were bad or the -/// action was of the wrong type. +/// Close a loaded PDF font. /// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFAction_GetFilePath(FPDF_ACTION action, -ffi.Pointer buffer, -int buflen, +/// font - Handle to the loaded font. +void FPDFFont_Close(FPDF_FONT font, ) { - return _FPDFAction_GetFilePath(action, -buffer, -buflen, + return _FPDFFont_Close(font, ); } -late final _FPDFAction_GetFilePathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetFilePath'); -late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction , int )>(); +late final _FPDFFont_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFFont_Close'); +late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); -/// Get the URI path of |action|. -/// -/// document - handle to the document. -/// action - handle to the action. Must be a |PDFACTION_URI|. -/// buffer - a buffer for the path string. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the URI path, including the trailing NUL -/// character, or 0 on error, typically because the arguments were bad or the -/// action was of the wrong type. -/// -/// The |buffer| may contain badly encoded data. The caller should validate the -/// output. e.g. Check to see if it is UTF-8. +/// Create a new text object using a loaded font. /// -/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. +/// document - handle to the document. +/// font - handle to the font object. +/// font_size - the font size for the new text object. /// -/// Historically, the documentation for this API claimed |buffer| is always -/// encoded in 7-bit ASCII, but did not actually enforce it. -/// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 -/// added that enforcement, but that did not work well for real world PDFs that -/// used UTF-8. As of this writing, this API reverted back to its original -/// behavior prior to commit d609e84cee. -int FPDFAction_GetURIPath(FPDF_DOCUMENT document, -FPDF_ACTION action, -ffi.Pointer buffer, -int buflen, +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, +FPDF_FONT font, +double font_size, ) { - return _FPDFAction_GetURIPath(document, -action, -buffer, -buflen, + return _FPDFPageObj_CreateTextObj(document, +font, +font_size, ); } -late final _FPDFAction_GetURIPathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetURIPath'); -late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction , int )>(); +late final _FPDFPageObj_CreateTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateTextObj'); +late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr.asFunction(); -/// Get the page index of |dest|. +/// Get the text rendering mode of a text object. /// -/// document - handle to the document. -/// dest - handle to the destination. +/// text - the handle to the text object. /// -/// Returns the 0-based page index containing |dest|. Returns -1 on error. -int FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, -FPDF_DEST dest, +/// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, +/// FPDF_TEXTRENDERMODE_UNKNOWN on error. +FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode(FPDF_PAGEOBJECT text, ) { - return _FPDFDest_GetDestPageIndex(document, -dest, -); + return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode(text, +)); } -late final _FPDFDest_GetDestPageIndexPtr = _lookup< - ffi.NativeFunction>('FPDFDest_GetDestPageIndex'); -late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr.asFunction(); +late final _FPDFTextObj_GetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetTextRenderMode'); +late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr.asFunction(); /// Experimental API. -/// Get the view (fit type) specified by |dest|. +/// Set the text rendering mode of a text object. /// -/// dest - handle to the destination. -/// pNumParams - receives the number of view parameters, which is at most 4. -/// pParams - buffer to write the view parameters. Must be at least 4 -/// FS_FLOATs long. -/// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if -/// |dest| does not specify a view. -int FPDFDest_GetView(FPDF_DEST dest, -ffi.Pointer pNumParams, -ffi.Pointer pParams, +/// text - the handle to the text object. +/// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to +/// FPDF_TEXTRENDERMODE_UNKNOWN). +/// +/// Returns TRUE on success. +DartFPDF_BOOL FPDFTextObj_SetTextRenderMode(FPDF_PAGEOBJECT text, +FPDF_TEXT_RENDERMODE render_mode, ) { - return _FPDFDest_GetView(dest, -pNumParams, -pParams, + return _FPDFTextObj_SetTextRenderMode(text, +render_mode.value, ); } -late final _FPDFDest_GetViewPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFDest_GetView'); -late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction , ffi.Pointer )>(); +late final _FPDFTextObj_SetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_SetTextRenderMode'); +late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr.asFunction(); -/// Get the (x, y, zoom) location of |dest| in the destination page, if the -/// destination is in [page /XYZ x y zoom] syntax. +/// Get the text of a text object. /// -/// dest - handle to the destination. -/// hasXVal - out parameter; true if the x value is not null -/// hasYVal - out parameter; true if the y value is not null -/// hasZoomVal - out parameter; true if the zoom value is not null -/// x - out parameter; the x coordinate, in page coordinates. -/// y - out parameter; the y coordinate, in page coordinates. -/// zoom - out parameter; the zoom value. -/// Returns TRUE on successfully reading the /XYZ value. +/// text_object - the handle to the text object. +/// text_page - the handle to the text page. +/// buffer - the address of a buffer that receives the text. +/// length - the size, in bytes, of |buffer|. /// -/// Note the [x, y, zoom] values are only set if the corresponding hasXVal, -/// hasYVal or hasZoomVal flags are true. -int FPDFDest_GetLocationInPage(FPDF_DEST dest, -ffi.Pointer hasXVal, -ffi.Pointer hasYVal, -ffi.Pointer hasZoomVal, -ffi.Pointer x, -ffi.Pointer y, -ffi.Pointer zoom, +/// Returns the number of bytes in the text (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFTextObj_GetText(FPDF_PAGEOBJECT text_object, +FPDF_TEXTPAGE text_page, +ffi.Pointer buffer, +int length, ) { - return _FPDFDest_GetLocationInPage(dest, -hasXVal, -hasYVal, -hasZoomVal, -x, -y, -zoom, + return _FPDFTextObj_GetText(text_object, +text_page, +buffer, +length, ); } -late final _FPDFDest_GetLocationInPagePtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFDest_GetLocationInPage'); -late final _FPDFDest_GetLocationInPage = _FPDFDest_GetLocationInPagePtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFTextObj_GetTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFTextObj_GetText'); +late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction , int )>(); -/// Find a link at point (|x|,|y|) on |page|. -/// -/// page - handle to the document page. -/// x - the x coordinate, in the page coordinate system. -/// y - the y coordinate, in the page coordinate system. +/// Experimental API. +/// Get a bitmap rasterization of |text_object|. To render correctly, the caller +/// must provide the |document| associated with |text_object|. If there is a +/// |page| associated with |text_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. /// -/// Returns a handle to the link, or NULL if no link found at the given point. +/// document - handle to a document associated with |text_object|. +/// page - handle to an optional page associated with |text_object|. +/// text_object - handle to a text object. +/// scale - the scaling factor, which must be greater than 0. /// -/// You can convert coordinates from screen coordinates to page coordinates using -/// FPDF_DeviceToPage(). -FPDF_LINK FPDFLink_GetLinkAtPoint(FPDF_PAGE page, -double x, -double y, +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT text_object, +double scale, ) { - return _FPDFLink_GetLinkAtPoint(page, -x, -y, + return _FPDFTextObj_GetRenderedBitmap(document, +page, +text_object, +scale, ); } -late final _FPDFLink_GetLinkAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetLinkAtPoint'); -late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction(); +late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetRenderedBitmap'); +late final _FPDFTextObj_GetRenderedBitmap = _FPDFTextObj_GetRenderedBitmapPtr.asFunction(); -/// Find the Z-order of link at point (|x|,|y|) on |page|. +/// Experimental API. +/// Get the font of a text object. /// -/// page - handle to the document page. -/// x - the x coordinate, in the page coordinate system. -/// y - the y coordinate, in the page coordinate system. +/// text - the handle to the text object. /// -/// Returns the Z-order of the link, or -1 if no link found at the given point. -/// Larger Z-order numbers are closer to the front. -/// -/// You can convert coordinates from screen coordinates to page coordinates using -/// FPDF_DeviceToPage(). -int FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, -double x, -double y, +/// Returns a handle to the font object held by |text| which retains ownership. +FPDF_FONT FPDFTextObj_GetFont(FPDF_PAGEOBJECT text, ) { - return _FPDFLink_GetLinkZOrderAtPoint(page, -x, -y, + return _FPDFTextObj_GetFont(text, ); } -late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetLinkZOrderAtPoint'); -late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr.asFunction(); +late final _FPDFTextObj_GetFontPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetFont'); +late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); -/// Get destination info for |link|. +/// Experimental API. +/// Get the base name of a font. /// -/// document - handle to the document. -/// link - handle to the link. +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the base font name. +/// length - the size, in bytes, of |buffer|. /// -/// Returns a handle to the destination, or NULL if there is no destination -/// associated with the link. In this case, you should call FPDFLink_GetAction() -/// to retrieve the action associated with |link|. -FPDF_DEST FPDFLink_GetDest(FPDF_DOCUMENT document, -FPDF_LINK link, +/// Returns the number of bytes in the base name (including the trailing NUL +/// character) on success, 0 on error. The base name is typically the font's +/// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetBaseFontName(FPDF_FONT font, +ffi.Pointer buffer, +int length, ) { - return _FPDFLink_GetDest(document, -link, + return _FPDFFont_GetBaseFontName(font, +buffer, +length, ); } -late final _FPDFLink_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetDest'); -late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction(); +late final _FPDFFont_GetBaseFontNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetBaseFontName'); +late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr.asFunction , int )>(); -/// Get action info for |link|. +/// Experimental API. +/// Get the family name of a font. /// -/// link - handle to the link. +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the font name. +/// length - the size, in bytes, of |buffer|. /// -/// Returns a handle to the action associated to |link|, or NULL if no action. -/// If this function returns a valid handle, it is valid as long as |link| is -/// valid. -FPDF_ACTION FPDFLink_GetAction(FPDF_LINK link, +/// Returns the number of bytes in the family name (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetFamilyName(FPDF_FONT font, +ffi.Pointer buffer, +int length, ) { - return _FPDFLink_GetAction(link, + return _FPDFFont_GetFamilyName(font, +buffer, +length, ); } -late final _FPDFLink_GetActionPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetAction'); -late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); +late final _FPDFFont_GetFamilyNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetFamilyName'); +late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction , int )>(); -/// Enumerates all the link annotations in |page|. +/// Experimental API. +/// Get the decoded data from the |font| object. /// -/// page - handle to the page. -/// start_pos - the start position, should initially be 0 and is updated with -/// the next start position on return. -/// link_annot - the link handle for |startPos|. +/// font - The handle to the font object. (Required) +/// buffer - The address of a buffer that receives the font data. +/// buflen - Length of the buffer. +/// out_buflen - Pointer to variable that will receive the minimum buffer size +/// to contain the font data. Not filled if the return value is +/// FALSE. (Required) /// -/// Returns TRUE on success. -int FPDFLink_Enumerate(FPDF_PAGE page, -ffi.Pointer start_pos, -ffi.Pointer link_annot, +/// Returns TRUE on success. In which case, |out_buflen| will be filled, and +/// |buffer| will be filled if it is large enough. Returns FALSE if any of the +/// required parameters are null. +/// +/// The decoded data is the uncompressed font data. i.e. the raw font data after +/// having all stream filters applied, when the data is embedded. +/// +/// If the font is not embedded, then this API will instead return the data for +/// the substitution font it is using. +int FPDFFont_GetFontData(FPDF_FONT font, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFLink_Enumerate(page, -start_pos, -link_annot, + return _FPDFFont_GetFontData(font, +buffer, +buflen, +out_buflen, ); } -late final _FPDFLink_EnumeratePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_Enumerate'); -late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction , ffi.Pointer )>(); +late final _FPDFFont_GetFontDataPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFFont_GetFontData'); +late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Gets FPDF_ANNOTATION object for |link_annot|. +/// Get whether |font| is embedded or not. /// -/// page - handle to the page in which FPDF_LINK object is present. -/// link_annot - handle to link annotation. +/// font - the handle to the font object. /// -/// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, -/// if the input link annot or page is NULL. -FPDF_ANNOTATION FPDFLink_GetAnnot(FPDF_PAGE page, -FPDF_LINK link_annot, +/// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. +int FPDFFont_GetIsEmbedded(FPDF_FONT font, ) { - return _FPDFLink_GetAnnot(page, -link_annot, + return _FPDFFont_GetIsEmbedded(font, ); } -late final _FPDFLink_GetAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetAnnot'); -late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction(); +late final _FPDFFont_GetIsEmbeddedPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetIsEmbedded'); +late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); -/// Get the rectangle for |link_annot|. +/// Experimental API. +/// Get the descriptor flags of a font. /// -/// link_annot - handle to the link annotation. -/// rect - the annotation rectangle. +/// font - the handle to the font object. /// -/// Returns true on success. -int FPDFLink_GetAnnotRect(FPDF_LINK link_annot, -ffi.Pointer rect, +/// Returns the bit flags specifying various characteristics of the font as +/// defined in ISO 32000-1:2008, table 123, -1 on failure. +int FPDFFont_GetFlags(FPDF_FONT font, ) { - return _FPDFLink_GetAnnotRect(link_annot, -rect, + return _FPDFFont_GetFlags(font, ); } -late final _FPDFLink_GetAnnotRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFLink_GetAnnotRect'); -late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction )>(); +late final _FPDFFont_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetFlags'); +late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); -/// Get the count of quadrilateral points to the |link_annot|. +/// Experimental API. +/// Get the font weight of a font. /// -/// link_annot - handle to the link annotation. +/// font - the handle to the font object. /// -/// Returns the count of quadrilateral points. -int FPDFLink_CountQuadPoints(FPDF_LINK link_annot, +/// Returns the font weight, -1 on failure. +/// Typical values are 400 (normal) and 700 (bold). +int FPDFFont_GetWeight(FPDF_FONT font, ) { - return _FPDFLink_CountQuadPoints(link_annot, + return _FPDFFont_GetWeight(font, ); } -late final _FPDFLink_CountQuadPointsPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountQuadPoints'); -late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); +late final _FPDFFont_GetWeightPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetWeight'); +late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); -/// Get the quadrilateral points for the specified |quad_index| in |link_annot|. +/// Experimental API. +/// Get the italic angle of a font. /// -/// link_annot - handle to the link annotation. -/// quad_index - the specified quad point index. -/// quad_points - receives the quadrilateral points. +/// font - the handle to the font object. +/// angle - pointer where the italic angle will be stored /// -/// Returns true on success. -int FPDFLink_GetQuadPoints(FPDF_LINK link_annot, -int quad_index, -ffi.Pointer quad_points, +/// The italic angle of a |font| is defined as degrees counterclockwise +/// from vertical. For a font that slopes to the right, this will be negative. +/// +/// Returns TRUE on success; |angle| unmodified on failure. +int FPDFFont_GetItalicAngle(FPDF_FONT font, +ffi.Pointer angle, ) { - return _FPDFLink_GetQuadPoints(link_annot, -quad_index, -quad_points, + return _FPDFFont_GetItalicAngle(font, +angle, ); } -late final _FPDFLink_GetQuadPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFLink_GetQuadPoints'); -late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction )>(); +late final _FPDFFont_GetItalicAnglePtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetItalicAngle'); +late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction )>(); -/// Experimental API -/// Gets an additional-action from |page|. +/// Experimental API. +/// Get ascent distance of a font. /// -/// page - handle to the page, as returned by FPDF_LoadPage(). -/// aa_type - the type of the page object's addtional-action, defined -/// in public/fpdf_formfill.h +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// ascent - pointer where the font ascent will be stored /// -/// Returns the handle to the action data, or NULL if there is no -/// additional-action of type |aa_type|. -/// If this function returns a valid handle, it is valid as long as |page| is -/// valid. -FPDF_ACTION FPDF_GetPageAAction(FPDF_PAGE page, -int aa_type, +/// Ascent is the maximum distance in points above the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |ascent| unmodified on failure. +int FPDFFont_GetAscent(FPDF_FONT font, +double font_size, +ffi.Pointer ascent, ) { - return _FPDF_GetPageAAction(page, -aa_type, + return _FPDFFont_GetAscent(font, +font_size, +ascent, ); } -late final _FPDF_GetPageAActionPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageAAction'); -late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction(); +late final _FPDFFont_GetAscentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetAscent'); +late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction )>(); /// Experimental API. -/// Get the file identifer defined in the trailer of |document|. -/// -/// document - handle to the document. -/// id_type - the file identifier type to retrieve. -/// buffer - a buffer for the file identifier. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. +/// Get descent distance of a font. /// -/// Returns the number of bytes in the file identifier, including the NUL -/// terminator. +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// descent - pointer where the font descent will be stored /// -/// The |buffer| is always a byte string. The |buffer| is followed by a NUL -/// terminator. If |buflen| is less than the returned length, or |buffer| is -/// NULL, |buffer| will not be modified. -int FPDF_GetFileIdentifier(FPDF_DOCUMENT document, -FPDF_FILEIDTYPE id_type, -ffi.Pointer buffer, -int buflen, +/// Descent is the maximum distance in points below the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |descent| unmodified on failure. +int FPDFFont_GetDescent(FPDF_FONT font, +double font_size, +ffi.Pointer descent, ) { - return _FPDF_GetFileIdentifier(document, -id_type.value, -buffer, -buflen, + return _FPDFFont_GetDescent(font, +font_size, +descent, ); } -late final _FPDF_GetFileIdentifierPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetFileIdentifier'); -late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction , int )>(); +late final _FPDFFont_GetDescentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetDescent'); +late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction )>(); -/// Get meta-data |tag| content from |document|. -/// -/// document - handle to the document. -/// tag - the tag to retrieve. The tag can be one of: -/// Title, Author, Subject, Keywords, Creator, Producer, -/// CreationDate, or ModDate. -/// For detailed explanations of these tags and their respective -/// values, please refer to PDF Reference 1.6, section 10.2.1, -/// 'Document Information Dictionary'. -/// buffer - a buffer for the tag. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. +/// Experimental API. +/// Get the width of a glyph in a font. /// -/// Returns the number of bytes in the tag, including trailing zeros. +/// font - the handle to the font object. +/// glyph - the glyph. +/// font_size - the size of the font. +/// width - pointer where the glyph width will be stored /// -/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two -/// bytes of zeros indicating the end of the string. If |buflen| is less than -/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +/// Glyph width is the distance from the end of the prior glyph to the next +/// glyph. This will be the vertical distance for vertical writing. /// -/// For linearized files, FPDFAvail_IsFormAvail must be called before this, and -/// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there -/// is no guarantee the metadata has been loaded. -int FPDF_GetMetaText(FPDF_DOCUMENT document, -FPDF_BYTESTRING tag, -ffi.Pointer buffer, -int buflen, +/// Returns TRUE on success; |width| unmodified on failure. +int FPDFFont_GetGlyphWidth(FPDF_FONT font, +int glyph, +double font_size, +ffi.Pointer width, ) { - return _FPDF_GetMetaText(document, -tag, -buffer, -buflen, + return _FPDFFont_GetGlyphWidth(font, +glyph, +font_size, +width, ); } -late final _FPDF_GetMetaTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetMetaText'); -late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction , int )>(); +late final _FPDFFont_GetGlyphWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetGlyphWidth'); +late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction )>(); -/// Get the page label for |page_index| from |document|. -/// -/// document - handle to the document. -/// page_index - the 0-based index of the page. -/// buffer - a buffer for the page label. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. +/// Experimental API. +/// Get the glyphpath describing how to draw a font glyph. /// -/// Returns the number of bytes in the page label, including trailing zeros. +/// font - the handle to the font object. +/// glyph - the glyph being drawn. +/// font_size - the size of the font. /// -/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two -/// bytes of zeros indicating the end of the string. If |buflen| is less than -/// the returned length, or |buffer| is NULL, |buffer| will not be modified. -int FPDF_GetPageLabel(FPDF_DOCUMENT document, -int page_index, -ffi.Pointer buffer, -int buflen, +/// Returns the handle to the segment, or NULL on faiure. +FPDF_GLYPHPATH FPDFFont_GetGlyphPath(FPDF_FONT font, +int glyph, +double font_size, ) { - return _FPDF_GetPageLabel(document, -page_index, -buffer, -buflen, + return _FPDFFont_GetGlyphPath(font, +glyph, +font_size, ); } -late final _FPDF_GetPageLabelPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetPageLabel'); -late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction , int )>(); +late final _FPDFFont_GetGlyphPathPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetGlyphPath'); +late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction(); -/// Create a new PDF document. +/// Experimental API. +/// Get number of segments inside glyphpath. /// -/// Returns a handle to a new document, or NULL on failure. -FPDF_DOCUMENT FPDF_CreateNewDocument() { - return _FPDF_CreateNewDocument(); +/// glyphpath - handle to a glyph path. +/// +/// Returns the number of objects in |glyphpath| or -1 on failure. +int FPDFGlyphPath_CountGlyphSegments(FPDF_GLYPHPATH glyphpath, +) { + return _FPDFGlyphPath_CountGlyphSegments(glyphpath, +); } -late final _FPDF_CreateNewDocumentPtr = _lookup< - ffi.NativeFunction>('FPDF_CreateNewDocument'); -late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); +late final _FPDFGlyphPath_CountGlyphSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_CountGlyphSegments'); +late final _FPDFGlyphPath_CountGlyphSegments = _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction(); -/// Create a new PDF page. -/// -/// document - handle to document. -/// page_index - suggested 0-based index of the page to create. If it is larger -/// than document's current last index(L), the created page index -/// is the next available index -- L+1. -/// width - the page width in points. -/// height - the page height in points. +/// Experimental API. +/// Get segment in glyphpath at index. /// -/// Returns the handle to the new page or NULL on failure. +/// glyphpath - handle to a glyph path. +/// index - the index of a segment. /// -/// The page should be closed with FPDF_ClosePage() when finished as -/// with any other page in the document. -FPDF_PAGE FPDFPage_New(FPDF_DOCUMENT document, -int page_index, -double width, -double height, +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment(FPDF_GLYPHPATH glyphpath, +int index, ) { - return _FPDFPage_New(document, -page_index, -width, -height, + return _FPDFGlyphPath_GetGlyphPathSegment(glyphpath, +index, ); } -late final _FPDFPage_NewPtr = _lookup< - ffi.NativeFunction>('FPDFPage_New'); -late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction(); +late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_GetGlyphPathSegment'); +late final _FPDFGlyphPath_GetGlyphPathSegment = _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction(); -/// Delete the page at |page_index|. +/// Get number of page objects inside |form_object|. /// -/// document - handle to document. -/// page_index - the index of the page to delete. -void FPDFPage_Delete(FPDF_DOCUMENT document, -int page_index, +/// form_object - handle to a form object. +/// +/// Returns the number of objects in |form_object| on success, -1 on error. +int FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object, ) { - return _FPDFPage_Delete(document, -page_index, + return _FPDFFormObj_CountObjects(form_object, ); } -late final _FPDFPage_DeletePtr = _lookup< - ffi.NativeFunction>('FPDFPage_Delete'); -late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); +late final _FPDFFormObj_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_CountObjects'); +late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); -/// Experimental API. -/// Move the given pages to a new index position. -/// -/// page_indices - the ordered list of pages to move. No duplicates allowed. -/// page_indices_len - the number of elements in |page_indices| -/// dest_page_index - the new index position to which the pages in -/// |page_indices| are moved. -/// -/// Returns TRUE on success. If it returns FALSE, the document may be left in an -/// indeterminate state. +/// Get page object in |form_object| at |index|. /// -/// Example: The PDF document starts out with pages [A, B, C, D], with indices -/// [0, 1, 2, 3]. +/// form_object - handle to a form object. +/// index - the 0-based index of a page object. /// -/// > Move(doc, [3, 2], 2, 1); // returns true -/// > // The document has pages [A, D, C, B]. -/// > -/// > Move(doc, [0, 4, 3], 3, 1); // returns false -/// > // Returned false because index 4 is out of range. -/// > -/// > Move(doc, [0, 3, 1], 3, 2); // returns false -/// > // Returned false because index 2 is out of range for 3 page indices. -/// > -/// > Move(doc, [2, 2], 2, 0); // returns false -/// > // Returned false because [2, 2] contains duplicates. -int FPDF_MovePages(FPDF_DOCUMENT document, -ffi.Pointer page_indices, -int page_indices_len, -int dest_page_index, +/// Returns the handle to the page object, or NULL on error. +FPDF_PAGEOBJECT FPDFFormObj_GetObject(FPDF_PAGEOBJECT form_object, +int index, ) { - return _FPDF_MovePages(document, -page_indices, -page_indices_len, -dest_page_index, + return _FPDFFormObj_GetObject(form_object, +index, ); } -late final _FPDF_MovePagesPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_MovePages'); -late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction , int , int )>(); +late final _FPDFFormObj_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_GetObject'); +late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction(); -/// Get the rotation of |page|. +/// Experimental API. /// -/// page - handle to a page +/// Remove |page_object| from |form_object|. /// -/// Returns one of the following indicating the page rotation: -/// 0 - No rotation. -/// 1 - Rotated 90 degrees clockwise. -/// 2 - Rotated 180 degrees clockwise. -/// 3 - Rotated 270 degrees clockwise. -int FPDFPage_GetRotation(FPDF_PAGE page, +/// form_object - handle to a form object. +/// page_object - handle to a page object to be removed from the form. +/// +/// Returns TRUE on success. +/// +/// Ownership of the removed |page_object| is transferred to the caller. +/// Call FPDFPageObj_Destroy() on the removed page_object to free it. +int FPDFFormObj_RemoveObject(FPDF_PAGEOBJECT form_object, +FPDF_PAGEOBJECT page_object, ) { - return _FPDFPage_GetRotation(page, + return _FPDFFormObj_RemoveObject(form_object, +page_object, ); } -late final _FPDFPage_GetRotationPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetRotation'); -late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); +late final _FPDFFormObj_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_RemoveObject'); +late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); -/// Set rotation for |page|. +/// Experimental API. +/// Get the number of embedded files in |document|. /// -/// page - handle to a page. -/// rotate - the rotation value, one of: -/// 0 - No rotation. -/// 1 - Rotated 90 degrees clockwise. -/// 2 - Rotated 180 degrees clockwise. -/// 3 - Rotated 270 degrees clockwise. -void FPDFPage_SetRotation(FPDF_PAGE page, -int rotate, +/// document - handle to a document. +/// +/// Returns the number of embedded files in |document|. +int FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document, ) { - return _FPDFPage_SetRotation(page, -rotate, + return _FPDFDoc_GetAttachmentCount(document, ); } -late final _FPDFPage_SetRotationPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetRotation'); -late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); +late final _FPDFDoc_GetAttachmentCountPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetAttachmentCount'); +late final _FPDFDoc_GetAttachmentCount = _FPDFDoc_GetAttachmentCountPtr.asFunction(); -/// Insert |page_object| into |page|. +/// Experimental API. +/// Add an embedded file with |name| in |document|. If |name| is empty, or if +/// |name| is the name of a existing embedded file in |document|, or if +/// |document|'s embedded file name tree is too deep (i.e. |document| has too +/// many embedded files already), then a new attachment will not be added. /// -/// page - handle to a page -/// page_object - handle to a page object. The |page_object| will be -/// automatically freed. -void FPDFPage_InsertObject(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, +/// document - handle to a document. +/// name - name of the new attachment. +/// +/// Returns a handle to the new attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFDoc_AddAttachment(FPDF_DOCUMENT document, +FPDF_WIDESTRING name, ) { - return _FPDFPage_InsertObject(page, -page_object, + return _FPDFDoc_AddAttachment(document, +name, ); } -late final _FPDFPage_InsertObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_InsertObject'); -late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); +late final _FPDFDoc_AddAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_AddAttachment'); +late final _FPDFDoc_AddAttachment = _FPDFDoc_AddAttachmentPtr.asFunction(); -/// Insert |page_object| into |page| at the specified |index|. +/// Experimental API. +/// Get the embedded attachment at |index| in |document|. Note that the returned +/// attachment handle is only valid while |document| is open. /// -/// page - handle to a page -/// page_object - handle to a page object as previously obtained by -/// FPDFPageObj_CreateNew{Path|Rect}() or -/// FPDFPageObj_New{Text|Image}Obj(). Ownership of the object -/// is transferred back to PDFium. -/// index - the index position to insert the object at. If index equals -/// the current object count, the object will be appended to the -/// end. If index is greater than the object count, the function -/// will fail and return false. +/// document - handle to a document. +/// index - the index of the requested embedded file. /// -/// Returns true if successful. -int FPDFPage_InsertObjectAtIndex(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, +/// Returns the handle to the attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFDoc_GetAttachment(FPDF_DOCUMENT document, int index, ) { - return _FPDFPage_InsertObjectAtIndex(page, -page_object, + return _FPDFDoc_GetAttachment(document, index, ); } -late final _FPDFPage_InsertObjectAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDFPage_InsertObjectAtIndex'); -late final _FPDFPage_InsertObjectAtIndex = _FPDFPage_InsertObjectAtIndexPtr.asFunction(); +late final _FPDFDoc_GetAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetAttachment'); +late final _FPDFDoc_GetAttachment = _FPDFDoc_GetAttachmentPtr.asFunction(); /// Experimental API. -/// Remove |page_object| from |page|. -/// -/// page - handle to a page -/// page_object - handle to a page object to be removed. +/// Delete the embedded attachment at |index| in |document|. Note that this does +/// not remove the attachment data from the PDF file; it simply removes the +/// file's entry in the embedded files name tree so that it does not appear in +/// the attachment list. This behavior may change in the future. /// -/// Returns TRUE on success. +/// document - handle to a document. +/// index - the index of the embedded file to be deleted. /// -/// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free -/// it. -/// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all -/// FPDF_TEXTPAGE handles for |page| are no longer valid. -int FPDFPage_RemoveObject(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, +/// Returns true if successful. +int FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, +int index, ) { - return _FPDFPage_RemoveObject(page, -page_object, + return _FPDFDoc_DeleteAttachment(document, +index, ); } -late final _FPDFPage_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_RemoveObject'); -late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction(); +late final _FPDFDoc_DeleteAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_DeleteAttachment'); +late final _FPDFDoc_DeleteAttachment = _FPDFDoc_DeleteAttachmentPtr.asFunction(); -/// Get number of page objects inside |page|. +/// Experimental API. +/// Get the name of the |attachment| file. |buffer| is only modified if |buflen| +/// is longer than the length of the file name. On errors, |buffer| is unmodified +/// and the returned length is 0. /// -/// page - handle to a page. +/// attachment - handle to an attachment. +/// buffer - buffer for holding the file name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns the number of objects in |page|. -int FPDFPage_CountObjects(FPDF_PAGE page, +/// Returns the length of the file name in bytes. +int FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPage_CountObjects(page, + return _FPDFAttachment_GetName(attachment, +buffer, +buflen, ); } -late final _FPDFPage_CountObjectsPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CountObjects'); -late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); +late final _FPDFAttachment_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetName'); +late final _FPDFAttachment_GetName = _FPDFAttachment_GetNamePtr.asFunction , int )>(); -/// Get object in |page| at |index|. +/// Experimental API. +/// Check if the params dictionary of |attachment| has |key| as a key. /// -/// page - handle to a page. -/// index - the index of a page object. +/// attachment - handle to an attachment. +/// key - the key to look for, encoded in UTF-8. /// -/// Returns the handle to the page object, or NULL on failed. -FPDF_PAGEOBJECT FPDFPage_GetObject(FPDF_PAGE page, -int index, +/// Returns true if |key| exists. +int FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, ) { - return _FPDFPage_GetObject(page, -index, + return _FPDFAttachment_HasKey(attachment, +key, ); } -late final _FPDFPage_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetObject'); -late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction(); +late final _FPDFAttachment_HasKeyPtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_HasKey'); +late final _FPDFAttachment_HasKey = _FPDFAttachment_HasKeyPtr.asFunction(); -/// Checks if |page| contains transparency. +/// Experimental API. +/// Get the type of the value corresponding to |key| in the params dictionary of +/// the embedded |attachment|. /// -/// page - handle to a page. +/// attachment - handle to an attachment. +/// key - the key to look for, encoded in UTF-8. /// -/// Returns TRUE if |page| contains transparency. -int FPDFPage_HasTransparency(FPDF_PAGE page, +/// Returns the type of the dictionary value. +int FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, ) { - return _FPDFPage_HasTransparency(page, + return _FPDFAttachment_GetValueType(attachment, +key, ); } -late final _FPDFPage_HasTransparencyPtr = _lookup< - ffi.NativeFunction>('FPDFPage_HasTransparency'); -late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); +late final _FPDFAttachment_GetValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_GetValueType'); +late final _FPDFAttachment_GetValueType = _FPDFAttachment_GetValueTypePtr.asFunction(); -/// Generate the content of |page|. -/// -/// page - handle to a page. +/// Experimental API. +/// Set the string value corresponding to |key| in the params dictionary of the +/// embedded file |attachment|, overwriting the existing value if any. The value +/// type should be FPDF_OBJECT_STRING after this function call succeeds. /// -/// Returns TRUE on success. +/// attachment - handle to an attachment. +/// key - the key to the dictionary entry, encoded in UTF-8. +/// value - the string value to be set, encoded in UTF-16LE. /// -/// Before you save the page to a file, or reload the page, you must call -/// |FPDFPage_GenerateContent| or any changes to |page| will be lost. -int FPDFPage_GenerateContent(FPDF_PAGE page, +/// Returns true if successful. +int FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +FPDF_WIDESTRING value, ) { - return _FPDFPage_GenerateContent(page, + return _FPDFAttachment_SetStringValue(attachment, +key, +value, ); } -late final _FPDFPage_GenerateContentPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GenerateContent'); -late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); +late final _FPDFAttachment_SetStringValuePtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_SetStringValue'); +late final _FPDFAttachment_SetStringValue = _FPDFAttachment_SetStringValuePtr.asFunction(); -/// Destroy |page_object| by releasing its resources. |page_object| must have -/// been created by FPDFPageObj_CreateNew{Path|Rect}() or -/// FPDFPageObj_New{Text|Image}Obj(). This function must be called on -/// newly-created objects if they are not added to a page through -/// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). +/// Experimental API. +/// Get the string value corresponding to |key| in the params dictionary of the +/// embedded file |attachment|. |buffer| is only modified if |buflen| is longer +/// than the length of the string value. Note that if |key| does not exist in the +/// dictionary or if |key|'s corresponding value in the dictionary is not a +/// string (i.e. the value is not of type FPDF_OBJECT_STRING or +/// FPDF_OBJECT_NAME), then an empty string would be copied to |buffer| and the +/// return value would be 2. On other errors, nothing would be added to |buffer| +/// and the return value would be 0. /// -/// page_object - handle to a page object. -void FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_object, +/// attachment - handle to an attachment. +/// key - the key to the requested string value, encoded in UTF-8. +/// buffer - buffer for holding the string value encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the dictionary value string in bytes. +int FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPageObj_Destroy(page_object, + return _FPDFAttachment_GetStringValue(attachment, +key, +buffer, +buflen, ); } -late final _FPDFPageObj_DestroyPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_Destroy'); -late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); +late final _FPDFAttachment_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetStringValue'); +late final _FPDFAttachment_GetStringValue = _FPDFAttachment_GetStringValuePtr.asFunction , int )>(); -/// Checks if |page_object| contains transparency. +/// Experimental API. +/// Set the file data of |attachment|, overwriting the existing file data if any. +/// The creation date and checksum will be updated, while all other dictionary +/// entries will be deleted. Note that only contents with |len| smaller than +/// INT_MAX is supported. /// -/// page_object - handle to a page object. +/// attachment - handle to an attachment. +/// contents - buffer holding the file data to write to |attachment|. +/// len - length of file data in bytes. /// -/// Returns TRUE if |page_object| contains transparency. -int FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT page_object, +/// Returns true if successful. +int FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, +FPDF_DOCUMENT document, +ffi.Pointer contents, +int len, ) { - return _FPDFPageObj_HasTransparency(page_object, + return _FPDFAttachment_SetFile(attachment, +document, +contents, +len, ); } -late final _FPDFPageObj_HasTransparencyPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_HasTransparency'); -late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr.asFunction(); +late final _FPDFAttachment_SetFilePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_SetFile'); +late final _FPDFAttachment_SetFile = _FPDFAttachment_SetFilePtr.asFunction , int )>(); -/// Get type of |page_object|. +/// Experimental API. +/// Get the file data of |attachment|. +/// When the attachment file data is readable, true is returned, and |out_buflen| +/// is updated to indicate the file data size. |buffer| is only modified if +/// |buflen| is non-null and long enough to contain the entire file data. Callers +/// must check both the return value and the input |buflen| is no less than the +/// returned |out_buflen| before using the data. /// -/// page_object - handle to a page object. +/// Otherwise, when the attachment file data is unreadable or when |out_buflen| +/// is null, false is returned and |buffer| and |out_buflen| remain unmodified. /// -/// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on -/// error. -int FPDFPageObj_GetType(FPDF_PAGEOBJECT page_object, +/// attachment - handle to an attachment. +/// buffer - buffer for holding the file data from |attachment|. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to the variable that will receive the minimum buffer +/// size to contain the file data of |attachment|. +/// +/// Returns true on success, false otherwise. +int FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, ) { - return _FPDFPageObj_GetType(page_object, + return _FPDFAttachment_GetFile(attachment, +buffer, +buflen, +out_buflen, ); } -late final _FPDFPageObj_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetType'); -late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); +late final _FPDFAttachment_GetFilePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFAttachment_GetFile'); +late final _FPDFAttachment_GetFile = _FPDFAttachment_GetFilePtr.asFunction , int , ffi.Pointer )>(); /// Experimental API. -/// Gets active state for |page_object| within page. -/// -/// page_object - handle to a page object. -/// active - pointer to variable that will receive if the page object is -/// active. This is a required parameter. Not filled if FALSE -/// is returned. +/// Get the MIME type (Subtype) of the embedded file |attachment|. |buffer| is +/// only modified if |buflen| is longer than the length of the MIME type string. +/// If the Subtype is not found or if there is no file stream, an empty string +/// would be copied to |buffer| and the return value would be 2. On other errors, +/// nothing would be added to |buffer| and the return value would be 0. /// -/// For page objects where |active| is filled with FALSE, the |page_object| is -/// treated as if it wasn't in the document even though it is still held -/// internally. +/// attachment - handle to an attachment. +/// buffer - buffer for holding the MIME type string encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObj_GetIsActive(FPDF_PAGEOBJECT page_object, -ffi.Pointer active, +/// Returns the length of the MIME type string in bytes. +int FPDFAttachment_GetSubtype(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPageObj_GetIsActive(page_object, -active, + return _FPDFAttachment_GetSubtype(attachment, +buffer, +buflen, ); } -late final _FPDFPageObj_GetIsActivePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetIsActive'); -late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction )>(); +late final _FPDFAttachment_GetSubtypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetSubtype'); +late final _FPDFAttachment_GetSubtype = _FPDFAttachment_GetSubtypePtr.asFunction , int )>(); -/// Experimental API. -/// Sets if |page_object| is active within page. -/// -/// page_object - handle to a page object. -/// active - a boolean specifying if the object is active. -/// -/// Returns TRUE on success. -/// -/// Page objects all start in the active state by default, and remain in that -/// state unless this function is called. -/// -/// When |active| is false, this makes the |page_object| be treated as if it -/// wasn't in the document even though it is still held internally. -int FPDFPageObj_SetIsActive(FPDF_PAGEOBJECT page_object, -int active, +/// Function: FPDFDOC_InitFormFillEnvironment +/// Initialize form fill environment. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// formInfo - Pointer to a FPDF_FORMFILLINFO structure. +/// Return Value: +/// Handle to the form fill module, or NULL on failure. +/// Comments: +/// This function should be called before any form fill operation. +/// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until +/// the returned FPDF_FORMHANDLE is closed. +FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, +ffi.Pointer formInfo, ) { - return _FPDFPageObj_SetIsActive(page_object, -active, + return _FPDFDOC_InitFormFillEnvironment(document, +formInfo, ); } -late final _FPDFPageObj_SetIsActivePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetIsActive'); -late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction(); +late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction )>>('FPDFDOC_InitFormFillEnvironment'); +late final _FPDFDOC_InitFormFillEnvironment = _FPDFDOC_InitFormFillEnvironmentPtr.asFunction )>(); -/// Transform |page_object| by the given matrix. -/// -/// page_object - handle to a page object. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |page_object|. -void FPDFPageObj_Transform(FPDF_PAGEOBJECT page_object, -double a, -double b, -double c, -double d, -double e, -double f, +/// Function: FPDFDOC_ExitFormFillEnvironment +/// Take ownership of |hHandle| and exit form fill environment. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This function is a no-op when |hHandle| is null. +void FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle, ) { - return _FPDFPageObj_Transform(page_object, -a, -b, -c, -d, -e, -f, + return _FPDFDOC_ExitFormFillEnvironment(hHandle, ); } -late final _FPDFPageObj_TransformPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_Transform'); -late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction(); +late final _FPDFDOC_ExitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction>('FPDFDOC_ExitFormFillEnvironment'); +late final _FPDFDOC_ExitFormFillEnvironment = _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction(); -/// Experimental API. -/// Transform |page_object| by the given matrix. -/// -/// page_object - handle to a page object. -/// matrix - the transform matrix. -/// -/// Returns TRUE on success. -/// -/// This can be used to scale, rotate, shear and translate the |page_object|. -/// It is an improved version of FPDFPageObj_Transform() that does not do -/// unnecessary double to float conversions, and only uses 1 parameter for the -/// matrix. It also returns whether the operation succeeded or not. -int FPDFPageObj_TransformF(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, +/// Function: FORM_OnAfterLoadPage +/// This method is required for implementing all the form related +/// functions. Should be invoked after user successfully loaded a +/// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnAfterLoadPage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, ) { - return _FPDFPageObj_TransformF(page_object, -matrix, + return _FORM_OnAfterLoadPage(page, +hHandle, ); } -late final _FPDFPageObj_TransformFPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_TransformF'); -late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction )>(); +late final _FORM_OnAfterLoadPagePtr = _lookup< + ffi.NativeFunction>('FORM_OnAfterLoadPage'); +late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction(); -/// Experimental API. -/// Get the transform matrix of a page object. -/// -/// page_object - handle to a page object. -/// matrix - pointer to struct to receive the matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and used to scale, rotate, shear and translate the page object. -/// -/// For page objects outside form objects, the matrix values are relative to the -/// page that contains it. -/// For page objects inside form objects, the matrix values are relative to the -/// form that contains it. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetMatrix(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, +/// Function: FORM_OnBeforeClosePage +/// This method is required for implementing all the form related +/// functions. Should be invoked before user closes the PDF page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnBeforeClosePage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, ) { - return _FPDFPageObj_GetMatrix(page_object, -matrix, + return _FORM_OnBeforeClosePage(page, +hHandle, ); } -late final _FPDFPageObj_GetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetMatrix'); -late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction )>(); +late final _FORM_OnBeforeClosePagePtr = _lookup< + ffi.NativeFunction>('FORM_OnBeforeClosePage'); +late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction(); -/// Experimental API. -/// Set the transform matrix of a page object. -/// -/// page_object - handle to a page object. -/// matrix - pointer to struct with the matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the page object. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, +/// Function: FORM_DoDocumentJSAction +/// This method is required for performing document-level JavaScript +/// actions. It should be invoked after the PDF document has been loaded. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// If there is document-level JavaScript action embedded in the +/// document, this method will execute the JavaScript action. Otherwise, +/// the method will do nothing. +void FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle, ) { - return _FPDFPageObj_SetMatrix(page_object, -matrix, + return _FORM_DoDocumentJSAction(hHandle, ); } -late final _FPDFPageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_SetMatrix'); -late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction )>(); +late final _FORM_DoDocumentJSActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentJSAction'); +late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); -/// Transform all annotations in |page|. -/// -/// page - handle to a page. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |page| annotations. -void FPDFPage_TransformAnnots(FPDF_PAGE page, -double a, -double b, -double c, -double d, -double e, -double f, +/// Function: FORM_DoDocumentOpenAction +/// This method is required for performing open-action when the document +/// is opened. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there are no open-actions embedded +/// in the document. +void FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle, ) { - return _FPDFPage_TransformAnnots(page, -a, -b, -c, -d, -e, -f, + return _FORM_DoDocumentOpenAction(hHandle, ); } -late final _FPDFPage_TransformAnnotsPtr = _lookup< - ffi.NativeFunction>('FPDFPage_TransformAnnots'); -late final _FPDFPage_TransformAnnots = _FPDFPage_TransformAnnotsPtr.asFunction(); +late final _FORM_DoDocumentOpenActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentOpenAction'); +late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr.asFunction(); -/// Create a new image object. -/// -/// document - handle to a document. -/// -/// Returns a handle to a new image object. -FPDF_PAGEOBJECT FPDFPageObj_NewImageObj(FPDF_DOCUMENT document, +/// Function: FORM_DoDocumentAAction +/// This method is required for performing the document's +/// additional-action. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// aaType - The type of the additional-actions which defined +/// above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there is no document +/// additional-action corresponding to the specified |aaType|. +void FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, +int aaType, ) { - return _FPDFPageObj_NewImageObj(document, + return _FORM_DoDocumentAAction(hHandle, +aaType, ); } -late final _FPDFPageObj_NewImageObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_NewImageObj'); -late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction(); +late final _FORM_DoDocumentAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentAAction'); +late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction(); -/// Experimental API. -/// Get the marked content ID for the object. -/// -/// page_object - handle to a page object. -/// -/// Returns the page object's marked content ID, or -1 on error. -int FPDFPageObj_GetMarkedContentID(FPDF_PAGEOBJECT page_object, +/// Function: FORM_DoPageAAction +/// This method is required for performing the page object's +/// additional-action when opened or closed. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// aaType - The type of the page object's additional-actions +/// which defined above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if no additional-action corresponding +/// to the specified |aaType| exists. +void FORM_DoPageAAction(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +int aaType, ) { - return _FPDFPageObj_GetMarkedContentID(page_object, + return _FORM_DoPageAAction(page, +hHandle, +aaType, ); } -late final _FPDFPageObj_GetMarkedContentIDPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetMarkedContentID'); -late final _FPDFPageObj_GetMarkedContentID = _FPDFPageObj_GetMarkedContentIDPtr.asFunction(); +late final _FORM_DoPageAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoPageAAction'); +late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction(); -/// Experimental API. -/// Get number of content marks in |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns the number of content marks in |page_object|, or -1 in case of -/// failure. -int FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object, +/// Function: FORM_OnMouseMove +/// Call this member function when the mouse cursor moves. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObj_CountMarks(page_object, + return _FORM_OnMouseMove(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObj_CountMarksPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CountMarks'); -late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); +late final _FORM_OnMouseMovePtr = _lookup< + ffi.NativeFunction>('FORM_OnMouseMove'); +late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction(); -/// Experimental API. -/// Get content mark in |page_object| at |index|. -/// -/// page_object - handle to a page object. -/// index - the index of a page object. -/// -/// Returns the handle to the content mark, or NULL on failure. The handle is -/// still owned by the library, and it should not be freed directly. It becomes -/// invalid if the page object is destroyed, either directly or indirectly by -/// unloading the page. -FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, -int index, +/// Experimental API +/// Function: FORM_OnMouseWheel +/// Call this member function when the user scrolls the mouse wheel. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_coord - Specifies the coordinates of the cursor in PDF user +/// space. +/// delta_x - Specifies the amount of wheel movement on the x-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean left. +/// delta_y - Specifies the amount of wheel movement on the y-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean down. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// For |delta_x| and |delta_y|, the caller must normalize +/// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 +/// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines +/// WHEEL_DELTA as 120. +int FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +ffi.Pointer page_coord, +int delta_x, +int delta_y, ) { - return _FPDFPageObj_GetMark(page_object, -index, + return _FORM_OnMouseWheel(hHandle, +page, +modifier, +page_coord, +delta_x, +delta_y, ); } -late final _FPDFPageObj_GetMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetMark'); -late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction(); +late final _FORM_OnMouseWheelPtr = _lookup< + ffi.NativeFunction , ffi.Int , ffi.Int )>>('FORM_OnMouseWheel'); +late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction , int , int )>(); -/// Experimental API. -/// Add a new content mark to a |page_object|. -/// -/// page_object - handle to a page object. -/// name - the name (tag) of the mark. -/// -/// Returns the handle to the content mark, or NULL on failure. The handle is -/// still owned by the library, and it should not be freed directly. It becomes -/// invalid if the page object is destroyed, either directly or indirectly by -/// unloading the page. -FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, -FPDF_BYTESTRING name, +/// Function: FORM_OnFocus +/// This function focuses the form annotation at a given point. If the +/// annotation at the point already has focus, nothing happens. If there +/// is no annotation at the point, removes form focus. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True if there is an annotation at the given point and it has focus. +int FORM_OnFocus(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObj_AddMark(page_object, -name, + return _FORM_OnFocus(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObj_AddMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_AddMark'); -late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction(); +late final _FORM_OnFocusPtr = _lookup< + ffi.NativeFunction>('FORM_OnFocus'); +late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction(); -/// Experimental API. -/// Removes a content |mark| from a |page_object|. -/// The mark handle will be invalid after the removal. -/// -/// page_object - handle to a page object. -/// mark - handle to a content mark in that object to remove. -/// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObj_RemoveMark(FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, +/// Function: FORM_OnLButtonDown +/// Call this member function when the user presses the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObj_RemoveMark(page_object, -mark, + return _FORM_OnLButtonDown(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObj_RemoveMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_RemoveMark'); -late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction(); +late final _FORM_OnLButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDown'); +late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction(); -/// Experimental API. -/// Get the name of a content mark. -/// -/// mark - handle to a content mark. -/// buffer - buffer for holding the returned name in UTF-16LE. This is only -/// modified if |buflen| is large enough to store the name. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +/// Function: FORM_OnRButtonDown +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObjMark_GetName(mark, -buffer, -buflen, -out_buflen, + return _FORM_OnRButtonDown(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObjMark_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetName'); -late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction , int , ffi.Pointer )>(); +late final _FORM_OnRButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonDown'); +late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction(); -/// Experimental API. -/// Get the number of key/value pair parameters in |mark|. -/// -/// mark - handle to a content mark. -/// -/// Returns the number of key/value pair parameters |mark|, or -1 in case of -/// failure. -int FPDFPageObjMark_CountParams(FPDF_PAGEOBJECTMARK mark, +/// Function: FORM_OnLButtonUp +/// Call this member function when the user releases the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in device. +/// page_y - Specifies the y-coordinate of the cursor in device. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObjMark_CountParams(mark, -); -} - -late final _FPDFPageObjMark_CountParamsPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_CountParams'); -late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr.asFunction(); + return _FORM_OnLButtonUp(hHandle, +page, +modifier, +page_x, +page_y, +); +} -/// Experimental API. -/// Get the key of a property in a content mark. -/// -/// mark - handle to a content mark. -/// index - index of the property. -/// buffer - buffer for holding the returned key in UTF-16LE. This is only -/// modified if |buflen| is large enough to store the key. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the operation was successful, FALSE otherwise. -int FPDFPageObjMark_GetParamKey(FPDF_PAGEOBJECTMARK mark, -int index, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +late final _FORM_OnLButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonUp'); +late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction(); + +/// Function: FORM_OnRButtonUp +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObjMark_GetParamKey(mark, -index, -buffer, -buflen, -out_buflen, + return _FORM_OnRButtonUp(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamKey'); -late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction , int , ffi.Pointer )>(); +late final _FORM_OnRButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonUp'); +late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction(); -/// Experimental API. -/// Get the type of the value of a property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// -/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. -int FPDFPageObjMark_GetParamValueType(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, +/// Function: FORM_OnLButtonDoubleClick +/// Call this member function when the user double clicks the +/// left mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDoubleClick(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, ) { - return _FPDFPageObjMark_GetParamValueType(mark, -key, + return _FORM_OnLButtonDoubleClick(hHandle, +page, +modifier, +page_x, +page_y, ); } -late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_GetParamValueType'); -late final _FPDFPageObjMark_GetParamValueType = _FPDFPageObjMark_GetParamValueTypePtr.asFunction(); +late final _FORM_OnLButtonDoubleClickPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDoubleClick'); +late final _FORM_OnLButtonDoubleClick = _FORM_OnLButtonDoubleClickPtr.asFunction(); -/// Experimental API. -/// Get the value of a number property in a content mark by key as int. -/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER -/// for this property. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// out_value - pointer to variable that will receive the value. Not filled if -/// false is returned. -/// -/// Returns TRUE if the key maps to a number value, FALSE otherwise. -int FPDFPageObjMark_GetParamIntValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer out_value, +/// Function: FORM_OnKeyDown +/// Call this member function when a nonsystem key is pressed. +/// Parameters: +/// hHandle - Handle to the form fill module, aseturned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, ) { - return _FPDFPageObjMark_GetParamIntValue(mark, -key, -out_value, + return _FORM_OnKeyDown(hHandle, +page, +nKeyCode, +modifier, ); } -late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObjMark_GetParamIntValue'); -late final _FPDFPageObjMark_GetParamIntValue = _FPDFPageObjMark_GetParamIntValuePtr.asFunction )>(); +late final _FORM_OnKeyDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyDown'); +late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction(); -/// Experimental API. -/// Get the value of a string property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// buffer - buffer for holding the returned value in UTF-16LE. This is -/// only modified if |buflen| is large enough to store the value. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. -int FPDFPageObjMark_GetParamStringValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +/// Function: FORM_OnKeyUp +/// Call this member function when a nonsystem key is released. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// Currently unimplemented and always returns false. PDFium reserves this +/// API and may implement it in the future on an as-needed basis. +int FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, ) { - return _FPDFPageObjMark_GetParamStringValue(mark, -key, -buffer, -buflen, -out_buflen, + return _FORM_OnKeyUp(hHandle, +page, +nKeyCode, +modifier, ); } -late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamStringValue'); -late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction , int , ffi.Pointer )>(); +late final _FORM_OnKeyUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyUp'); +late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction(); -/// Experimental API. -/// Get the value of a blob property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// buffer - buffer for holding the returned value. This is only modified -/// if |buflen| is large enough to store the value. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. -int FPDFPageObjMark_GetParamBlobValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +/// Function: FORM_OnChar +/// Call this member function when a keystroke translates to a +/// nonsystem character. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nChar - The character code value itself. +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnChar(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nChar, +int modifier, ) { - return _FPDFPageObjMark_GetParamBlobValue(mark, -key, -buffer, -buflen, -out_buflen, + return _FORM_OnChar(hHandle, +page, +nChar, +modifier, ); } -late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamBlobValue'); -late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction , int , ffi.Pointer )>(); +late final _FORM_OnCharPtr = _lookup< + ffi.NativeFunction>('FORM_OnChar'); +late final _FORM_OnChar = _FORM_OnCharPtr.asFunction(); -/// Experimental API. -/// Set the value of an int property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - int value to set. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetIntParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -int value, +/// Experimental API +/// Function: FORM_GetFocusedText +/// Call this function to obtain the text within the current focused +/// field, if any. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the form text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the form text string, |buffer| is +/// not modified. +/// Return Value: +/// Length in bytes for the text in the focused field. +int FORM_GetFocusedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPageObjMark_SetIntParam(document, -page_object, -mark, -key, -value, + return _FORM_GetFocusedText(hHandle, +page, +buffer, +buflen, ); } -late final _FPDFPageObjMark_SetIntParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_SetIntParam'); -late final _FPDFPageObjMark_SetIntParam = _FPDFPageObjMark_SetIntParamPtr.asFunction(); +late final _FORM_GetFocusedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetFocusedText'); +late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction , int )>(); -/// Experimental API. -/// Set the value of a string property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - string value to set. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetStringParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -FPDF_BYTESTRING value, +/// Function: FORM_GetSelectedText +/// Call this function to obtain selected text within a form text +/// field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the selected text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the selected text string, +/// |buffer| is not modified. +/// Return Value: +/// Length in bytes of selected text in form text field or form combobox +/// text field. +int FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFPageObjMark_SetStringParam(document, -page_object, -mark, -key, -value, + return _FORM_GetSelectedText(hHandle, +page, +buffer, +buflen, ); } -late final _FPDFPageObjMark_SetStringParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_SetStringParam'); -late final _FPDFPageObjMark_SetStringParam = _FPDFPageObjMark_SetStringParamPtr.asFunction(); +late final _FORM_GetSelectedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetSelectedText'); +late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction , int )>(); -/// Experimental API. -/// Set the value of a blob property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - pointer to blob value to set. -/// value_len - size in bytes of |value|. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetBlobParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer value, -int value_len, +/// Experimental API +/// Function: FORM_ReplaceAndKeepSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the inserted text +/// will be selected. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceAndKeepSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, ) { - return _FPDFPageObjMark_SetBlobParam(document, -page_object, -mark, -key, -value, -value_len, + return _FORM_ReplaceAndKeepSelection(hHandle, +page, +wsText, ); } -late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPageObjMark_SetBlobParam'); -late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction , int )>(); +late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceAndKeepSelection'); +late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr.asFunction(); -/// Experimental API. -/// Removes a property from a content mark by key. -/// -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_RemoveParam(FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, +/// Function: FORM_ReplaceSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the selection range +/// will be set to empty. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, ) { - return _FPDFPageObjMark_RemoveParam(page_object, -mark, -key, + return _FORM_ReplaceSelection(hHandle, +page, +wsText, ); } -late final _FPDFPageObjMark_RemoveParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_RemoveParam'); -late final _FPDFPageObjMark_RemoveParam = _FPDFPageObjMark_RemoveParamPtr.asFunction(); +late final _FORM_ReplaceSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceSelection'); +late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction(); -/// Load an image from a JPEG image file and then set it into |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// file_access - file access handler which specifies the JPEG image file. -/// -/// Returns TRUE on success. -/// -/// The image object might already have an associated image, which is shared and -/// cached by the loaded pages. In that case, we need to clear the cached image -/// for all the loaded pages. Pass |pages| and page count (|count|) to this API -/// to clear the image cache. If the image is not previously shared, or NULL is a -/// valid |pages| value. -int FPDFImageObj_LoadJpegFile(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -ffi.Pointer file_access, +/// Experimental API +/// Function: FORM_SelectAllText +/// Call this function to select all the text within the currently focused +/// form text field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// Whether the operation succeeded or not. +int FORM_SelectAllText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, ) { - return _FPDFImageObj_LoadJpegFile(pages, -count, -image_object, -file_access, + return _FORM_SelectAllText(hHandle, +page, ); } -late final _FPDFImageObj_LoadJpegFilePtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFile'); -late final _FPDFImageObj_LoadJpegFile = _FPDFImageObj_LoadJpegFilePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); - -/// Load an image from a JPEG image file and then set it into |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// file_access - file access handler which specifies the JPEG image file. -/// -/// Returns TRUE on success. -/// -/// The image object might already have an associated image, which is shared and -/// cached by the loaded pages. In that case, we need to clear the cached image -/// for all the loaded pages. Pass |pages| and page count (|count|) to this API -/// to clear the image cache. If the image is not previously shared, or NULL is a -/// valid |pages| value. This function loads the JPEG image inline, so the image -/// content is copied to the file. This allows |file_access| and its associated -/// data to be deleted after this function returns. -int FPDFImageObj_LoadJpegFileInline(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -ffi.Pointer file_access, -) { - return _FPDFImageObj_LoadJpegFileInline(pages, -count, -image_object, -file_access, -); -} +late final _FORM_SelectAllTextPtr = _lookup< + ffi.NativeFunction>('FORM_SelectAllText'); +late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction(); -late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFileInline'); -late final _FPDFImageObj_LoadJpegFileInline = _FPDFImageObj_LoadJpegFileInlinePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); - -/// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. -/// -/// Set the transform matrix of |image_object|. -/// -/// image_object - handle to an image object. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |image_object|. -/// -/// Returns TRUE on success. -int FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, -double a, -double b, -double c, -double d, -double e, -double f, +/// Function: FORM_CanUndo +/// Find out if it is possible for the current focused widget in a given +/// form to perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to undo. +int FORM_CanUndo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, ) { - return _FPDFImageObj_SetMatrix(image_object, -a, -b, -c, -d, -e, -f, + return _FORM_CanUndo(hHandle, +page, ); } -late final _FPDFImageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_SetMatrix'); -late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction(); +late final _FORM_CanUndoPtr = _lookup< + ffi.NativeFunction>('FORM_CanUndo'); +late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); -/// Set |bitmap| to |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// bitmap - handle of the bitmap. -/// -/// Returns TRUE on success. -int FPDFImageObj_SetBitmap(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -FPDF_BITMAP bitmap, +/// Function: FORM_CanRedo +/// Find out if it is possible for the current focused widget in a given +/// form to perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to redo. +int FORM_CanRedo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, ) { - return _FPDFImageObj_SetBitmap(pages, -count, -image_object, -bitmap, + return _FORM_CanRedo(hHandle, +page, ); } -late final _FPDFImageObj_SetBitmapPtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , FPDF_BITMAP )>>('FPDFImageObj_SetBitmap'); -late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction , int , FPDF_PAGEOBJECT , FPDF_BITMAP )>(); +late final _FORM_CanRedoPtr = _lookup< + ffi.NativeFunction>('FORM_CanRedo'); +late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); -/// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only -/// operates on |image_object| and does not take the associated image mask into -/// account. It also ignores the matrix for |image_object|. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. -/// -/// image_object - handle to an image object. -/// -/// Returns the bitmap. -FPDF_BITMAP FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object, +/// Function: FORM_Undo +/// Make the current focused widget perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the undo operation succeeded. +int FORM_Undo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, ) { - return _FPDFImageObj_GetBitmap(image_object, + return _FORM_Undo(hHandle, +page, ); } -late final _FPDFImageObj_GetBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetBitmap'); -late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction(); +late final _FORM_UndoPtr = _lookup< + ffi.NativeFunction>('FORM_Undo'); +late final _FORM_Undo = _FORM_UndoPtr.asFunction(); -/// Experimental API. -/// Get a bitmap rasterization of |image_object| that takes the image mask and -/// image matrix into account. To render correctly, the caller must provide the -/// |document| associated with |image_object|. If there is a |page| associated -/// with |image_object|, the caller should provide that as well. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. -/// -/// document - handle to a document associated with |image_object|. -/// page - handle to an optional page associated with |image_object|. -/// image_object - handle to an image object. -/// -/// Returns the bitmap or NULL on failure. -FPDF_BITMAP FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, +/// Function: FORM_Redo +/// Make the current focused widget perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the redo operation succeeded. +int FORM_Redo(FPDF_FORMHANDLE hHandle, FPDF_PAGE page, -FPDF_PAGEOBJECT image_object, ) { - return _FPDFImageObj_GetRenderedBitmap(document, + return _FORM_Redo(hHandle, page, -image_object, ); } -late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetRenderedBitmap'); -late final _FPDFImageObj_GetRenderedBitmap = _FPDFImageObj_GetRenderedBitmapPtr.asFunction(); +late final _FORM_RedoPtr = _lookup< + ffi.NativeFunction>('FORM_Redo'); +late final _FORM_Redo = _FORM_RedoPtr.asFunction(); -/// Get the decoded image data of |image_object|. The decoded data is the -/// uncompressed image data, i.e. the raw image data after having all filters -/// applied. |buffer| is only modified if |buflen| is longer than the length of -/// the decoded image data. -/// -/// image_object - handle to an image object. -/// buffer - buffer for holding the decoded image data. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the decoded image data. -int FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, -ffi.Pointer buffer, -int buflen, +/// Function: FORM_ForceToKillFocus. +/// Call this member function to force to kill the focus of the form +/// field which has focus. If it would kill the focus of a form field, +/// save the value of form field if was changed by theuser. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle, ) { - return _FPDFImageObj_GetImageDataDecoded(image_object, -buffer, -buflen, + return _FORM_ForceToKillFocus(hHandle, ); } -late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataDecoded'); -late final _FPDFImageObj_GetImageDataDecoded = _FPDFImageObj_GetImageDataDecodedPtr.asFunction , int )>(); +late final _FORM_ForceToKillFocusPtr = _lookup< + ffi.NativeFunction>('FORM_ForceToKillFocus'); +late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); -/// Get the raw image data of |image_object|. The raw data is the image data as -/// stored in the PDF without applying any filters. |buffer| is only modified if -/// |buflen| is longer than the length of the raw image data. -/// -/// image_object - handle to an image object. -/// buffer - buffer for holding the raw image data. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the raw image data. -int FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, -ffi.Pointer buffer, -int buflen, +/// Experimental API. +/// Function: FORM_GetFocusedAnnot. +/// Call this member function to get the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page_index - Buffer to hold the index number of the page which +/// contains the focused annotation. 0 for the first page. +/// Can't be NULL. +/// annot - Buffer to hold the focused annotation. Can't be NULL. +/// Return Value: +/// On success, return true and write to the out parameters. Otherwise +/// return false and leave the out parameters unmodified. +/// Comments: +/// Not currently supported for XFA forms - will report no focused +/// annotation. +/// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| +/// by this function is no longer needed. +/// This will return true and set |page_index| to -1 and |annot| to NULL, +/// if there is no focused annotation. +int FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle, +ffi.Pointer page_index, +ffi.Pointer annot, ) { - return _FPDFImageObj_GetImageDataRaw(image_object, -buffer, -buflen, + return _FORM_GetFocusedAnnot(handle, +page_index, +annot, ); } -late final _FPDFImageObj_GetImageDataRawPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataRaw'); -late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr.asFunction , int )>(); +late final _FORM_GetFocusedAnnotPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FORM_GetFocusedAnnot'); +late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction , ffi.Pointer )>(); -/// Get the number of filters (i.e. decoders) of the image in |image_object|. -/// -/// image_object - handle to an image object. -/// -/// Returns the number of |image_object|'s filters. -int FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object, +/// Experimental API. +/// Function: FORM_SetFocusedAnnot. +/// Call this member function to set the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - Handle to an annotation. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() +/// instead. +int FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, ) { - return _FPDFImageObj_GetImageFilterCount(image_object, + return _FORM_SetFocusedAnnot(handle, +annot, ); } -late final _FPDFImageObj_GetImageFilterCountPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetImageFilterCount'); -late final _FPDFImageObj_GetImageFilterCount = _FPDFImageObj_GetImageFilterCountPtr.asFunction(); - -/// Get the filter at |index| of |image_object|'s list of filters. Note that the -/// filters need to be applied in order, i.e. the first filter should be applied -/// first, then the second, etc. |buffer| is only modified if |buflen| is longer -/// than the length of the filter string. -/// -/// image_object - handle to an image object. -/// index - the index of the filter requested. -/// buffer - buffer for holding filter string, encoded in UTF-8. -/// buflen - length of the buffer. -/// -/// Returns the length of the filter string. -int FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, -int index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFImageObj_GetImageFilter(image_object, -index, -buffer, -buflen, -); -} - -late final _FPDFImageObj_GetImageFilterPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageFilter'); -late final _FPDFImageObj_GetImageFilter = _FPDFImageObj_GetImageFilterPtr.asFunction , int )>(); +late final _FORM_SetFocusedAnnotPtr = _lookup< + ffi.NativeFunction>('FORM_SetFocusedAnnot'); +late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction(); -/// Get the image metadata of |image_object|, including dimension, DPI, bits per -/// pixel, and colorspace. If the |image_object| is not an image object or if it -/// does not have an image, then the return value will be false. Otherwise, -/// failure to retrieve any specific parameter would result in its value being 0. -/// -/// image_object - handle to an image object. -/// page - handle to the page that |image_object| is on. Required for -/// retrieving the image's bits per pixel and colorspace. -/// metadata - receives the image metadata; must not be NULL. -/// -/// Returns true if successful. -int FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, +/// Function: FPDFPage_HasFormFieldAtPoint +/// Get the form field type by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the type of the form field; -1 indicates no field. +/// See field types above. +int FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, FPDF_PAGE page, -ffi.Pointer metadata, +double page_x, +double page_y, ) { - return _FPDFImageObj_GetImageMetadata(image_object, + return _FPDFPage_HasFormFieldAtPoint(hHandle, page, -metadata, -); -} - -late final _FPDFImageObj_GetImageMetadataPtr = _lookup< - ffi.NativeFunction )>>('FPDFImageObj_GetImageMetadata'); -late final _FPDFImageObj_GetImageMetadata = _FPDFImageObj_GetImageMetadataPtr.asFunction )>(); - -/// Experimental API. -/// Get the image size in pixels. Faster method to get only image size. -/// -/// image_object - handle to an image object. -/// width - receives the image width in pixels; must not be NULL. -/// height - receives the image height in pixels; must not be NULL. -/// -/// Returns true if successful. -int FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, -ffi.Pointer width, -ffi.Pointer height, -) { - return _FPDFImageObj_GetImagePixelSize(image_object, -width, -height, +page_x, +page_y, ); } -late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFImageObj_GetImagePixelSize'); -late final _FPDFImageObj_GetImagePixelSize = _FPDFImageObj_GetImagePixelSizePtr.asFunction , ffi.Pointer )>(); +late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasFormFieldAtPoint'); +late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr.asFunction(); -/// Experimental API. -/// Get ICC profile decoded data of |image_object|. If the |image_object| is not -/// an image object or if it does not have an image, then the return value will -/// be false. It also returns false if the |image_object| has no ICC profile. -/// |buffer| is only modified if ICC profile exists and |buflen| is longer than -/// the length of the ICC profile decoded data. -/// -/// image_object - handle to an image object; must not be NULL. -/// page - handle to the page containing |image_object|; must not be -/// NULL. Required for retrieving the image's colorspace. -/// buffer - Buffer to receive ICC profile data; may be NULL if querying -/// required size via |out_buflen|. -/// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. -/// out_buflen - Pointer to receive the ICC profile data size in bytes; must -/// not be NULL. Will be set if this API returns true. -/// -/// Returns true if |out_buflen| is not null and an ICC profile exists for the -/// given |image_object|. -int FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, +/// Function: FPDFPage_FormFieldZOrderAtPoint +/// Get the form field z-order by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the z-order of the form field; -1 indicates no field. +/// Higher numbers are closer to the front. +int FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +double page_x, +double page_y, ) { - return _FPDFImageObj_GetIccProfileDataDecoded(image_object, + return _FPDFPage_FormFieldZOrderAtPoint(hHandle, page, -buffer, -buflen, -out_buflen, +page_x, +page_y, ); } -late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFImageObj_GetIccProfileDataDecoded'); -late final _FPDFImageObj_GetIccProfileDataDecoded = _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction , int , ffi.Pointer )>(); +late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_FormFieldZOrderAtPoint'); +late final _FPDFPage_FormFieldZOrderAtPoint = _FPDFPage_FormFieldZOrderAtPointPtr.asFunction(); -/// Create a new path object at an initial position. -/// -/// x - initial horizontal position. -/// y - initial vertical position. -/// -/// Returns a handle to a new path object. -FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath(double x, -double y, +/// Function: FPDF_SetFormFieldHighlightColor +/// Set the highlight color of the specified (or all) form fields +/// in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returned by +/// FPDF_LoadDocument(). +/// fieldType - A 32-bit integer indicating the type of a form +/// field (defined above). +/// color - The highlight color of the form field. Constructed by +/// 0xxxrrggbb. +/// Return Value: +/// None. +/// Comments: +/// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the +/// highlight color will be applied to all the form fields in the +/// document. +/// Please refresh the client window to show the highlight immediately +/// if necessary. +void FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, +int fieldType, +int color, ) { - return _FPDFPageObj_CreateNewPath(x, -y, + return _FPDF_SetFormFieldHighlightColor(hHandle, +fieldType, +color, ); } -late final _FPDFPageObj_CreateNewPathPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateNewPath'); -late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr.asFunction(); +late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightColor'); +late final _FPDF_SetFormFieldHighlightColor = _FPDF_SetFormFieldHighlightColorPtr.asFunction(); -/// Create a closed path consisting of a rectangle. -/// -/// x - horizontal position for the left boundary of the rectangle. -/// y - vertical position for the bottom boundary of the rectangle. -/// w - width of the rectangle. -/// h - height of the rectangle. -/// -/// Returns a handle to the new path object. -FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect(double x, -double y, -double w, -double h, +/// Function: FPDF_SetFormFieldHighlightAlpha +/// Set the transparency of the form field highlight color in the +/// document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returaned by +/// FPDF_LoadDocument(). +/// alpha - The transparency of the form field highlight color, +/// between 0-255. +/// Return Value: +/// None. +void FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, +int alpha, ) { - return _FPDFPageObj_CreateNewRect(x, -y, -w, -h, + return _FPDF_SetFormFieldHighlightAlpha(hHandle, +alpha, ); } -late final _FPDFPageObj_CreateNewRectPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateNewRect'); -late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr.asFunction(); +late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightAlpha'); +late final _FPDF_SetFormFieldHighlightAlpha = _FPDF_SetFormFieldHighlightAlphaPtr.asFunction(); -/// Get the bounding box of |page_object|. -/// -/// page_object - handle to a page object. -/// left - pointer where the left coordinate will be stored -/// bottom - pointer where the bottom coordinate will be stored -/// right - pointer where the right coordinate will be stored -/// top - pointer where the top coordinate will be stored -/// -/// On success, returns TRUE and fills in the 4 coordinates. -int FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, +/// Function: FPDF_RemoveFormFieldHighlight +/// Remove the form field highlight color in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// Please refresh the client window to remove the highlight immediately +/// if necessary. +void FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle, ) { - return _FPDFPageObj_GetBounds(page_object, -left, -bottom, -right, -top, + return _FPDF_RemoveFormFieldHighlight(hHandle, ); } -late final _FPDFPageObj_GetBoundsPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetBounds'); -late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Get the quad points that bounds |page_object|. -/// -/// page_object - handle to a page object. -/// quad_points - pointer where the quadrilateral points will be stored. -/// -/// On success, returns TRUE and fills in |quad_points|. -/// -/// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page -/// object. When the object is rotated by a non-multiple of 90 degrees, this API -/// returns a tighter bound that cannot be represented with just the 4 sides of -/// a rectangle. -/// -/// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and -/// FPDF_PAGEOBJ_IMAGE. -int FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, -ffi.Pointer quad_points, -) { - return _FPDFPageObj_GetRotatedBounds(page_object, -quad_points, -); -} - -late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetRotatedBounds'); -late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr.asFunction )>(); +late final _FPDF_RemoveFormFieldHighlightPtr = _lookup< + ffi.NativeFunction>('FPDF_RemoveFormFieldHighlight'); +late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr.asFunction(); -/// Set the blend mode of |page_object|. -/// -/// page_object - handle to a page object. -/// blend_mode - string containing the blend mode. -/// -/// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, -/// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, -/// Overlay, Saturation, Screen, SoftLight -void FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, -FPDF_BYTESTRING blend_mode, +/// Function: FPDF_FFLDraw +/// Render FormFields and popup window on a page to a device independent +/// bitmap. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handles can be created by +/// FPDFBitmap_Create(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// start_x - Left pixel position of the display area in the +/// device coordinates. +/// start_y - Top pixel position of the display area in the device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees +/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 +/// degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined above. +/// Return Value: +/// None. +/// Comments: +/// This function is designed to render annotations that are +/// user-interactive, which are widget annotations (for FormFields) and +/// popup annotations. +/// With the FPDF_ANNOT flag, this function will render a popup annotation +/// when users mouse-hover on a non-widget annotation. Regardless of +/// FPDF_ANNOT flag, this function will always render widget annotations +/// for FormFields. +/// In order to implement the FormFill functions, implementation should +/// call this function after rendering functions, such as +/// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have +/// finished rendering the page contents. +void FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, +FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, ) { - return _FPDFPageObj_SetBlendMode(page_object, -blend_mode, + return _FPDF_FFLDraw(hHandle, +bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, ); } -late final _FPDFPageObj_SetBlendModePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetBlendMode'); -late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr.asFunction(); +late final _FPDF_FFLDrawPtr = _lookup< + ffi.NativeFunction>('FPDF_FFLDraw'); +late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction(); -/// Set the stroke RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component for the object's stroke color. -/// G - the green component for the object's stroke color. -/// B - the blue component for the object's stroke color. -/// A - the stroke alpha for the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, -int R, -int G, -int B, -int A, +/// Experimental API +/// Function: FPDF_GetFormType +/// Returns the type of form contained in the PDF document. +/// Parameters: +/// document - Handle to document. +/// Return Value: +/// Integer value representing one of the FORMTYPE_ values. +/// Comments: +/// If |document| is NULL, then the return value is FORMTYPE_NONE. +int FPDF_GetFormType(FPDF_DOCUMENT document, ) { - return _FPDFPageObj_SetStrokeColor(page_object, -R, -G, -B, -A, + return _FPDF_GetFormType(document, ); } -late final _FPDFPageObj_SetStrokeColorPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetStrokeColor'); -late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr.asFunction(); +late final _FPDF_GetFormTypePtr = _lookup< + ffi.NativeFunction>('FPDF_GetFormType'); +late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); -/// Get the stroke RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component of the path stroke color. -/// G - the green component of the object's stroke color. -/// B - the blue component of the object's stroke color. -/// A - the stroke alpha of the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetStrokeColor(FPDF_PAGEOBJECT page_object, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, +/// Experimental API +/// Function: FORM_SetIndexSelected +/// Selects/deselects the value at the given |index| of the focused +/// annotation. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based index of value to be set as +/// selected/unselected +/// selected - true to select, false to deselect +/// Return Value: +/// TRUE if the operation succeeded. +/// FALSE if the operation failed or widget is not a supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Comboboxes +/// have at most a single value selected at a time which cannot be +/// deselected. Deselect on a combobox is a no-op that returns false. +/// Default implementation is a no-op that will return false for +/// other types. +/// Not currently supported for XFA forms - will return false. +int FORM_SetIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, +int selected, ) { - return _FPDFPageObj_GetStrokeColor(page_object, -R, -G, -B, -A, + return _FORM_SetIndexSelected(hHandle, +page, +index, +selected, ); } -late final _FPDFPageObj_GetStrokeColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetStrokeColor'); -late final _FPDFPageObj_GetStrokeColor = _FPDFPageObj_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FORM_SetIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_SetIndexSelected'); +late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction(); -/// Set the stroke width of a page object. -/// -/// path - the handle to the page object. -/// width - the width of the stroke. -/// -/// Returns TRUE on success -int FPDFPageObj_SetStrokeWidth(FPDF_PAGEOBJECT page_object, -double width, +/// Experimental API +/// Function: FORM_IsIndexSelected +/// Returns whether or not the value at |index| of the focused +/// annotation is currently selected. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based Index of value to check +/// Return Value: +/// TRUE if value at |index| is currently selected. +/// FALSE if value at |index| is not selected or widget is not a +/// supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Default +/// implementation is a no-op that will return false for other types. +/// Not currently supported for XFA forms - will return false. +int FORM_IsIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, ) { - return _FPDFPageObj_SetStrokeWidth(page_object, -width, + return _FORM_IsIndexSelected(hHandle, +page, +index, ); } -late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetStrokeWidth'); -late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr.asFunction(); +late final _FORM_IsIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_IsIndexSelected'); +late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction(); -/// Get the stroke width of a page object. -/// -/// path - the handle to the page object. -/// width - the width of the stroke. -/// -/// Returns TRUE on success -int FPDFPageObj_GetStrokeWidth(FPDF_PAGEOBJECT page_object, -ffi.Pointer width, +/// Function: FPDF_LoadXFA +/// If the document consists of XFA fields, call this method to +/// attempt to load XFA fields. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// Return Value: +/// TRUE upon success, otherwise FALSE. If XFA support is not built +/// into PDFium, performs no action and always returns FALSE. +int FPDF_LoadXFA(FPDF_DOCUMENT document, ) { - return _FPDFPageObj_GetStrokeWidth(page_object, -width, + return _FPDF_LoadXFA(document, ); } -late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetStrokeWidth'); -late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr.asFunction )>(); +late final _FPDF_LoadXFAPtr = _lookup< + ffi.NativeFunction>('FPDF_LoadXFA'); +late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); -/// Get the line join of |page_object|. +/// Experimental API. +/// Check if an annotation subtype is currently supported for creation. +/// Currently supported subtypes: +/// - circle +/// - fileattachment +/// - freetext +/// - highlight +/// - ink +/// - link +/// - popup +/// - square, +/// - squiggly +/// - stamp +/// - strikeout +/// - text +/// - underline /// -/// page_object - handle to a page object. +/// subtype - the subtype to be checked. /// -/// Returns the line join, or -1 on failure. -/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, -/// FPDF_LINEJOIN_BEVEL -int FPDFPageObj_GetLineJoin(FPDF_PAGEOBJECT page_object, +/// Returns true if this subtype supported. +int FPDFAnnot_IsSupportedSubtype(int subtype, ) { - return _FPDFPageObj_GetLineJoin(page_object, + return _FPDFAnnot_IsSupportedSubtype(subtype, ); } -late final _FPDFPageObj_GetLineJoinPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetLineJoin'); -late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); +late final _FPDFAnnot_IsSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsSupportedSubtype'); +late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); -/// Set the line join of |page_object|. +/// Experimental API. +/// Create an annotation in |page| of the subtype |subtype|. If the specified +/// subtype is illegal or unsupported, then a new annotation will not be created. +/// Must call FPDFPage_CloseAnnot() when the annotation returned by this +/// function is no longer needed. /// -/// page_object - handle to a page object. -/// line_join - line join +/// page - handle to a page. +/// subtype - the subtype of the new annotation. /// -/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, -/// FPDF_LINEJOIN_BEVEL -int FPDFPageObj_SetLineJoin(FPDF_PAGEOBJECT page_object, -int line_join, +/// Returns a handle to the new annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_CreateAnnot(FPDF_PAGE page, +int subtype, ) { - return _FPDFPageObj_SetLineJoin(page_object, -line_join, + return _FPDFPage_CreateAnnot(page, +subtype, ); } -late final _FPDFPageObj_SetLineJoinPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetLineJoin'); -late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction(); +late final _FPDFPage_CreateAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CreateAnnot'); +late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction(); -/// Get the line cap of |page_object|. +/// Experimental API. +/// Get the number of annotations in |page|. /// -/// page_object - handle to a page object. +/// page - handle to a page. /// -/// Returns the line cap, or -1 on failure. -/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, -/// FPDF_LINECAP_PROJECTING_SQUARE -int FPDFPageObj_GetLineCap(FPDF_PAGEOBJECT page_object, +/// Returns the number of annotations in |page|. +int FPDFPage_GetAnnotCount(FPDF_PAGE page, ) { - return _FPDFPageObj_GetLineCap(page_object, + return _FPDFPage_GetAnnotCount(page, ); } -late final _FPDFPageObj_GetLineCapPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetLineCap'); -late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); +late final _FPDFPage_GetAnnotCountPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotCount'); +late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); -/// Set the line cap of |page_object|. +/// Experimental API. +/// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the +/// annotation returned by this function is no longer needed. /// -/// page_object - handle to a page object. -/// line_cap - line cap +/// page - handle to a page. +/// index - the index of the annotation. /// -/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, -/// FPDF_LINECAP_PROJECTING_SQUARE -int FPDFPageObj_SetLineCap(FPDF_PAGEOBJECT page_object, -int line_cap, +/// Returns a handle to the annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_GetAnnot(FPDF_PAGE page, +int index, ) { - return _FPDFPageObj_SetLineCap(page_object, -line_cap, + return _FPDFPage_GetAnnot(page, +index, ); } -late final _FPDFPageObj_SetLineCapPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetLineCap'); -late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction(); +late final _FPDFPage_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnot'); +late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction(); -/// Set the fill RGBA of a page object. Range of values: 0 - 255. +/// Experimental API. +/// Get the index of |annot| in |page|. This is the opposite of +/// FPDFPage_GetAnnot(). /// -/// page_object - the handle to the page object. -/// R - the red component for the object's fill color. -/// G - the green component for the object's fill color. -/// B - the blue component for the object's fill color. -/// A - the fill alpha for the object. +/// page - handle to the page that the annotation is on. +/// annot - handle to an annotation. /// -/// Returns TRUE on success. -int FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, -int R, -int G, -int B, -int A, +/// Returns the index of |annot|, or -1 on failure. +int FPDFPage_GetAnnotIndex(FPDF_PAGE page, +FPDF_ANNOTATION annot, ) { - return _FPDFPageObj_SetFillColor(page_object, -R, -G, -B, -A, + return _FPDFPage_GetAnnotIndex(page, +annot, ); } -late final _FPDFPageObj_SetFillColorPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetFillColor'); -late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr.asFunction(); +late final _FPDFPage_GetAnnotIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotIndex'); +late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction(); -/// Get the fill RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component of the object's fill color. -/// G - the green component of the object's fill color. -/// B - the blue component of the object's fill color. -/// A - the fill alpha of the object. +/// Experimental API. +/// Close an annotation. Must be called when the annotation returned by +/// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This +/// function does not remove the annotation from the document. /// -/// Returns TRUE on success. -int FPDFPageObj_GetFillColor(FPDF_PAGEOBJECT page_object, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, +/// annot - handle to an annotation. +void FPDFPage_CloseAnnot(FPDF_ANNOTATION annot, ) { - return _FPDFPageObj_GetFillColor(page_object, -R, -G, -B, -A, + return _FPDFPage_CloseAnnot(annot, ); } -late final _FPDFPageObj_GetFillColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetFillColor'); -late final _FPDFPageObj_GetFillColor = _FPDFPageObj_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +late final _FPDFPage_CloseAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CloseAnnot'); +late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); /// Experimental API. -/// Get the line dash |phase| of |page_object|. +/// Remove the annotation in |page| at |index|. /// -/// page_object - handle to a page object. -/// phase - pointer where the dashing phase will be stored. +/// page - handle to a page. +/// index - the index of the annotation. /// -/// Returns TRUE on success. -int FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, -ffi.Pointer phase, +/// Returns true if successful. +int FPDFPage_RemoveAnnot(FPDF_PAGE page, +int index, ) { - return _FPDFPageObj_GetDashPhase(page_object, -phase, + return _FPDFPage_RemoveAnnot(page, +index, ); } -late final _FPDFPageObj_GetDashPhasePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetDashPhase'); -late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr.asFunction )>(); +late final _FPDFPage_RemoveAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveAnnot'); +late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); /// Experimental API. -/// Set the line dash phase of |page_object|. +/// Get the subtype of an annotation. /// -/// page_object - handle to a page object. -/// phase - line dash phase. +/// annot - handle to an annotation. /// -/// Returns TRUE on success. -int FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, -double phase, +/// Returns the annotation subtype. +int FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot, ) { - return _FPDFPageObj_SetDashPhase(page_object, -phase, + return _FPDFAnnot_GetSubtype(annot, ); } -late final _FPDFPageObj_SetDashPhasePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetDashPhase'); -late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr.asFunction(); +late final _FPDFAnnot_GetSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetSubtype'); +late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); /// Experimental API. -/// Get the line dash array of |page_object|. +/// Check if an annotation subtype is currently supported for object extraction, +/// update, and removal. +/// Currently supported subtypes: ink and stamp. /// -/// page_object - handle to a page object. +/// subtype - the subtype to be checked. /// -/// Returns the line dash array size or -1 on failure. -int FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object, +/// Returns true if this subtype supported. +int FPDFAnnot_IsObjectSupportedSubtype(int subtype, ) { - return _FPDFPageObj_GetDashCount(page_object, + return _FPDFAnnot_IsObjectSupportedSubtype(subtype, ); } -late final _FPDFPageObj_GetDashCountPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetDashCount'); -late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); +late final _FPDFAnnot_IsObjectSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsObjectSupportedSubtype'); +late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); /// Experimental API. -/// Get the line dash array of |page_object|. +/// Update |obj| in |annot|. |obj| must be in |annot| already and must have +/// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp +/// annotations are supported by this API. Also note that only path, image, and +/// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and +/// FPDFImageObj_*(). /// -/// page_object - handle to a page object. -/// dash_array - pointer where the dashing array will be stored. -/// dash_count - number of elements in |dash_array|. +/// annot - handle to an annotation. +/// obj - handle to the object that |annot| needs to update. /// -/// Returns TRUE on success. -int FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object, -ffi.Pointer dash_array, -int dash_count, +/// Return true if successful. +int FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, ) { - return _FPDFPageObj_GetDashArray(page_object, -dash_array, -dash_count, + return _FPDFAnnot_UpdateObject(annot, +obj, ); } -late final _FPDFPageObj_GetDashArrayPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFPageObj_GetDashArray'); -late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr.asFunction , int )>(); +late final _FPDFAnnot_UpdateObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_UpdateObject'); +late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction(); /// Experimental API. -/// Set the line dash array of |page_object|. +/// Add a new InkStroke, represented by an array of points, to the InkList of +/// |annot|. The API creates an InkList if one doesn't already exist in |annot|. +/// This API works only for ink annotations. Please refer to ISO 32000-1:2008 +/// spec, section 12.5.6.13. /// -/// page_object - handle to a page object. -/// dash_array - the dash array. -/// dash_count - number of elements in |dash_array|. -/// phase - the line dash phase. +/// annot - handle to an annotation. +/// points - pointer to a FS_POINTF array representing input points. +/// point_count - number of elements in |points| array. This should not exceed +/// the maximum value that can be represented by an int32_t). /// -/// Returns TRUE on success. -int FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, -ffi.Pointer dash_array, -int dash_count, -double phase, +/// Returns the 0-based index at which the new InkStroke is added in the InkList +/// of the |annot|. Returns -1 on failure. +int FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot, +ffi.Pointer points, +int point_count, ) { - return _FPDFPageObj_SetDashArray(page_object, -dash_array, -dash_count, -phase, + return _FPDFAnnot_AddInkStroke(annot, +points, +point_count, ); } -late final _FPDFPageObj_SetDashArrayPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Float )>>('FPDFPageObj_SetDashArray'); -late final _FPDFPageObj_SetDashArray = _FPDFPageObj_SetDashArrayPtr.asFunction , int , double )>(); +late final _FPDFAnnot_AddInkStrokePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_AddInkStroke'); +late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction , int )>(); -/// Get number of segments inside |path|. -/// -/// path - handle to a path. +/// Experimental API. +/// Removes an InkList in |annot|. +/// This API works only for ink annotations. /// -/// A segment is a command, created by e.g. FPDFPath_MoveTo(), -/// FPDFPath_LineTo() or FPDFPath_BezierTo(). +/// annot - handle to an annotation. /// -/// Returns the number of objects in |path| or -1 on failure. -int FPDFPath_CountSegments(FPDF_PAGEOBJECT path, +/// Return true on successful removal of /InkList entry from context of the +/// non-null ink |annot|. Returns false on failure. +int FPDFAnnot_RemoveInkList(FPDF_ANNOTATION annot, ) { - return _FPDFPath_CountSegments(path, + return _FPDFAnnot_RemoveInkList(annot, ); } -late final _FPDFPath_CountSegmentsPtr = _lookup< - ffi.NativeFunction>('FPDFPath_CountSegments'); -late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); +late final _FPDFAnnot_RemoveInkListPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveInkList'); +late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); -/// Get segment in |path| at |index|. +/// Experimental API. +/// Add |obj| to |annot|. |obj| must have been created by +/// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and +/// will be owned by |annot|. Note that an |obj| cannot belong to more than one +/// |annot|. Currently, only ink and stamp annotations are supported by this API. +/// Also note that only path, image, and text objects have APIs for creation. /// -/// path - handle to a path. -/// index - the index of a segment. +/// annot - handle to an annotation. +/// obj - handle to the object that is to be added to |annot|. /// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_PATHSEGMENT FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, -int index, +/// Return true if successful. +int FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, ) { - return _FPDFPath_GetPathSegment(path, -index, + return _FPDFAnnot_AppendObject(annot, +obj, ); } -late final _FPDFPath_GetPathSegmentPtr = _lookup< - ffi.NativeFunction>('FPDFPath_GetPathSegment'); -late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction(); +late final _FPDFAnnot_AppendObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AppendObject'); +late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction(); -/// Get coordinates of |segment|. +/// Experimental API. +/// Get the total number of objects in |annot|, including path objects, text +/// objects, external objects, image objects, and shading objects. /// -/// segment - handle to a segment. -/// x - the horizontal position of the segment. -/// y - the vertical position of the segment. +/// annot - handle to an annotation. /// -/// Returns TRUE on success, otherwise |x| and |y| is not set. -int FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, -ffi.Pointer x, -ffi.Pointer y, +/// Returns the number of objects in |annot|. +int FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot, ) { - return _FPDFPathSegment_GetPoint(segment, -x, -y, + return _FPDFAnnot_GetObjectCount(annot, ); } -late final _FPDFPathSegment_GetPointPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFPathSegment_GetPoint'); -late final _FPDFPathSegment_GetPoint = _FPDFPathSegment_GetPointPtr.asFunction , ffi.Pointer )>(); +late final _FPDFAnnot_GetObjectCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObjectCount'); +late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); -/// Get type of |segment|. +/// Experimental API. +/// Get the object in |annot| at |index|. /// -/// segment - handle to a segment. +/// annot - handle to an annotation. +/// index - the index of the object. /// -/// Returns one of the FPDF_SEGMENT_* values on success, -/// FPDF_SEGMENT_UNKNOWN on error. -int FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment, +/// Return a handle to the object, or NULL on failure. +FPDF_PAGEOBJECT FPDFAnnot_GetObject(FPDF_ANNOTATION annot, +int index, ) { - return _FPDFPathSegment_GetType(segment, + return _FPDFAnnot_GetObject(annot, +index, ); } -late final _FPDFPathSegment_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFPathSegment_GetType'); -late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); +late final _FPDFAnnot_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObject'); +late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction(); -/// Gets if the |segment| closes the current subpath of a given path. +/// Experimental API. +/// Remove the object in |annot| at |index|. /// -/// segment - handle to a segment. +/// annot - handle to an annotation. +/// index - the index of the object to be removed. /// -/// Returns close flag for non-NULL segment, FALSE otherwise. -int FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment, +/// Return true if successful. +int FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, +int index, ) { - return _FPDFPathSegment_GetClose(segment, + return _FPDFAnnot_RemoveObject(annot, +index, ); } -late final _FPDFPathSegment_GetClosePtr = _lookup< - ffi.NativeFunction>('FPDFPathSegment_GetClose'); -late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); +late final _FPDFAnnot_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveObject'); +late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction(); -/// Move a path's current point. -/// -/// path - the handle to the path object. -/// x - the horizontal position of the new current point. -/// y - the vertical position of the new current point. +/// Experimental API. +/// Set the color of an annotation. Fails when called on annotations with +/// appearance streams already defined; instead use +/// FPDFPageObj_Set{Stroke|Fill}Color(). /// -/// Note that no line will be created between the previous current point and the -/// new one. +/// annot - handle to an annotation. +/// type - type of the color to be set. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. /// -/// Returns TRUE on success -int FPDFPath_MoveTo(FPDF_PAGEOBJECT path, -double x, -double y, +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +int R, +int G, +int B, +int A, ) { - return _FPDFPath_MoveTo(path, -x, -y, + return _FPDFAnnot_SetColor(annot, +type.value, +R, +G, +B, +A, ); } -late final _FPDFPath_MoveToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_MoveTo'); -late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction(); +late final _FPDFAnnot_SetColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetColor'); +late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction(); -/// Add a line between the current point and a new point in the path. -/// -/// path - the handle to the path object. -/// x - the horizontal position of the new point. -/// y - the vertical position of the new point. +/// Experimental API. +/// Get the color of an annotation. If no color is specified, default to yellow +/// for highlight annotation, black for all else. Fails when called on +/// annotations with appearance streams already defined; instead use +/// FPDFPageObj_Get{Stroke|Fill}Color(). /// -/// The path's current point is changed to (x, y). +/// annot - handle to an annotation. +/// type - type of the color requested. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. /// -/// Returns TRUE on success -int FPDFPath_LineTo(FPDF_PAGEOBJECT path, -double x, -double y, +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, ) { - return _FPDFPath_LineTo(path, -x, -y, + return _FPDFAnnot_GetColor(annot, +type.value, +R, +G, +B, +A, ); } -late final _FPDFPath_LineToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_LineTo'); -late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction(); +late final _FPDFAnnot_GetColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetColor'); +late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); -/// Add a cubic Bezier curve to the given path, starting at the current point. +/// Experimental API. +/// Check if the annotation is of a type that has attachment points +/// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that +/// encompasses the texts affected by the annotation. They provide the +/// coordinates in the page where the annotation is attached. Only text markup +/// annotations (i.e. highlight, strikeout, squiggly, and underline) and link +/// annotations have quadpoints. /// -/// path - the handle to the path object. -/// x1 - the horizontal position of the first Bezier control point. -/// y1 - the vertical position of the first Bezier control point. -/// x2 - the horizontal position of the second Bezier control point. -/// y2 - the vertical position of the second Bezier control point. -/// x3 - the horizontal position of the ending point of the Bezier curve. -/// y3 - the vertical position of the ending point of the Bezier curve. +/// annot - handle to an annotation. /// -/// Returns TRUE on success -int FPDFPath_BezierTo(FPDF_PAGEOBJECT path, -double x1, -double y1, -double x2, -double y2, -double x3, -double y3, -) { - return _FPDFPath_BezierTo(path, -x1, -y1, -x2, -y2, -x3, -y3, +/// Returns true if the annotation is of a type that has quadpoints, false +/// otherwise. +int FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_HasAttachmentPoints(annot, ); } -late final _FPDFPath_BezierToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_BezierTo'); -late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction(); +late final _FPDFAnnot_HasAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasAttachmentPoints'); +late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr.asFunction(); -/// Close the current subpath of a given path. -/// -/// path - the handle to the path object. +/// Experimental API. +/// Replace the attachment points (i.e. quadpoints) set of an annotation at +/// |quad_index|. This index needs to be within the result of +/// FPDFAnnot_CountAttachmentPoints(). +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. /// -/// This will add a line between the current point and the initial point of the -/// subpath, thus terminating the current subpath. +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - the quadpoints to be set. /// -/// Returns TRUE on success -int FPDFPath_Close(FPDF_PAGEOBJECT path, +/// Returns true if successful. +int FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, ) { - return _FPDFPath_Close(path, + return _FPDFAnnot_SetAttachmentPoints(annot, +quad_index, +quad_points, ); } -late final _FPDFPath_ClosePtr = _lookup< - ffi.NativeFunction>('FPDFPath_Close'); -late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); +late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetAttachmentPoints'); +late final _FPDFAnnot_SetAttachmentPoints = _FPDFAnnot_SetAttachmentPointsPtr.asFunction )>(); -/// Set the drawing mode of a path. +/// Experimental API. +/// Append to the list of attachment points (i.e. quadpoints) of an annotation. +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. /// -/// path - the handle to the path object. -/// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. -/// stroke - a boolean specifying if the path should be stroked or not. +/// annot - handle to an annotation. +/// quad_points - the quadpoints to be set. /// -/// Returns TRUE on success -int FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, -int fillmode, -int stroke, +/// Returns true if successful. +int FPDFAnnot_AppendAttachmentPoints(FPDF_ANNOTATION annot, +ffi.Pointer quad_points, ) { - return _FPDFPath_SetDrawMode(path, -fillmode, -stroke, + return _FPDFAnnot_AppendAttachmentPoints(annot, +quad_points, ); } -late final _FPDFPath_SetDrawModePtr = _lookup< - ffi.NativeFunction>('FPDFPath_SetDrawMode'); -late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction(); +late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_AppendAttachmentPoints'); +late final _FPDFAnnot_AppendAttachmentPoints = _FPDFAnnot_AppendAttachmentPointsPtr.asFunction )>(); -/// Get the drawing mode of a path. +/// Experimental API. +/// Get the number of sets of quadpoints of an annotation. /// -/// path - the handle to the path object. -/// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. -/// stroke - a boolean specifying if the path is stroked or not. +/// annot - handle to an annotation. /// -/// Returns TRUE on success -int FPDFPath_GetDrawMode(FPDF_PAGEOBJECT path, -ffi.Pointer fillmode, -ffi.Pointer stroke, +/// Returns the number of sets of quadpoints, or 0 on failure. +int FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot, ) { - return _FPDFPath_GetDrawMode(path, -fillmode, -stroke, + return _FPDFAnnot_CountAttachmentPoints(annot, ); } -late final _FPDFPath_GetDrawModePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFPath_GetDrawMode'); -late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction , ffi.Pointer )>(); +late final _FPDFAnnot_CountAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_CountAttachmentPoints'); +late final _FPDFAnnot_CountAttachmentPoints = _FPDFAnnot_CountAttachmentPointsPtr.asFunction(); -/// Create a new text object using one of the standard PDF fonts. +/// Experimental API. +/// Get the attachment points (i.e. quadpoints) of an annotation. /// -/// document - handle to the document. -/// font - string containing the font name, without spaces. -/// font_size - the font size for the new text object. +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - receives the quadpoints; must not be NULL. /// -/// Returns a handle to a new text object, or NULL on failure -FPDF_PAGEOBJECT FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, -FPDF_BYTESTRING font, -double font_size, +/// Returns true if successful. +int FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, ) { - return _FPDFPageObj_NewTextObj(document, -font, -font_size, + return _FPDFAnnot_GetAttachmentPoints(annot, +quad_index, +quad_points, ); } -late final _FPDFPageObj_NewTextObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_NewTextObj'); -late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction(); +late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetAttachmentPoints'); +late final _FPDFAnnot_GetAttachmentPoints = _FPDFAnnot_GetAttachmentPointsPtr.asFunction )>(); -/// Set the text for a text object. If it had text, it will be replaced. +/// Experimental API. +/// Set the annotation rectangle defining the location of the annotation. If the +/// annotation's appearance stream is defined and this annotation is of a type +/// without quadpoints, then update the bounding box too if the new rectangle +/// defines a bigger one. /// -/// text_object - handle to the text object. -/// text - the UTF-16LE encoded string containing the text to be added. +/// annot - handle to an annotation. +/// rect - the annotation rectangle to be set. /// -/// Returns TRUE on success -int FPDFText_SetText(FPDF_PAGEOBJECT text_object, -FPDF_WIDESTRING text, +/// Returns true if successful. +int FPDFAnnot_SetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, ) { - return _FPDFText_SetText(text_object, -text, + return _FPDFAnnot_SetRect(annot, +rect, ); } -late final _FPDFText_SetTextPtr = _lookup< - ffi.NativeFunction>('FPDFText_SetText'); -late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction(); +late final _FPDFAnnot_SetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetRect'); +late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction )>(); /// Experimental API. -/// Set the text using charcodes for a text object. If it had text, it will be -/// replaced. +/// Get the annotation rectangle defining the location of the annotation. /// -/// text_object - handle to the text object. -/// charcodes - pointer to an array of charcodes to be added. -/// count - number of elements in |charcodes|. +/// annot - handle to an annotation. +/// rect - receives the rectangle; must not be NULL. /// -/// Returns TRUE on success -int FPDFText_SetCharcodes(FPDF_PAGEOBJECT text_object, -ffi.Pointer charcodes, -int count, +/// Returns true if successful. +int FPDFAnnot_GetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, ) { - return _FPDFText_SetCharcodes(text_object, -charcodes, -count, + return _FPDFAnnot_GetRect(annot, +rect, ); } -late final _FPDFText_SetCharcodesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFText_SetCharcodes'); -late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction , int )>(); +late final _FPDFAnnot_GetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetRect'); +late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction )>(); -/// Returns a font object loaded from a stream of data. The font is loaded -/// into the document. Various font data structures, such as the ToUnicode data, -/// are auto-generated based on the inputs. -/// -/// document - handle to the document. -/// data - the stream of font data, which will be copied by the font object. -/// size - the size of the font data, in bytes. -/// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. -/// cid - a boolean specifying if the font is a CID font or not. +/// Experimental API. +/// Get the vertices of a polygon or polyline annotation. |buffer| is an array of +/// points of the annotation. If |length| is less than the returned length, or +/// |annot| or |buffer| is NULL, |buffer| will not be modified. /// -/// The loaded font can be closed using FPDFFont_Close(). +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. /// -/// Returns NULL on failure -FPDF_FONT FPDFText_LoadFont(FPDF_DOCUMENT document, -ffi.Pointer data, -int size, -int font_type, -int cid, +/// Returns the number of points if the annotation is of type polygon or +/// polyline, 0 otherwise. +int FPDFAnnot_GetVertices(FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int length, ) { - return _FPDFText_LoadFont(document, -data, -size, -font_type, -cid, + return _FPDFAnnot_GetVertices(annot, +buffer, +length, ); } -late final _FPDFText_LoadFontPtr = _lookup< - ffi.NativeFunction , ffi.Uint32 , ffi.Int , FPDF_BOOL )>>('FPDFText_LoadFont'); -late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction , int , int , int )>(); +late final _FPDFAnnot_GetVerticesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetVertices'); +late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction , int )>(); /// Experimental API. -/// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred -/// way of using font style is using a dash to separate the name from the style, -/// for example 'Helvetica-BoldItalic'. -/// -/// document - handle to the document. -/// font - string containing the font name, without spaces. +/// Get the number of paths in the ink list of an ink annotation. /// -/// The loaded font can be closed using FPDFFont_Close(). +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() /// -/// Returns NULL on failure. -FPDF_FONT FPDFText_LoadStandardFont(FPDF_DOCUMENT document, -FPDF_BYTESTRING font, +/// Returns the number of paths in the ink list if the annotation is of type ink, +/// 0 otherwise. +int FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot, ) { - return _FPDFText_LoadStandardFont(document, -font, + return _FPDFAnnot_GetInkListCount(annot, ); } -late final _FPDFText_LoadStandardFontPtr = _lookup< - ffi.NativeFunction>('FPDFText_LoadStandardFont'); -late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr.asFunction(); +late final _FPDFAnnot_GetInkListCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetInkListCount'); +late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); /// Experimental API. -/// Returns a font object loaded from a stream of data for a type 2 CID font. The -/// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode -/// data and the CIDToGIDMap data are caller provided, instead of auto-generated. +/// Get a path in the ink list of an ink annotation. |buffer| is an array of +/// points of the path. If |length| is less than the returned length, or |annot| +/// or |buffer| is NULL, |buffer| will not be modified. /// -/// document - handle to the document. -/// font_data - the stream of font data, which will be copied by -/// the font object. -/// font_data_size - the size of the font data, in bytes. -/// to_unicode_cmap - the ToUnicode data. -/// cid_to_gid_map_data - the stream of CIDToGIDMap data. -/// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. -/// -/// The loaded font can be closed using FPDFFont_Close(). +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// path_index - index of the path +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. /// -/// Returns NULL on failure. -FPDF_FONT FPDFText_LoadCidType2Font(FPDF_DOCUMENT document, -ffi.Pointer font_data, -int font_data_size, -FPDF_BYTESTRING to_unicode_cmap, -ffi.Pointer cid_to_gid_map_data, -int cid_to_gid_map_data_size, +/// Returns the number of points of the path if the annotation is of type ink, 0 +/// otherwise. +int FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot, +int path_index, +ffi.Pointer buffer, +int length, ) { - return _FPDFText_LoadCidType2Font(document, -font_data, -font_data_size, -to_unicode_cmap, -cid_to_gid_map_data, -cid_to_gid_map_data_size, + return _FPDFAnnot_GetInkListPath(annot, +path_index, +buffer, +length, ); } -late final _FPDFText_LoadCidType2FontPtr = _lookup< - ffi.NativeFunction , ffi.Uint32 , FPDF_BYTESTRING , ffi.Pointer , ffi.Uint32 )>>('FPDFText_LoadCidType2Font'); -late final _FPDFText_LoadCidType2Font = _FPDFText_LoadCidType2FontPtr.asFunction , int , FPDF_BYTESTRING , ffi.Pointer , int )>(); +late final _FPDFAnnot_GetInkListPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetInkListPath'); +late final _FPDFAnnot_GetInkListPath = _FPDFAnnot_GetInkListPathPtr.asFunction , int )>(); -/// Get the font size of a text object. +/// Experimental API. +/// Get the starting and ending coordinates of a line annotation. /// -/// text - handle to a text. -/// size - pointer to the font size of the text object, measured in points -/// (about 1/72 inch) +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// start - starting point +/// end - ending point /// -/// Returns TRUE on success. -int FPDFTextObj_GetFontSize(FPDF_PAGEOBJECT text, -ffi.Pointer size, +/// Returns true if the annotation is of type line, |start| and |end| are not +/// NULL, false otherwise. +int FPDFAnnot_GetLine(FPDF_ANNOTATION annot, +ffi.Pointer start, +ffi.Pointer end, ) { - return _FPDFTextObj_GetFontSize(text, -size, + return _FPDFAnnot_GetLine(annot, +start, +end, ); } -late final _FPDFTextObj_GetFontSizePtr = _lookup< - ffi.NativeFunction )>>('FPDFTextObj_GetFontSize'); -late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction )>(); +late final _FPDFAnnot_GetLinePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFAnnot_GetLine'); +late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction , ffi.Pointer )>(); -/// Close a loaded PDF font. +/// Experimental API. +/// Set the characteristics of the annotation's border (rounded rectangle). /// -/// font - Handle to the loaded font. -void FPDFFont_Close(FPDF_FONT font, -) { - return _FPDFFont_Close(font, -); -} - -late final _FPDFFont_ClosePtr = _lookup< - ffi.NativeFunction>('FPDFFont_Close'); -late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); - -/// Create a new text object using a loaded font. +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units /// -/// document - handle to the document. -/// font - handle to the font object. -/// font_size - the font size for the new text object. +/// Returns true if setting the border for |annot| succeeds, false otherwise. /// -/// Returns a handle to a new text object, or NULL on failure -FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, -FPDF_FONT font, -double font_size, +/// If |annot| contains an appearance stream that overrides the border values, +/// then the appearance stream will be removed on success. +int FPDFAnnot_SetBorder(FPDF_ANNOTATION annot, +double horizontal_radius, +double vertical_radius, +double border_width, ) { - return _FPDFPageObj_CreateTextObj(document, -font, -font_size, + return _FPDFAnnot_SetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, ); } -late final _FPDFPageObj_CreateTextObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateTextObj'); -late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr.asFunction(); - -/// Get the text rendering mode of a text object. -/// -/// text - the handle to the text object. -/// -/// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, -/// FPDF_TEXTRENDERMODE_UNKNOWN on error. -FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode(FPDF_PAGEOBJECT text, -) { - return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode(text, -)); -} - -late final _FPDFTextObj_GetTextRenderModePtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetTextRenderMode'); -late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr.asFunction(); +late final _FPDFAnnot_SetBorderPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetBorder'); +late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction(); /// Experimental API. -/// Set the text rendering mode of a text object. +/// Get the characteristics of the annotation's border (rounded rectangle). /// -/// text - the handle to the text object. -/// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to -/// FPDF_TEXTRENDERMODE_UNKNOWN). +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units /// -/// Returns TRUE on success. -DartFPDF_BOOL FPDFTextObj_SetTextRenderMode(FPDF_PAGEOBJECT text, -FPDF_TEXT_RENDERMODE render_mode, +/// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are +/// not NULL, false otherwise. +int FPDFAnnot_GetBorder(FPDF_ANNOTATION annot, +ffi.Pointer horizontal_radius, +ffi.Pointer vertical_radius, +ffi.Pointer border_width, ) { - return _FPDFTextObj_SetTextRenderMode(text, -render_mode.value, + return _FPDFAnnot_GetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, ); } -late final _FPDFTextObj_SetTextRenderModePtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_SetTextRenderMode'); -late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr.asFunction(); +late final _FPDFAnnot_GetBorderPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetBorder'); +late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); -/// Get the text of a text object. -/// -/// text_object - the handle to the text object. -/// text_page - the handle to the text page. -/// buffer - the address of a buffer that receives the text. -/// length - the size, in bytes, of |buffer|. +/// Experimental API. +/// Get the JavaScript of an event of the annotation's additional actions. +/// |buffer| is only modified if |buflen| is large enough to hold the whole +/// JavaScript string. If |buflen| is smaller, the total size of the JavaScript +/// is still returned, but nothing is copied. If there is no JavaScript for +/// |event| in |annot|, an empty string is written to |buf| and 2 is returned, +/// denoting the size of the null terminator in the buffer. On other errors, +/// nothing is written to |buffer| and 0 is returned. /// -/// Returns the number of bytes in the text (including the trailing NUL -/// character) on success, 0 on error. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// event - event type, one of the FPDF_ANNOT_AACTION_* values. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFTextObj_GetText(FPDF_PAGEOBJECT text_object, -FPDF_TEXTPAGE text_page, +/// Returns the length of the string value in bytes, including the 2-byte +/// null terminator. +int FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +int event, ffi.Pointer buffer, -int length, +int buflen, ) { - return _FPDFTextObj_GetText(text_object, -text_page, + return _FPDFAnnot_GetFormAdditionalActionJavaScript(hHandle, +annot, +event, buffer, -length, +buflen, ); } -late final _FPDFTextObj_GetTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFTextObj_GetText'); -late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction , int )>(); +late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormAdditionalActionJavaScript'); +late final _FPDFAnnot_GetFormAdditionalActionJavaScript = _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction , int )>(); /// Experimental API. -/// Get a bitmap rasterization of |text_object|. To render correctly, the caller -/// must provide the |document| associated with |text_object|. If there is a -/// |page| associated with |text_object|, the caller should provide that as well. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. +/// Check if |annot|'s dictionary has |key| as a key. /// -/// document - handle to a document associated with |text_object|. -/// page - handle to an optional page associated with |text_object|. -/// text_object - handle to a text object. -/// scale - the scaling factor, which must be greater than 0. +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. /// -/// Returns the bitmap or NULL on failure. -FPDF_BITMAP FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, -FPDF_PAGE page, -FPDF_PAGEOBJECT text_object, -double scale, +/// Returns true if |key| exists. +int FPDFAnnot_HasKey(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, ) { - return _FPDFTextObj_GetRenderedBitmap(document, -page, -text_object, -scale, + return _FPDFAnnot_HasKey(annot, +key, ); } -late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetRenderedBitmap'); -late final _FPDFTextObj_GetRenderedBitmap = _FPDFTextObj_GetRenderedBitmapPtr.asFunction(); +late final _FPDFAnnot_HasKeyPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasKey'); +late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction(); /// Experimental API. -/// Get the font of a text object. +/// Get the type of the value corresponding to |key| in |annot|'s dictionary. /// -/// text - the handle to the text object. +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. /// -/// Returns a handle to the font object held by |text| which retains ownership. -FPDF_FONT FPDFTextObj_GetFont(FPDF_PAGEOBJECT text, +/// Returns the type of the dictionary value. +int FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, ) { - return _FPDFTextObj_GetFont(text, + return _FPDFAnnot_GetValueType(annot, +key, ); } -late final _FPDFTextObj_GetFontPtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetFont'); -late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); - +late final _FPDFAnnot_GetValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetValueType'); +late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction(); + /// Experimental API. -/// Get the base name of a font. -/// -/// font - the handle to the font object. -/// buffer - the address of a buffer that receives the base font name. -/// length - the size, in bytes, of |buffer|. +/// Set the string value corresponding to |key| in |annot|'s dictionary, +/// overwriting the existing value if any. The value type would be +/// FPDF_OBJECT_STRING after this function call succeeds. /// -/// Returns the number of bytes in the base name (including the trailing NUL -/// character) on success, 0 on error. The base name is typically the font's -/// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. +/// annot - handle to an annotation. +/// key - the key to the dictionary entry to be set, encoded in UTF-8. +/// value - the string value to be set, encoded in UTF-16LE. /// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFFont_GetBaseFontName(FPDF_FONT font, -ffi.Pointer buffer, -int length, +/// Returns true if successful. +int FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +FPDF_WIDESTRING value, ) { - return _FPDFFont_GetBaseFontName(font, -buffer, -length, + return _FPDFAnnot_SetStringValue(annot, +key, +value, ); } -late final _FPDFFont_GetBaseFontNamePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetBaseFontName'); -late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr.asFunction , int )>(); +late final _FPDFAnnot_SetStringValuePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetStringValue'); +late final _FPDFAnnot_SetStringValue = _FPDFAnnot_SetStringValuePtr.asFunction(); /// Experimental API. -/// Get the family name of a font. -/// -/// font - the handle to the font object. -/// buffer - the address of a buffer that receives the font name. -/// length - the size, in bytes, of |buffer|. +/// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| +/// is only modified if |buflen| is longer than the length of contents. Note that +/// if |key| does not exist in the dictionary or if |key|'s corresponding value +/// in the dictionary is not a string (i.e. the value is not of type +/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied +/// to |buffer| and the return value would be 2. On other errors, nothing would +/// be added to |buffer| and the return value would be 0. /// -/// Returns the number of bytes in the family name (including the trailing NUL -/// character) on success, 0 on error. +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFFont_GetFamilyName(FPDF_FONT font, -ffi.Pointer buffer, -int length, +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFFont_GetFamilyName(font, + return _FPDFAnnot_GetStringValue(annot, +key, buffer, -length, +buflen, ); } -late final _FPDFFont_GetFamilyNamePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetFamilyName'); -late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction , int )>(); +late final _FPDFAnnot_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetStringValue'); +late final _FPDFAnnot_GetStringValue = _FPDFAnnot_GetStringValuePtr.asFunction , int )>(); /// Experimental API. -/// Get the decoded data from the |font| object. -/// -/// font - The handle to the font object. (Required) -/// buffer - The address of a buffer that receives the font data. -/// buflen - Length of the buffer. -/// out_buflen - Pointer to variable that will receive the minimum buffer size -/// to contain the font data. Not filled if the return value is -/// FALSE. (Required) -/// -/// Returns TRUE on success. In which case, |out_buflen| will be filled, and -/// |buffer| will be filled if it is large enough. Returns FALSE if any of the -/// required parameters are null. +/// Get the float value corresponding to |key| in |annot|'s dictionary. Writes +/// value to |value| and returns True if |key| exists in the dictionary and +/// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False +/// otherwise. /// -/// The decoded data is the uncompressed font data. i.e. the raw font data after -/// having all stream filters applied, when the data is embedded. +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// value - receives the value, must not be NULL. /// -/// If the font is not embedded, then this API will instead return the data for -/// the substitution font it is using. -int FPDFFont_GetFontData(FPDF_FONT font, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, +/// Returns True if value found, False otherwise. +int FPDFAnnot_GetNumberValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer value, ) { - return _FPDFFont_GetFontData(font, -buffer, -buflen, -out_buflen, + return _FPDFAnnot_GetNumberValue(annot, +key, +value, ); } -late final _FPDFFont_GetFontDataPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFFont_GetFontData'); -late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction , int , ffi.Pointer )>(); +late final _FPDFAnnot_GetNumberValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetNumberValue'); +late final _FPDFAnnot_GetNumberValue = _FPDFAnnot_GetNumberValuePtr.asFunction )>(); /// Experimental API. -/// Get whether |font| is embedded or not. +/// Set the AP (appearance string) in |annot|'s dictionary for a given +/// |appearanceMode|. /// -/// font - the handle to the font object. +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// value - the string value to be set, encoded in UTF-16LE. If +/// nullptr is passed, the AP is cleared for that mode. If the +/// mode is Normal, APs for all modes are cleared. /// -/// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. -int FPDFFont_GetIsEmbedded(FPDF_FONT font, +/// Returns true if successful. +int FPDFAnnot_SetAP(FPDF_ANNOTATION annot, +int appearanceMode, +FPDF_WIDESTRING value, ) { - return _FPDFFont_GetIsEmbedded(font, + return _FPDFAnnot_SetAP(annot, +appearanceMode, +value, ); } -late final _FPDFFont_GetIsEmbeddedPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetIsEmbedded'); -late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); +late final _FPDFAnnot_SetAPPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetAP'); +late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction(); /// Experimental API. -/// Get the descriptor flags of a font. +/// Get the AP (appearance string) from |annot|'s dictionary for a given +/// |appearanceMode|. +/// |buffer| is only modified if |buflen| is large enough to hold the whole AP +/// string. If |buflen| is smaller, the total size of the AP is still returned, +/// but nothing is copied. +/// If there is no appearance stream for |annot| in |appearanceMode|, an empty +/// string is written to |buf| and 2 is returned. +/// On other errors, nothing is written to |buffer| and 0 is returned. /// -/// font - the handle to the font object. +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns the bit flags specifying various characteristics of the font as -/// defined in ISO 32000-1:2008, table 123, -1 on failure. -int FPDFFont_GetFlags(FPDF_FONT font, +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetAP(FPDF_ANNOTATION annot, +int appearanceMode, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFFont_GetFlags(font, + return _FPDFAnnot_GetAP(annot, +appearanceMode, +buffer, +buflen, ); } -late final _FPDFFont_GetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetFlags'); -late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); +late final _FPDFAnnot_GetAPPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetAP'); +late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction , int )>(); /// Experimental API. -/// Get the font weight of a font. +/// Get the annotation corresponding to |key| in |annot|'s dictionary. Common +/// keys for linking annotations include "IRT" and "Popup". Must call +/// FPDFPage_CloseAnnot() when the annotation returned by this function is no +/// longer needed. /// -/// font - the handle to the font object. +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. /// -/// Returns the font weight, -1 on failure. -/// Typical values are 400 (normal) and 700 (bold). -int FPDFFont_GetWeight(FPDF_FONT font, +/// Returns a handle to the linked annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, ) { - return _FPDFFont_GetWeight(font, + return _FPDFAnnot_GetLinkedAnnot(annot, +key, ); } -late final _FPDFFont_GetWeightPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetWeight'); -late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); +late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLinkedAnnot'); +late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr.asFunction(); /// Experimental API. -/// Get the italic angle of a font. -/// -/// font - the handle to the font object. -/// angle - pointer where the italic angle will be stored +/// Get the annotation flags of |annot|. /// -/// The italic angle of a |font| is defined as degrees counterclockwise -/// from vertical. For a font that slopes to the right, this will be negative. +/// annot - handle to an annotation. /// -/// Returns TRUE on success; |angle| unmodified on failure. -int FPDFFont_GetItalicAngle(FPDF_FONT font, -ffi.Pointer angle, +/// Returns the annotation flags. +int FPDFAnnot_GetFlags(FPDF_ANNOTATION annot, ) { - return _FPDFFont_GetItalicAngle(font, -angle, + return _FPDFAnnot_GetFlags(annot, ); } -late final _FPDFFont_GetItalicAnglePtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetItalicAngle'); -late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction )>(); +late final _FPDFAnnot_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFlags'); +late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); /// Experimental API. -/// Get ascent distance of a font. -/// -/// font - the handle to the font object. -/// font_size - the size of the |font|. -/// ascent - pointer where the font ascent will be stored +/// Set the |annot|'s flags to be of the value |flags|. /// -/// Ascent is the maximum distance in points above the baseline reached by the -/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// annot - handle to an annotation. +/// flags - the flag values to be set. /// -/// Returns TRUE on success; |ascent| unmodified on failure. -int FPDFFont_GetAscent(FPDF_FONT font, -double font_size, -ffi.Pointer ascent, +/// Returns true if successful. +int FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, +int flags, ) { - return _FPDFFont_GetAscent(font, -font_size, -ascent, + return _FPDFAnnot_SetFlags(annot, +flags, ); } -late final _FPDFFont_GetAscentPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetAscent'); -late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction )>(); - -/// Experimental API. -/// Get descent distance of a font. -/// -/// font - the handle to the font object. -/// font_size - the size of the |font|. -/// descent - pointer where the font descent will be stored +late final _FPDFAnnot_SetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFlags'); +late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); + +/// Experimental API. +/// Get the annotation flags of |annot|. /// -/// Descent is the maximum distance in points below the baseline reached by the -/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. /// -/// Returns TRUE on success; |descent| unmodified on failure. -int FPDFFont_GetDescent(FPDF_FONT font, -double font_size, -ffi.Pointer descent, +/// Returns the annotation flags specific to interactive forms. +int FPDFAnnot_GetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, ) { - return _FPDFFont_GetDescent(font, -font_size, -descent, + return _FPDFAnnot_GetFormFieldFlags(handle, +annot, ); } -late final _FPDFFont_GetDescentPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetDescent'); -late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction )>(); +late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldFlags'); +late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr.asFunction(); /// Experimental API. -/// Get the width of a glyph in a font. -/// -/// font - the handle to the font object. -/// glyph - the glyph. -/// font_size - the size of the font. -/// width - pointer where the glyph width will be stored +/// Sets the form field flags for an interactive form annotation. /// -/// Glyph width is the distance from the end of the prior glyph to the next -/// glyph. This will be the vertical distance for vertical writing. +/// handle - the handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// flags - the form field flags to be set. /// -/// Returns TRUE on success; |width| unmodified on failure. -int FPDFFont_GetGlyphWidth(FPDF_FONT font, -int glyph, -double font_size, -ffi.Pointer width, +/// Returns true if successful. +int FPDFAnnot_SetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int flags, ) { - return _FPDFFont_GetGlyphWidth(font, -glyph, -font_size, -width, + return _FPDFAnnot_SetFormFieldFlags(handle, +annot, +flags, ); } -late final _FPDFFont_GetGlyphWidthPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetGlyphWidth'); -late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction )>(); +late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFormFieldFlags'); +late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr.asFunction(); /// Experimental API. -/// Get the glyphpath describing how to draw a font glyph. +/// Retrieves an interactive form annotation whose rectangle contains a given +/// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned +/// is no longer needed. /// -/// font - the handle to the font object. -/// glyph - the glyph being drawn. -/// font_size - the size of the font. /// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_GLYPHPATH FPDFFont_GetGlyphPath(FPDF_FONT font, -int glyph, -double font_size, +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - handle to the page, returned by FPDF_LoadPage function. +/// point - position in PDF "user space". +/// +/// Returns the interactive form annotation whose rectangle contains the given +/// coordinates on the page. If there is no such annotation, return NULL. +FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer point, ) { - return _FPDFFont_GetGlyphPath(font, -glyph, -font_size, + return _FPDFAnnot_GetFormFieldAtPoint(hHandle, +page, +point, ); } -late final _FPDFFont_GetGlyphPathPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetGlyphPath'); -late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction(); +late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFormFieldAtPoint'); +late final _FPDFAnnot_GetFormFieldAtPoint = _FPDFAnnot_GetFormFieldAtPointPtr.asFunction )>(); /// Experimental API. -/// Get number of segments inside glyphpath. +/// Gets the name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". /// -/// glyphpath - handle to a glyph path. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the name string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns the number of objects in |glyphpath| or -1 on failure. -int FPDFGlyphPath_CountGlyphSegments(FPDF_GLYPHPATH glyphpath, +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFGlyphPath_CountGlyphSegments(glyphpath, + return _FPDFAnnot_GetFormFieldName(hHandle, +annot, +buffer, +buflen, ); } -late final _FPDFGlyphPath_CountGlyphSegmentsPtr = _lookup< - ffi.NativeFunction>('FPDFGlyphPath_CountGlyphSegments'); -late final _FPDFGlyphPath_CountGlyphSegments = _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction(); +late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldName'); +late final _FPDFAnnot_GetFormFieldName = _FPDFAnnot_GetFormFieldNamePtr.asFunction , int )>(); /// Experimental API. -/// Get segment in glyphpath at index. +/// Gets the alternate name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". /// -/// glyphpath - handle to a glyph path. -/// index - the index of a segment. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the alternate name string, encoded in +/// UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment(FPDF_GLYPHPATH glyphpath, -int index, +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldAlternateName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFGlyphPath_GetGlyphPathSegment(glyphpath, -index, + return _FPDFAnnot_GetFormFieldAlternateName(hHandle, +annot, +buffer, +buflen, ); } -late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< - ffi.NativeFunction>('FPDFGlyphPath_GetGlyphPathSegment'); -late final _FPDFGlyphPath_GetGlyphPathSegment = _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction(); +late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldAlternateName'); +late final _FPDFAnnot_GetFormFieldAlternateName = _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction , int )>(); -/// Get number of page objects inside |form_object|. +/// Experimental API. +/// Gets the form field type of |annot|, which is an interactive form annotation. /// -/// form_object - handle to a form object. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. /// -/// Returns the number of objects in |form_object| on success, -1 on error. -int FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object, +/// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on +/// success. Returns -1 on error. +/// See field types in fpdf_formfill.h. +int FPDFAnnot_GetFormFieldType(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, ) { - return _FPDFFormObj_CountObjects(form_object, + return _FPDFAnnot_GetFormFieldType(hHandle, +annot, ); } -late final _FPDFFormObj_CountObjectsPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_CountObjects'); -late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); +late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldType'); +late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr.asFunction(); -/// Get page object in |form_object| at |index|. +/// Experimental API. +/// Gets the value of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". /// -/// form_object - handle to a form object. -/// index - the 0-based index of a page object. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns the handle to the page object, or NULL on error. -FPDF_PAGEOBJECT FPDFFormObj_GetObject(FPDF_PAGEOBJECT form_object, -int index, +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, ) { - return _FPDFFormObj_GetObject(form_object, -index, + return _FPDFAnnot_GetFormFieldValue(hHandle, +annot, +buffer, +buflen, ); } -late final _FPDFFormObj_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_GetObject'); -late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction(); +late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldValue'); +late final _FPDFAnnot_GetFormFieldValue = _FPDFAnnot_GetFormFieldValuePtr.asFunction , int )>(); /// Experimental API. +/// Get the number of options in the |annot|'s "Opt" dictionary. Intended for +/// use with listbox and combobox widget annotations. /// -/// Remove |page_object| from |form_object|. -/// -/// form_object - handle to a form object. -/// page_object - handle to a page object to be removed from the form. -/// -/// Returns TRUE on success. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. /// -/// Ownership of the removed |page_object| is transferred to the caller. -/// Call FPDFPageObj_Destroy() on the removed page_object to free it. -int FPDFFormObj_RemoveObject(FPDF_PAGEOBJECT form_object, -FPDF_PAGEOBJECT page_object, +/// Returns the number of options in "Opt" dictionary on success. Return value +/// will be -1 if annotation does not have an "Opt" dictionary or other error. +int FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, ) { - return _FPDFFormObj_RemoveObject(form_object, -page_object, + return _FPDFAnnot_GetOptionCount(hHandle, +annot, ); } -late final _FPDFFormObj_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_RemoveObject'); -late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); +late final _FPDFAnnot_GetOptionCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetOptionCount'); +late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr.asFunction(); /// Experimental API. -/// Import pages to a FPDF_DOCUMENT. +/// Get the string value for the label of the option at |index| in |annot|'s +/// "Opt" dictionary. Intended for use with listbox and combobox widget +/// annotations. |buffer| is only modified if |buflen| is longer than the length +/// of contents. If index is out of range or in case of other error, nothing +/// will be added to |buffer| and the return value will be 0. Note that +/// return value of empty string is 2 for "\0\0". /// -/// dest_doc - The destination document for the pages. -/// src_doc - The document to be imported. -/// page_indices - An array of page indices to be imported. The first page is -/// zero. If |page_indices| is NULL, all pages from |src_doc| -/// are imported. -/// length - The length of the |page_indices| array. -/// index - The page index at which to insert the first imported page -/// into |dest_doc|. The first page is zero. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. /// -/// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is -/// invalid. -int FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -ffi.Pointer page_indices, -int length, +/// Returns the length of the string value in bytes. +/// If |annot| does not have an "Opt" array, |index| is out of range or if any +/// other error occurs, returns 0. +int FPDFAnnot_GetOptionLabel(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, int index, +ffi.Pointer buffer, +int buflen, ) { - return _FPDF_ImportPagesByIndex(dest_doc, -src_doc, -page_indices, -length, + return _FPDFAnnot_GetOptionLabel(hHandle, +annot, index, +buffer, +buflen, ); } -late final _FPDF_ImportPagesByIndexPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_ImportPagesByIndex'); -late final _FPDF_ImportPagesByIndex = _FPDF_ImportPagesByIndexPtr.asFunction , int , int )>(); +late final _FPDFAnnot_GetOptionLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetOptionLabel'); +late final _FPDFAnnot_GetOptionLabel = _FPDFAnnot_GetOptionLabelPtr.asFunction , int )>(); -/// Import pages to a FPDF_DOCUMENT. +/// Experimental API. +/// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary +/// is selected. Intended for use with listbox and combobox widget annotations. /// -/// dest_doc - The destination document for the pages. -/// src_doc - The document to be imported. -/// pagerange - A page range string, Such as "1,3,5-7". The first page is one. -/// If |pagerange| is NULL, all pages from |src_doc| are imported. -/// index - The page index at which to insert the first imported page into -/// |dest_doc|. The first page is zero. +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array. /// -/// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is -/// invalid or if |pagerange| cannot be read. -int FPDF_ImportPages(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -FPDF_BYTESTRING pagerange, +/// Returns true if the option at |index| in |annot|'s "Opt" dictionary is +/// selected, false otherwise. +int FPDFAnnot_IsOptionSelected(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, int index, ) { - return _FPDF_ImportPages(dest_doc, -src_doc, -pagerange, + return _FPDFAnnot_IsOptionSelected(handle, +annot, index, ); } -late final _FPDF_ImportPagesPtr = _lookup< - ffi.NativeFunction>('FPDF_ImportPages'); -late final _FPDF_ImportPages = _FPDF_ImportPagesPtr.asFunction(); +late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsOptionSelected'); +late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr.asFunction(); /// Experimental API. -/// Create a new document from |src_doc|. The pages of |src_doc| will be -/// combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per -/// |output_doc| page. -/// -/// src_doc - The document to be imported. -/// output_width - The output page width in PDF "user space" units. -/// output_height - The output page height in PDF "user space" units. -/// num_pages_on_x_axis - The number of pages on X Axis. -/// num_pages_on_y_axis - The number of pages on Y Axis. +/// Get the float value of the font size for an |annot| with variable text. +/// If 0, the font is to be auto-sized: its size is computed as a function of +/// the height of the annotation rectangle. /// -/// Return value: -/// A handle to the created document, or NULL on failure. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// value - Required. Float which will be set to font size on success. /// -/// Comments: -/// number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis -FPDF_DOCUMENT FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, -double output_width, -double output_height, -int num_pages_on_x_axis, -int num_pages_on_y_axis, +/// Returns true if the font size was set in |value|, false on error or if +/// |value| not provided. +int FPDFAnnot_GetFontSize(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer value, ) { - return _FPDF_ImportNPagesToOne(src_doc, -output_width, -output_height, -num_pages_on_x_axis, -num_pages_on_y_axis, + return _FPDFAnnot_GetFontSize(hHandle, +annot, +value, ); } -late final _FPDF_ImportNPagesToOnePtr = _lookup< - ffi.NativeFunction>('FPDF_ImportNPagesToOne'); -late final _FPDF_ImportNPagesToOne = _FPDF_ImportNPagesToOnePtr.asFunction(); +late final _FPDFAnnot_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFontSize'); +late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction )>(); /// Experimental API. -/// Create a template to generate form xobjects from |src_doc|'s page at -/// |src_page_index|, for use in |dest_doc|. +/// Set the text color of an annotation. /// -/// Returns a handle on success, or NULL on failure. Caller owns the newly -/// created object. -FPDF_XOBJECT FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -int src_page_index, +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R - the red component for the text color. +/// G - the green component for the text color. +/// B - the blue component for the text color. +/// +/// Returns true if successful. +/// +/// Currently supported subtypes: freetext. +/// The range for the color components is 0 to 255. +int FPDFAnnot_SetFontColor(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int R, +int G, +int B, ) { - return _FPDF_NewXObjectFromPage(dest_doc, -src_doc, -src_page_index, + return _FPDFAnnot_SetFontColor(handle, +annot, +R, +G, +B, ); } -late final _FPDF_NewXObjectFromPagePtr = _lookup< - ffi.NativeFunction>('FPDF_NewXObjectFromPage'); -late final _FPDF_NewXObjectFromPage = _FPDF_NewXObjectFromPagePtr.asFunction(); +late final _FPDFAnnot_SetFontColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFontColor'); +late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction(); /// Experimental API. -/// Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage(). -/// FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected. -void FPDF_CloseXObject(FPDF_XOBJECT xobject, +/// Get the RGB value of the font color for an |annot| with variable text. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// +/// Returns true if the font color was set, false on error or if the font +/// color was not provided. +int FPDFAnnot_GetFontColor(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, ) { - return _FPDF_CloseXObject(xobject, + return _FPDFAnnot_GetFontColor(hHandle, +annot, +R, +G, +B, ); } -late final _FPDF_CloseXObjectPtr = _lookup< - ffi.NativeFunction>('FPDF_CloseXObject'); -late final _FPDF_CloseXObject = _FPDF_CloseXObjectPtr.asFunction(); +late final _FPDFAnnot_GetFontColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetFontColor'); +late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); /// Experimental API. -/// Create a new form object from an FPDF_XOBJECT object. +/// Determine if |annot| is a form widget that is checked. Intended for use with +/// checkbox and radio button widgets. /// -/// Returns a new form object on success, or NULL on failure. Caller owns the -/// newly created object. -FPDF_PAGEOBJECT FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject, +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns true if |annot| is a form widget and is checked, false otherwise. +int FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, ) { - return _FPDF_NewFormObjectFromXObject(xobject, + return _FPDFAnnot_IsChecked(hHandle, +annot, ); } -late final _FPDF_NewFormObjectFromXObjectPtr = _lookup< - ffi.NativeFunction>('FPDF_NewFormObjectFromXObject'); -late final _FPDF_NewFormObjectFromXObject = _FPDF_NewFormObjectFromXObjectPtr.asFunction(); +late final _FPDFAnnot_IsCheckedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsChecked'); +late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction(); -/// Copy the viewer preferences from |src_doc| into |dest_doc|. +/// Experimental API. +/// Set the list of focusable annotation subtypes. Annotations of subtype +/// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API +/// will override the existing subtypes. /// -/// dest_doc - Document to write the viewer preferences into. -/// src_doc - Document to read the viewer preferences from. +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - list of annotation subtype which can be tabbed over. +/// count - total number of annotation subtype in list. +/// Returns true if list of annotation subtype is set successfully, false +/// otherwise. +int FPDFAnnot_SetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, +) { + return _FPDFAnnot_SetFocusableSubtypes(hHandle, +subtypes, +count, +); +} + +late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_SetFocusableSubtypes'); +late final _FPDFAnnot_SetFocusableSubtypes = _FPDFAnnot_SetFocusableSubtypesPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the count of focusable annotation subtypes as set by host +/// for a |hHandle|. /// -/// Returns TRUE on success. -int FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// Returns the count of focusable annotation subtypes or -1 on error. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypesCount(FPDF_FORMHANDLE hHandle, ) { - return _FPDF_CopyViewerPreferences(dest_doc, -src_doc, + return _FPDFAnnot_GetFocusableSubtypesCount(hHandle, ); } -late final _FPDF_CopyViewerPreferencesPtr = _lookup< - ffi.NativeFunction>('FPDF_CopyViewerPreferences'); -late final _FPDF_CopyViewerPreferences = _FPDF_CopyViewerPreferencesPtr.asFunction(); +late final _FPDFAnnot_GetFocusableSubtypesCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFocusableSubtypesCount'); +late final _FPDFAnnot_GetFocusableSubtypesCount = _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction(); -/// Function: FPDF_SaveAsCopy -/// Saves the copy of specified document in custom way. -/// Parameters: -/// document - Handle to document, as returned by -/// FPDF_LoadDocument() or FPDF_CreateNewDocument(). -/// pFileWrite - A pointer to a custom file write structure. -/// flags - Flags above that affect how the PDF gets saved. -/// Pass in 0 when there are no flags. -/// Return value: -/// TRUE for succeed, FALSE for failed. -int FPDF_SaveAsCopy(FPDF_DOCUMENT document, -ffi.Pointer pFileWrite, -int flags, +/// Experimental API. +/// Get the list of focusable annotation subtype as set by host. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - receives the list of annotation subtype which can be tabbed +/// over. Caller must have allocated |subtypes| more than or +/// equal to the count obtained from +/// FPDFAnnot_GetFocusableSubtypesCount() API. +/// count - size of |subtypes|. +/// Returns true on success and set list of annotation subtype to |subtypes|, +/// false otherwise. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, ) { - return _FPDF_SaveAsCopy(document, -pFileWrite, -flags, + return _FPDFAnnot_GetFocusableSubtypes(hHandle, +subtypes, +count, ); } -late final _FPDF_SaveAsCopyPtr = _lookup< - ffi.NativeFunction , FPDF_DWORD )>>('FPDF_SaveAsCopy'); -late final _FPDF_SaveAsCopy = _FPDF_SaveAsCopyPtr.asFunction , int )>(); +late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_GetFocusableSubtypes'); +late final _FPDFAnnot_GetFocusableSubtypes = _FPDFAnnot_GetFocusableSubtypesPtr.asFunction , int )>(); -/// Function: FPDF_SaveWithVersion -/// Same as FPDF_SaveAsCopy(), except the file version of the -/// saved document can be specified by the caller. -/// Parameters: -/// document - Handle to document. -/// pFileWrite - A pointer to a custom file write structure. -/// flags - The creating flags. -/// fileVersion - The PDF file version. File version: 14 for 1.4, -/// 15 for 1.5, ... -/// Return value: -/// TRUE if succeed, FALSE if failed. -int FPDF_SaveWithVersion(FPDF_DOCUMENT document, -ffi.Pointer pFileWrite, -int flags, -int fileVersion, +/// Experimental API. +/// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. +/// +/// annot - handle to an annotation. +/// +/// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, +/// if the input annot is NULL or input annot's subtype is not link. +FPDF_LINK FPDFAnnot_GetLink(FPDF_ANNOTATION annot, ) { - return _FPDF_SaveWithVersion(document, -pFileWrite, -flags, -fileVersion, + return _FPDFAnnot_GetLink(annot, ); } -late final _FPDF_SaveWithVersionPtr = _lookup< - ffi.NativeFunction , FPDF_DWORD , ffi.Int )>>('FPDF_SaveWithVersion'); -late final _FPDF_SaveWithVersion = _FPDF_SaveWithVersionPtr.asFunction , int , int )>(); +late final _FPDFAnnot_GetLinkPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLink'); +late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); -/// Function: FPDF_GetDefaultTTFMap -/// Returns a pointer to the default character set to TT Font name map. The -/// map is an array of FPDF_CharsetFontMap structs, with its end indicated -/// by a { -1, NULL } entry. -/// Parameters: -/// None. -/// Return Value: -/// Pointer to the Charset Font Map. -/// Note: -/// Once FPDF_GetDefaultTTFMapCount() and FPDF_GetDefaultTTFMapEntry() are no -/// longer experimental, this API will be marked as deprecated. -/// See https://crbug.com/348468114 -ffi.Pointer FPDF_GetDefaultTTFMap() { - return _FPDF_GetDefaultTTFMap(); +/// Experimental API. +/// Gets the count of annotations in the |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns number of controls in its control group or -1 on error. +int FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlCount(hHandle, +annot, +); } -late final _FPDF_GetDefaultTTFMapPtr = _lookup< - ffi.NativeFunction Function()>>('FPDF_GetDefaultTTFMap'); -late final _FPDF_GetDefaultTTFMap = _FPDF_GetDefaultTTFMapPtr.asFunction Function()>(); +late final _FPDFAnnot_GetFormControlCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlCount'); +late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr.asFunction(); /// Experimental API. +/// Gets the index of |annot| in |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. /// -/// Function: FPDF_GetDefaultTTFMapCount -/// Returns the number of entries in the default character set to TT Font name -/// map. -/// Parameters: -/// None. -/// Return Value: -/// The number of entries in the map. -int FPDF_GetDefaultTTFMapCount() { - return _FPDF_GetDefaultTTFMapCount(); +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns index of a given |annot| in its control group or -1 on error. +int FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlIndex(hHandle, +annot, +); } -late final _FPDF_GetDefaultTTFMapCountPtr = _lookup< - ffi.NativeFunction>('FPDF_GetDefaultTTFMapCount'); -late final _FPDF_GetDefaultTTFMapCount = _FPDF_GetDefaultTTFMapCountPtr.asFunction(); +late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlIndex'); +late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr.asFunction(); /// Experimental API. +/// Gets the export value of |annot| which is an interactive form annotation. +/// Intended for use with radio button and checkbox widget annotations. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value +/// will be 0. Note that return value of empty string is 2 for "\0\0". /// -/// Function: FPDF_GetDefaultTTFMapEntry -/// Returns an entry in the default character set to TT Font name map. -/// Parameters: -/// index - The index to the entry in the map to retrieve. -/// Return Value: -/// A pointer to the entry, if it is in the map, or NULL if the index is out -/// of bounds. -ffi.Pointer FPDF_GetDefaultTTFMapEntry(int index, +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, ) { - return _FPDF_GetDefaultTTFMapEntry(index, + return _FPDFAnnot_GetFormFieldExportValue(hHandle, +annot, +buffer, +buflen, ); } -late final _FPDF_GetDefaultTTFMapEntryPtr = _lookup< - ffi.NativeFunction Function(ffi.Size )>>('FPDF_GetDefaultTTFMapEntry'); -late final _FPDF_GetDefaultTTFMapEntry = _FPDF_GetDefaultTTFMapEntryPtr.asFunction Function(int )>(); +late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldExportValue'); +late final _FPDFAnnot_GetFormFieldExportValue = _FPDFAnnot_GetFormFieldExportValuePtr.asFunction , int )>(); -/// Function: FPDF_AddInstalledFont -/// Add a system font to the list in PDFium. -/// Comments: -/// This function is only called during the system font list building -/// process. -/// Parameters: -/// mapper - Opaque pointer to Foxit font mapper -/// face - The font face name -/// charset - Font character set. See above defined constants. -/// Return Value: -/// None. -void FPDF_AddInstalledFont(ffi.Pointer mapper, -ffi.Pointer face, -int charset, +/// Experimental API. +/// Add a URI action to |annot|, overwriting the existing action, if any. +/// +/// annot - handle to a link annotation. +/// uri - the URI to be set, encoded in 7-bit ASCII. +/// +/// Returns true if successful. +int FPDFAnnot_SetURI(FPDF_ANNOTATION annot, +ffi.Pointer uri, ) { - return _FPDF_AddInstalledFont(mapper, -face, -charset, + return _FPDFAnnot_SetURI(annot, +uri, ); } -late final _FPDF_AddInstalledFontPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_AddInstalledFont'); -late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction , ffi.Pointer , int )>(); +late final _FPDFAnnot_SetURIPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetURI'); +late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction )>(); -/// Function: FPDF_SetSystemFontInfo -/// Set the system font info interface into PDFium -/// Parameters: -/// font_info - Pointer to a FPDF_SYSFONTINFO structure -/// Return Value: -/// None -/// Comments: -/// Platform support implementation should implement required methods of -/// FFDF_SYSFONTINFO interface, then call this function during PDFium -/// initialization process. +/// Experimental API. +/// Get the attachment from |annot|. /// -/// Call this with NULL to tell PDFium to stop using a previously set -/// |FPDF_SYSFONTINFO|. -void FPDF_SetSystemFontInfo(ffi.Pointer font_info, +/// annot - handle to a file annotation. +/// +/// Returns the handle to the attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot, ) { - return _FPDF_SetSystemFontInfo(font_info, + return _FPDFAnnot_GetFileAttachment(annot, ); } -late final _FPDF_SetSystemFontInfoPtr = _lookup< - ffi.NativeFunction )>>('FPDF_SetSystemFontInfo'); -late final _FPDF_SetSystemFontInfo = _FPDF_SetSystemFontInfoPtr.asFunction )>(); - -/// Function: FPDF_GetDefaultSystemFontInfo -/// Get default system font info interface for current platform -/// Parameters: -/// None -/// Return Value: -/// Pointer to a FPDF_SYSFONTINFO structure describing the default -/// interface, or NULL if the platform doesn't have a default interface. -/// Application should call FPDF_FreeDefaultSystemFontInfo to free the -/// returned pointer. -/// Comments: -/// For some platforms, PDFium implements a default version of system -/// font info interface. The default implementation can be passed to -/// FPDF_SetSystemFontInfo(). -ffi.Pointer FPDF_GetDefaultSystemFontInfo() { - return _FPDF_GetDefaultSystemFontInfo(); -} +late final _FPDFAnnot_GetFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFileAttachment'); +late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr.asFunction(); -late final _FPDF_GetDefaultSystemFontInfoPtr = _lookup< - ffi.NativeFunction Function()>>('FPDF_GetDefaultSystemFontInfo'); -late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.asFunction Function()>(); - -/// Function: FPDF_FreeDefaultSystemFontInfo -/// Free a default system font info interface -/// Parameters: -/// font_info - Pointer to a FPDF_SYSFONTINFO structure -/// Return Value: -/// None -/// Comments: -/// This function should be called on the output from -/// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. -void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer font_info, +/// Experimental API. +/// Add an embedded file with |name| to |annot|. +/// +/// annot - handle to a file annotation. +/// name - name of the new attachment. +/// +/// Returns a handle to the new attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, +FPDF_WIDESTRING name, ) { - return _FPDF_FreeDefaultSystemFontInfo(font_info, + return _FPDFAnnot_AddFileAttachment(annot, +name, ); } -late final _FPDF_FreeDefaultSystemFontInfoPtr = _lookup< - ffi.NativeFunction )>>('FPDF_FreeDefaultSystemFontInfo'); -late final _FPDF_FreeDefaultSystemFontInfo = _FPDF_FreeDefaultSystemFontInfoPtr.asFunction )>(); +late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AddFileAttachment'); +late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr.asFunction(); +/// Experimental API. +/// +/// Determine if |document| represents a tagged PDF. +/// +/// For the definition of tagged PDF, See (see 10.7 "Tagged PDF" in PDF +/// Reference 1.7). +/// +/// document - handle to a document. +/// +/// Returns |true| iff |document| is a tagged PDF. +int FPDFCatalog_IsTagged(FPDF_DOCUMENT document, +) { + return _FPDFCatalog_IsTagged(document, +); } -/// PDF text rendering modes -enum FPDF_TEXT_RENDERMODE { - FPDF_TEXTRENDERMODE_UNKNOWN(-1), - FPDF_TEXTRENDERMODE_FILL(0), - FPDF_TEXTRENDERMODE_STROKE(1), - FPDF_TEXTRENDERMODE_FILL_STROKE(2), - FPDF_TEXTRENDERMODE_INVISIBLE(3), - FPDF_TEXTRENDERMODE_FILL_CLIP(4), - FPDF_TEXTRENDERMODE_STROKE_CLIP(5), - FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP(6), - FPDF_TEXTRENDERMODE_CLIP(7); - - static const FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP; - - final int value; - const FPDF_TEXT_RENDERMODE(this.value); - - static FPDF_TEXT_RENDERMODE fromValue(int value) => switch (value) { - -1 => FPDF_TEXTRENDERMODE_UNKNOWN, - 0 => FPDF_TEXTRENDERMODE_FILL, - 1 => FPDF_TEXTRENDERMODE_STROKE, - 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, - 3 => FPDF_TEXTRENDERMODE_INVISIBLE, - 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, - 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, - 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, - 7 => FPDF_TEXTRENDERMODE_CLIP, - _ => throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), - }; - - @override - String toString() { - if (this == FPDF_TEXTRENDERMODE_CLIP) return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; - return super.toString(); - }} - -final class fpdf_action_t__ extends ffi.Opaque{ -} +late final _FPDFCatalog_IsTaggedPtr = _lookup< + ffi.NativeFunction>('FPDFCatalog_IsTagged'); +late final _FPDFCatalog_IsTagged = _FPDFCatalog_IsTaggedPtr.asFunction(); -/// PDF types - use incomplete types (never completed) to force API type safety. -typedef FPDF_ACTION = ffi.Pointer; -final class fpdf_annotation_t__ extends ffi.Opaque{ +/// Experimental API. +/// Sets the language of |document| to |language|. +/// +/// document - handle to a document. +/// language - the language to set to. +/// +/// Returns TRUE on success. +int FPDFCatalog_SetLanguage(FPDF_DOCUMENT document, +FPDF_BYTESTRING language, +) { + return _FPDFCatalog_SetLanguage(document, +language, +); } -typedef FPDF_ANNOTATION = ffi.Pointer; -final class fpdf_attachment_t__ extends ffi.Opaque{ -} +late final _FPDFCatalog_SetLanguagePtr = _lookup< + ffi.NativeFunction>('FPDFCatalog_SetLanguage'); +late final _FPDFCatalog_SetLanguage = _FPDFCatalog_SetLanguagePtr.asFunction(); -typedef FPDF_ATTACHMENT = ffi.Pointer; -final class fpdf_avail_t__ extends ffi.Opaque{ +/// Experimental API. +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// page_indices - An array of page indices to be imported. The first page is +/// zero. If |page_indices| is NULL, all pages from |src_doc| +/// are imported. +/// length - The length of the |page_indices| array. +/// index - The page index at which to insert the first imported page +/// into |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is +/// invalid. +int FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +ffi.Pointer page_indices, +int length, +int index, +) { + return _FPDF_ImportPagesByIndex(dest_doc, +src_doc, +page_indices, +length, +index, +); } -typedef FPDF_AVAIL = ffi.Pointer; -final class fpdf_bitmap_t__ extends ffi.Opaque{ -} +late final _FPDF_ImportPagesByIndexPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_ImportPagesByIndex'); +late final _FPDF_ImportPagesByIndex = _FPDF_ImportPagesByIndexPtr.asFunction , int , int )>(); -typedef FPDF_BITMAP = ffi.Pointer; -final class fpdf_bookmark_t__ extends ffi.Opaque{ +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// pagerange - A page range string, Such as "1,3,5-7". The first page is one. +/// If |pagerange| is NULL, all pages from |src_doc| are imported. +/// index - The page index at which to insert the first imported page into +/// |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is +/// invalid or if |pagerange| cannot be read. +int FPDF_ImportPages(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +FPDF_BYTESTRING pagerange, +int index, +) { + return _FPDF_ImportPages(dest_doc, +src_doc, +pagerange, +index, +); } -typedef FPDF_BOOKMARK = ffi.Pointer; -final class fpdf_clippath_t__ extends ffi.Opaque{ -} +late final _FPDF_ImportPagesPtr = _lookup< + ffi.NativeFunction>('FPDF_ImportPages'); +late final _FPDF_ImportPages = _FPDF_ImportPagesPtr.asFunction(); -typedef FPDF_CLIPPATH = ffi.Pointer; -final class fpdf_dest_t__ extends ffi.Opaque{ +/// Experimental API. +/// Create a new document from |src_doc|. The pages of |src_doc| will be +/// combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per +/// |output_doc| page. +/// +/// src_doc - The document to be imported. +/// output_width - The output page width in PDF "user space" units. +/// output_height - The output page height in PDF "user space" units. +/// num_pages_on_x_axis - The number of pages on X Axis. +/// num_pages_on_y_axis - The number of pages on Y Axis. +/// +/// Return value: +/// A handle to the created document, or NULL on failure. +/// +/// Comments: +/// number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis +FPDF_DOCUMENT FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, +double output_width, +double output_height, +int num_pages_on_x_axis, +int num_pages_on_y_axis, +) { + return _FPDF_ImportNPagesToOne(src_doc, +output_width, +output_height, +num_pages_on_x_axis, +num_pages_on_y_axis, +); } -typedef FPDF_DEST = ffi.Pointer; -final class fpdf_document_t__ extends ffi.Opaque{ -} +late final _FPDF_ImportNPagesToOnePtr = _lookup< + ffi.NativeFunction>('FPDF_ImportNPagesToOne'); +late final _FPDF_ImportNPagesToOne = _FPDF_ImportNPagesToOnePtr.asFunction(); -typedef FPDF_DOCUMENT = ffi.Pointer; -final class fpdf_font_t__ extends ffi.Opaque{ +/// Experimental API. +/// Create a template to generate form xobjects from |src_doc|'s page at +/// |src_page_index|, for use in |dest_doc|. +/// +/// Returns a handle on success, or NULL on failure. Caller owns the newly +/// created object. +FPDF_XOBJECT FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +int src_page_index, +) { + return _FPDF_NewXObjectFromPage(dest_doc, +src_doc, +src_page_index, +); } -typedef FPDF_FONT = ffi.Pointer; -final class fpdf_form_handle_t__ extends ffi.Opaque{ -} +late final _FPDF_NewXObjectFromPagePtr = _lookup< + ffi.NativeFunction>('FPDF_NewXObjectFromPage'); +late final _FPDF_NewXObjectFromPage = _FPDF_NewXObjectFromPagePtr.asFunction(); -typedef FPDF_FORMHANDLE = ffi.Pointer; -final class fpdf_glyphpath_t__ extends ffi.Opaque{ +/// Experimental API. +/// Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage(). +/// FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected. +void FPDF_CloseXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_CloseXObject(xobject, +); } -typedef FPDF_GLYPHPATH = ffi.Pointer; -final class fpdf_javascript_action_t extends ffi.Opaque{ -} +late final _FPDF_CloseXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_CloseXObject'); +late final _FPDF_CloseXObject = _FPDF_CloseXObjectPtr.asFunction(); -typedef FPDF_JAVASCRIPT_ACTION = ffi.Pointer; -final class fpdf_link_t__ extends ffi.Opaque{ +/// Experimental API. +/// Create a new form object from an FPDF_XOBJECT object. +/// +/// Returns a new form object on success, or NULL on failure. Caller owns the +/// newly created object. +FPDF_PAGEOBJECT FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_NewFormObjectFromXObject(xobject, +); } -typedef FPDF_LINK = ffi.Pointer; -final class fpdf_page_t__ extends ffi.Opaque{ -} +late final _FPDF_NewFormObjectFromXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_NewFormObjectFromXObject'); +late final _FPDF_NewFormObjectFromXObject = _FPDF_NewFormObjectFromXObjectPtr.asFunction(); -typedef FPDF_PAGE = ffi.Pointer; -final class fpdf_pagelink_t__ extends ffi.Opaque{ +/// Copy the viewer preferences from |src_doc| into |dest_doc|. +/// +/// dest_doc - Document to write the viewer preferences into. +/// src_doc - Document to read the viewer preferences from. +/// +/// Returns TRUE on success. +int FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +) { + return _FPDF_CopyViewerPreferences(dest_doc, +src_doc, +); } -typedef FPDF_PAGELINK = ffi.Pointer; -final class fpdf_pageobject_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGEOBJECT = ffi.Pointer; -final class fpdf_pageobjectmark_t__ extends ffi.Opaque{ -} +late final _FPDF_CopyViewerPreferencesPtr = _lookup< + ffi.NativeFunction>('FPDF_CopyViewerPreferences'); +late final _FPDF_CopyViewerPreferences = _FPDF_CopyViewerPreferencesPtr.asFunction(); -typedef FPDF_PAGEOBJECTMARK = ffi.Pointer; -final class fpdf_pagerange_t__ extends ffi.Opaque{ +/// Function: FPDF_SaveAsCopy +/// Saves the copy of specified document in custom way. +/// Parameters: +/// document - Handle to document, as returned by +/// FPDF_LoadDocument() or FPDF_CreateNewDocument(). +/// pFileWrite - A pointer to a custom file write structure. +/// flags - Flags above that affect how the PDF gets saved. +/// Pass in 0 when there are no flags. +/// Return value: +/// TRUE for succeed, FALSE for failed. +int FPDF_SaveAsCopy(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +) { + return _FPDF_SaveAsCopy(document, +pFileWrite, +flags, +); } -typedef FPDF_PAGERANGE = ffi.Pointer; -final class fpdf_pathsegment_t extends ffi.Opaque{ -} +late final _FPDF_SaveAsCopyPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD )>>('FPDF_SaveAsCopy'); +late final _FPDF_SaveAsCopy = _FPDF_SaveAsCopyPtr.asFunction , int )>(); -typedef FPDF_PATHSEGMENT = ffi.Pointer; -final class fpdf_schhandle_t__ extends ffi.Opaque{ +/// Function: FPDF_SaveWithVersion +/// Same as FPDF_SaveAsCopy(), except the file version of the +/// saved document can be specified by the caller. +/// Parameters: +/// document - Handle to document. +/// pFileWrite - A pointer to a custom file write structure. +/// flags - The creating flags. +/// fileVersion - The PDF file version. File version: 14 for 1.4, +/// 15 for 1.5, ... +/// Return value: +/// TRUE if succeed, FALSE if failed. +int FPDF_SaveWithVersion(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +int fileVersion, +) { + return _FPDF_SaveWithVersion(document, +pFileWrite, +flags, +fileVersion, +); } -typedef FPDF_SCHHANDLE = ffi.Pointer; -final class fpdf_signature_t__ extends ffi.Opaque{ -} +late final _FPDF_SaveWithVersionPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD , ffi.Int )>>('FPDF_SaveWithVersion'); +late final _FPDF_SaveWithVersion = _FPDF_SaveWithVersionPtr.asFunction , int , int )>(); -typedef FPDF_SIGNATURE = ffi.Pointer; -typedef FPDF_SKIA_CANVAS = ffi.Pointer; -final class fpdf_structelement_t__ extends ffi.Opaque{ +/// Get the first child of |bookmark|, or the first top-level bookmark item. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. Pass NULL for the first top +/// level item. +/// +/// Returns a handle to the first child of |bookmark| or the first top-level +/// bookmark item. NULL if no child or top-level bookmark found. +/// Note that another name for the bookmarks is the document outline, as +/// described in ISO 32000-1:2008, section 12.3.3. +FPDF_BOOKMARK FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetFirstChild(document, +bookmark, +); } -typedef FPDF_STRUCTELEMENT = ffi.Pointer; -final class fpdf_structelement_attr_t__ extends ffi.Opaque{ -} +late final _FPDFBookmark_GetFirstChildPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetFirstChild'); +late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr.asFunction(); -typedef FPDF_STRUCTELEMENT_ATTR = ffi.Pointer; -final class fpdf_structelement_attr_value_t__ extends ffi.Opaque{ +/// Get the next sibling of |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. +/// +/// Returns a handle to the next sibling of |bookmark|, or NULL if this is the +/// last bookmark at this level. +/// +/// Note that the caller is responsible for handling circular bookmark +/// references, as may arise from malformed documents. +FPDF_BOOKMARK FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetNextSibling(document, +bookmark, +); } -typedef FPDF_STRUCTELEMENT_ATTR_VALUE = ffi.Pointer; -final class fpdf_structtree_t__ extends ffi.Opaque{ -} +late final _FPDFBookmark_GetNextSiblingPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetNextSibling'); +late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr.asFunction(); -typedef FPDF_STRUCTTREE = ffi.Pointer; -final class fpdf_textpage_t__ extends ffi.Opaque{ +/// Get the title of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// buffer - buffer for the title. May be NULL. +/// buflen - the length of the buffer in bytes. May be 0. +/// +/// Returns the number of bytes in the title, including the terminating NUL +/// character. The number of bytes is returned regardless of the |buffer| and +/// |buflen| parameters. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The +/// string is terminated by a UTF16 NUL character. If |buflen| is less than the +/// required length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFBookmark_GetTitle(bookmark, +buffer, +buflen, +); } -typedef FPDF_TEXTPAGE = ffi.Pointer; -final class fpdf_widget_t__ extends ffi.Opaque{ -} +late final _FPDFBookmark_GetTitlePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFBookmark_GetTitle'); +late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction , int )>(); -typedef FPDF_WIDGET = ffi.Pointer; -final class fpdf_xobject_t__ extends ffi.Opaque{ +/// Experimental API. +/// Get the number of chlidren of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns a signed integer that represents the number of sub-items the given +/// bookmark has. If the value is positive, child items shall be shown by default +/// (open state). If the value is negative, child items shall be hidden by +/// default (closed state). Please refer to PDF 32000-1:2008, Table 153. +/// Returns 0 if the bookmark has no children or is invalid. +int FPDFBookmark_GetCount(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetCount(bookmark, +); } -typedef FPDF_XOBJECT = ffi.Pointer; -/// Basic data types -typedef FPDF_BOOL = ffi.Int; -typedef DartFPDF_BOOL = int; -typedef FPDF_RESULT = ffi.Int; -typedef DartFPDF_RESULT = int; -typedef FPDF_DWORD = ffi.UnsignedLong; -typedef DartFPDF_DWORD = int; -typedef FS_FLOAT = ffi.Float; -typedef DartFS_FLOAT = double; -/// Duplex types -enum _FPDF_DUPLEXTYPE_ { - DuplexUndefined(0), - Simplex(1), - DuplexFlipShortEdge(2), - DuplexFlipLongEdge(3); - +late final _FPDFBookmark_GetCountPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetCount'); +late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); - final int value; - const _FPDF_DUPLEXTYPE_(this.value); +/// Find the bookmark with |title| in |document|. +/// +/// document - handle to the document. +/// title - the UTF-16LE encoded Unicode title for which to search. +/// +/// Returns the handle to the bookmark, or NULL if |title| can't be found. +/// +/// FPDFBookmark_Find() will always return the first bookmark found even if +/// multiple bookmarks have the same |title|. +FPDF_BOOKMARK FPDFBookmark_Find(FPDF_DOCUMENT document, +FPDF_WIDESTRING title, +) { + return _FPDFBookmark_Find(document, +title, +); +} - static _FPDF_DUPLEXTYPE_ fromValue(int value) => switch (value) { - 0 => DuplexUndefined, - 1 => Simplex, - 2 => DuplexFlipShortEdge, - 3 => DuplexFlipLongEdge, - _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), - }; +late final _FPDFBookmark_FindPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_Find'); +late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction(); +/// Get the destination associated with |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the destination data, or NULL if no destination is +/// associated with |bookmark|. +FPDF_DEST FPDFBookmark_GetDest(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetDest(document, +bookmark, +); } -/// String types -typedef FPDF_WCHAR = ffi.UnsignedShort; -typedef DartFPDF_WCHAR = int; -/// Public PDFium API type for byte strings. -typedef FPDF_BYTESTRING = ffi.Pointer; -/// The public PDFium API always uses UTF-16LE encoded wide strings, each -/// character uses 2 bytes (except surrogation), with the low byte first. -typedef FPDF_WIDESTRING = ffi.Pointer; -/// Structure for persisting a string beyond the duration of a callback. -/// Note: although represented as a char*, string may be interpreted as -/// a UTF-16LE formated string. Used only by XFA callbacks. -final class FPDF_BSTR_ extends ffi.Struct{ - /// String buffer, manipulate only with FPDF_BStr_* methods. - external ffi.Pointer str; - - /// Length of the string, in bytes. - @ffi.Int() - external int len; +late final _FPDFBookmark_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetDest'); +late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction(); +/// Get the action associated with |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the action data, or NULL if no action is associated +/// with |bookmark|. +/// If this function returns a valid handle, it is valid as long as |bookmark| is +/// valid. +/// If this function returns NULL, FPDFBookmark_GetDest() should be called to get +/// the |bookmark| destination data. +FPDF_ACTION FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetAction(bookmark, +); } -/// Structure for persisting a string beyond the duration of a callback. -/// Note: although represented as a char*, string may be interpreted as -/// a UTF-16LE formated string. Used only by XFA callbacks. -typedef FPDF_BSTR = FPDF_BSTR_; -/// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a -/// Windows unicode string, however, special care needs to be taken if you -/// expect to process Unicode larger than 0xffff. +late final _FPDFBookmark_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetAction'); +late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction(); + +/// Get the type of |action|. /// -/// For Linux/Unix programmers: most compiler/library environments use 4 bytes -/// for a Unicode character, and you have to convert between FPDF_WIDESTRING and -/// system wide string by yourself. -typedef FPDF_STRING = ffi.Pointer; -/// Matrix for transformation, in the form [a b c d e f], equivalent to: -/// | a b 0 | -/// | c d 0 | -/// | e f 1 | +/// action - handle to the action. /// -/// Translation is performed with [1 0 0 1 tx ty]. -/// Scaling is performed with [sx 0 0 sy 0 0]. -/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. -final class _FS_MATRIX_ extends ffi.Struct{ - @ffi.Float() - external double a; +/// Returns one of: +/// PDFACTION_UNSUPPORTED +/// PDFACTION_GOTO +/// PDFACTION_REMOTEGOTO +/// PDFACTION_URI +/// PDFACTION_LAUNCH +int FPDFAction_GetType(FPDF_ACTION action, +) { + return _FPDFAction_GetType(action, +); +} - @ffi.Float() - external double b; +late final _FPDFAction_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetType'); +late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); - @ffi.Float() - external double c; +/// Get the destination of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. |action| must be a |PDFACTION_GOTO| or +/// |PDFACTION_REMOTEGOTO|. +/// +/// Returns a handle to the destination data, or NULL on error, typically +/// because the arguments were bad or the action was of the wrong type. +/// +/// In the case of |PDFACTION_REMOTEGOTO|, you must first call +/// FPDFAction_GetFilePath(), then load the document at that path, then pass +/// the document handle from that document as |document| to FPDFAction_GetDest(). +FPDF_DEST FPDFAction_GetDest(FPDF_DOCUMENT document, +FPDF_ACTION action, +) { + return _FPDFAction_GetDest(document, +action, +); +} - @ffi.Float() - external double d; +late final _FPDFAction_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetDest'); +late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction(); - @ffi.Float() - external double e; +/// Get the file path of |action|. +/// +/// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or +/// |PDFACTION_REMOTEGOTO|. +/// buffer - a buffer for output the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFAction_GetFilePath(FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetFilePath(action, +buffer, +buflen, +); +} - @ffi.Float() - external double f; +late final _FPDFAction_GetFilePathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetFilePath'); +late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction , int )>(); +/// Get the URI path of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. Must be a |PDFACTION_URI|. +/// buffer - a buffer for the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the URI path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// The |buffer| may contain badly encoded data. The caller should validate the +/// output. e.g. Check to see if it is UTF-8. +/// +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +/// +/// Historically, the documentation for this API claimed |buffer| is always +/// encoded in 7-bit ASCII, but did not actually enforce it. +/// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 +/// added that enforcement, but that did not work well for real world PDFs that +/// used UTF-8. As of this writing, this API reverted back to its original +/// behavior prior to commit d609e84cee. +int FPDFAction_GetURIPath(FPDF_DOCUMENT document, +FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetURIPath(document, +action, +buffer, +buflen, +); } -/// Matrix for transformation, in the form [a b c d e f], equivalent to: -/// | a b 0 | -/// | c d 0 | -/// | e f 1 | +late final _FPDFAction_GetURIPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetURIPath'); +late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction , int )>(); + +/// Get the page index of |dest|. /// -/// Translation is performed with [1 0 0 1 tx ty]. -/// Scaling is performed with [sx 0 0 sy 0 0]. -/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. -typedef FS_MATRIX = _FS_MATRIX_; -/// Rectangle area(float) in device or page coordinate system. -final class _FS_RECTF_ extends ffi.Struct{ - /// The x-coordinate of the left-top corner. - @ffi.Float() - external double left; +/// document - handle to the document. +/// dest - handle to the destination. +/// +/// Returns the 0-based page index containing |dest|. Returns -1 on error. +int FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, +FPDF_DEST dest, +) { + return _FPDFDest_GetDestPageIndex(document, +dest, +); +} - /// The y-coordinate of the left-top corner. - @ffi.Float() - external double top; +late final _FPDFDest_GetDestPageIndexPtr = _lookup< + ffi.NativeFunction>('FPDFDest_GetDestPageIndex'); +late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr.asFunction(); - /// The x-coordinate of the right-bottom corner. - @ffi.Float() - external double right; +/// Experimental API. +/// Get the view (fit type) specified by |dest|. +/// +/// dest - handle to the destination. +/// pNumParams - receives the number of view parameters, which is at most 4. +/// pParams - buffer to write the view parameters. Must be at least 4 +/// FS_FLOATs long. +/// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if +/// |dest| does not specify a view. +int FPDFDest_GetView(FPDF_DEST dest, +ffi.Pointer pNumParams, +ffi.Pointer pParams, +) { + return _FPDFDest_GetView(dest, +pNumParams, +pParams, +); +} - /// The y-coordinate of the right-bottom corner. - @ffi.Float() - external double bottom; +late final _FPDFDest_GetViewPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFDest_GetView'); +late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction , ffi.Pointer )>(); +/// Get the (x, y, zoom) location of |dest| in the destination page, if the +/// destination is in [page /XYZ x y zoom] syntax. +/// +/// dest - handle to the destination. +/// hasXVal - out parameter; true if the x value is not null +/// hasYVal - out parameter; true if the y value is not null +/// hasZoomVal - out parameter; true if the zoom value is not null +/// x - out parameter; the x coordinate, in page coordinates. +/// y - out parameter; the y coordinate, in page coordinates. +/// zoom - out parameter; the zoom value. +/// Returns TRUE on successfully reading the /XYZ value. +/// +/// Note the [x, y, zoom] values are only set if the corresponding hasXVal, +/// hasYVal or hasZoomVal flags are true. +int FPDFDest_GetLocationInPage(FPDF_DEST dest, +ffi.Pointer hasXVal, +ffi.Pointer hasYVal, +ffi.Pointer hasZoomVal, +ffi.Pointer x, +ffi.Pointer y, +ffi.Pointer zoom, +) { + return _FPDFDest_GetLocationInPage(dest, +hasXVal, +hasYVal, +hasZoomVal, +x, +y, +zoom, +); } -/// Rectangle area(float) in device or page coordinate system. -typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; -typedef FS_RECTF = _FS_RECTF_; -/// Const Pointer to FS_RECTF structure. -typedef FS_LPCRECTF = ffi.Pointer; -/// Rectangle size. Coordinate system agnostic. -final class FS_SIZEF_ extends ffi.Struct{ - @ffi.Float() - external double width; - - @ffi.Float() - external double height; +late final _FPDFDest_GetLocationInPagePtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFDest_GetLocationInPage'); +late final _FPDFDest_GetLocationInPage = _FPDFDest_GetLocationInPagePtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); +/// Find a link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns a handle to the link, or NULL if no link found at the given point. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +FPDF_LINK FPDFLink_GetLinkAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkAtPoint(page, +x, +y, +); } -/// Rectangle size. Coordinate system agnostic. -typedef FS_LPSIZEF = ffi.Pointer; -typedef FS_SIZEF = FS_SIZEF_; -/// Const Pointer to FS_SIZEF structure. -typedef FS_LPCSIZEF = ffi.Pointer; -/// 2D Point. Coordinate system agnostic. -final class FS_POINTF_ extends ffi.Struct{ - @ffi.Float() - external double x; - - @ffi.Float() - external double y; +late final _FPDFLink_GetLinkAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkAtPoint'); +late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction(); +/// Find the Z-order of link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns the Z-order of the link, or -1 if no link found at the given point. +/// Larger Z-order numbers are closer to the front. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +int FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkZOrderAtPoint(page, +x, +y, +); } -/// 2D Point. Coordinate system agnostic. -typedef FS_LPPOINTF = ffi.Pointer; -typedef FS_POINTF = FS_POINTF_; -/// Const Pointer to FS_POINTF structure. -typedef FS_LPCPOINTF = ffi.Pointer; -final class _FS_QUADPOINTSF extends ffi.Struct{ - @FS_FLOAT() - external double x1; - - @FS_FLOAT() - external double y1; - - @FS_FLOAT() - external double x2; - - @FS_FLOAT() - external double y2; - - @FS_FLOAT() - external double x3; - - @FS_FLOAT() - external double y3; +late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkZOrderAtPoint'); +late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr.asFunction(); - @FS_FLOAT() - external double x4; +/// Get destination info for |link|. +/// +/// document - handle to the document. +/// link - handle to the link. +/// +/// Returns a handle to the destination, or NULL if there is no destination +/// associated with the link. In this case, you should call FPDFLink_GetAction() +/// to retrieve the action associated with |link|. +FPDF_DEST FPDFLink_GetDest(FPDF_DOCUMENT document, +FPDF_LINK link, +) { + return _FPDFLink_GetDest(document, +link, +); +} - @FS_FLOAT() - external double y4; +late final _FPDFLink_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetDest'); +late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction(); +/// Get action info for |link|. +/// +/// link - handle to the link. +/// +/// Returns a handle to the action associated to |link|, or NULL if no action. +/// If this function returns a valid handle, it is valid as long as |link| is +/// valid. +FPDF_ACTION FPDFLink_GetAction(FPDF_LINK link, +) { + return _FPDFLink_GetAction(link, +); } -typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; -/// Annotation enums. -typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; -typedef DartFPDF_ANNOTATION_SUBTYPE = int; -typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; -typedef DartFPDF_ANNOT_APPEARANCEMODE = int; -/// Dictionary value types. -typedef FPDF_OBJECT_TYPE = ffi.Int; -typedef DartFPDF_OBJECT_TYPE = int; -/// PDF renderer types - Experimental. -/// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. -enum FPDF_RENDERER_TYPE { - /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ - FPDF_RENDERERTYPE_AGG(0), - /// Skia - https://skia.org/ - FPDF_RENDERERTYPE_SKIA(1); - +late final _FPDFLink_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAction'); +late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); - final int value; - const FPDF_RENDERER_TYPE(this.value); +/// Enumerates all the link annotations in |page|. +/// +/// page - handle to the page. +/// start_pos - the start position, should initially be 0 and is updated with +/// the next start position on return. +/// link_annot - the link handle for |startPos|. +/// +/// Returns TRUE on success. +int FPDFLink_Enumerate(FPDF_PAGE page, +ffi.Pointer start_pos, +ffi.Pointer link_annot, +) { + return _FPDFLink_Enumerate(page, +start_pos, +link_annot, +); +} - static FPDF_RENDERER_TYPE fromValue(int value) => switch (value) { - 0 => FPDF_RENDERERTYPE_AGG, - 1 => FPDF_RENDERERTYPE_SKIA, - _ => throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), - }; +late final _FPDFLink_EnumeratePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_Enumerate'); +late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction , ffi.Pointer )>(); +/// Experimental API. +/// Gets FPDF_ANNOTATION object for |link_annot|. +/// +/// page - handle to the page in which FPDF_LINK object is present. +/// link_annot - handle to link annotation. +/// +/// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, +/// if the input link annot or page is NULL. +FPDF_ANNOTATION FPDFLink_GetAnnot(FPDF_PAGE page, +FPDF_LINK link_annot, +) { + return _FPDFLink_GetAnnot(page, +link_annot, +); } -/// Process-wide options for initializing the library. -final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct{ - /// Version number of the interface. Currently must be 2. - /// Support for version 1 will be deprecated in the future. - @ffi.Int() - external int version; +late final _FPDFLink_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAnnot'); +late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction(); - /// Array of paths to scan in place of the defaults when using built-in - /// FXGE font loading code. The array is terminated by a NULL pointer. - /// The Array may be NULL itself to use the default paths. May be ignored - /// entirely depending upon the platform. - external ffi.Pointer> m_pUserFontPaths; +/// Get the rectangle for |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// rect - the annotation rectangle. +/// +/// Returns true on success. +int FPDFLink_GetAnnotRect(FPDF_LINK link_annot, +ffi.Pointer rect, +) { + return _FPDFLink_GetAnnotRect(link_annot, +rect, +); +} - /// Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. - external ffi.Pointer m_pIsolate; +late final _FPDFLink_GetAnnotRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetAnnotRect'); +late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction )>(); - /// The embedder data slot to use in the v8::Isolate to store PDFium's - /// per-isolate data. The value needs to be in the range - /// [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most - /// embedders. - @ffi.UnsignedInt() - external int m_v8EmbedderSlot; +/// Get the count of quadrilateral points to the |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// +/// Returns the count of quadrilateral points. +int FPDFLink_CountQuadPoints(FPDF_LINK link_annot, +) { + return _FPDFLink_CountQuadPoints(link_annot, +); +} - /// Pointer to the V8::Platform to use. - external ffi.Pointer m_pPlatform; +late final _FPDFLink_CountQuadPointsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountQuadPoints'); +late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); - /// Explicit specification of core renderer to use. |m_RendererType| must be - /// a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, - /// or else the initialization will fail with an immediate crash. - /// Note that use of a specified |FPDF_RENDERER_TYPE| value for which the - /// corresponding render library is not included in the build will similarly - /// fail with an immediate crash. - @ffi.UnsignedInt() - external int m_RendererTypeAsInt; +/// Get the quadrilateral points for the specified |quad_index| in |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// quad_index - the specified quad point index. +/// quad_points - receives the quadrilateral points. +/// +/// Returns true on success. +int FPDFLink_GetQuadPoints(FPDF_LINK link_annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFLink_GetQuadPoints(link_annot, +quad_index, +quad_points, +); +} -FPDF_RENDERER_TYPE get m_RendererType => FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); +late final _FPDFLink_GetQuadPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetQuadPoints'); +late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction )>(); +/// Experimental API +/// Gets an additional-action from |page|. +/// +/// page - handle to the page, as returned by FPDF_LoadPage(). +/// aa_type - the type of the page object's addtional-action, defined +/// in public/fpdf_formfill.h +/// +/// Returns the handle to the action data, or NULL if there is no +/// additional-action of type |aa_type|. +/// If this function returns a valid handle, it is valid as long as |page| is +/// valid. +FPDF_ACTION FPDF_GetPageAAction(FPDF_PAGE page, +int aa_type, +) { + return _FPDF_GetPageAAction(page, +aa_type, +); } -/// Process-wide options for initializing the library. -typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; -/// Structure for custom file access. -final class FPDF_FILEACCESS extends ffi.Struct{ - /// File length, in bytes. - @ffi.UnsignedLong() - external int m_FileLen; +late final _FPDF_GetPageAActionPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageAAction'); +late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction(); - /// A function pointer for getting a block of data from a specific position. - /// Position is specified by byte offset from the beginning of the file. - /// The pointer to the buffer is never NULL and the size is never 0. - /// The position and size will never go out of range of the file length. - /// It may be possible for PDFium to call this function multiple times for - /// the same position. - /// Return value: should be non-zero if successful, zero for error. - external ffi.Pointer param, ffi.UnsignedLong position, ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; +/// Experimental API. +/// Get the file identifer defined in the trailer of |document|. +/// +/// document - handle to the document. +/// id_type - the file identifier type to retrieve. +/// buffer - a buffer for the file identifier. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file identifier, including the NUL +/// terminator. +/// +/// The |buffer| is always a byte string. The |buffer| is followed by a NUL +/// terminator. If |buflen| is less than the returned length, or |buffer| is +/// NULL, |buffer| will not be modified. +int FPDF_GetFileIdentifier(FPDF_DOCUMENT document, +FPDF_FILEIDTYPE id_type, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetFileIdentifier(document, +id_type.value, +buffer, +buflen, +); +} - /// A custom pointer for all implementation specific data. This pointer will - /// be used as the first parameter to the m_GetBlock callback. - external ffi.Pointer m_Param; +late final _FPDF_GetFileIdentifierPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetFileIdentifier'); +late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction , int )>(); +/// Get meta-data |tag| content from |document|. +/// +/// document - handle to the document. +/// tag - the tag to retrieve. The tag can be one of: +/// Title, Author, Subject, Keywords, Creator, Producer, +/// CreationDate, or ModDate. +/// For detailed explanations of these tags and their respective +/// values, please refer to PDF Reference 1.6, section 10.2.1, +/// 'Document Information Dictionary'. +/// buffer - a buffer for the tag. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the tag, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +/// +/// For linearized files, FPDFAvail_IsFormAvail must be called before this, and +/// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there +/// is no guarantee the metadata has been loaded. +int FPDF_GetMetaText(FPDF_DOCUMENT document, +FPDF_BYTESTRING tag, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetMetaText(document, +tag, +buffer, +buflen, +); } -/// Structure for file reading or writing (I/O). +late final _FPDF_GetMetaTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetMetaText'); +late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction , int )>(); + +/// Get the page label for |page_index| from |document|. /// -/// Note: This is a handler and should be implemented by callers, -/// and is only used from XFA. -final class FPDF_FILEHANDLER_ extends ffi.Struct{ - /// User-defined data. - /// Note: Callers can use this field to track controls. - external ffi.Pointer clientData; +/// document - handle to the document. +/// page_index - the 0-based index of the page. +/// buffer - a buffer for the page label. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the page label, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDF_GetPageLabel(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetPageLabel(document, +page_index, +buffer, +buflen, +); +} - /// Callback function to release the current file stream object. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// None. - external ffi.Pointer clientData)>> Release; +late final _FPDF_GetPageLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetPageLabel'); +late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction , int )>(); - /// Callback function to retrieve the current file stream size. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// Size of file stream. - external ffi.Pointer clientData)>> GetSize; +/// Function: FPDF_StructTree_GetForPage +/// Get the structure tree for a page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return value: +/// A handle to the structure tree or NULL on error. The caller owns the +/// returned handle and must use FPDF_StructTree_Close() to release it. +/// The handle should be released before |page| gets released. +FPDF_STRUCTTREE FPDF_StructTree_GetForPage(FPDF_PAGE page, +) { + return _FPDF_StructTree_GetForPage(page, +); +} - /// Callback function to read data from the current file stream. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// offset - Offset position starts from the beginning of file - /// stream. This parameter indicates reading position. - /// buffer - Memory buffer to store data which are read from - /// file stream. This parameter should not be NULL. - /// size - Size of data which should be read from file stream, - /// in bytes. The buffer indicated by |buffer| must be - /// large enough to store specified data. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; +late final _FPDF_StructTree_GetForPagePtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_GetForPage'); +late final _FPDF_StructTree_GetForPage = _FPDF_StructTree_GetForPagePtr.asFunction(); - /// Callback function to write data into the current file stream. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// offset - Offset position starts from the beginning of file - /// stream. This parameter indicates writing position. - /// buffer - Memory buffer contains data which is written into - /// file stream. This parameter should not be NULL. - /// size - Size of data which should be written into file - /// stream, in bytes. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; +/// Function: FPDF_StructTree_Close +/// Release a resource allocated by FPDF_StructTree_GetForPage(). +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// Return value: +/// None. +void FPDF_StructTree_Close(FPDF_STRUCTTREE struct_tree, +) { + return _FPDF_StructTree_Close(struct_tree, +); +} - /// Callback function to flush all internal accessing buffers. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData)>> Flush; +late final _FPDF_StructTree_ClosePtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_Close'); +late final _FPDF_StructTree_Close = _FPDF_StructTree_ClosePtr.asFunction(); - /// Callback function to change file size. - /// - /// Description: - /// This function is called under writing mode usually. Implementer - /// can determine whether to realize it based on application requests. - /// Parameters: - /// clientData - Pointer to user-defined data. - /// size - New size of file stream, in bytes. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; +/// Function: FPDF_StructTree_CountChildren +/// Count the number of children for the structure tree. +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructTree_CountChildren(FPDF_STRUCTTREE struct_tree, +) { + return _FPDF_StructTree_CountChildren(struct_tree, +); +} +late final _FPDF_StructTree_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_CountChildren'); +late final _FPDF_StructTree_CountChildren = _FPDF_StructTree_CountChildrenPtr.asFunction(); + +/// Function: FPDF_StructTree_GetChildAtIndex +/// Get a child in the structure tree. +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. The caller does not +/// own the handle. The handle remains valid as long as |struct_tree| +/// remains valid. +/// Comments: +/// The |index| must be less than the FPDF_StructTree_CountChildren() +/// return value. +FPDF_STRUCTELEMENT FPDF_StructTree_GetChildAtIndex(FPDF_STRUCTTREE struct_tree, +int index, +) { + return _FPDF_StructTree_GetChildAtIndex(struct_tree, +index, +); } -/// Structure for file reading or writing (I/O). -/// -/// Note: This is a handler and should be implemented by callers, -/// and is only used from XFA. -typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; -/// Struct for color scheme. -/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. -final class FPDF_COLORSCHEME_ extends ffi.Struct{ - @FPDF_DWORD() - external int path_fill_color; +late final _FPDF_StructTree_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_GetChildAtIndex'); +late final _FPDF_StructTree_GetChildAtIndex = _FPDF_StructTree_GetChildAtIndexPtr.asFunction(); - @FPDF_DWORD() - external int path_stroke_color; +/// Function: FPDF_StructElement_GetAltText +/// Get the alt text for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the alt text. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the alt text, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetAltText(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetAltText(struct_element, +buffer, +buflen, +); +} - @FPDF_DWORD() - external int text_fill_color; +late final _FPDF_StructElement_GetAltTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetAltText'); +late final _FPDF_StructElement_GetAltText = _FPDF_StructElement_GetAltTextPtr.asFunction , int )>(); - @FPDF_DWORD() - external int text_stroke_color; +/// Experimental API. +/// Function: FPDF_StructElement_GetActualText +/// Get the actual text for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the actual text. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the actual text, including the terminating +/// NUL character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetActualText(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetActualText(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetActualTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetActualText'); +late final _FPDF_StructElement_GetActualText = _FPDF_StructElement_GetActualTextPtr.asFunction , int )>(); +/// Function: FPDF_StructElement_GetID +/// Get the ID for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the ID string. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the ID string, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetID(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetID(struct_element, +buffer, +buflen, +); } -/// Struct for color scheme. -/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. -typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; -final class _IPDF_JsPlatform extends ffi.Struct{ - /// Version number of the interface. Currently must be 2. - @ffi.Int() - external int version; +late final _FPDF_StructElement_GetIDPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetID'); +late final _FPDF_StructElement_GetID = _FPDF_StructElement_GetIDPtr.asFunction , int )>(); - /// Method: app_alert - /// Pop up a dialog to show warning or hint. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes +/// Experimental API. +/// Function: FPDF_StructElement_GetLang +/// Get the case-insensitive IETF BCP 47 language code for an element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the lang string. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the ID string, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetLang(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetLang(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetLangPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetLang'); +late final _FPDF_StructElement_GetLang = _FPDF_StructElement_GetLangPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetStringAttribute +/// Get a struct element attribute of type "name" or "string". +/// Parameters: +/// struct_element - Handle to the struct element. +/// attr_name - The name of the attribute to retrieve. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the attribute value, including the +/// terminating NUL character. The number of bytes is returned +/// regardless of the |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetStringAttribute(FPDF_STRUCTELEMENT struct_element, +FPDF_BYTESTRING attr_name, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetStringAttribute(struct_element, +attr_name, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetStringAttributePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetStringAttribute'); +late final _FPDF_StructElement_GetStringAttribute = _FPDF_StructElement_GetStringAttributePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_GetMarkedContentID +/// Get the marked content ID for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The marked content ID of the element. If no ID exists, returns +/// -1. +/// Comments: +/// FPDF_StructElement_GetMarkedContentIdAtIndex() may be able to +/// extract more marked content IDs out of |struct_element|. This API +/// may be deprecated in the future. +int FPDF_StructElement_GetMarkedContentID(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetMarkedContentID(struct_element, +); +} + +late final _FPDF_StructElement_GetMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentID'); +late final _FPDF_StructElement_GetMarkedContentID = _FPDF_StructElement_GetMarkedContentIDPtr.asFunction(); + +/// Function: FPDF_StructElement_GetType +/// Get the type (/S) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the type, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetType(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetType(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetTypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetType'); +late final _FPDF_StructElement_GetType = _FPDF_StructElement_GetTypePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetObjType +/// Get the object type (/Type) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the object type, including the terminating +/// NUL character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetObjType(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetObjType(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetObjTypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetObjType'); +late final _FPDF_StructElement_GetObjType = _FPDF_StructElement_GetObjTypePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_GetTitle +/// Get the title (/T) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the title, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetTitle(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetTitle(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetTitlePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetTitle'); +late final _FPDF_StructElement_GetTitle = _FPDF_StructElement_GetTitlePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_CountChildren +/// Count the number of children for the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructElement_CountChildren(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_CountChildren(struct_element, +); +} + +late final _FPDF_StructElement_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_CountChildren'); +late final _FPDF_StructElement_CountChildren = _FPDF_StructElement_CountChildrenPtr.asFunction(); + +/// Function: FPDF_StructElement_GetChildAtIndex +/// Get a child in the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. +/// Comments: +/// If the child exists but is not an element, then this function will +/// return NULL. This will also return NULL for out of bounds indices. +/// The |index| must be less than the FPDF_StructElement_CountChildren() +/// return value. +FPDF_STRUCTELEMENT FPDF_StructElement_GetChildAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetChildAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetChildAtIndex'); +late final _FPDF_StructElement_GetChildAtIndex = _FPDF_StructElement_GetChildAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetChildMarkedContentID +/// Get the child's content id +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the child, 0-based. +/// Return value: +/// The marked content ID of the child. If no ID exists, returns -1. +/// Comments: +/// If the child exists but is not a stream or object, then this +/// function will return -1. This will also return -1 for out of bounds +/// indices. Compared to FPDF_StructElement_GetMarkedContentIdAtIndex, +/// it is scoped to the current page. +/// The |index| must be less than the FPDF_StructElement_CountChildren() +/// return value. +int FPDF_StructElement_GetChildMarkedContentID(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetChildMarkedContentID(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetChildMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetChildMarkedContentID'); +late final _FPDF_StructElement_GetChildMarkedContentID = _FPDF_StructElement_GetChildMarkedContentIDPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetParent +/// Get the parent of the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The parent structure element or NULL on error. +/// Comments: +/// If structure element is StructTreeRoot, then this function will +/// return NULL. +FPDF_STRUCTELEMENT FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetParent(struct_element, +); +} + +late final _FPDF_StructElement_GetParentPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetParent'); +late final _FPDF_StructElement_GetParent = _FPDF_StructElement_GetParentPtr.asFunction(); + +/// Function: FPDF_StructElement_GetAttributeCount +/// Count the number of attributes for the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The number of attributes, or -1 on error. +int FPDF_StructElement_GetAttributeCount(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetAttributeCount(struct_element, +); +} + +late final _FPDF_StructElement_GetAttributeCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetAttributeCount'); +late final _FPDF_StructElement_GetAttributeCount = _FPDF_StructElement_GetAttributeCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetAttributeAtIndex +/// Get an attribute object in the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the attribute object, 0-based. +/// Return value: +/// The attribute object at the n-th index or NULL on error. +/// Comments: +/// If the attribute object exists but is not a dict, then this +/// function will return NULL. This will also return NULL for out of +/// bounds indices. The caller does not own the handle. The handle +/// remains valid as long as |struct_element| remains valid. +/// The |index| must be less than the +/// FPDF_StructElement_GetAttributeCount() return value. +FPDF_STRUCTELEMENT_ATTR FPDF_StructElement_GetAttributeAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetAttributeAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetAttributeAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetAttributeAtIndex'); +late final _FPDF_StructElement_GetAttributeAtIndex = _FPDF_StructElement_GetAttributeAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetCount +/// Count the number of attributes in a structure element attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// Return value: +/// The number of attributes, or -1 on error. +int FPDF_StructElement_Attr_GetCount(FPDF_STRUCTELEMENT_ATTR struct_attribute, +) { + return _FPDF_StructElement_Attr_GetCount(struct_attribute, +); +} + +late final _FPDF_StructElement_Attr_GetCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetCount'); +late final _FPDF_StructElement_Attr_GetCount = _FPDF_StructElement_Attr_GetCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetName +/// Get the name of an attribute in a structure element attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// index - The index of attribute in the map. +/// buffer - A buffer for output. May be NULL. This is only +/// modified if |buflen| is longer than the length +/// of the key. Optional, pass null to just +/// retrieve the size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the +/// minimum buffer size to contain the key. Not +/// filled if FALSE is returned. +/// Return value: +/// TRUE if the operation was successful, FALSE otherwise. +int FPDF_StructElement_Attr_GetName(FPDF_STRUCTELEMENT_ATTR struct_attribute, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetName(struct_attribute, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetName'); +late final _FPDF_StructElement_Attr_GetName = _FPDF_StructElement_Attr_GetNamePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetValue +/// Get a handle to a value for an attribute in a structure element +/// attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// name - The attribute name. +/// Return value: +/// Returns a handle to the value associated with the input, if any. +/// Returns NULL on failure. The caller does not own the handle. +/// The handle remains valid as long as |struct_attribute| remains +/// valid. +FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetValue(FPDF_STRUCTELEMENT_ATTR struct_attribute, +FPDF_BYTESTRING name, +) { + return _FPDF_StructElement_Attr_GetValue(struct_attribute, +name, +); +} + +late final _FPDF_StructElement_Attr_GetValuePtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetValue'); +late final _FPDF_StructElement_Attr_GetValue = _FPDF_StructElement_Attr_GetValuePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetType +/// Get the type of an attribute in a structure element attribute map. +/// Parameters: +/// value - Handle to the value. +/// Return value: +/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of +/// failure. Note that this will never return FPDF_OBJECT_REFERENCE, as +/// references are always dereferenced. +int FPDF_StructElement_Attr_GetType(FPDF_STRUCTELEMENT_ATTR_VALUE value, +) { + return _FPDF_StructElement_Attr_GetType(value, +); +} + +late final _FPDF_StructElement_Attr_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetType'); +late final _FPDF_StructElement_Attr_GetType = _FPDF_StructElement_Attr_GetTypePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetBooleanValue +/// Get the value of a boolean attribute in an attribute map as +/// FPDF_BOOL. FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_BOOLEAN for this property. +/// Parameters: +/// value - Handle to the value. +/// out_value - A pointer to variable that will receive the value. Not +/// filled if false is returned. +/// Return value: +/// Returns TRUE if the attribute maps to a boolean value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetBooleanValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer out_value, +) { + return _FPDF_StructElement_Attr_GetBooleanValue(value, +out_value, +); +} + +late final _FPDF_StructElement_Attr_GetBooleanValuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetBooleanValue'); +late final _FPDF_StructElement_Attr_GetBooleanValue = _FPDF_StructElement_Attr_GetBooleanValuePtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetNumberValue +/// Get the value of a number attribute in an attribute map as float. +/// FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_NUMBER for this property. +/// Parameters: +/// value - Handle to the value. +/// out_value - A pointer to variable that will receive the value. Not +/// filled if false is returned. +/// Return value: +/// Returns TRUE if the attribute maps to a number value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetNumberValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer out_value, +) { + return _FPDF_StructElement_Attr_GetNumberValue(value, +out_value, +); +} + +late final _FPDF_StructElement_Attr_GetNumberValuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetNumberValue'); +late final _FPDF_StructElement_Attr_GetNumberValue = _FPDF_StructElement_Attr_GetNumberValuePtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetStringValue +/// Get the value of a string attribute in an attribute map as string. +/// FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME for this property. +/// Parameters: +/// value - Handle to the value. +/// buffer - A buffer for holding the returned key in UTF-16LE. +/// This is only modified if |buflen| is longer than the +/// length of the key. Optional, pass null to just +/// retrieve the size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the minimum +/// buffer size to contain the key. Not filled if FALSE is +/// returned. +/// Return value: +/// Returns TRUE if the attribute maps to a string value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetStringValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetStringValue(value, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetStringValue'); +late final _FPDF_StructElement_Attr_GetStringValue = _FPDF_StructElement_Attr_GetStringValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetBlobValue +/// Get the value of a blob attribute in an attribute map as string. +/// Parameters: +/// value - Handle to the value. +/// buffer - A buffer for holding the returned value. This is only +/// modified if |buflen| is at least as long as the length +/// of the value. Optional, pass null to just retrieve the +/// size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the minimum +/// buffer size to contain the key. Not filled if FALSE is +/// returned. +/// Return value: +/// Returns TRUE if the attribute maps to a string value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetBlobValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetBlobValue(value, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetBlobValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetBlobValue'); +late final _FPDF_StructElement_Attr_GetBlobValue = _FPDF_StructElement_Attr_GetBlobValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_CountChildren +/// Count the number of children values in an attribute. +/// Parameters: +/// value - Handle to the value. +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructElement_Attr_CountChildren(FPDF_STRUCTELEMENT_ATTR_VALUE value, +) { + return _FPDF_StructElement_Attr_CountChildren(value, +); +} + +late final _FPDF_StructElement_Attr_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_CountChildren'); +late final _FPDF_StructElement_Attr_CountChildren = _FPDF_StructElement_Attr_CountChildrenPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetChildAtIndex +/// Get a child from an attribute. +/// Parameters: +/// value - Handle to the value. +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. +/// Comments: +/// The |index| must be less than the +/// FPDF_StructElement_Attr_CountChildren() return value. +FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetChildAtIndex(FPDF_STRUCTELEMENT_ATTR_VALUE value, +int index, +) { + return _FPDF_StructElement_Attr_GetChildAtIndex(value, +index, +); +} + +late final _FPDF_StructElement_Attr_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetChildAtIndex'); +late final _FPDF_StructElement_Attr_GetChildAtIndex = _FPDF_StructElement_Attr_GetChildAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetMarkedContentIdCount +/// Get the count of marked content ids for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The count of marked content ids or -1 if none exists. +int FPDF_StructElement_GetMarkedContentIdCount(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetMarkedContentIdCount(struct_element, +); +} + +late final _FPDF_StructElement_GetMarkedContentIdCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdCount'); +late final _FPDF_StructElement_GetMarkedContentIdCount = _FPDF_StructElement_GetMarkedContentIdCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetMarkedContentIdAtIndex +/// Get the marked content id at a given index for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index of the marked content id, 0-based. +/// Return value: +/// The marked content ID of the element. If no ID exists, returns +/// -1. +/// Comments: +/// The |index| must be less than the +/// FPDF_StructElement_GetMarkedContentIdCount() return value. +/// This will likely supersede FPDF_StructElement_GetMarkedContentID(). +int FPDF_StructElement_GetMarkedContentIdAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetMarkedContentIdAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetMarkedContentIdAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdAtIndex'); +late final _FPDF_StructElement_GetMarkedContentIdAtIndex = _FPDF_StructElement_GetMarkedContentIdAtIndexPtr.asFunction(); + +/// Create a document availability provider. +/// +/// file_avail - pointer to file availability interface. +/// file - pointer to a file access interface. +/// +/// Returns a handle to the document availability provider, or NULL on error. +/// +/// FPDFAvail_Destroy() must be called when done with the availability provider. +FPDF_AVAIL FPDFAvail_Create(ffi.Pointer file_avail, +ffi.Pointer file, +) { + return _FPDFAvail_Create(file_avail, +file, +); +} + +late final _FPDFAvail_CreatePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFAvail_Create'); +late final _FPDFAvail_Create = _FPDFAvail_CreatePtr.asFunction , ffi.Pointer )>(); + +/// Destroy the |avail| document availability provider. +/// +/// avail - handle to document availability provider to be destroyed. +void FPDFAvail_Destroy(FPDF_AVAIL avail, +) { + return _FPDFAvail_Destroy(avail, +); +} + +late final _FPDFAvail_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_Destroy'); +late final _FPDFAvail_Destroy = _FPDFAvail_DestroyPtr.asFunction(); + +/// Checks if the document is ready for loading, if not, gets download hints. +/// +/// avail - handle to document availability provider. +/// hints - pointer to a download hints interface. +/// +/// Returns one of: +/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. +/// PDF_DATA_NOTAVAIL: Data not yet available. +/// PDF_DATA_AVAIL: Data available. +/// +/// Applications should call this function whenever new data arrives, and process +/// all the generated download hints, if any, until the function returns +/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. +/// if hints is nullptr, the function just check current document availability. +/// +/// Once all data is available, call FPDFAvail_GetDocument() to get a document +/// handle. +int FPDFAvail_IsDocAvail(FPDF_AVAIL avail, +ffi.Pointer hints, +) { + return _FPDFAvail_IsDocAvail(avail, +hints, +); +} + +late final _FPDFAvail_IsDocAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsDocAvail'); +late final _FPDFAvail_IsDocAvail = _FPDFAvail_IsDocAvailPtr.asFunction )>(); + +/// Get document from the availability provider. +/// +/// avail - handle to document availability provider. +/// password - password for decrypting the PDF file. Optional. +/// +/// Returns a handle to the document. +/// +/// When FPDFAvail_IsDocAvail() returns TRUE, call FPDFAvail_GetDocument() to +/// retrieve the document handle. +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +FPDF_DOCUMENT FPDFAvail_GetDocument(FPDF_AVAIL avail, +FPDF_BYTESTRING password, +) { + return _FPDFAvail_GetDocument(avail, +password, +); +} + +late final _FPDFAvail_GetDocumentPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_GetDocument'); +late final _FPDFAvail_GetDocument = _FPDFAvail_GetDocumentPtr.asFunction(); + +/// Get the page number for the first available page in a linearized PDF. +/// +/// doc - document handle. +/// +/// Returns the zero-based index for the first available page. +/// +/// For most linearized PDFs, the first available page will be the first page, +/// however, some PDFs might make another page the first available page. +/// For non-linearized PDFs, this function will always return zero. +int FPDFAvail_GetFirstPageNum(FPDF_DOCUMENT doc, +) { + return _FPDFAvail_GetFirstPageNum(doc, +); +} + +late final _FPDFAvail_GetFirstPageNumPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_GetFirstPageNum'); +late final _FPDFAvail_GetFirstPageNum = _FPDFAvail_GetFirstPageNumPtr.asFunction(); + +/// Check if |page_index| is ready for loading, if not, get the +/// |FX_DOWNLOADHINTS|. +/// +/// avail - handle to document availability provider. +/// page_index - index number of the page. Zero for the first page. +/// hints - pointer to a download hints interface. Populated if +/// |page_index| is not available. +/// +/// Returns one of: +/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. +/// PDF_DATA_NOTAVAIL: Data not yet available. +/// PDF_DATA_AVAIL: Data available. +/// +/// This function can be called only after FPDFAvail_GetDocument() is called. +/// Applications should call this function whenever new data arrives and process +/// all the generated download |hints|, if any, until this function returns +/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. Applications can then perform page +/// loading. +/// if hints is nullptr, the function just check current availability of +/// specified page. +int FPDFAvail_IsPageAvail(FPDF_AVAIL avail, +int page_index, +ffi.Pointer hints, +) { + return _FPDFAvail_IsPageAvail(avail, +page_index, +hints, +); +} + +late final _FPDFAvail_IsPageAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsPageAvail'); +late final _FPDFAvail_IsPageAvail = _FPDFAvail_IsPageAvailPtr.asFunction )>(); + +/// Check if form data is ready for initialization, if not, get the +/// |FX_DOWNLOADHINTS|. +/// +/// avail - handle to document availability provider. +/// hints - pointer to a download hints interface. Populated if form is not +/// ready for initialization. +/// +/// Returns one of: +/// PDF_FORM_ERROR: A common eror, in general incorrect parameters. +/// PDF_FORM_NOTAVAIL: Data not available. +/// PDF_FORM_AVAIL: Data available. +/// PDF_FORM_NOTEXIST: No form data. +/// +/// This function can be called only after FPDFAvail_GetDocument() is called. +/// The application should call this function whenever new data arrives and +/// process all the generated download |hints|, if any, until the function +/// |PDF_FORM_ERROR|, |PDF_FORM_AVAIL| or |PDF_FORM_NOTEXIST|. +/// if hints is nullptr, the function just check current form availability. +/// +/// Applications can then perform page loading. It is recommend to call +/// FPDFDOC_InitFormFillEnvironment() when |PDF_FORM_AVAIL| is returned. +int FPDFAvail_IsFormAvail(FPDF_AVAIL avail, +ffi.Pointer hints, +) { + return _FPDFAvail_IsFormAvail(avail, +hints, +); +} + +late final _FPDFAvail_IsFormAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsFormAvail'); +late final _FPDFAvail_IsFormAvail = _FPDFAvail_IsFormAvailPtr.asFunction )>(); + +/// Check whether a document is a linearized PDF. +/// +/// avail - handle to document availability provider. +/// +/// Returns one of: +/// PDF_LINEARIZED +/// PDF_NOT_LINEARIZED +/// PDF_LINEARIZATION_UNKNOWN +/// +/// FPDFAvail_IsLinearized() will return |PDF_LINEARIZED| or |PDF_NOT_LINEARIZED| +/// when we have 1k of data. If the files size less than 1k, it returns +/// |PDF_LINEARIZATION_UNKNOWN| as there is insufficient information to determine +/// if the PDF is linearlized. +int FPDFAvail_IsLinearized(FPDF_AVAIL avail, +) { + return _FPDFAvail_IsLinearized(avail, +); +} + +late final _FPDFAvail_IsLinearizedPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_IsLinearized'); +late final _FPDFAvail_IsLinearized = _FPDFAvail_IsLinearizedPtr.asFunction(); + +/// Setup an unsupported object handler. +/// +/// unsp_info - Pointer to an UNSUPPORT_INFO structure. +/// +/// Returns TRUE on success. +int FSDK_SetUnSpObjProcessHandler(ffi.Pointer unsp_info, +) { + return _FSDK_SetUnSpObjProcessHandler(unsp_info, +); +} + +late final _FSDK_SetUnSpObjProcessHandlerPtr = _lookup< + ffi.NativeFunction )>>('FSDK_SetUnSpObjProcessHandler'); +late final _FSDK_SetUnSpObjProcessHandler = _FSDK_SetUnSpObjProcessHandlerPtr.asFunction )>(); + +/// Set replacement function for calls to time(). +/// +/// This API is intended to be used only for testing, thus may cause PDFium to +/// behave poorly in production environments. +/// +/// func - Function pointer to alternate implementation of time(), or +/// NULL to restore to actual time() call itself. +void FSDK_SetTimeFunction(ffi.Pointer> func, +) { + return _FSDK_SetTimeFunction(func, +); +} + +late final _FSDK_SetTimeFunctionPtr = _lookup< + ffi.NativeFunction> )>>('FSDK_SetTimeFunction'); +late final _FSDK_SetTimeFunction = _FSDK_SetTimeFunctionPtr.asFunction> )>(); + +/// Set replacement function for calls to localtime(). +/// +/// This API is intended to be used only for testing, thus may cause PDFium to +/// behave poorly in production environments. +/// +/// func - Function pointer to alternate implementation of localtime(), or +/// NULL to restore to actual localtime() call itself. +void FSDK_SetLocaltimeFunction(ffi.Pointer Function(ffi.Pointer )>> func, +) { + return _FSDK_SetLocaltimeFunction(func, +); +} + +late final _FSDK_SetLocaltimeFunctionPtr = _lookup< + ffi.NativeFunction Function(ffi.Pointer )>> )>>('FSDK_SetLocaltimeFunction'); +late final _FSDK_SetLocaltimeFunction = _FSDK_SetLocaltimeFunctionPtr.asFunction Function(ffi.Pointer )>> )>(); + +/// Get the document's PageMode. +/// +/// doc - Handle to document. +/// +/// Returns one of the |PAGEMODE_*| flags defined above. +/// +/// The page mode defines how the document should be initially displayed. +int FPDFDoc_GetPageMode(FPDF_DOCUMENT document, +) { + return _FPDFDoc_GetPageMode(document, +); +} + +late final _FPDFDoc_GetPageModePtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetPageMode'); +late final _FPDFDoc_GetPageMode = _FPDFDoc_GetPageModePtr.asFunction(); + +/// Set "MediaBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetMediaBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetMediaBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetMediaBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetMediaBox'); +late final _FPDFPage_SetMediaBox = _FPDFPage_SetMediaBoxPtr.asFunction(); + +/// Set "CropBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetCropBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetCropBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetCropBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetCropBox'); +late final _FPDFPage_SetCropBox = _FPDFPage_SetCropBoxPtr.asFunction(); + +/// Set "BleedBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetBleedBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetBleedBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetBleedBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetBleedBox'); +late final _FPDFPage_SetBleedBox = _FPDFPage_SetBleedBoxPtr.asFunction(); + +/// Set "TrimBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetTrimBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetTrimBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetTrimBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetTrimBox'); +late final _FPDFPage_SetTrimBox = _FPDFPage_SetTrimBoxPtr.asFunction(); + +/// Set "ArtBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetArtBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetArtBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetArtBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetArtBox'); +late final _FPDFPage_SetArtBox = _FPDFPage_SetArtBoxPtr.asFunction(); + +/// Get "MediaBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetMediaBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetMediaBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetMediaBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetMediaBox'); +late final _FPDFPage_GetMediaBox = _FPDFPage_GetMediaBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "CropBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetCropBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetCropBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetCropBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetCropBox'); +late final _FPDFPage_GetCropBox = _FPDFPage_GetCropBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "BleedBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetBleedBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetBleedBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetBleedBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetBleedBox'); +late final _FPDFPage_GetBleedBox = _FPDFPage_GetBleedBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "TrimBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetTrimBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetTrimBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetTrimBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetTrimBox'); +late final _FPDFPage_GetTrimBox = _FPDFPage_GetTrimBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "ArtBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetArtBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetArtBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetArtBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetArtBox'); +late final _FPDFPage_GetArtBox = _FPDFPage_GetArtBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Apply transforms to |page|. +/// +/// If |matrix| is provided it will be applied to transform the page. +/// If |clipRect| is provided it will be used to clip the resulting page. +/// If neither |matrix| or |clipRect| are provided this method returns |false|. +/// Returns |true| if transforms are applied. +/// +/// This function will transform the whole page, and would take effect to all the +/// objects in the page. +/// +/// page - Page handle. +/// matrix - Transform matrix. +/// clipRect - Clipping rectangle. +int FPDFPage_TransFormWithClip(FPDF_PAGE page, +ffi.Pointer matrix, +ffi.Pointer clipRect, +) { + return _FPDFPage_TransFormWithClip(page, +matrix, +clipRect, +); +} + +late final _FPDFPage_TransFormWithClipPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPage_TransFormWithClip'); +late final _FPDFPage_TransFormWithClip = _FPDFPage_TransFormWithClipPtr.asFunction , ffi.Pointer )>(); + +/// Transform (scale, rotate, shear, move) the clip path of page object. +/// page_object - Handle to a page object. Returned by +/// FPDFPageObj_NewImageObj(). +/// +/// a - The coefficient "a" of the matrix. +/// b - The coefficient "b" of the matrix. +/// c - The coefficient "c" of the matrix. +/// d - The coefficient "d" of the matrix. +/// e - The coefficient "e" of the matrix. +/// f - The coefficient "f" of the matrix. +void FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPageObj_TransformClipPath(page_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPageObj_TransformClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_TransformClipPath'); +late final _FPDFPageObj_TransformClipPath = _FPDFPageObj_TransformClipPathPtr.asFunction(); + +/// Experimental API. +/// Get the clip path of the page object. +/// +/// page object - Handle to a page object. Returned by e.g. +/// FPDFPage_GetObject(). +/// +/// Returns the handle to the clip path, or NULL on failure. The caller does not +/// take ownership of the returned FPDF_CLIPPATH. Instead, it remains valid until +/// FPDF_ClosePage() is called for the page containing |page_object|. +FPDF_CLIPPATH FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetClipPath(page_object, +); +} + +late final _FPDFPageObj_GetClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetClipPath'); +late final _FPDFPageObj_GetClipPath = _FPDFPageObj_GetClipPathPtr.asFunction(); + +/// Experimental API. +/// Get number of paths inside |clip_path|. +/// +/// clip_path - handle to a clip_path. +/// +/// Returns the number of objects in |clip_path| or -1 on failure. +int FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path, +) { + return _FPDFClipPath_CountPaths(clip_path, +); +} + +late final _FPDFClipPath_CountPathsPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_CountPaths'); +late final _FPDFClipPath_CountPaths = _FPDFClipPath_CountPathsPtr.asFunction(); + +/// Experimental API. +/// Get number of segments inside one path of |clip_path|. +/// +/// clip_path - handle to a clip_path. +/// path_index - index into the array of paths of the clip path. +/// +/// Returns the number of segments or -1 on failure. +int FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, +int path_index, +) { + return _FPDFClipPath_CountPathSegments(clip_path, +path_index, +); +} + +late final _FPDFClipPath_CountPathSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_CountPathSegments'); +late final _FPDFClipPath_CountPathSegments = _FPDFClipPath_CountPathSegmentsPtr.asFunction(); + +/// Experimental API. +/// Get segment in one specific path of |clip_path| at index. +/// +/// clip_path - handle to a clip_path. +/// path_index - the index of a path. +/// segment_index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on failure. The caller does not +/// take ownership of the returned FPDF_PATHSEGMENT. Instead, it remains valid +/// until FPDF_ClosePage() is called for the page containing |clip_path|. +FPDF_PATHSEGMENT FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path, +int path_index, +int segment_index, +) { + return _FPDFClipPath_GetPathSegment(clip_path, +path_index, +segment_index, +); +} + +late final _FPDFClipPath_GetPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_GetPathSegment'); +late final _FPDFClipPath_GetPathSegment = _FPDFClipPath_GetPathSegmentPtr.asFunction(); + +/// Create a new clip path, with a rectangle inserted. +/// +/// Caller takes ownership of the returned FPDF_CLIPPATH. It should be freed with +/// FPDF_DestroyClipPath(). +/// +/// left - The left of the clip box. +/// bottom - The bottom of the clip box. +/// right - The right of the clip box. +/// top - The top of the clip box. +FPDF_CLIPPATH FPDF_CreateClipPath(double left, +double bottom, +double right, +double top, +) { + return _FPDF_CreateClipPath(left, +bottom, +right, +top, +); +} + +late final _FPDF_CreateClipPathPtr = _lookup< + ffi.NativeFunction>('FPDF_CreateClipPath'); +late final _FPDF_CreateClipPath = _FPDF_CreateClipPathPtr.asFunction(); + +/// Destroy the clip path. +/// +/// clipPath - A handle to the clip path. It will be invalid after this call. +void FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath, +) { + return _FPDF_DestroyClipPath(clipPath, +); +} + +late final _FPDF_DestroyClipPathPtr = _lookup< + ffi.NativeFunction>('FPDF_DestroyClipPath'); +late final _FPDF_DestroyClipPath = _FPDF_DestroyClipPathPtr.asFunction(); + +/// Clip the page content, the page content that outside the clipping region +/// become invisible. +/// +/// A clip path will be inserted before the page content stream or content array. +/// In this way, the page content will be clipped by this clip path. +/// +/// page - A page handle. +/// clipPath - A handle to the clip path. (Does not take ownership.) +void FPDFPage_InsertClipPath(FPDF_PAGE page, +FPDF_CLIPPATH clipPath, +) { + return _FPDFPage_InsertClipPath(page, +clipPath, +); +} + +late final _FPDFPage_InsertClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertClipPath'); +late final _FPDFPage_InsertClipPath = _FPDFPage_InsertClipPathPtr.asFunction(); + +/// Flatten annotations and form fields into the page contents. +/// +/// page - handle to the page. +/// nFlag - One of the |FLAT_*| values denoting the page usage. +/// +/// Returns one of the |FLATTEN_*| values. +/// +/// Currently, all failures return |FLATTEN_FAIL| with no indication of the +/// cause. +int FPDFPage_Flatten(FPDF_PAGE page, +int nFlag, +) { + return _FPDFPage_Flatten(page, +nFlag, +); +} + +late final _FPDFPage_FlattenPtr = _lookup< + ffi.NativeFunction>('FPDFPage_Flatten'); +late final _FPDFPage_Flatten = _FPDFPage_FlattenPtr.asFunction(); + +/// Experimental API. +/// Gets the decoded data from the thumbnail of |page| if it exists. +/// This only modifies |buffer| if |buflen| less than or equal to the +/// size of the decoded data. Returns the size of the decoded +/// data or 0 if thumbnail DNE. Optional, pass null to just retrieve +/// the size of the buffer needed. +/// +/// page - handle to a page. +/// buffer - buffer for holding the decoded image data. +/// buflen - length of the buffer in bytes. +int FPDFPage_GetDecodedThumbnailData(FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFPage_GetDecodedThumbnailData(page, +buffer, +buflen, +); +} + +late final _FPDFPage_GetDecodedThumbnailDataPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetDecodedThumbnailData'); +late final _FPDFPage_GetDecodedThumbnailData = _FPDFPage_GetDecodedThumbnailDataPtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the raw data from the thumbnail of |page| if it exists. +/// This only modifies |buffer| if |buflen| is less than or equal to +/// the size of the raw data. Returns the size of the raw data or 0 +/// if thumbnail DNE. Optional, pass null to just retrieve the size +/// of the buffer needed. +/// +/// page - handle to a page. +/// buffer - buffer for holding the raw image data. +/// buflen - length of the buffer in bytes. +int FPDFPage_GetRawThumbnailData(FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFPage_GetRawThumbnailData(page, +buffer, +buflen, +); +} + +late final _FPDFPage_GetRawThumbnailDataPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetRawThumbnailData'); +late final _FPDFPage_GetRawThumbnailData = _FPDFPage_GetRawThumbnailDataPtr.asFunction , int )>(); + +/// Experimental API. +/// Returns the thumbnail of |page| as a FPDF_BITMAP. Returns a nullptr +/// if unable to access the thumbnail's stream. +/// +/// page - handle to a page. +FPDF_BITMAP FPDFPage_GetThumbnailAsBitmap(FPDF_PAGE page, +) { + return _FPDFPage_GetThumbnailAsBitmap(page, +); +} + +late final _FPDFPage_GetThumbnailAsBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetThumbnailAsBitmap'); +late final _FPDFPage_GetThumbnailAsBitmap = _FPDFPage_GetThumbnailAsBitmapPtr.asFunction(); + +} + +/// PDF text rendering modes +enum FPDF_TEXT_RENDERMODE { + FPDF_TEXTRENDERMODE_UNKNOWN(-1), + FPDF_TEXTRENDERMODE_FILL(0), + FPDF_TEXTRENDERMODE_STROKE(1), + FPDF_TEXTRENDERMODE_FILL_STROKE(2), + FPDF_TEXTRENDERMODE_INVISIBLE(3), + FPDF_TEXTRENDERMODE_FILL_CLIP(4), + FPDF_TEXTRENDERMODE_STROKE_CLIP(5), + FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP(6), + FPDF_TEXTRENDERMODE_CLIP(7); + + static const FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP; + + final int value; + const FPDF_TEXT_RENDERMODE(this.value); + + static FPDF_TEXT_RENDERMODE fromValue(int value) => switch (value) { + -1 => FPDF_TEXTRENDERMODE_UNKNOWN, + 0 => FPDF_TEXTRENDERMODE_FILL, + 1 => FPDF_TEXTRENDERMODE_STROKE, + 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, + 3 => FPDF_TEXTRENDERMODE_INVISIBLE, + 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, + 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, + 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, + 7 => FPDF_TEXTRENDERMODE_CLIP, + _ => throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), + }; + + @override + String toString() { + if (this == FPDF_TEXTRENDERMODE_CLIP) return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; + return super.toString(); + }} + +final class fpdf_action_t__ extends ffi.Opaque{ +} + +/// PDF types - use incomplete types (never completed) to force API type safety. +typedef FPDF_ACTION = ffi.Pointer; +final class fpdf_annotation_t__ extends ffi.Opaque{ +} + +typedef FPDF_ANNOTATION = ffi.Pointer; +final class fpdf_attachment_t__ extends ffi.Opaque{ +} + +typedef FPDF_ATTACHMENT = ffi.Pointer; +final class fpdf_avail_t__ extends ffi.Opaque{ +} + +typedef FPDF_AVAIL = ffi.Pointer; +final class fpdf_bitmap_t__ extends ffi.Opaque{ +} + +typedef FPDF_BITMAP = ffi.Pointer; +final class fpdf_bookmark_t__ extends ffi.Opaque{ +} + +typedef FPDF_BOOKMARK = ffi.Pointer; +final class fpdf_clippath_t__ extends ffi.Opaque{ +} + +typedef FPDF_CLIPPATH = ffi.Pointer; +final class fpdf_dest_t__ extends ffi.Opaque{ +} + +typedef FPDF_DEST = ffi.Pointer; +final class fpdf_document_t__ extends ffi.Opaque{ +} + +typedef FPDF_DOCUMENT = ffi.Pointer; +final class fpdf_font_t__ extends ffi.Opaque{ +} + +typedef FPDF_FONT = ffi.Pointer; +final class fpdf_form_handle_t__ extends ffi.Opaque{ +} + +typedef FPDF_FORMHANDLE = ffi.Pointer; +final class fpdf_glyphpath_t__ extends ffi.Opaque{ +} + +typedef FPDF_GLYPHPATH = ffi.Pointer; +final class fpdf_javascript_action_t extends ffi.Opaque{ +} + +typedef FPDF_JAVASCRIPT_ACTION = ffi.Pointer; +final class fpdf_link_t__ extends ffi.Opaque{ +} + +typedef FPDF_LINK = ffi.Pointer; +final class fpdf_page_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGE = ffi.Pointer; +final class fpdf_pagelink_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGELINK = ffi.Pointer; +final class fpdf_pageobject_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGEOBJECT = ffi.Pointer; +final class fpdf_pageobjectmark_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGEOBJECTMARK = ffi.Pointer; +final class fpdf_pagerange_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGERANGE = ffi.Pointer; +final class fpdf_pathsegment_t extends ffi.Opaque{ +} + +typedef FPDF_PATHSEGMENT = ffi.Pointer; +final class fpdf_schhandle_t__ extends ffi.Opaque{ +} + +typedef FPDF_SCHHANDLE = ffi.Pointer; +final class fpdf_signature_t__ extends ffi.Opaque{ +} + +typedef FPDF_SIGNATURE = ffi.Pointer; +typedef FPDF_SKIA_CANVAS = ffi.Pointer; +final class fpdf_structelement_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT = ffi.Pointer; +final class fpdf_structelement_attr_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT_ATTR = ffi.Pointer; +final class fpdf_structelement_attr_value_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT_ATTR_VALUE = ffi.Pointer; +final class fpdf_structtree_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTTREE = ffi.Pointer; +final class fpdf_textpage_t__ extends ffi.Opaque{ +} + +typedef FPDF_TEXTPAGE = ffi.Pointer; +final class fpdf_widget_t__ extends ffi.Opaque{ +} + +typedef FPDF_WIDGET = ffi.Pointer; +final class fpdf_xobject_t__ extends ffi.Opaque{ +} + +typedef FPDF_XOBJECT = ffi.Pointer; +/// Basic data types +typedef FPDF_BOOL = ffi.Int; +typedef DartFPDF_BOOL = int; +typedef FPDF_RESULT = ffi.Int; +typedef DartFPDF_RESULT = int; +typedef FPDF_DWORD = ffi.UnsignedLong; +typedef DartFPDF_DWORD = int; +typedef FS_FLOAT = ffi.Float; +typedef DartFS_FLOAT = double; +/// Duplex types +enum _FPDF_DUPLEXTYPE_ { + DuplexUndefined(0), + Simplex(1), + DuplexFlipShortEdge(2), + DuplexFlipLongEdge(3); + + + final int value; + const _FPDF_DUPLEXTYPE_(this.value); + + static _FPDF_DUPLEXTYPE_ fromValue(int value) => switch (value) { + 0 => DuplexUndefined, + 1 => Simplex, + 2 => DuplexFlipShortEdge, + 3 => DuplexFlipLongEdge, + _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), + }; + +} + +/// String types +typedef FPDF_WCHAR = ffi.UnsignedShort; +typedef DartFPDF_WCHAR = int; +/// Public PDFium API type for byte strings. +typedef FPDF_BYTESTRING = ffi.Pointer; +/// The public PDFium API always uses UTF-16LE encoded wide strings, each +/// character uses 2 bytes (except surrogation), with the low byte first. +typedef FPDF_WIDESTRING = ffi.Pointer; +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. +final class FPDF_BSTR_ extends ffi.Struct{ + /// String buffer, manipulate only with FPDF_BStr_* methods. + external ffi.Pointer str; + + /// Length of the string, in bytes. + @ffi.Int() + external int len; + +} + +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. +typedef FPDF_BSTR = FPDF_BSTR_; +/// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a +/// Windows unicode string, however, special care needs to be taken if you +/// expect to process Unicode larger than 0xffff. +/// +/// For Linux/Unix programmers: most compiler/library environments use 4 bytes +/// for a Unicode character, and you have to convert between FPDF_WIDESTRING and +/// system wide string by yourself. +typedef FPDF_STRING = ffi.Pointer; +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. +final class _FS_MATRIX_ extends ffi.Struct{ + @ffi.Float() + external double a; + + @ffi.Float() + external double b; + + @ffi.Float() + external double c; + + @ffi.Float() + external double d; + + @ffi.Float() + external double e; + + @ffi.Float() + external double f; + +} + +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. +typedef FS_MATRIX = _FS_MATRIX_; +/// Rectangle area(float) in device or page coordinate system. +final class _FS_RECTF_ extends ffi.Struct{ + /// The x-coordinate of the left-top corner. + @ffi.Float() + external double left; + + /// The y-coordinate of the left-top corner. + @ffi.Float() + external double top; + + /// The x-coordinate of the right-bottom corner. + @ffi.Float() + external double right; + + /// The y-coordinate of the right-bottom corner. + @ffi.Float() + external double bottom; + +} + +/// Rectangle area(float) in device or page coordinate system. +typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; +typedef FS_RECTF = _FS_RECTF_; +/// Const Pointer to FS_RECTF structure. +typedef FS_LPCRECTF = ffi.Pointer; +/// Rectangle size. Coordinate system agnostic. +final class FS_SIZEF_ extends ffi.Struct{ + @ffi.Float() + external double width; + + @ffi.Float() + external double height; + +} + +/// Rectangle size. Coordinate system agnostic. +typedef FS_LPSIZEF = ffi.Pointer; +typedef FS_SIZEF = FS_SIZEF_; +/// Const Pointer to FS_SIZEF structure. +typedef FS_LPCSIZEF = ffi.Pointer; +/// 2D Point. Coordinate system agnostic. +final class FS_POINTF_ extends ffi.Struct{ + @ffi.Float() + external double x; + + @ffi.Float() + external double y; + +} + +/// 2D Point. Coordinate system agnostic. +typedef FS_LPPOINTF = ffi.Pointer; +typedef FS_POINTF = FS_POINTF_; +/// Const Pointer to FS_POINTF structure. +typedef FS_LPCPOINTF = ffi.Pointer; +final class _FS_QUADPOINTSF extends ffi.Struct{ + @FS_FLOAT() + external double x1; + + @FS_FLOAT() + external double y1; + + @FS_FLOAT() + external double x2; + + @FS_FLOAT() + external double y2; + + @FS_FLOAT() + external double x3; + + @FS_FLOAT() + external double y3; + + @FS_FLOAT() + external double x4; + + @FS_FLOAT() + external double y4; + +} + +typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; +/// Annotation enums. +typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; +typedef DartFPDF_ANNOTATION_SUBTYPE = int; +typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; +typedef DartFPDF_ANNOT_APPEARANCEMODE = int; +/// Dictionary value types. +typedef FPDF_OBJECT_TYPE = ffi.Int; +typedef DartFPDF_OBJECT_TYPE = int; +/// PDF renderer types - Experimental. +/// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. +enum FPDF_RENDERER_TYPE { + /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ + FPDF_RENDERERTYPE_AGG(0), + /// Skia - https://skia.org/ + FPDF_RENDERERTYPE_SKIA(1); + + + final int value; + const FPDF_RENDERER_TYPE(this.value); + + static FPDF_RENDERER_TYPE fromValue(int value) => switch (value) { + 0 => FPDF_RENDERERTYPE_AGG, + 1 => FPDF_RENDERERTYPE_SKIA, + _ => throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), + }; + +} + +/// Process-wide options for initializing the library. +final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct{ + /// Version number of the interface. Currently must be 2. + /// Support for version 1 will be deprecated in the future. + @ffi.Int() + external int version; + + /// Array of paths to scan in place of the defaults when using built-in + /// FXGE font loading code. The array is terminated by a NULL pointer. + /// The Array may be NULL itself to use the default paths. May be ignored + /// entirely depending upon the platform. + external ffi.Pointer> m_pUserFontPaths; + + /// Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. + external ffi.Pointer m_pIsolate; + + /// The embedder data slot to use in the v8::Isolate to store PDFium's + /// per-isolate data. The value needs to be in the range + /// [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most + /// embedders. + @ffi.UnsignedInt() + external int m_v8EmbedderSlot; + + /// Pointer to the V8::Platform to use. + external ffi.Pointer m_pPlatform; + + /// Explicit specification of core renderer to use. |m_RendererType| must be + /// a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, + /// or else the initialization will fail with an immediate crash. + /// Note that use of a specified |FPDF_RENDERER_TYPE| value for which the + /// corresponding render library is not included in the build will similarly + /// fail with an immediate crash. + @ffi.UnsignedInt() + external int m_RendererTypeAsInt; + +FPDF_RENDERER_TYPE get m_RendererType => FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); + +} + +/// Process-wide options for initializing the library. +typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; +/// Structure for custom file access. +final class FPDF_FILEACCESS extends ffi.Struct{ + /// File length, in bytes. + @ffi.UnsignedLong() + external int m_FileLen; + + /// A function pointer for getting a block of data from a specific position. + /// Position is specified by byte offset from the beginning of the file. + /// The pointer to the buffer is never NULL and the size is never 0. + /// The position and size will never go out of range of the file length. + /// It may be possible for PDFium to call this function multiple times for + /// the same position. + /// Return value: should be non-zero if successful, zero for error. + external ffi.Pointer param, ffi.UnsignedLong position, ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; + + /// A custom pointer for all implementation specific data. This pointer will + /// be used as the first parameter to the m_GetBlock callback. + external ffi.Pointer m_Param; + +} + +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. +final class FPDF_FILEHANDLER_ extends ffi.Struct{ + /// User-defined data. + /// Note: Callers can use this field to track controls. + external ffi.Pointer clientData; + + /// Callback function to release the current file stream object. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// None. + external ffi.Pointer clientData)>> Release; + + /// Callback function to retrieve the current file stream size. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// Size of file stream. + external ffi.Pointer clientData)>> GetSize; + + /// Callback function to read data from the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates reading position. + /// buffer - Memory buffer to store data which are read from + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be read from file stream, + /// in bytes. The buffer indicated by |buffer| must be + /// large enough to store specified data. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; + + /// Callback function to write data into the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates writing position. + /// buffer - Memory buffer contains data which is written into + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be written into file + /// stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; + + /// Callback function to flush all internal accessing buffers. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData)>> Flush; + + /// Callback function to change file size. + /// + /// Description: + /// This function is called under writing mode usually. Implementer + /// can determine whether to realize it based on application requests. + /// Parameters: + /// clientData - Pointer to user-defined data. + /// size - New size of file stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; + +} + +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. +typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. +final class FPDF_COLORSCHEME_ extends ffi.Struct{ + @FPDF_DWORD() + external int path_fill_color; + + @FPDF_DWORD() + external int path_stroke_color; + + @FPDF_DWORD() + external int text_fill_color; + + @FPDF_DWORD() + external int text_stroke_color; + +} + +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. +typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +final class _FPDF_SYSFONTINFO extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: Release + /// Give implementation a chance to release any data after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None + /// Comments: + /// Called by PDFium during the final cleanup process. + external ffi.Pointer pThis)>> Release; + + /// Method: EnumFonts + /// Enumerate all fonts installed on the system + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// pMapper - An opaque pointer to internal font mapper, used + /// when calling FPDF_AddInstalledFont(). + /// Return Value: + /// None + /// Comments: + /// Implementations should call FPDF_AddInstalledFont() function for + /// each font found. Only TrueType/OpenType and Type1 fonts are + /// accepted by PDFium. + external ffi.Pointer pThis, ffi.Pointer pMapper)>> EnumFonts; + + /// Method: MapFont + /// Use the system font mapper to get a font handle from requested + /// parameters. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if GetFont method is not implemented. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// weight - Weight of the requested font. 400 is normal and + /// 700 is bold. + /// bItalic - Italic option of the requested font, TRUE or + /// FALSE. + /// charset - Character set identifier for the requested font. + /// See above defined constants. + /// pitch_family - A combination of flags. See above defined + /// constants. + /// face - Typeface name. Currently use system local encoding + /// only. + /// bExact - Obsolete: this parameter is now ignored. + /// Return Value: + /// An opaque pointer for font handle, or NULL if system mapping is + /// not supported. + /// Comments: + /// If the system supports native font mapper (like Windows), + /// implementation can implement this method to get a font handle. + /// Otherwise, PDFium will do the mapping and then call GetFont + /// method. Only TrueType/OpenType and Type1 fonts are accepted + /// by PDFium. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Int weight, FPDF_BOOL bItalic, ffi.Int charset, ffi.Int pitch_family, ffi.Pointer face, ffi.Pointer bExact)>> MapFont; + + /// Method: GetFont + /// Get a handle to a particular font by its internal ID + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if MapFont method is not implemented. + /// Return Value: + /// An opaque pointer for font handle. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// face - Typeface name in system local encoding. + /// Comments: + /// If the system mapping not supported, PDFium will do the font + /// mapping and use this method to get a font handle. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Pointer face)>> GetFont; + + /// Method: GetFontData + /// Get font data from a font + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// table - TrueType/OpenType table identifier (refer to + /// TrueType specification), or 0 for the whole file. + /// buffer - The buffer receiving the font data. Can be NULL if + /// not provided. + /// buf_size - Buffer size, can be zero if not provided. + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + /// Comments: + /// Can read either the full font file, or a particular + /// TrueType/OpenType table. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.UnsignedInt table, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFontData; + + /// Method: GetFaceName + /// Get face name from a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// buffer - The buffer receiving the face name. Can be NULL if + /// not provided + /// buf_size - Buffer size, can be zero if not provided + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFaceName; + + /// Method: GetFontCharset + /// Get character set information for a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// Character set identifier. See defined constants above. + external ffi.Pointer pThis, ffi.Pointer hFont)>> GetFontCharset; + + /// Method: DeleteFont + /// Delete a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// None + external ffi.Pointer pThis, ffi.Pointer hFont)>> DeleteFont; + +} + +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +typedef FPDF_SYSFONTINFO = _FPDF_SYSFONTINFO; +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +final class FPDF_CharsetFontMap_ extends ffi.Struct{ + /// Character Set Enum value, see FXFONT_*_CHARSET above. + @ffi.Int() + external int charset; + + /// Name of default font to use with that charset. + external ffi.Pointer fontname; + +} + +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +typedef FPDF_CharsetFontMap = FPDF_CharsetFontMap_; +/// IFPDF_RENDERINFO interface. +final class _IFSDK_PAUSE extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: NeedToPauseNow + /// Check if we need to pause a progressive process now. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// Non-zero for pause now, 0 for continue. + external ffi.Pointer pThis)>> NeedToPauseNow; + + /// A user defined data pointer, used by user's application. Can be NULL. + external ffi.Pointer user; + +} + +/// IFPDF_RENDERINFO interface. +typedef IFSDK_PAUSE = _IFSDK_PAUSE; +final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ + /// The image width in pixels. + @ffi.UnsignedInt() + external int width; + + /// The image height in pixels. + @ffi.UnsignedInt() + external int height; + + /// The image's horizontal pixel-per-inch. + @ffi.Float() + external double horizontal_dpi; + + /// The image's vertical pixel-per-inch. + @ffi.Float() + external double vertical_dpi; + + /// The number of bits used to represent each pixel. + @ffi.UnsignedInt() + external int bits_per_pixel; + + /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. + @ffi.Int() + external int colorspace; + + /// The image's marked content ID. Useful for pairing with associated alt-text. + /// A value of -1 indicates no ID. + @ffi.Int() + external int marked_content_id; + +} + +final class _IPDF_JsPlatform extends ffi.Struct{ + /// Version number of the interface. Currently must be 2. + @ffi.Int() + external int version; + + /// Method: app_alert + /// Pop up a dialog to show warning or hint. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Msg - A string containing the message to be displayed. + /// Title - The title of the dialog. + /// Type - The type of button group, one of the + /// JSPLATFORM_ALERT_BUTTON_* values above. + /// nIcon - The type of the icon, one of the + /// JSPLATFORM_ALERT_ICON_* above. + /// Return Value: + /// Option selected by user in dialogue, one of the + /// JSPLATFORM_ALERT_RETURN_* values above. + external ffi.Pointer pThis, FPDF_WIDESTRING Msg, FPDF_WIDESTRING Title, ffi.Int Type, ffi.Int Icon)>> app_alert; + + /// Method: app_beep + /// Causes the system to play a sound. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nType - The sound type, see JSPLATFORM_BEEP_TYPE_* + /// above. + /// Return Value: + /// None + external ffi.Pointer pThis, ffi.Int nType)>> app_beep; + + /// Method: app_response + /// Displays a dialog box containing a question and an entry field for + /// the user to reply to the question. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Question - The question to be posed to the user. + /// Title - The title of the dialog box. + /// Default - A default value for the answer to the question. If + /// not specified, no default value is presented. + /// cLabel - A short string to appear in front of and on the + /// same line as the edit text field. + /// bPassword - If true, indicates that the user's response should + /// be shown as asterisks (*) or bullets (?) to mask + /// the response, which might be sensitive information. + /// response - A string buffer allocated by PDFium, to receive the + /// user's response. + /// length - The length of the buffer in bytes. Currently, it is + /// always 2048. + /// Return Value: + /// Number of bytes the complete user input would actually require, not + /// including trailing zeros, regardless of the value of the length + /// parameter or the presence of the response buffer. + /// Comments: + /// No matter on what platform, the response buffer should be always + /// written using UTF-16LE encoding. If a response buffer is + /// present and the size of the user input exceeds the capacity of the + /// buffer as specified by the length parameter, only the + /// first "length" bytes of the user input are to be written to the + /// buffer. + external ffi.Pointer pThis, FPDF_WIDESTRING Question, FPDF_WIDESTRING Title, FPDF_WIDESTRING Default, FPDF_WIDESTRING cLabel, FPDF_BOOL bPassword, ffi.Pointer response, ffi.Int length)>> app_response; + + /// Method: Doc_getFilePath + /// Get the file path of the current document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// filePath - The string buffer to receive the file path. Can + /// be NULL. + /// length - The length of the buffer, number of bytes. Can + /// be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in the local encoding. + /// The return value always indicated number of bytes required for + /// the buffer, even when there is no buffer specified, or the buffer + /// size is less than required. In this case, the buffer will not + /// be modified. + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; + + /// Method: Doc_mail + /// Mails the data buffer as an attachment to all recipients, with or + /// without user interaction. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// mailData - Pointer to the data buffer to be sent. Can be NULL. + /// length - The size,in bytes, of the buffer pointed by + /// mailData parameter. Can be 0. + /// bUI - If true, the rest of the parameters are used in a + /// compose-new-message window that is displayed to the + /// user. If false, the cTo parameter is required and + /// all others are optional. + /// To - A semicolon-delimited list of recipients for the + /// message. + /// Subject - The subject of the message. The length limit is + /// 64 KB. + /// CC - A semicolon-delimited list of CC recipients for + /// the message. + /// BCC - A semicolon-delimited list of BCC recipients for + /// the message. + /// Msg - The content of the message. The length limit is + /// 64 KB. + /// Return Value: + /// None. + /// Comments: + /// If the parameter mailData is NULL or length is 0, the current + /// document will be mailed as an attachment to all recipients. + external ffi.Pointer pThis, ffi.Pointer mailData, ffi.Int length, FPDF_BOOL bUI, FPDF_WIDESTRING To, FPDF_WIDESTRING Subject, FPDF_WIDESTRING CC, FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; + + /// Method: Doc_print + /// Prints all or a specific number of pages of the document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// bUI - If true, will cause a UI to be presented to the + /// user to obtain printing information and confirm + /// the action. + /// nStart - A 0-based index that defines the start of an + /// inclusive range of pages. + /// nEnd - A 0-based index that defines the end of an + /// inclusive page range. + /// bSilent - If true, suppresses the cancel dialog box while + /// the document is printing. The default is false. + /// bShrinkToFit - If true, the page is shrunk (if necessary) to + /// fit within the imageable area of the printed page. + /// bPrintAsImage - If true, print pages as an image. + /// bReverse - If true, print from nEnd to nStart. + /// bAnnotations - If true (the default), annotations are + /// printed. + /// Return Value: + /// None. + external ffi.Pointer pThis, FPDF_BOOL bUI, ffi.Int nStart, ffi.Int nEnd, FPDF_BOOL bSilent, FPDF_BOOL bShrinkToFit, FPDF_BOOL bPrintAsImage, FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; + + /// Method: Doc_submitForm + /// Send the form data to a specified URL. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// formData - Pointer to the data buffer to be sent. + /// length - The size,in bytes, of the buffer pointed by + /// formData parameter. + /// URL - The URL to send to. + /// Return Value: + /// None. + external ffi.Pointer pThis, ffi.Pointer formData, ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; + + /// Method: Doc_gotoPage + /// Jump to a specified page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nPageNum - The specified page number, zero for the first page. + /// Return Value: + /// None. + external ffi.Pointer pThis, ffi.Int nPageNum)>> Doc_gotoPage; + + /// Method: Field_browse + /// Show a file selection dialog, and return the selected file path. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// filePath - Pointer to the data buffer to receive the file + /// path. Can be NULL. + /// length - The length of the buffer, in bytes. Can be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in local encoding. + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; + + /// Pointer for embedder-specific data. Unused by PDFium, and despite + /// its name, can be any data the embedder desires, though traditionally + /// a FPDF_FORMFILLINFO interface. + external ffi.Pointer m_pFormfillinfo; + + /// Unused in v3, retain for compatibility. + external ffi.Pointer m_isolate; + + /// Unused in v3, retain for compatibility. + @ffi.UnsignedInt() + external int m_v8EmbedderSlot; + +} + +typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; +typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); +typedef DartTimerCallbackFunction = void Function(int idEvent); +/// Function signature for the callback function passed to the FFI_SetTimer +/// method. +/// Parameters: +/// idEvent - Identifier of the timer. +/// Return value: +/// None. +typedef TimerCallback = ffi.Pointer>; +/// Declares of a struct type to the local system time. +final class _FPDF_SYSTEMTIME extends ffi.Struct{ + /// years since 1900 + @ffi.UnsignedShort() + external int wYear; + + /// months since January - [0,11] + @ffi.UnsignedShort() + external int wMonth; + + /// days since Sunday - [0,6] + @ffi.UnsignedShort() + external int wDayOfWeek; + + /// day of the month - [1,31] + @ffi.UnsignedShort() + external int wDay; + + /// hours since midnight - [0,23] + @ffi.UnsignedShort() + external int wHour; + + /// minutes after the hour - [0,59] + @ffi.UnsignedShort() + external int wMinute; + + /// seconds after the minute - [0,59] + @ffi.UnsignedShort() + external int wSecond; + + /// milliseconds after the second - [0,999] + @ffi.UnsignedShort() + external int wMilliseconds; + +} + +/// Declares of a struct type to the local system time. +typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; +final class _FPDF_FORMFILLINFO extends ffi.Struct{ + /// Version number of the interface. + /// Version 1 contains stable interfaces. Version 2 has additional + /// experimental interfaces. + /// When PDFium is built without the XFA module, version can be 1 or 2. + /// With version 1, only stable interfaces are called. With version 2, + /// additional experimental interfaces are also called. + /// When PDFium is built with the XFA module, version must be 2. + /// All the XFA related interfaces are experimental. If PDFium is built with + /// the XFA module and version 1 then none of the XFA related interfaces + /// would be called. When PDFium is built with XFA module then the version + /// must be 2. + @ffi.Int() + external int version; + + /// Method: Release + /// Give the implementation a chance to release any resources after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Comments: + /// Called by PDFium during the final cleanup process. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None + external ffi.Pointer pThis)>> Release; + + /// Method: FFI_Invalidate + /// Invalidate the client area within the specified rectangle. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// All positions are measured in PDF "user space". + /// Implementation should call FPDF_RenderPageBitmap() for repainting + /// the specified page area. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; + + /// Method: FFI_OutputSelectedRect + /// When the user selects text in form fields with the mouse, this + /// callback function will be invoked with the selected areas. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage()/ + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// This callback function is useful for implementing special text + /// selection effects. An implementation should first record the + /// returned rectangles, then draw them one by one during the next + /// painting period. Lastly, it should remove all the recorded + /// rectangles when finished painting. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; + + /// Method: FFI_SetCursor + /// Set the Cursor shape. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nCursorType - Cursor type, see Flags for Cursor type for details. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Int nCursorType)>> FFI_SetCursor; + + /// Method: FFI_SetTimer + /// This method installs a system timer. An interval value is specified, + /// and every time that interval elapses, the system must call into the + /// callback function with the timer ID as returned by this function. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// uElapse - Specifies the time-out value, in milliseconds. + /// lpTimerFunc - A pointer to the callback function-TimerCallback. + /// Return value: + /// The timer identifier of the new timer if the function is successful. + /// An application passes this value to the FFI_KillTimer method to kill + /// the timer. Nonzero if it is successful; otherwise, it is zero. + external ffi.Pointer pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; + + /// Method: FFI_KillTimer + /// This method uninstalls a system timer, as set by an earlier call to + /// FFI_SetTimer. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nTimerID - The timer ID returned by FFI_SetTimer function. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Int nTimerID)>> FFI_KillTimer; + + /// Method: FFI_GetLocalTime + /// This method receives the current local time on the system. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// The local time. See FPDF_SYSTEMTIME above for details. + /// Note: Unused. + external ffi.Pointer pThis)>> FFI_GetLocalTime; + + /// Method: FFI_OnChange + /// This method will be invoked to notify the implementation when the + /// value of any FormField on the document had been changed. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// None. + external ffi.Pointer pThis)>> FFI_OnChange; + + /// Method: FFI_GetPage + /// This method receives the page handle associated with a specified + /// page index. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// nPageIndex - Index number of the page. 0 for the first page. + /// Return value: + /// Handle to the page, as previously returned to the implementation by + /// FPDF_LoadPage(). + /// Comments: + /// The implementation is expected to keep track of the page handles it + /// receives from PDFium, and their mappings to page numbers. In some + /// cases, the document-level JavaScript action may refer to a page + /// which hadn't been loaded yet. To successfully run the Javascript + /// action, the implementation needs to load the page. + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; + + /// Method: FFI_GetCurrentPage + /// This method receives the handle to the current page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes when V8 support is present, otherwise unused. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// Return value: + /// Handle to the page. Returned by FPDF_LoadPage(). + /// Comments: + /// PDFium doesn't keep keep track of the "current page" (e.g. the one + /// that is most visible on screen), so it must ask the embedder for + /// this information. + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; + + /// Method: FFI_GetRotation + /// This method receives currently rotation of the page view. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page, as returned by FPDF_LoadPage(). + /// Return value: + /// A number to indicate the page rotation in 90 degree increments + /// in a clockwise direction: + /// 0 - 0 degrees + /// 1 - 90 degrees + /// 2 - 180 degrees + /// 3 - 270 degrees + /// Note: Unused. + external ffi.Pointer pThis, FPDF_PAGE page)>> FFI_GetRotation; + + /// Method: FFI_ExecuteNamedAction + /// This method will execute a named action. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// namedAction - A byte string which indicates the named action, + /// terminated by 0. + /// Return value: + /// None. + /// Comments: + /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the + /// standard named actions, but note that a document may supply any + /// name of its choosing. + external ffi.Pointer pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; + + /// Method: FFI_SetTextFieldFocus + /// Called when a text field is getting or losing focus. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// value - The string value of the form field, in UTF-16LE + /// format. + /// valueLen - The length of the string value. This is the + /// number of characters, not bytes. + /// is_focus - True if the form field is getting focus, false + /// if the form field is losing focus. + /// Return value: + /// None. + /// Comments: + /// Only supports text fields and combobox fields. + external ffi.Pointer pThis, FPDF_WIDESTRING value, FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; + + /// Method: FFI_DoURIAction + /// Ask the implementation to navigate to a uniform resource identifier. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Msg - A string containing the message to be displayed. - /// Title - The title of the dialog. - /// Type - The type of button group, one of the - /// JSPLATFORM_ALERT_BUTTON_* values above. - /// nIcon - The type of the icon, one of the - /// JSPLATFORM_ALERT_ICON_* above. - /// Return Value: - /// Option selected by user in dialogue, one of the - /// JSPLATFORM_ALERT_RETURN_* values above. - external ffi.Pointer pThis, FPDF_WIDESTRING Msg, FPDF_WIDESTRING Title, ffi.Int Type, ffi.Int Icon)>> app_alert; + /// pThis - Pointer to the interface structure itself. + /// bsURI - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// Return value: + /// None. + /// Comments: + /// If the embedder is version 2 or higher and have implementation for + /// FFI_DoURIActionWithKeyboardModifier, then + /// FFI_DoURIActionWithKeyboardModifier takes precedence over + /// FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. + external ffi.Pointer pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; + + /// Method: FFI_DoGoToAction + /// This action changes the view to a specified destination. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nPageIndex - The index of the PDF page. + /// zoomMode - The zoom mode for viewing page. See below. + /// fPosArray - The float array which carries the position info. + /// sizeofArray - The size of float array. + /// PDFZoom values: + /// - XYZ = 1 + /// - FITPAGE = 2 + /// - FITHORZ = 3 + /// - FITVERT = 4 + /// - FITRECT = 5 + /// - FITBBOX = 6 + /// - FITBHORZ = 7 + /// - FITBVERT = 8 + /// Return value: + /// None. + /// Comments: + /// See the Destinations description of <> + /// in 8.2.1 for more details. + external ffi.Pointer pThis, ffi.Int nPageIndex, ffi.Int zoomMode, ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; + + /// Pointer to IPDF_JSPLATFORM interface. + /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then + /// JavaScript will be prevented from executing while rendering the document. + external ffi.Pointer m_pJsPlatform; + + /// Whether the XFA module is disabled when built with the XFA module. + /// Interface Version: + /// Ignored if |version| < 2. + @FPDF_BOOL() + external int xfa_disabled; + + /// Method: FFI_DisplayCaret + /// This method will show the caret at specified position. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_BOOL bVisible, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; + + /// Method: FFI_GetCurrentPageIndex + /// This method will get the current page index. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// Return value: + /// The index of current page. + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; + + /// Method: FFI_SetCurrentPage + /// This method will set the current page. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// iCurPage - The index of the PDF page. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; + + /// Method: FFI_GotoURL + /// This method will navigate to the specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// wsURL - The string value of the URL, in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; + + /// Method: FFI_GetPageViewRect + /// This method will get the current page view rectangle. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - The pointer to receive left position of the page + /// view area in PDF page coordinates. + /// top - The pointer to receive top position of the page + /// view area in PDF page coordinates. + /// right - The pointer to receive right position of the + /// page view area in PDF page coordinates. + /// bottom - The pointer to receive bottom position of the + /// page view area in PDF page coordinates. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Pointer left, ffi.Pointer top, ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; + + /// Method: FFI_PageEvent + /// This method fires when pages have been added to or deleted from + /// the XFA document. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page_count - The number of pages to be added or deleted. + /// event_type - See FXFA_PAGEVIEWEVENT_* above. + /// Return value: + /// None. + /// Comments: + /// The pages to be added or deleted always start from the last page + /// of document. This means that if parameter page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTADDED, 2 new pages have been + /// appended to the tail of document; If page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages + /// have been deleted. + external ffi.Pointer pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; + + /// Method: FFI_PopupMenu + /// This method will track the right context menu for XFA fields. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// hWidget - Always null, exists for compatibility. + /// menuFlag - The menu flags. Please refer to macro definition + /// of FXFA_MENU_XXX and this can be one or a + /// combination of these macros. + /// x - X position of the client area in PDF page + /// coordinates. + /// y - Y position of the client area in PDF page + /// coordinates. + /// Return value: + /// TRUE indicates success; otherwise false. + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_WIDGET hWidget, ffi.Int menuFlag, ffi.Float x, ffi.Float y)>> FFI_PopupMenu; + + /// Method: FFI_OpenFile + /// This method will open the specified file with the specified mode. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// wsURL - The string value of the file URL, in UTF-16LE + /// format. + /// mode - The mode for open file, e.g. "rb" or "wb". + /// Return value: + /// The handle to FPDF_FILEHANDLER. + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int fileFlag, FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; + + /// Method: FFI_EmailTo + /// This method will email the specified file stream to the specified + /// contact. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// pTo - A semicolon-delimited list of recipients for the + /// message,in UTF-16LE format. + /// pSubject - The subject of the message,in UTF-16LE format. + /// pCC - A semicolon-delimited list of CC recipients for + /// the message,in UTF-16LE format. + /// pBcc - A semicolon-delimited list of BCC recipients for + /// the message,in UTF-16LE format. + /// pMsg - Pointer to the data buffer to be sent.Can be + /// NULL,in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Pointer fileHandler, FPDF_WIDESTRING pTo, FPDF_WIDESTRING pSubject, FPDF_WIDESTRING pCC, FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; + + /// Method: FFI_UploadTo + /// This method will upload the specified file stream to the + /// specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// uploadTo - Pointer to the URL path, in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Pointer fileHandler, ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; - /// Method: app_beep - /// Causes the system to play a sound. + /// Method: FFI_GetPlatform + /// This method will get the current platform. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// Required for XFA, otherwise set to NULL. /// Parameters: - /// pThis - Pointer to the interface structure itself - /// nType - The sound type, see JSPLATFORM_BEEP_TYPE_* - /// above. - /// Return Value: - /// None - external ffi.Pointer pThis, ffi.Int nType)>> app_beep; + /// pThis - Pointer to the interface structure itself. + /// platform - Pointer to the data buffer to receive the + /// platform,in UTF-16LE format. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. + external ffi.Pointer pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; - /// Method: app_response - /// Displays a dialog box containing a question and an entry field for - /// the user to reply to the question. + /// Method: FFI_GetLanguage + /// This method will get the current language. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// Required for XFA, otherwise set to NULL. /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Question - The question to be posed to the user. - /// Title - The title of the dialog box. - /// Default - A default value for the answer to the question. If - /// not specified, no default value is presented. - /// cLabel - A short string to appear in front of and on the - /// same line as the edit text field. - /// bPassword - If true, indicates that the user's response should - /// be shown as asterisks (*) or bullets (?) to mask - /// the response, which might be sensitive information. - /// response - A string buffer allocated by PDFium, to receive the - /// user's response. - /// length - The length of the buffer in bytes. Currently, it is - /// always 2048. - /// Return Value: - /// Number of bytes the complete user input would actually require, not - /// including trailing zeros, regardless of the value of the length - /// parameter or the presence of the response buffer. - /// Comments: - /// No matter on what platform, the response buffer should be always - /// written using UTF-16LE encoding. If a response buffer is - /// present and the size of the user input exceeds the capacity of the - /// buffer as specified by the length parameter, only the - /// first "length" bytes of the user input are to be written to the - /// buffer. - external ffi.Pointer pThis, FPDF_WIDESTRING Question, FPDF_WIDESTRING Title, FPDF_WIDESTRING Default, FPDF_WIDESTRING cLabel, FPDF_BOOL bPassword, ffi.Pointer response, ffi.Int length)>> app_response; + /// pThis - Pointer to the interface structure itself. + /// language - Pointer to the data buffer to receive the + /// current language. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. + external ffi.Pointer pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; - /// Method: Doc_getFilePath - /// Get the file path of the current document. + /// Method: FFI_DownloadFromURL + /// This method will download the specified file from the URL. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// Required for XFA, otherwise set to NULL. /// Parameters: - /// pThis - Pointer to the interface structure itself - /// filePath - The string buffer to receive the file path. Can - /// be NULL. - /// length - The length of the buffer, number of bytes. Can - /// be 0. - /// Return Value: - /// Number of bytes the filePath consumes, including trailing zeros. - /// Comments: - /// The filePath should always be provided in the local encoding. - /// The return value always indicated number of bytes required for - /// the buffer, even when there is no buffer specified, or the buffer - /// size is less than required. In this case, the buffer will not - /// be modified. - external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; + /// pThis - Pointer to the interface structure itself. + /// URL - The string value of the file URL, in UTF-16LE + /// format. + /// Return value: + /// The handle to FPDF_FILEHANDLER. + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; - /// Method: Doc_mail - /// Mails the data buffer as an attachment to all recipients, with or - /// without user interaction. + /// Method: FFI_PostRequestURL + /// This method will post the request to the server URL. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// Required for XFA, otherwise set to NULL. /// Parameters: - /// pThis - Pointer to the interface structure itself - /// mailData - Pointer to the data buffer to be sent. Can be NULL. - /// length - The size,in bytes, of the buffer pointed by - /// mailData parameter. Can be 0. - /// bUI - If true, the rest of the parameters are used in a - /// compose-new-message window that is displayed to the - /// user. If false, the cTo parameter is required and - /// all others are optional. - /// To - A semicolon-delimited list of recipients for the - /// message. - /// Subject - The subject of the message. The length limit is - /// 64 KB. - /// CC - A semicolon-delimited list of CC recipients for - /// the message. - /// BCC - A semicolon-delimited list of BCC recipients for - /// the message. - /// Msg - The content of the message. The length limit is - /// 64 KB. - /// Return Value: - /// None. - /// Comments: - /// If the parameter mailData is NULL or length is 0, the current - /// document will be mailed as an attachment to all recipients. - external ffi.Pointer pThis, ffi.Pointer mailData, ffi.Int length, FPDF_BOOL bUI, FPDF_WIDESTRING To, FPDF_WIDESTRING Subject, FPDF_WIDESTRING CC, FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The post data,in UTF-16LE format. + /// wsContentType - The content type of the request data, in + /// UTF-16LE format. + /// wsEncode - The encode type, in UTF-16LE format. + /// wsHeader - The request header,in UTF-16LE format. + /// response - Pointer to the FPDF_BSTR to receive the response + /// data from the server, in UTF-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsContentType, FPDF_WIDESTRING wsEncode, FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; - /// Method: Doc_print - /// Prints all or a specific number of pages of the document. + /// Method: FFI_PutRequestURL + /// This method will put the request to the server URL. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// Required for XFA, otherwise set to NULL. /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// bUI - If true, will cause a UI to be presented to the - /// user to obtain printing information and confirm - /// the action. - /// nStart - A 0-based index that defines the start of an - /// inclusive range of pages. - /// nEnd - A 0-based index that defines the end of an - /// inclusive page range. - /// bSilent - If true, suppresses the cancel dialog box while - /// the document is printing. The default is false. - /// bShrinkToFit - If true, the page is shrunk (if necessary) to - /// fit within the imageable area of the printed page. - /// bPrintAsImage - If true, print pages as an image. - /// bReverse - If true, print from nEnd to nStart. - /// bAnnotations - If true (the default), annotations are - /// printed. - /// Return Value: - /// None. - external ffi.Pointer pThis, FPDF_BOOL bUI, ffi.Int nStart, ffi.Int nEnd, FPDF_BOOL bSilent, FPDF_BOOL bShrinkToFit, FPDF_BOOL bPrintAsImage, FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The put data, in UTF-16LE format. + /// wsEncode - The encode type, in UTR-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; - /// Method: Doc_submitForm - /// Send the form data to a specified URL. + /// Method: FFI_OnFocusChange + /// Called when the focused annotation is updated. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// No /// Parameters: - /// pThis - Pointer to the interface structure itself - /// formData - Pointer to the data buffer to be sent. - /// length - The size,in bytes, of the buffer pointed by - /// formData parameter. - /// URL - The URL to send to. - /// Return Value: + /// param - Pointer to the interface structure itself. + /// annot - The focused annotation. + /// page_index - Index number of the page which contains the + /// focused annotation. 0 for the first page. + /// Return value: /// None. - external ffi.Pointer pThis, ffi.Pointer formData, ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; + /// Comments: + /// This callback function is useful for implementing any view based + /// action such as scrolling the annotation rect into view. The + /// embedder should not copy and store the annot as its scope is + /// limited to this call only. + external ffi.Pointer param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; - /// Method: Doc_gotoPage - /// Jump to a specified page. + /// Method: FFI_DoURIActionWithKeyboardModifier + /// Ask the implementation to navigate to a uniform resource identifier + /// with the specified modifiers. /// Interface Version: - /// 1 + /// Ignored if |version| < 2. /// Implementation Required: - /// yes + /// No /// Parameters: - /// pThis - Pointer to the interface structure itself - /// nPageNum - The specified page number, zero for the first page. - /// Return Value: + /// param - Pointer to the interface structure itself. + /// uri - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// modifiers - Keyboard modifier that indicates which of + /// the virtual keys are down, if any. + /// Return value: /// None. - external ffi.Pointer pThis, ffi.Int nPageNum)>> Doc_gotoPage; + /// Comments: + /// If the embedder who is version 2 and does not implement this API, + /// then a call will be redirected to FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. + external ffi.Pointer param, FPDF_BYTESTRING uri, ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; - /// Method: Field_browse - /// Show a file selection dialog, and return the selected file path. +} + +typedef FPDF_FORMFILLINFO = _FPDF_FORMFILLINFO; +enum FPDFANNOT_COLORTYPE { + FPDFANNOT_COLORTYPE_Color(0), + FPDFANNOT_COLORTYPE_InteriorColor(1); + + + final int value; + const FPDFANNOT_COLORTYPE(this.value); + + static FPDFANNOT_COLORTYPE fromValue(int value) => switch (value) { + 0 => FPDFANNOT_COLORTYPE_Color, + 1 => FPDFANNOT_COLORTYPE_InteriorColor, + _ => throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), + }; + +} + +/// Structure for custom file write +final class FPDF_FILEWRITE_ extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: WriteBlock + /// Output a block of data in your custom way. /// Interface Version: /// 1 /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// filePath - Pointer to the data buffer to receive the file - /// path. Can be NULL. - /// length - The length of the buffer, in bytes. Can be 0. - /// Return Value: - /// Number of bytes the filePath consumes, including trailing zeros. + /// Yes /// Comments: - /// The filePath should always be provided in local encoding. - external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; + /// Called by function FPDF_SaveDocument + /// Parameters: + /// pThis - Pointer to the structure itself + /// pData - Pointer to a buffer to output + /// size - The size of the buffer. + /// Return value: + /// Should be non-zero if successful, zero for error. + external ffi.Pointer pThis, ffi.Pointer pData, ffi.UnsignedLong size)>> WriteBlock; - /// Pointer for embedder-specific data. Unused by PDFium, and despite - /// its name, can be any data the embedder desires, though traditionally - /// a FPDF_FORMFILLINFO interface. - external ffi.Pointer m_pFormfillinfo; +} - /// Unused in v3, retain for compatibility. - external ffi.Pointer m_isolate; +/// Structure for custom file write +typedef FPDF_FILEWRITE = FPDF_FILEWRITE_; +/// The file identifier entry type. See section 14.4 "File Identifiers" of the +/// ISO 32000-1:2008 spec. +enum FPDF_FILEIDTYPE { + FILEIDTYPE_PERMANENT(0), + FILEIDTYPE_CHANGING(1); - /// Unused in v3, retain for compatibility. - @ffi.UnsignedInt() - external int m_v8EmbedderSlot; + + final int value; + const FPDF_FILEIDTYPE(this.value); + + static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { + 0 => FILEIDTYPE_PERMANENT, + 1 => FILEIDTYPE_CHANGING, + _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), + }; } -typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; -typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); -typedef DartTimerCallbackFunction = void Function(int idEvent); -/// Function signature for the callback function passed to the FFI_SetTimer -/// method. -/// Parameters: -/// idEvent - Identifier of the timer. -/// Return value: -/// None. -typedef TimerCallback = ffi.Pointer>; -/// Declares of a struct type to the local system time. -final class _FPDF_SYSTEMTIME extends ffi.Struct{ - /// years since 1900 - @ffi.UnsignedShort() - external int wYear; +/// Interface for checking whether sections of the file are available. +final class _FX_FILEAVAIL extends ffi.Struct{ + /// Version number of the interface. Must be 1. + @ffi.Int() + external int version; - /// months since January - [0,11] - @ffi.UnsignedShort() - external int wMonth; + /// Reports if the specified data section is currently available. A section is + /// available if all bytes in the section are available. + /// + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// offset - the offset of the data section in the file. + /// size - the size of the data section. + /// + /// Returns true if the specified data section at |offset| of |size| + /// is available. + external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> IsDataAvail; - /// days since Sunday - [0,6] - @ffi.UnsignedShort() - external int wDayOfWeek; +} - /// day of the month - [1,31] - @ffi.UnsignedShort() - external int wDay; +/// Interface for checking whether sections of the file are available. +typedef FX_FILEAVAIL = _FX_FILEAVAIL; +/// Download hints interface. Used to receive hints for further downloading. +final class _FX_DOWNLOADHINTS extends ffi.Struct{ + /// Version number of the interface. Must be 1. + @ffi.Int() + external int version; - /// hours since midnight - [0,23] - @ffi.UnsignedShort() - external int wHour; + /// Add a section to be downloaded. + /// + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// offset - the offset of the hint reported to be downloaded. + /// size - the size of the hint reported to be downloaded. + /// + /// The |offset| and |size| of the section may not be unique. Part of the + /// section might be already available. The download manager must deal with + /// overlapping sections. + external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> AddSegment; - /// minutes after the hour - [0,59] - @ffi.UnsignedShort() - external int wMinute; +} - /// seconds after the minute - [0,59] - @ffi.UnsignedShort() - external int wSecond; +/// Download hints interface. Used to receive hints for further downloading. +typedef FX_DOWNLOADHINTS = _FX_DOWNLOADHINTS; +/// Key flags. +enum FWL_EVENTFLAG { + FWL_EVENTFLAG_ShiftKey(1), + FWL_EVENTFLAG_ControlKey(2), + FWL_EVENTFLAG_AltKey(4), + FWL_EVENTFLAG_MetaKey(8), + FWL_EVENTFLAG_KeyPad(16), + FWL_EVENTFLAG_AutoRepeat(32), + FWL_EVENTFLAG_LeftButtonDown(64), + FWL_EVENTFLAG_MiddleButtonDown(128), + FWL_EVENTFLAG_RightButtonDown(256); - /// milliseconds after the second - [0,999] - @ffi.UnsignedShort() - external int wMilliseconds; + + final int value; + const FWL_EVENTFLAG(this.value); + + static FWL_EVENTFLAG fromValue(int value) => switch (value) { + 1 => FWL_EVENTFLAG_ShiftKey, + 2 => FWL_EVENTFLAG_ControlKey, + 4 => FWL_EVENTFLAG_AltKey, + 8 => FWL_EVENTFLAG_MetaKey, + 16 => FWL_EVENTFLAG_KeyPad, + 32 => FWL_EVENTFLAG_AutoRepeat, + 64 => FWL_EVENTFLAG_LeftButtonDown, + 128 => FWL_EVENTFLAG_MiddleButtonDown, + 256 => FWL_EVENTFLAG_RightButtonDown, + _ => throw ArgumentError('Unknown value for FWL_EVENTFLAG: $value'), + }; } -/// Declares of a struct type to the local system time. -typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; -final class _FPDF_FORMFILLINFO extends ffi.Struct{ - /// Version number of the interface. - /// Version 1 contains stable interfaces. Version 2 has additional - /// experimental interfaces. - /// When PDFium is built without the XFA module, version can be 1 or 2. - /// With version 1, only stable interfaces are called. With version 2, - /// additional experimental interfaces are also called. - /// When PDFium is built with the XFA module, version must be 2. - /// All the XFA related interfaces are experimental. If PDFium is built with - /// the XFA module and version 1 then none of the XFA related interfaces - /// would be called. When PDFium is built with XFA module then the version - /// must be 2. +/// Virtual keycodes. +enum FWL_VKEYCODE { + FWL_VKEY_Back(8), + FWL_VKEY_Tab(9), + FWL_VKEY_NewLine(10), + FWL_VKEY_Clear(12), + FWL_VKEY_Return(13), + FWL_VKEY_Shift(16), + FWL_VKEY_Control(17), + FWL_VKEY_Menu(18), + FWL_VKEY_Pause(19), + FWL_VKEY_Capital(20), + FWL_VKEY_Kana(21), + FWL_VKEY_Junja(23), + FWL_VKEY_Final(24), + FWL_VKEY_Hanja(25), + FWL_VKEY_Escape(27), + FWL_VKEY_Convert(28), + FWL_VKEY_NonConvert(29), + FWL_VKEY_Accept(30), + FWL_VKEY_ModeChange(31), + FWL_VKEY_Space(32), + FWL_VKEY_Prior(33), + FWL_VKEY_Next(34), + FWL_VKEY_End(35), + FWL_VKEY_Home(36), + FWL_VKEY_Left(37), + FWL_VKEY_Up(38), + FWL_VKEY_Right(39), + FWL_VKEY_Down(40), + FWL_VKEY_Select(41), + FWL_VKEY_Print(42), + FWL_VKEY_Execute(43), + FWL_VKEY_Snapshot(44), + FWL_VKEY_Insert(45), + FWL_VKEY_Delete(46), + FWL_VKEY_Help(47), + FWL_VKEY_0(48), + FWL_VKEY_1(49), + FWL_VKEY_2(50), + FWL_VKEY_3(51), + FWL_VKEY_4(52), + FWL_VKEY_5(53), + FWL_VKEY_6(54), + FWL_VKEY_7(55), + FWL_VKEY_8(56), + FWL_VKEY_9(57), + FWL_VKEY_A(65), + FWL_VKEY_B(66), + FWL_VKEY_C(67), + FWL_VKEY_D(68), + FWL_VKEY_E(69), + FWL_VKEY_F(70), + FWL_VKEY_G(71), + FWL_VKEY_H(72), + FWL_VKEY_I(73), + FWL_VKEY_J(74), + FWL_VKEY_K(75), + FWL_VKEY_L(76), + FWL_VKEY_M(77), + FWL_VKEY_N(78), + FWL_VKEY_O(79), + FWL_VKEY_P(80), + FWL_VKEY_Q(81), + FWL_VKEY_R(82), + FWL_VKEY_S(83), + FWL_VKEY_T(84), + FWL_VKEY_U(85), + FWL_VKEY_V(86), + FWL_VKEY_W(87), + FWL_VKEY_X(88), + FWL_VKEY_Y(89), + FWL_VKEY_Z(90), + FWL_VKEY_LWin(91), + FWL_VKEY_RWin(92), + FWL_VKEY_Apps(93), + FWL_VKEY_Sleep(95), + FWL_VKEY_NumPad0(96), + FWL_VKEY_NumPad1(97), + FWL_VKEY_NumPad2(98), + FWL_VKEY_NumPad3(99), + FWL_VKEY_NumPad4(100), + FWL_VKEY_NumPad5(101), + FWL_VKEY_NumPad6(102), + FWL_VKEY_NumPad7(103), + FWL_VKEY_NumPad8(104), + FWL_VKEY_NumPad9(105), + FWL_VKEY_Multiply(106), + FWL_VKEY_Add(107), + FWL_VKEY_Separator(108), + FWL_VKEY_Subtract(109), + FWL_VKEY_Decimal(110), + FWL_VKEY_Divide(111), + FWL_VKEY_F1(112), + FWL_VKEY_F2(113), + FWL_VKEY_F3(114), + FWL_VKEY_F4(115), + FWL_VKEY_F5(116), + FWL_VKEY_F6(117), + FWL_VKEY_F7(118), + FWL_VKEY_F8(119), + FWL_VKEY_F9(120), + FWL_VKEY_F10(121), + FWL_VKEY_F11(122), + FWL_VKEY_F12(123), + FWL_VKEY_F13(124), + FWL_VKEY_F14(125), + FWL_VKEY_F15(126), + FWL_VKEY_F16(127), + FWL_VKEY_F17(128), + FWL_VKEY_F18(129), + FWL_VKEY_F19(130), + FWL_VKEY_F20(131), + FWL_VKEY_F21(132), + FWL_VKEY_F22(133), + FWL_VKEY_F23(134), + FWL_VKEY_F24(135), + FWL_VKEY_NunLock(144), + FWL_VKEY_Scroll(145), + FWL_VKEY_LShift(160), + FWL_VKEY_RShift(161), + FWL_VKEY_LControl(162), + FWL_VKEY_RControl(163), + FWL_VKEY_LMenu(164), + FWL_VKEY_RMenu(165), + FWL_VKEY_BROWSER_Back(166), + FWL_VKEY_BROWSER_Forward(167), + FWL_VKEY_BROWSER_Refresh(168), + FWL_VKEY_BROWSER_Stop(169), + FWL_VKEY_BROWSER_Search(170), + FWL_VKEY_BROWSER_Favorites(171), + FWL_VKEY_BROWSER_Home(172), + FWL_VKEY_VOLUME_Mute(173), + FWL_VKEY_VOLUME_Down(174), + FWL_VKEY_VOLUME_Up(175), + FWL_VKEY_MEDIA_NEXT_Track(176), + FWL_VKEY_MEDIA_PREV_Track(177), + FWL_VKEY_MEDIA_Stop(178), + FWL_VKEY_MEDIA_PLAY_Pause(179), + FWL_VKEY_MEDIA_LAUNCH_Mail(180), + FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select(181), + FWL_VKEY_MEDIA_LAUNCH_APP1(182), + FWL_VKEY_MEDIA_LAUNCH_APP2(183), + FWL_VKEY_OEM_1(186), + FWL_VKEY_OEM_Plus(187), + FWL_VKEY_OEM_Comma(188), + FWL_VKEY_OEM_Minus(189), + FWL_VKEY_OEM_Period(190), + FWL_VKEY_OEM_2(191), + FWL_VKEY_OEM_3(192), + FWL_VKEY_OEM_4(219), + FWL_VKEY_OEM_5(220), + FWL_VKEY_OEM_6(221), + FWL_VKEY_OEM_7(222), + FWL_VKEY_OEM_8(223), + FWL_VKEY_OEM_102(226), + FWL_VKEY_ProcessKey(229), + FWL_VKEY_Packet(231), + FWL_VKEY_Attn(246), + FWL_VKEY_Crsel(247), + FWL_VKEY_Exsel(248), + FWL_VKEY_Ereof(249), + FWL_VKEY_Play(250), + FWL_VKEY_Zoom(251), + FWL_VKEY_NoName(252), + FWL_VKEY_PA1(253), + FWL_VKEY_OEM_Clear(254), + FWL_VKEY_Unknown(0); + + static const FWL_VKEY_Hangul = FWL_VKEY_Kana; + static const FWL_VKEY_Kanji = FWL_VKEY_Hanja; + static const FWL_VKEY_Command = FWL_VKEY_LWin; + + final int value; + const FWL_VKEYCODE(this.value); + + static FWL_VKEYCODE fromValue(int value) => switch (value) { + 8 => FWL_VKEY_Back, + 9 => FWL_VKEY_Tab, + 10 => FWL_VKEY_NewLine, + 12 => FWL_VKEY_Clear, + 13 => FWL_VKEY_Return, + 16 => FWL_VKEY_Shift, + 17 => FWL_VKEY_Control, + 18 => FWL_VKEY_Menu, + 19 => FWL_VKEY_Pause, + 20 => FWL_VKEY_Capital, + 21 => FWL_VKEY_Kana, + 23 => FWL_VKEY_Junja, + 24 => FWL_VKEY_Final, + 25 => FWL_VKEY_Hanja, + 27 => FWL_VKEY_Escape, + 28 => FWL_VKEY_Convert, + 29 => FWL_VKEY_NonConvert, + 30 => FWL_VKEY_Accept, + 31 => FWL_VKEY_ModeChange, + 32 => FWL_VKEY_Space, + 33 => FWL_VKEY_Prior, + 34 => FWL_VKEY_Next, + 35 => FWL_VKEY_End, + 36 => FWL_VKEY_Home, + 37 => FWL_VKEY_Left, + 38 => FWL_VKEY_Up, + 39 => FWL_VKEY_Right, + 40 => FWL_VKEY_Down, + 41 => FWL_VKEY_Select, + 42 => FWL_VKEY_Print, + 43 => FWL_VKEY_Execute, + 44 => FWL_VKEY_Snapshot, + 45 => FWL_VKEY_Insert, + 46 => FWL_VKEY_Delete, + 47 => FWL_VKEY_Help, + 48 => FWL_VKEY_0, + 49 => FWL_VKEY_1, + 50 => FWL_VKEY_2, + 51 => FWL_VKEY_3, + 52 => FWL_VKEY_4, + 53 => FWL_VKEY_5, + 54 => FWL_VKEY_6, + 55 => FWL_VKEY_7, + 56 => FWL_VKEY_8, + 57 => FWL_VKEY_9, + 65 => FWL_VKEY_A, + 66 => FWL_VKEY_B, + 67 => FWL_VKEY_C, + 68 => FWL_VKEY_D, + 69 => FWL_VKEY_E, + 70 => FWL_VKEY_F, + 71 => FWL_VKEY_G, + 72 => FWL_VKEY_H, + 73 => FWL_VKEY_I, + 74 => FWL_VKEY_J, + 75 => FWL_VKEY_K, + 76 => FWL_VKEY_L, + 77 => FWL_VKEY_M, + 78 => FWL_VKEY_N, + 79 => FWL_VKEY_O, + 80 => FWL_VKEY_P, + 81 => FWL_VKEY_Q, + 82 => FWL_VKEY_R, + 83 => FWL_VKEY_S, + 84 => FWL_VKEY_T, + 85 => FWL_VKEY_U, + 86 => FWL_VKEY_V, + 87 => FWL_VKEY_W, + 88 => FWL_VKEY_X, + 89 => FWL_VKEY_Y, + 90 => FWL_VKEY_Z, + 91 => FWL_VKEY_LWin, + 92 => FWL_VKEY_RWin, + 93 => FWL_VKEY_Apps, + 95 => FWL_VKEY_Sleep, + 96 => FWL_VKEY_NumPad0, + 97 => FWL_VKEY_NumPad1, + 98 => FWL_VKEY_NumPad2, + 99 => FWL_VKEY_NumPad3, + 100 => FWL_VKEY_NumPad4, + 101 => FWL_VKEY_NumPad5, + 102 => FWL_VKEY_NumPad6, + 103 => FWL_VKEY_NumPad7, + 104 => FWL_VKEY_NumPad8, + 105 => FWL_VKEY_NumPad9, + 106 => FWL_VKEY_Multiply, + 107 => FWL_VKEY_Add, + 108 => FWL_VKEY_Separator, + 109 => FWL_VKEY_Subtract, + 110 => FWL_VKEY_Decimal, + 111 => FWL_VKEY_Divide, + 112 => FWL_VKEY_F1, + 113 => FWL_VKEY_F2, + 114 => FWL_VKEY_F3, + 115 => FWL_VKEY_F4, + 116 => FWL_VKEY_F5, + 117 => FWL_VKEY_F6, + 118 => FWL_VKEY_F7, + 119 => FWL_VKEY_F8, + 120 => FWL_VKEY_F9, + 121 => FWL_VKEY_F10, + 122 => FWL_VKEY_F11, + 123 => FWL_VKEY_F12, + 124 => FWL_VKEY_F13, + 125 => FWL_VKEY_F14, + 126 => FWL_VKEY_F15, + 127 => FWL_VKEY_F16, + 128 => FWL_VKEY_F17, + 129 => FWL_VKEY_F18, + 130 => FWL_VKEY_F19, + 131 => FWL_VKEY_F20, + 132 => FWL_VKEY_F21, + 133 => FWL_VKEY_F22, + 134 => FWL_VKEY_F23, + 135 => FWL_VKEY_F24, + 144 => FWL_VKEY_NunLock, + 145 => FWL_VKEY_Scroll, + 160 => FWL_VKEY_LShift, + 161 => FWL_VKEY_RShift, + 162 => FWL_VKEY_LControl, + 163 => FWL_VKEY_RControl, + 164 => FWL_VKEY_LMenu, + 165 => FWL_VKEY_RMenu, + 166 => FWL_VKEY_BROWSER_Back, + 167 => FWL_VKEY_BROWSER_Forward, + 168 => FWL_VKEY_BROWSER_Refresh, + 169 => FWL_VKEY_BROWSER_Stop, + 170 => FWL_VKEY_BROWSER_Search, + 171 => FWL_VKEY_BROWSER_Favorites, + 172 => FWL_VKEY_BROWSER_Home, + 173 => FWL_VKEY_VOLUME_Mute, + 174 => FWL_VKEY_VOLUME_Down, + 175 => FWL_VKEY_VOLUME_Up, + 176 => FWL_VKEY_MEDIA_NEXT_Track, + 177 => FWL_VKEY_MEDIA_PREV_Track, + 178 => FWL_VKEY_MEDIA_Stop, + 179 => FWL_VKEY_MEDIA_PLAY_Pause, + 180 => FWL_VKEY_MEDIA_LAUNCH_Mail, + 181 => FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select, + 182 => FWL_VKEY_MEDIA_LAUNCH_APP1, + 183 => FWL_VKEY_MEDIA_LAUNCH_APP2, + 186 => FWL_VKEY_OEM_1, + 187 => FWL_VKEY_OEM_Plus, + 188 => FWL_VKEY_OEM_Comma, + 189 => FWL_VKEY_OEM_Minus, + 190 => FWL_VKEY_OEM_Period, + 191 => FWL_VKEY_OEM_2, + 192 => FWL_VKEY_OEM_3, + 219 => FWL_VKEY_OEM_4, + 220 => FWL_VKEY_OEM_5, + 221 => FWL_VKEY_OEM_6, + 222 => FWL_VKEY_OEM_7, + 223 => FWL_VKEY_OEM_8, + 226 => FWL_VKEY_OEM_102, + 229 => FWL_VKEY_ProcessKey, + 231 => FWL_VKEY_Packet, + 246 => FWL_VKEY_Attn, + 247 => FWL_VKEY_Crsel, + 248 => FWL_VKEY_Exsel, + 249 => FWL_VKEY_Ereof, + 250 => FWL_VKEY_Play, + 251 => FWL_VKEY_Zoom, + 252 => FWL_VKEY_NoName, + 253 => FWL_VKEY_PA1, + 254 => FWL_VKEY_OEM_Clear, + 0 => FWL_VKEY_Unknown, + _ => throw ArgumentError('Unknown value for FWL_VKEYCODE: $value'), + }; + + @override + String toString() { + if (this == FWL_VKEY_Kana) return "FWL_VKEYCODE.FWL_VKEY_Kana, FWL_VKEYCODE.FWL_VKEY_Hangul"; + if (this == FWL_VKEY_Hanja) return "FWL_VKEYCODE.FWL_VKEY_Hanja, FWL_VKEYCODE.FWL_VKEY_Kanji"; + if (this == FWL_VKEY_LWin) return "FWL_VKEYCODE.FWL_VKEY_LWin, FWL_VKEYCODE.FWL_VKEY_Command"; + return super.toString(); + }} + +/// Interface for unsupported feature notifications. +final class _UNSUPPORT_INFO extends ffi.Struct{ + /// Version number of the interface. Must be 1. @ffi.Int() external int version; - /// Method: Release - /// Give the implementation a chance to release any resources after the - /// interface is no longer used. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Comments: - /// Called by PDFium during the final cleanup process. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Return Value: - /// None - external ffi.Pointer pThis)>> Release; + /// Unsupported object notification function. + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// nType - the type of unsupported object. One of the |FPDF_UNSP_*| entries. + external ffi.Pointer pThis, ffi.Int nType)>> FSDK_UnSupport_Handler; - /// Method: FFI_Invalidate - /// Invalidate the client area within the specified rectangle. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return Value: - /// None. - /// Comments: - /// All positions are measured in PDF "user space". - /// Implementation should call FPDF_RenderPageBitmap() for repainting - /// the specified page area. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; +} + +/// Interface for unsupported feature notifications. +typedef UNSUPPORT_INFO = _UNSUPPORT_INFO; +typedef __time_t = ffi.Long; +typedef Dart__time_t = int; +typedef time_t = __time_t; +final class tm extends ffi.Struct{ + @ffi.Int() + external int tm_sec; + + @ffi.Int() + external int tm_min; + + @ffi.Int() + external int tm_hour; + + @ffi.Int() + external int tm_mday; + + @ffi.Int() + external int tm_mon; + + @ffi.Int() + external int tm_year; + + @ffi.Int() + external int tm_wday; + + @ffi.Int() + external int tm_yday; + + @ffi.Int() + external int tm_isdst; + + @ffi.Long() + external int tm_gmtoff; + + external ffi.Pointer tm_zone; + +} + + +const int FPDF_OBJECT_UNKNOWN = 0; + + +const int FPDF_OBJECT_BOOLEAN = 1; + + +const int FPDF_OBJECT_NUMBER = 2; + + +const int FPDF_OBJECT_STRING = 3; + + +const int FPDF_OBJECT_NAME = 4; + + +const int FPDF_OBJECT_ARRAY = 5; + + +const int FPDF_OBJECT_DICTIONARY = 6; + + +const int FPDF_OBJECT_STREAM = 7; + + +const int FPDF_OBJECT_NULLOBJ = 8; + + +const int FPDF_OBJECT_REFERENCE = 9; + + +const int FPDF_POLICY_MACHINETIME_ACCESS = 0; + + +const int FPDF_ERR_SUCCESS = 0; + + +const int FPDF_ERR_UNKNOWN = 1; + + +const int FPDF_ERR_FILE = 2; + + +const int FPDF_ERR_FORMAT = 3; - /// Method: FFI_OutputSelectedRect - /// When the user selects text in form fields with the mouse, this - /// callback function will be invoked with the selected areas. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to the page. Returned by FPDF_LoadPage()/ - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return Value: - /// None. - /// Comments: - /// This callback function is useful for implementing special text - /// selection effects. An implementation should first record the - /// returned rectangles, then draw them one by one during the next - /// painting period. Lastly, it should remove all the recorded - /// rectangles when finished painting. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; - /// Method: FFI_SetCursor - /// Set the Cursor shape. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nCursorType - Cursor type, see Flags for Cursor type for details. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Int nCursorType)>> FFI_SetCursor; +const int FPDF_ERR_PASSWORD = 4; - /// Method: FFI_SetTimer - /// This method installs a system timer. An interval value is specified, - /// and every time that interval elapses, the system must call into the - /// callback function with the timer ID as returned by this function. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// uElapse - Specifies the time-out value, in milliseconds. - /// lpTimerFunc - A pointer to the callback function-TimerCallback. - /// Return value: - /// The timer identifier of the new timer if the function is successful. - /// An application passes this value to the FFI_KillTimer method to kill - /// the timer. Nonzero if it is successful; otherwise, it is zero. - external ffi.Pointer pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; - /// Method: FFI_KillTimer - /// This method uninstalls a system timer, as set by an earlier call to - /// FFI_SetTimer. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nTimerID - The timer ID returned by FFI_SetTimer function. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Int nTimerID)>> FFI_KillTimer; +const int FPDF_ERR_SECURITY = 5; - /// Method: FFI_GetLocalTime - /// This method receives the current local time on the system. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Return value: - /// The local time. See FPDF_SYSTEMTIME above for details. - /// Note: Unused. - external ffi.Pointer pThis)>> FFI_GetLocalTime; - /// Method: FFI_OnChange - /// This method will be invoked to notify the implementation when the - /// value of any FormField on the document had been changed. - /// Interface Version: - /// 1 - /// Implementation Required: - /// no - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Return value: - /// None. - external ffi.Pointer pThis)>> FFI_OnChange; +const int FPDF_ERR_PAGE = 6; - /// Method: FFI_GetPage - /// This method receives the page handle associated with a specified - /// page index. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// nPageIndex - Index number of the page. 0 for the first page. - /// Return value: - /// Handle to the page, as previously returned to the implementation by - /// FPDF_LoadPage(). - /// Comments: - /// The implementation is expected to keep track of the page handles it - /// receives from PDFium, and their mappings to page numbers. In some - /// cases, the document-level JavaScript action may refer to a page - /// which hadn't been loaded yet. To successfully run the Javascript - /// action, the implementation needs to load the page. - external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; - /// Method: FFI_GetCurrentPage - /// This method receives the handle to the current page. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes when V8 support is present, otherwise unused. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// Return value: - /// Handle to the page. Returned by FPDF_LoadPage(). - /// Comments: - /// PDFium doesn't keep keep track of the "current page" (e.g. the one - /// that is most visible on screen), so it must ask the embedder for - /// this information. - external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; +const int FPDF_ANNOT = 1; - /// Method: FFI_GetRotation - /// This method receives currently rotation of the page view. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page, as returned by FPDF_LoadPage(). - /// Return value: - /// A number to indicate the page rotation in 90 degree increments - /// in a clockwise direction: - /// 0 - 0 degrees - /// 1 - 90 degrees - /// 2 - 180 degrees - /// 3 - 270 degrees - /// Note: Unused. - external ffi.Pointer pThis, FPDF_PAGE page)>> FFI_GetRotation; - /// Method: FFI_ExecuteNamedAction - /// This method will execute a named action. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// namedAction - A byte string which indicates the named action, - /// terminated by 0. - /// Return value: - /// None. - /// Comments: - /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the - /// standard named actions, but note that a document may supply any - /// name of its choosing. - external ffi.Pointer pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; +const int FPDF_LCD_TEXT = 2; - /// Method: FFI_SetTextFieldFocus - /// Called when a text field is getting or losing focus. - /// Interface Version: - /// 1 - /// Implementation Required: - /// no - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// value - The string value of the form field, in UTF-16LE - /// format. - /// valueLen - The length of the string value. This is the - /// number of characters, not bytes. - /// is_focus - True if the form field is getting focus, false - /// if the form field is losing focus. - /// Return value: - /// None. - /// Comments: - /// Only supports text fields and combobox fields. - external ffi.Pointer pThis, FPDF_WIDESTRING value, FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; - /// Method: FFI_DoURIAction - /// Ask the implementation to navigate to a uniform resource identifier. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// bsURI - A byte string which indicates the uniform - /// resource identifier, terminated by 0. - /// Return value: - /// None. - /// Comments: - /// If the embedder is version 2 or higher and have implementation for - /// FFI_DoURIActionWithKeyboardModifier, then - /// FFI_DoURIActionWithKeyboardModifier takes precedence over - /// FFI_DoURIAction. - /// See the URI actions description of <> - /// for more details. - external ffi.Pointer pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; +const int FPDF_NO_NATIVETEXT = 4; - /// Method: FFI_DoGoToAction - /// This action changes the view to a specified destination. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nPageIndex - The index of the PDF page. - /// zoomMode - The zoom mode for viewing page. See below. - /// fPosArray - The float array which carries the position info. - /// sizeofArray - The size of float array. - /// PDFZoom values: - /// - XYZ = 1 - /// - FITPAGE = 2 - /// - FITHORZ = 3 - /// - FITVERT = 4 - /// - FITRECT = 5 - /// - FITBBOX = 6 - /// - FITBHORZ = 7 - /// - FITBVERT = 8 - /// Return value: - /// None. - /// Comments: - /// See the Destinations description of <> - /// in 8.2.1 for more details. - external ffi.Pointer pThis, ffi.Int nPageIndex, ffi.Int zoomMode, ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; - /// Pointer to IPDF_JSPLATFORM interface. - /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then - /// JavaScript will be prevented from executing while rendering the document. - external ffi.Pointer m_pJsPlatform; +const int FPDF_GRAYSCALE = 8; - /// Whether the XFA module is disabled when built with the XFA module. - /// Interface Version: - /// Ignored if |version| < 2. - @FPDF_BOOL() - external int xfa_disabled; - /// Method: FFI_DisplayCaret - /// This method will show the caret at specified position. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_PAGE page, FPDF_BOOL bVisible, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; +const int FPDF_DEBUG_INFO = 128; + + +const int FPDF_NO_CATCH = 256; + + +const int FPDF_RENDER_LIMITEDIMAGECACHE = 512; + + +const int FPDF_RENDER_FORCEHALFTONE = 1024; + + +const int FPDF_PRINTING = 2048; + + +const int FPDF_RENDER_NO_SMOOTHTEXT = 4096; - /// Method: FFI_GetCurrentPageIndex - /// This method will get the current page index. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// Return value: - /// The index of current page. - external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; - /// Method: FFI_SetCurrentPage - /// This method will set the current page. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// iCurPage - The index of the PDF page. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; +const int FPDF_RENDER_NO_SMOOTHIMAGE = 8192; - /// Method: FFI_GotoURL - /// This method will navigate to the specified URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// wsURL - The string value of the URL, in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; - /// Method: FFI_GetPageViewRect - /// This method will get the current page view rectangle. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// left - The pointer to receive left position of the page - /// view area in PDF page coordinates. - /// top - The pointer to receive top position of the page - /// view area in PDF page coordinates. - /// right - The pointer to receive right position of the - /// page view area in PDF page coordinates. - /// bottom - The pointer to receive bottom position of the - /// page view area in PDF page coordinates. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Pointer left, ffi.Pointer top, ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; +const int FPDF_RENDER_NO_SMOOTHPATH = 16384; - /// Method: FFI_PageEvent - /// This method fires when pages have been added to or deleted from - /// the XFA document. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page_count - The number of pages to be added or deleted. - /// event_type - See FXFA_PAGEVIEWEVENT_* above. - /// Return value: - /// None. - /// Comments: - /// The pages to be added or deleted always start from the last page - /// of document. This means that if parameter page_count is 2 and - /// event type is FXFA_PAGEVIEWEVENT_POSTADDED, 2 new pages have been - /// appended to the tail of document; If page_count is 2 and - /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages - /// have been deleted. - external ffi.Pointer pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; - /// Method: FFI_PopupMenu - /// This method will track the right context menu for XFA fields. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// hWidget - Always null, exists for compatibility. - /// menuFlag - The menu flags. Please refer to macro definition - /// of FXFA_MENU_XXX and this can be one or a - /// combination of these macros. - /// x - X position of the client area in PDF page - /// coordinates. - /// y - Y position of the client area in PDF page - /// coordinates. - /// Return value: - /// TRUE indicates success; otherwise false. - external ffi.Pointer pThis, FPDF_PAGE page, FPDF_WIDGET hWidget, ffi.Int menuFlag, ffi.Float x, ffi.Float y)>> FFI_PopupMenu; +const int FPDF_REVERSE_BYTE_ORDER = 16; - /// Method: FFI_OpenFile - /// This method will open the specified file with the specified mode. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// fileFlag - The file flag. Please refer to macro definition - /// of FXFA_SAVEAS_XXX and use one of these macros. - /// wsURL - The string value of the file URL, in UTF-16LE - /// format. - /// mode - The mode for open file, e.g. "rb" or "wb". - /// Return value: - /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int fileFlag, FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; - /// Method: FFI_EmailTo - /// This method will email the specified file stream to the specified - /// contact. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// pFileHandler - Handle to the FPDF_FILEHANDLER. - /// pTo - A semicolon-delimited list of recipients for the - /// message,in UTF-16LE format. - /// pSubject - The subject of the message,in UTF-16LE format. - /// pCC - A semicolon-delimited list of CC recipients for - /// the message,in UTF-16LE format. - /// pBcc - A semicolon-delimited list of BCC recipients for - /// the message,in UTF-16LE format. - /// pMsg - Pointer to the data buffer to be sent.Can be - /// NULL,in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Pointer fileHandler, FPDF_WIDESTRING pTo, FPDF_WIDESTRING pSubject, FPDF_WIDESTRING pCC, FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; +const int FPDF_CONVERT_FILL_TO_STROKE = 32; - /// Method: FFI_UploadTo - /// This method will upload the specified file stream to the - /// specified URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// pFileHandler - Handle to the FPDF_FILEHANDLER. - /// fileFlag - The file flag. Please refer to macro definition - /// of FXFA_SAVEAS_XXX and use one of these macros. - /// uploadTo - Pointer to the URL path, in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Pointer fileHandler, ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; - /// Method: FFI_GetPlatform - /// This method will get the current platform. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// platform - Pointer to the data buffer to receive the - /// platform,in UTF-16LE format. Can be NULL. - /// length - The length of the buffer in bytes. Can be - /// 0 to query the required size. - /// Return value: - /// The length of the buffer, number of bytes. - external ffi.Pointer pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; +const int FPDFBitmap_Unknown = 0; - /// Method: FFI_GetLanguage - /// This method will get the current language. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// language - Pointer to the data buffer to receive the - /// current language. Can be NULL. - /// length - The length of the buffer in bytes. Can be - /// 0 to query the required size. - /// Return value: - /// The length of the buffer, number of bytes. - external ffi.Pointer pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; - /// Method: FFI_DownloadFromURL - /// This method will download the specified file from the URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// URL - The string value of the file URL, in UTF-16LE - /// format. - /// Return value: - /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; +const int FPDFBitmap_Gray = 1; - /// Method: FFI_PostRequestURL - /// This method will post the request to the server URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// wsURL - The string value of the server URL, in UTF-16LE - /// format. - /// wsData - The post data,in UTF-16LE format. - /// wsContentType - The content type of the request data, in - /// UTF-16LE format. - /// wsEncode - The encode type, in UTF-16LE format. - /// wsHeader - The request header,in UTF-16LE format. - /// response - Pointer to the FPDF_BSTR to receive the response - /// data from the server, in UTF-16LE format. - /// Return value: - /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsContentType, FPDF_WIDESTRING wsEncode, FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; - /// Method: FFI_PutRequestURL - /// This method will put the request to the server URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// wsURL - The string value of the server URL, in UTF-16LE - /// format. - /// wsData - The put data, in UTF-16LE format. - /// wsEncode - The encode type, in UTR-16LE format. - /// Return value: - /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; +const int FPDFBitmap_BGR = 2; + + +const int FPDFBitmap_BGRx = 3; + + +const int FPDFBitmap_BGRA = 4; + + +const int FPDFBitmap_BGRA_Premul = 5; + + +const int FXFONT_ANSI_CHARSET = 0; + + +const int FXFONT_DEFAULT_CHARSET = 1; + + +const int FXFONT_SYMBOL_CHARSET = 2; - /// Method: FFI_OnFocusChange - /// Called when the focused annotation is updated. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// No - /// Parameters: - /// param - Pointer to the interface structure itself. - /// annot - The focused annotation. - /// page_index - Index number of the page which contains the - /// focused annotation. 0 for the first page. - /// Return value: - /// None. - /// Comments: - /// This callback function is useful for implementing any view based - /// action such as scrolling the annotation rect into view. The - /// embedder should not copy and store the annot as its scope is - /// limited to this call only. - external ffi.Pointer param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; - /// Method: FFI_DoURIActionWithKeyboardModifier - /// Ask the implementation to navigate to a uniform resource identifier - /// with the specified modifiers. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// No - /// Parameters: - /// param - Pointer to the interface structure itself. - /// uri - A byte string which indicates the uniform - /// resource identifier, terminated by 0. - /// modifiers - Keyboard modifier that indicates which of - /// the virtual keys are down, if any. - /// Return value: - /// None. - /// Comments: - /// If the embedder who is version 2 and does not implement this API, - /// then a call will be redirected to FFI_DoURIAction. - /// See the URI actions description of <> - /// for more details. - external ffi.Pointer param, FPDF_BYTESTRING uri, ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; +const int FXFONT_SHIFTJIS_CHARSET = 128; -} -typedef FPDF_FORMFILLINFO = _FPDF_FORMFILLINFO; -enum FPDFANNOT_COLORTYPE { - FPDFANNOT_COLORTYPE_Color(0), - FPDFANNOT_COLORTYPE_InteriorColor(1); +const int FXFONT_HANGEUL_CHARSET = 129; - final int value; - const FPDFANNOT_COLORTYPE(this.value); +const int FXFONT_GB2312_CHARSET = 134; - static FPDFANNOT_COLORTYPE fromValue(int value) => switch (value) { - 0 => FPDFANNOT_COLORTYPE_Color, - 1 => FPDFANNOT_COLORTYPE_InteriorColor, - _ => throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), - }; -} +const int FXFONT_CHINESEBIG5_CHARSET = 136; -/// The file identifier entry type. See section 14.4 "File Identifiers" of the -/// ISO 32000-1:2008 spec. -enum FPDF_FILEIDTYPE { - FILEIDTYPE_PERMANENT(0), - FILEIDTYPE_CHANGING(1); +const int FXFONT_GREEK_CHARSET = 161; - final int value; - const FPDF_FILEIDTYPE(this.value); - static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { - 0 => FILEIDTYPE_PERMANENT, - 1 => FILEIDTYPE_CHANGING, - _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), - }; +const int FXFONT_VIETNAMESE_CHARSET = 163; -} -final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ - /// The image width in pixels. - @ffi.UnsignedInt() - external int width; +const int FXFONT_HEBREW_CHARSET = 177; - /// The image height in pixels. - @ffi.UnsignedInt() - external int height; - /// The image's horizontal pixel-per-inch. - @ffi.Float() - external double horizontal_dpi; +const int FXFONT_ARABIC_CHARSET = 178; - /// The image's vertical pixel-per-inch. - @ffi.Float() - external double vertical_dpi; - /// The number of bits used to represent each pixel. - @ffi.UnsignedInt() - external int bits_per_pixel; +const int FXFONT_CYRILLIC_CHARSET = 204; - /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. - @ffi.Int() - external int colorspace; - /// The image's marked content ID. Useful for pairing with associated alt-text. - /// A value of -1 indicates no ID. - @ffi.Int() - external int marked_content_id; +const int FXFONT_THAI_CHARSET = 222; -} -/// Structure for custom file write -final class FPDF_FILEWRITE_ extends ffi.Struct{ - /// Version number of the interface. Currently must be 1. - @ffi.Int() - external int version; +const int FXFONT_EASTERNEUROPEAN_CHARSET = 238; - /// Method: WriteBlock - /// Output a block of data in your custom way. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Comments: - /// Called by function FPDF_SaveDocument - /// Parameters: - /// pThis - Pointer to the structure itself - /// pData - Pointer to a buffer to output - /// size - The size of the buffer. - /// Return value: - /// Should be non-zero if successful, zero for error. - external ffi.Pointer pThis, ffi.Pointer pData, ffi.UnsignedLong size)>> WriteBlock; -} +const int FXFONT_FF_FIXEDPITCH = 1; -/// Structure for custom file write -typedef FPDF_FILEWRITE = FPDF_FILEWRITE_; -/// Interface: FPDF_SYSFONTINFO -/// Interface for getting system font information and font mapping -final class _FPDF_SYSFONTINFO extends ffi.Struct{ - /// Version number of the interface. Currently must be 1. - @ffi.Int() - external int version; - /// Method: Release - /// Give implementation a chance to release any data after the - /// interface is no longer used. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Return Value: - /// None - /// Comments: - /// Called by PDFium during the final cleanup process. - external ffi.Pointer pThis)>> Release; +const int FXFONT_FF_ROMAN = 16; - /// Method: EnumFonts - /// Enumerate all fonts installed on the system - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// pMapper - An opaque pointer to internal font mapper, used - /// when calling FPDF_AddInstalledFont(). - /// Return Value: - /// None - /// Comments: - /// Implementations should call FPDF_AddInstalledFont() function for - /// each font found. Only TrueType/OpenType and Type1 fonts are - /// accepted by PDFium. - external ffi.Pointer pThis, ffi.Pointer pMapper)>> EnumFonts; - /// Method: MapFont - /// Use the system font mapper to get a font handle from requested - /// parameters. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Required if GetFont method is not implemented. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// weight - Weight of the requested font. 400 is normal and - /// 700 is bold. - /// bItalic - Italic option of the requested font, TRUE or - /// FALSE. - /// charset - Character set identifier for the requested font. - /// See above defined constants. - /// pitch_family - A combination of flags. See above defined - /// constants. - /// face - Typeface name. Currently use system local encoding - /// only. - /// bExact - Obsolete: this parameter is now ignored. - /// Return Value: - /// An opaque pointer for font handle, or NULL if system mapping is - /// not supported. - /// Comments: - /// If the system supports native font mapper (like Windows), - /// implementation can implement this method to get a font handle. - /// Otherwise, PDFium will do the mapping and then call GetFont - /// method. Only TrueType/OpenType and Type1 fonts are accepted - /// by PDFium. - external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Int weight, FPDF_BOOL bItalic, ffi.Int charset, ffi.Int pitch_family, ffi.Pointer face, ffi.Pointer bExact)>> MapFont; +const int FXFONT_FF_SCRIPT = 64; - /// Method: GetFont - /// Get a handle to a particular font by its internal ID - /// Interface Version: - /// 1 - /// Implementation Required: - /// Required if MapFont method is not implemented. - /// Return Value: - /// An opaque pointer for font handle. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// face - Typeface name in system local encoding. - /// Comments: - /// If the system mapping not supported, PDFium will do the font - /// mapping and use this method to get a font handle. - external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Pointer face)>> GetFont; - /// Method: GetFontData - /// Get font data from a font - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// table - TrueType/OpenType table identifier (refer to - /// TrueType specification), or 0 for the whole file. - /// buffer - The buffer receiving the font data. Can be NULL if - /// not provided. - /// buf_size - Buffer size, can be zero if not provided. - /// Return Value: - /// Number of bytes needed, if buffer not provided or not large - /// enough, or number of bytes written into buffer otherwise. - /// Comments: - /// Can read either the full font file, or a particular - /// TrueType/OpenType table. - external ffi.Pointer pThis, ffi.Pointer hFont, ffi.UnsignedInt table, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFontData; +const int FXFONT_FW_NORMAL = 400; + + +const int FXFONT_FW_BOLD = 700; + + +const int FPDF_MATCHCASE = 1; + + +const int FPDF_MATCHWHOLEWORD = 2; - /// Method: GetFaceName - /// Get face name from a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// buffer - The buffer receiving the face name. Can be NULL if - /// not provided - /// buf_size - Buffer size, can be zero if not provided - /// Return Value: - /// Number of bytes needed, if buffer not provided or not large - /// enough, or number of bytes written into buffer otherwise. - external ffi.Pointer pThis, ffi.Pointer hFont, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFaceName; - /// Method: GetFontCharset - /// Get character set information for a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// Return Value: - /// Character set identifier. See defined constants above. - external ffi.Pointer pThis, ffi.Pointer hFont)>> GetFontCharset; +const int FPDF_CONSECUTIVE = 4; - /// Method: DeleteFont - /// Delete a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// Return Value: - /// None - external ffi.Pointer pThis, ffi.Pointer hFont)>> DeleteFont; -} +const int FPDF_RENDER_READY = 0; -/// Interface: FPDF_SYSFONTINFO -/// Interface for getting system font information and font mapping -typedef FPDF_SYSFONTINFO = _FPDF_SYSFONTINFO; -/// Struct: FPDF_CharsetFontMap -/// Provides the name of a font to use for a given charset value. -final class FPDF_CharsetFontMap_ extends ffi.Struct{ - /// Character Set Enum value, see FXFONT_*_CHARSET above. - @ffi.Int() - external int charset; - /// Name of default font to use with that charset. - external ffi.Pointer fontname; +const int FPDF_RENDER_TOBECONTINUED = 1; -} -/// Struct: FPDF_CharsetFontMap -/// Provides the name of a font to use for a given charset value. -typedef FPDF_CharsetFontMap = FPDF_CharsetFontMap_; +const int FPDF_RENDER_DONE = 2; -const int FPDF_OBJECT_UNKNOWN = 0; +const int FPDF_RENDER_FAILED = 3; -const int FPDF_OBJECT_BOOLEAN = 1; + +const int FPDF_COLORSPACE_UNKNOWN = 0; -const int FPDF_OBJECT_NUMBER = 2; +const int FPDF_COLORSPACE_DEVICEGRAY = 1; -const int FPDF_OBJECT_STRING = 3; +const int FPDF_COLORSPACE_DEVICERGB = 2; -const int FPDF_OBJECT_NAME = 4; +const int FPDF_COLORSPACE_DEVICECMYK = 3; -const int FPDF_OBJECT_ARRAY = 5; +const int FPDF_COLORSPACE_CALGRAY = 4; -const int FPDF_OBJECT_DICTIONARY = 6; +const int FPDF_COLORSPACE_CALRGB = 5; -const int FPDF_OBJECT_STREAM = 7; +const int FPDF_COLORSPACE_LAB = 6; -const int FPDF_OBJECT_NULLOBJ = 8; +const int FPDF_COLORSPACE_ICCBASED = 7; -const int FPDF_OBJECT_REFERENCE = 9; +const int FPDF_COLORSPACE_SEPARATION = 8; -const int FPDF_POLICY_MACHINETIME_ACCESS = 0; +const int FPDF_COLORSPACE_DEVICEN = 9; -const int FPDF_ERR_SUCCESS = 0; +const int FPDF_COLORSPACE_INDEXED = 10; -const int FPDF_ERR_UNKNOWN = 1; +const int FPDF_COLORSPACE_PATTERN = 11; -const int FPDF_ERR_FILE = 2; +const int FPDF_PAGEOBJ_UNKNOWN = 0; -const int FPDF_ERR_FORMAT = 3; +const int FPDF_PAGEOBJ_TEXT = 1; -const int FPDF_ERR_PASSWORD = 4; +const int FPDF_PAGEOBJ_PATH = 2; -const int FPDF_ERR_SECURITY = 5; +const int FPDF_PAGEOBJ_IMAGE = 3; -const int FPDF_ERR_PAGE = 6; +const int FPDF_PAGEOBJ_SHADING = 4; -const int FPDF_ANNOT = 1; +const int FPDF_PAGEOBJ_FORM = 5; -const int FPDF_LCD_TEXT = 2; +const int FPDF_SEGMENT_UNKNOWN = -1; -const int FPDF_NO_NATIVETEXT = 4; +const int FPDF_SEGMENT_LINETO = 0; -const int FPDF_GRAYSCALE = 8; +const int FPDF_SEGMENT_BEZIERTO = 1; -const int FPDF_DEBUG_INFO = 128; +const int FPDF_SEGMENT_MOVETO = 2; -const int FPDF_NO_CATCH = 256; +const int FPDF_FILLMODE_NONE = 0; -const int FPDF_RENDER_LIMITEDIMAGECACHE = 512; +const int FPDF_FILLMODE_ALTERNATE = 1; -const int FPDF_RENDER_FORCEHALFTONE = 1024; +const int FPDF_FILLMODE_WINDING = 2; -const int FPDF_PRINTING = 2048; +const int FPDF_FONT_TYPE1 = 1; -const int FPDF_RENDER_NO_SMOOTHTEXT = 4096; +const int FPDF_FONT_TRUETYPE = 2; -const int FPDF_RENDER_NO_SMOOTHIMAGE = 8192; +const int FPDF_LINECAP_BUTT = 0; -const int FPDF_RENDER_NO_SMOOTHPATH = 16384; +const int FPDF_LINECAP_ROUND = 1; -const int FPDF_REVERSE_BYTE_ORDER = 16; +const int FPDF_LINECAP_PROJECTING_SQUARE = 2; -const int FPDF_CONVERT_FILL_TO_STROKE = 32; +const int FPDF_LINEJOIN_MITER = 0; -const int FPDFBitmap_Unknown = 0; +const int FPDF_LINEJOIN_ROUND = 1; -const int FPDFBitmap_Gray = 1; +const int FPDF_LINEJOIN_BEVEL = 2; -const int FPDFBitmap_BGR = 2; +const int FPDF_PRINTMODE_EMF = 0; -const int FPDFBitmap_BGRx = 3; +const int FPDF_PRINTMODE_TEXTONLY = 1; -const int FPDFBitmap_BGRA = 4; +const int FPDF_PRINTMODE_POSTSCRIPT2 = 2; -const int FPDFBitmap_BGRA_Premul = 5; +const int FPDF_PRINTMODE_POSTSCRIPT3 = 3; + + +const int FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5; + + +const int FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; const int FORMTYPE_NONE = 0; @@ -10691,13 +13842,13 @@ const int FPDF_ANNOT_AACTION_VALIDATE = 14; const int FPDF_ANNOT_AACTION_CALCULATE = 15; -const int FPDF_MATCHCASE = 1; +const int FPDF_INCREMENTAL = 1; -const int FPDF_MATCHWHOLEWORD = 2; +const int FPDF_NO_INCREMENTAL = 2; -const int FPDF_CONSECUTIVE = 4; +const int FPDF_REMOVE_SECURITY = 3; const int PDFACTION_UNSUPPORTED = 0; @@ -10745,194 +13896,113 @@ const int PDFDEST_VIEW_FITBH = 7; const int PDFDEST_VIEW_FITBV = 8; -const int FPDF_COLORSPACE_UNKNOWN = 0; - - -const int FPDF_COLORSPACE_DEVICEGRAY = 1; - - -const int FPDF_COLORSPACE_DEVICERGB = 2; - - -const int FPDF_COLORSPACE_DEVICECMYK = 3; - - -const int FPDF_COLORSPACE_CALGRAY = 4; - - -const int FPDF_COLORSPACE_CALRGB = 5; - - -const int FPDF_COLORSPACE_LAB = 6; - - -const int FPDF_COLORSPACE_ICCBASED = 7; - - -const int FPDF_COLORSPACE_SEPARATION = 8; - - -const int FPDF_COLORSPACE_DEVICEN = 9; - - -const int FPDF_COLORSPACE_INDEXED = 10; - - -const int FPDF_COLORSPACE_PATTERN = 11; - - -const int FPDF_PAGEOBJ_UNKNOWN = 0; - - -const int FPDF_PAGEOBJ_TEXT = 1; - - -const int FPDF_PAGEOBJ_PATH = 2; - - -const int FPDF_PAGEOBJ_IMAGE = 3; - - -const int FPDF_PAGEOBJ_SHADING = 4; - - -const int FPDF_PAGEOBJ_FORM = 5; - - -const int FPDF_SEGMENT_UNKNOWN = -1; - - -const int FPDF_SEGMENT_LINETO = 0; - - -const int FPDF_SEGMENT_BEZIERTO = 1; - - -const int FPDF_SEGMENT_MOVETO = 2; - - -const int FPDF_FILLMODE_NONE = 0; - - -const int FPDF_FILLMODE_ALTERNATE = 1; +const int PDF_LINEARIZATION_UNKNOWN = -1; -const int FPDF_FILLMODE_WINDING = 2; - - -const int FPDF_FONT_TYPE1 = 1; - - -const int FPDF_FONT_TRUETYPE = 2; - +const int PDF_NOT_LINEARIZED = 0; -const int FPDF_LINECAP_BUTT = 0; - - -const int FPDF_LINECAP_ROUND = 1; - -const int FPDF_LINECAP_PROJECTING_SQUARE = 2; +const int PDF_LINEARIZED = 1; -const int FPDF_LINEJOIN_MITER = 0; +const int PDF_DATA_ERROR = -1; -const int FPDF_LINEJOIN_ROUND = 1; +const int PDF_DATA_NOTAVAIL = 0; -const int FPDF_LINEJOIN_BEVEL = 2; +const int PDF_DATA_AVAIL = 1; -const int FPDF_PRINTMODE_EMF = 0; +const int PDF_FORM_ERROR = -1; -const int FPDF_PRINTMODE_TEXTONLY = 1; +const int PDF_FORM_NOTAVAIL = 0; -const int FPDF_PRINTMODE_POSTSCRIPT2 = 2; +const int PDF_FORM_AVAIL = 1; -const int FPDF_PRINTMODE_POSTSCRIPT3 = 3; +const int PDF_FORM_NOTEXIST = 2; -const int FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4; +const int FPDF_UNSP_DOC_XFAFORM = 1; -const int FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5; +const int FPDF_UNSP_DOC_PORTABLECOLLECTION = 2; -const int FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6; +const int FPDF_UNSP_DOC_ATTACHMENT = 3; -const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; +const int FPDF_UNSP_DOC_SECURITY = 4; -const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; +const int FPDF_UNSP_DOC_SHAREDREVIEW = 5; -const int FPDF_INCREMENTAL = 1; +const int FPDF_UNSP_DOC_SHAREDFORM_ACROBAT = 6; -const int FPDF_NO_INCREMENTAL = 2; +const int FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM = 7; -const int FPDF_REMOVE_SECURITY = 3; +const int FPDF_UNSP_DOC_SHAREDFORM_EMAIL = 8; -const int FXFONT_ANSI_CHARSET = 0; +const int FPDF_UNSP_ANNOT_3DANNOT = 11; -const int FXFONT_DEFAULT_CHARSET = 1; +const int FPDF_UNSP_ANNOT_MOVIE = 12; -const int FXFONT_SYMBOL_CHARSET = 2; +const int FPDF_UNSP_ANNOT_SOUND = 13; -const int FXFONT_SHIFTJIS_CHARSET = 128; +const int FPDF_UNSP_ANNOT_SCREEN_MEDIA = 14; -const int FXFONT_HANGEUL_CHARSET = 129; +const int FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA = 15; -const int FXFONT_GB2312_CHARSET = 134; +const int FPDF_UNSP_ANNOT_ATTACHMENT = 16; -const int FXFONT_CHINESEBIG5_CHARSET = 136; +const int FPDF_UNSP_ANNOT_SIG = 17; -const int FXFONT_GREEK_CHARSET = 161; +const int PAGEMODE_UNKNOWN = -1; -const int FXFONT_VIETNAMESE_CHARSET = 163; +const int PAGEMODE_USENONE = 0; -const int FXFONT_HEBREW_CHARSET = 177; +const int PAGEMODE_USEOUTLINES = 1; -const int FXFONT_ARABIC_CHARSET = 178; +const int PAGEMODE_USETHUMBS = 2; -const int FXFONT_CYRILLIC_CHARSET = 204; +const int PAGEMODE_FULLSCREEN = 3; -const int FXFONT_THAI_CHARSET = 222; +const int PAGEMODE_USEOC = 4; -const int FXFONT_EASTERNEUROPEAN_CHARSET = 238; +const int PAGEMODE_USEATTACHMENTS = 5; -const int FXFONT_FF_FIXEDPITCH = 1; +const int FLATTEN_FAIL = 0; -const int FXFONT_FF_ROMAN = 16; +const int FLATTEN_SUCCESS = 1; -const int FXFONT_FF_SCRIPT = 64; +const int FLATTEN_NOTHINGTODO = 2; -const int FXFONT_FW_NORMAL = 400; +const int FLAT_NORMALDISPLAY = 0; -const int FXFONT_FW_BOLD = 700; +const int FLAT_PRINT = 1; diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 3ca330a2..f96aca83 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -37,15 +37,28 @@ ffigen: bindings: "lib/src/native/pdfium_bindings.dart" headers: entry-points: - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdfview.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_annot.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_signature.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_sysfontinfo.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_javascript.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_text.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_searchex.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_progressive.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdfview.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_edit.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_attachment.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_annot.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_catalog.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_ppo.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_save.h" - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_formfill.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_sysfontinfo.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_save.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_structtree.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_dataavail.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_fwlevent.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_ext.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_transformpage.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_flatten.h" + - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_thumbnail.h" include-directives: - "/tmp/pdfrx.cache/pdfium/linux-x64/include/**" preamble: | From d35d867b7352316d155a59daea49a25027414260 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 02:46:52 +0900 Subject: [PATCH 536/663] Now uses xcframework built by another project. --- packages/pdfrx/darwin/Package.swift | 29 + .../pdfrx/darwin/Sources/pdfrx/pdfrx.swift | 16 + packages/pdfrx/darwin/pdfium/.gitignore | 3 - packages/pdfrx/darwin/pdfium/build | 45 -- packages/pdfrx/darwin/pdfium/build-config.sh | 161 ------ .../pdfium/patches/ios/libjpeg_turbo.patch | 13 - .../darwin/pdfium/patches/ios/pdfium.patch | 39 -- .../pdfium/patches/macos/build-config.patch | 13 - .../darwin/pdfium/patches/macos/pdfium.patch | 13 - packages/pdfrx/darwin/pdfrx.podspec | 55 +- packages/pdfrx/darwin/pdfrx/Package.swift | 43 -- .../pdfrx/Sources/interop/include/.gitkeep | 0 .../darwin/pdfrx/Sources/interop/pdfrx.cpp | 497 ------------------ .../pdfrx/Sources/main/PdfrxPlugin.swift | 70 --- .../Flutter/GeneratedPluginRegistrant.swift | 2 - .../pdfrx/example/viewer/ios/Podfile.lock | 4 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 - .../pdfrx/example/viewer/macos/Podfile.lock | 4 +- .../macos/Runner.xcodeproj/project.pbxproj | 14 +- .../pdfrx/lib/src/utils/native/native.dart | 13 - packages/pdfrx/pubspec.yaml | 2 - .../lib/src/native/apple_direct_lookup.dart | 25 - .../pdfrx_engine/lib/src/native/worker.dart | 2 - 23 files changed, 80 insertions(+), 985 deletions(-) create mode 100644 packages/pdfrx/darwin/Package.swift create mode 100644 packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift delete mode 100644 packages/pdfrx/darwin/pdfium/.gitignore delete mode 100755 packages/pdfrx/darwin/pdfium/build delete mode 100755 packages/pdfrx/darwin/pdfium/build-config.sh delete mode 100644 packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch delete mode 100644 packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch delete mode 100644 packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch delete mode 100644 packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch delete mode 100644 packages/pdfrx/darwin/pdfrx/Package.swift delete mode 100644 packages/pdfrx/darwin/pdfrx/Sources/interop/include/.gitkeep delete mode 100644 packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp delete mode 100644 packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift delete mode 100644 packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart diff --git a/packages/pdfrx/darwin/Package.swift b/packages/pdfrx/darwin/Package.swift new file mode 100644 index 00000000..b02d871c --- /dev/null +++ b/packages/pdfrx/darwin/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.6 +import PackageDescription + +let package = Package( + name: "pdfrx", + platforms: [ + .iOS(.v12), + .macOS(.v10_13), + ], + products: [ + .library( + name: "pdfrx", + targets: ["PDFium"] + ), + ], + targets: [ + .target( + name: "pdfrx", + dependencies: [ + .target(name: "PDFium"), + ] + ), + .binaryTarget( + name: "PDFium", + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-174316.xcframework.zip", + checksum: "b114cd6b6cc52bff705e9b705b450f83bc014b146f6fa532f37177b5c8f4b030" + ), + ] +) diff --git a/packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift b/packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift new file mode 100644 index 00000000..1d43d541 --- /dev/null +++ b/packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift @@ -0,0 +1,16 @@ +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +class PdfrxPlugin: NSObject, FlutterPlugin { + static func register(with _: FlutterPluginRegistrar) { + // This is an FFI plugin - no platform channel needed + // The native code is accessed via FFI from pdfrx_engine + } + + func dummyMethodToPreventStripping() { + // This method prevents the linker from stripping the PDFium framework + } +} diff --git a/packages/pdfrx/darwin/pdfium/.gitignore b/packages/pdfrx/darwin/pdfium/.gitignore deleted file mode 100644 index 1297b6c9..00000000 --- a/packages/pdfrx/darwin/pdfium/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.lib/ -.tmp/ -*.tgz diff --git a/packages/pdfrx/darwin/pdfium/build b/packages/pdfrx/darwin/pdfium/build deleted file mode 100755 index efa07305..00000000 --- a/packages/pdfrx/darwin/pdfium/build +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/zsh -e - -SCRIPT_DIR=$(cd $(dirname $0) && pwd) -cd $SCRIPT_DIR - -build_ios() { - if [[ ! -d ios/pdfium.xcframework ]]; then - ./build-config.sh ios arm64 - ./build-config.sh iossim arm64 - ./build-config.sh iossim x64 - - mkdir -p .tmp/out/lib/iossim-release - lipo -create .tmp/out/lib/iossim-arm64-release/libpdfium.a .tmp/out/lib/iossim-x64-release/libpdfium.a -output .tmp/out/lib/iossim-release/libpdfium.a - - mkdir -p ios/ - xcodebuild -create-xcframework -library .tmp/out/lib/ios-arm64-release/libpdfium.a -headers .tmp/out/include -library .tmp/out/lib/iossim-release/libpdfium.a -headers .tmp/out/include -output ios/pdfium.xcframework - - zip -r pdfium-ios.zip ios - fi -} - -build_macos() { - if [[ ! -d macos/pdfium.xcframework ]]; then - ./build-config.sh macos arm64 - ./build-config.sh macos x64 - - mkdir -p .tmp/out/lib/macos-release - lipo -create .tmp/out/lib/macos-arm64-release/libpdfium.a .tmp/out/lib/macos-x64-release/libpdfium.a -output .tmp/out/lib/macos-release/libpdfium.a - - mkdir -p macos/ - rm -rf macos/pdfium.xcframework - xcodebuild -create-xcframework -library .tmp/out/lib/macos-release/libpdfium.a -headers .tmp/out/include -output macos/pdfium.xcframework - - zip -r pdfium-macos.zip macos - fi -} - -if [[ "$1" == "macos" ]]; then - build_macos -elif [[ "$1" == "ios" ]]; then - build_ios -else - build_ios - build_macos -fi diff --git a/packages/pdfrx/darwin/pdfium/build-config.sh b/packages/pdfrx/darwin/pdfium/build-config.sh deleted file mode 100755 index 0f156160..00000000 --- a/packages/pdfrx/darwin/pdfium/build-config.sh +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/zsh -e - -if [ "$2" = "" ]; then - echo "Usage: $0 linux|android|macos|ios|iossim x86|x64|arm|arm64" - exit 1 -fi - -echo "**************************************************************" -echo " Building PDFium for $1/$2" -echo "**************************************************************" - -LAST_KNOWN_GOOD_COMMIT=chromium/7390 - -SCRIPT_DIR=$(cd $(dirname $0) && pwd) - -# linux, android, macos, ios -TARGET_OS_ORIG=$1 -# x64, x86, arm64, ... -TARGET_ARCH=$2 -# static or dll -STATIC_OR_DLL=static -# release or debug -REL_OR_DBG=release - - -if [[ "$TARGET_OS_ORIG" == "iossim" ]]; then - TARGET_OS=ios - TARGET_ENVIRONMENT=simulator -elif [[ "$TARGET_OS_ORIG" == "macos" ]]; then - TARGET_OS=mac -else - TARGET_OS=$TARGET_OS_ORIG - # only for ios; simulator or device - TARGET_ENVIRONMENT=device -fi - - -WORK_ROOT_DIR=$SCRIPT_DIR/.tmp -DIST_DIR=$WORK_ROOT_DIR/out - -DEPOT_DIR=$WORK_ROOT_DIR/depot_tools -WORK_DIR=$WORK_ROOT_DIR/work - -mkdir -p $WORK_ROOT_DIR $WORK_DIR $DIST_DIR - -if [[ ! -d $DEPOT_DIR ]]; then - pushd $WORK_ROOT_DIR - git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git - popd -fi - -export PATH=$DEPOT_DIR:$PATH - -IS_SHAREDLIB=false - -if [[ "$REL_OR_DBG" = "release" ]]; then - IS_DEBUG=false - DEBUG_DIR_SUFFIX= -else - IS_DEBUG=true - DEBUG_DIR_SUFFIX=/debug -fi - -if [[ "$TARGET_OS" == "mac" || "$TARGET_OS" == "ios" || "$TARGET_OS" == "android" ]]; then - IS_CLANG=true -else - IS_CLANG=false -fi - -cd $WORK_DIR -if [[ ! -e pdfium/.git/index ]]; then - fetch pdfium -fi - -PDFIUM_SRCDIR=$WORK_DIR/pdfium -BUILDDIR=$PDFIUM_SRCDIR/out/$TARGET_OS_ORIG-$TARGET_ARCH-$REL_OR_DBG -mkdir -p $BUILDDIR - -pushd $PDFIUM_SRCDIR -git reset --hard -if [[ "$LAST_KNOWN_GOOD_COMMIT" != "" ]]; then - git checkout "$LAST_KNOWN_GOOD_COMMIT" -fi - -cd $PDFIUM_SRCDIR/build -git reset --hard -cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo -git reset --hard -popd - -INCLUDE_DIR=$DIST_DIR/include -if [[ ! -d $INCLUDE_DIR ]]; then - mkdir -p $INCLUDE_DIR - cp -r $PDFIUM_SRCDIR/public/* $INCLUDE_DIR -fi - -if [[ "$TARGET_OS" == "ios" ]]; then - # (cd $PDFIUM_SRCDIR && git diff > ../../../patches/ios/pdfium.patch) - pushd $PDFIUM_SRCDIR - git apply --reject --whitespace=fix $SCRIPT_DIR/patches/ios/pdfium.patch - popd - # (cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo && git diff > ../../../../../patches/ios/libjpeg_turbo.patch) - pushd $PDFIUM_SRCDIR/third_party/libjpeg_turbo/ - git apply --reject --whitespace=fix $SCRIPT_DIR/patches/ios/libjpeg_turbo.patch - popd -fi - -if [[ "$TARGET_OS" == "mac" ]]; then - # (cd $PDFIUM_SRCDIR && git diff > ../../../patches/macos/pdfium.patch) - pushd $PDFIUM_SRCDIR - git apply --reject --whitespace=fix $SCRIPT_DIR/patches/macos/pdfium.patch - popd - # (cd $PDFIUM_SRCDIR/build && git diff > ../../../../patches/macos/build-config.patch) - pushd $PDFIUM_SRCDIR/build - git apply --reject --whitespace=fix $SCRIPT_DIR/patches/macos/build-config.patch - popd -fi - -cat < $BUILDDIR/args.gn -is_clang = $IS_CLANG -target_os = "$TARGET_OS" -target_cpu = "$TARGET_ARCH" -pdf_is_complete_lib = true -pdf_is_standalone = true -is_component_build = $IS_SHAREDLIB -is_debug = $IS_DEBUG -enable_iterator_debugging = $IS_DEBUG -pdf_enable_xfa = false -pdf_enable_v8 = false -EOF - -if [[ "$TARGET_OS" == "ios" ]]; then - # See ios/pdfium/.tmp/work/pdfium/build/config/ios/rules.gni - cat <> $BUILDDIR/args.gn -ios_enable_code_signing = false -ios_deployment_target = "12.0" -use_custom_libcxx = false -pdf_use_partition_alloc = false -target_environment = "$TARGET_ENVIRONMENT" -EOF -fi - -if [[ "$TARGET_OS" == "mac" ]]; then - cat <> $BUILDDIR/args.gn -use_custom_libcxx = false -pdf_use_partition_alloc = false -EOF -fi - -pushd $BUILDDIR -gn gen . -popd - -ninja -C $BUILDDIR pdfium - -LIB_DIR=$DIST_DIR/lib/$TARGET_OS_ORIG-$TARGET_ARCH-$REL_OR_DBG -rm -rf $LIB_DIR -mkdir -p $LIB_DIR -cp $BUILDDIR/obj/libpdfium.a $LIB_DIR - -cd $SCRIPT_DIR diff --git a/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch b/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch deleted file mode 100644 index 5f633124..00000000 --- a/packages/pdfrx/darwin/pdfium/patches/ios/libjpeg_turbo.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/BUILD.gn b/BUILD.gn -index b39d278..596ea7a 100644 ---- a/BUILD.gn -+++ b/BUILD.gn -@@ -12,7 +12,7 @@ if (current_cpu == "arm" || current_cpu == "arm64") { - } - - assert( -- use_blink, -+ true, - "This is not used if blink is not enabled, don't drag it in unintentionally") - - source_set("libjpeg_headers") { diff --git a/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch b/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch deleted file mode 100644 index 673d3f5a..00000000 --- a/packages/pdfrx/darwin/pdfium/patches/ios/pdfium.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn -index a07bcd089..3d1d197d2 100644 ---- a/core/fxge/BUILD.gn -+++ b/core/fxge/BUILD.gn -@@ -164,7 +164,7 @@ source_set("fxge") { - sources += [ "linux/fx_linux_impl.cpp" ] - } - -- if (is_mac) { -+ if (is_mac || is_ios) { - sources += [ - "apple/fx_apple_impl.cpp", - "apple/fx_apple_platform.cpp", -diff --git a/public/fpdfview.h b/public/fpdfview.h -index d96556b47..79b6f3ebb 100644 ---- a/public/fpdfview.h -+++ b/public/fpdfview.h -@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; - #endif // defined(FPDF_IMPLEMENTATION) - #endif // defined(WIN32) - #else --#define FPDF_EXPORT -+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #endif // defined(COMPONENT_BUILD) - - #if defined(WIN32) && defined(FPDFSDK_EXPORTS) -diff --git a/testing/test.gni b/testing/test.gni -index 6ad2c2d4a..bae5117d8 100644 ---- a/testing/test.gni -+++ b/testing/test.gni -@@ -207,7 +207,7 @@ template("test") { - } - - _bundle_id_suffix = target_name -- if (ios_automatically_manage_certs) { -+ if (true) { - # Use the same bundle identifier for all unit tests when managing - # certificates automatically as the number of free certs is limited. - _bundle_id_suffix = "generic-unit-test" diff --git a/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch b/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch deleted file mode 100644 index 7cd856db..00000000 --- a/packages/pdfrx/darwin/pdfium/patches/macos/build-config.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/config/mac/BUILD.gn b/config/mac/BUILD.gn -index 85a668d33..54691a2e0 100644 ---- a/config/mac/BUILD.gn -+++ b/config/mac/BUILD.gn -@@ -52,6 +52,8 @@ config("compiler") { - if (export_libcxxabi_from_executables) { - ldflags += [ "-Wl,-undefined,dynamic_lookup" ] - } -+ -+ cflags += [ "-Wno-unknown-warning-option" ] - } - - # This is included by reference in the //build/config/compiler:runtime_library diff --git a/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch b/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch deleted file mode 100644 index 53ac1c23..00000000 --- a/packages/pdfrx/darwin/pdfium/patches/macos/pdfium.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/public/fpdfview.h b/public/fpdfview.h -index d96556b47..79b6f3ebb 100644 ---- a/public/fpdfview.h -+++ b/public/fpdfview.h -@@ -220,7 +220,7 @@ typedef int FPDF_OBJECT_TYPE; - #endif // defined(FPDF_IMPLEMENTATION) - #endif // defined(WIN32) - #else --#define FPDF_EXPORT -+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used)) - #endif // defined(COMPONENT_BUILD) - - #if defined(WIN32) && defined(FPDFSDK_EXPORTS) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index a1446fc5..f6dc7f64 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -1,26 +1,20 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint pdfrx.podspec` to validate before publishing. -# -lib_tag = 'pdfium-apple-v11' - Pod::Spec.new do |s| s.name = 'pdfrx' - s.version = '0.0.12' + s.version = '0.1.3' s.summary = 'Yet another PDF renderer for Flutter using PDFium.' s.description = <<-DESC Yet another PDF renderer for Flutter using PDFium. DESC s.homepage = 'https://github.com/espresso3389/pdfrx' - s.license = { :type => 'BSD', :file => '../LICENSE' } + s.license = { :type => 'MIT', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = 'pdfrx/Sources/**/*' + s.source_files = 'Sources/**/*.swift' + s.preserve_paths = 'PDFium.xcframework/**/*' s.ios.deployment_target = '12.0' s.ios.dependency 'Flutter' - s.ios.private_header_files = "pdfium/.lib/#{lib_tag}/ios/pdfium.xcframework/ios-arm64/Headers/*.h" - s.ios.vendored_frameworks = "pdfium/.lib/#{lib_tag}/ios/pdfium.xcframework" + s.ios.vendored_frameworks = 'PDFium.xcframework' # Flutter.framework does not contain a i386 slice. s.ios.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', @@ -29,34 +23,23 @@ Pod::Spec.new do |s| s.osx.deployment_target = '10.13' s.osx.dependency 'FlutterMacOS' - s.osx.private_header_files = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework/macos-arm64_x86_64/Headers/*.h" - s.osx.vendored_frameworks = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework" - s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.osx.vendored_frameworks = 'PDFium.xcframework' + s.osx.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'OTHER_LDFLAGS' => '-framework PDFium' + } + + s.swift_version = '5.0' s.prepare_command = <<-CMD - mkdir -p pdfium/.lib/#{lib_tag} - cd pdfium/.lib/#{lib_tag} - # Check if iOS framework headers exist - if [ ! -f "ios/pdfium.xcframework/ios-arm64/Headers/fpdfview.h" ]; then - echo "Downloading iOS PDFium framework..." - rm -rf ios.zip ios/ - curl -Lo ios.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.zip - unzip -o ios.zip - rm -f ios.zip + if [ ! -d "PDFium.xcframework" ]; then + echo "Downloading PDFium xcframework..." + curl -L -o pdfium.zip "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-174316.xcframework.zip" + unzip -q pdfium.zip + rm pdfium.zip + echo "PDFium xcframework downloaded successfully" else - echo "iOS PDFium framework already exists, skipping download." - fi - # Check if macOS framework headers exist - if [ ! -f "macos/pdfium.xcframework/macos-arm64_x86_64/Headers/fpdfview.h" ]; then - echo "Downloading macOS PDFium framework..." - rm -rf macos.zip macos/ - curl -Lo macos.zip https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.zip - unzip -o macos.zip - rm -f macos.zip - else - echo "macOS PDFium framework already exists, skipping download." + echo "PDFium xcframework already exists, skipping download" fi CMD - - s.swift_version = '5.0' end diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift deleted file mode 100644 index 8328be15..00000000 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ /dev/null @@ -1,43 +0,0 @@ -// swift-tools-version:5.6 -import PackageDescription - -let package = Package( - name: "pdfrx", - platforms: [ - .iOS(.v12), - .macOS(.v10_13), - ], - products: [ - .library( - name: "pdfrx", - targets: ["pdfrx"] - ), - ], - targets: [ - .target( - name: "pdfrx", - dependencies: [ - .target(name: "pdfium"), - ], - path: "Sources/main" - ), - .target( - name: "pdfium", - dependencies: [ - .target(name: "pdfium-ios", condition: .when(platforms: [.iOS])), - .target(name: "pdfium-macos", condition: .when(platforms: [.macOS])), - ], - path: "Sources/interop" - ), - .binaryTarget( - name: "pdfium-ios", - url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-ios.zip", - checksum: "968e270318f9a52697f42b677ff5b46bde4da0702fb3930384d0a7f7e62c3073" - ), - .binaryTarget( - name: "pdfium-macos", - url: "https://github.com/espresso3389/pdfrx/releases/download/pdfium-apple-v11/pdfium-macos.zip", - checksum: "682ebbbb750fc185295e5b803f497e6ce25ab967476478253a1911977fe22c93" - ), - ] -) diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/include/.gitkeep b/packages/pdfrx/darwin/pdfrx/Sources/interop/include/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp b/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp deleted file mode 100644 index 051ea8d9..00000000 --- a/packages/pdfrx/darwin/pdfrx/Sources/interop/pdfrx.cpp +++ /dev/null @@ -1,497 +0,0 @@ -// PDFium bindings for Darwin (iOS/macOS) -#include "fpdf_signature.h" -#include "fpdf_sysfontinfo.h" -#include "fpdf_javascript.h" -#include "fpdf_text.h" -#include "fpdf_searchex.h" -#include "fpdf_progressive.h" -#include "fpdfview.h" -#include "fpdf_edit.h" -#include "fpdf_attachment.h" -#include "fpdf_annot.h" -#include "fpdf_catalog.h" -#include "fpdf_ppo.h" -#include "fpdf_formfill.h" -#include "fpdf_save.h" -#include "fpdf_doc.h" -#include "fpdf_structtree.h" -#include "fpdf_dataavail.h" -#include "fpdf_fwlevent.h" -#include "fpdf_ext.h" -#include "fpdf_transformpage.h" -#include "fpdf_flatten.h" -#include "fpdf_thumbnail.h" - -#if defined(__APPLE__) -#define PDFRX_EXPORT __attribute__((visibility("default"))) __attribute__((used)) -#else -#define PDFRX_EXPORT -#endif - -// This function is used to keep the linker from stripping out the PDFium -// functions that are not directly referenced in this file. This is necessary -// because we are dynamically loading the functions at runtime via DynamicLibrary. -extern "C" PDFRX_EXPORT void const *const * pdfrx_binding() -{ -#define KEEP_FUNC(func) reinterpret_cast(#func), reinterpret_cast(func) - - static void const * const bindings[] = { - // PDFium functions - KEEP_FUNC(FPDF_InitLibraryWithConfig), - KEEP_FUNC(FPDF_InitLibrary), - KEEP_FUNC(FPDF_DestroyLibrary), - KEEP_FUNC(FPDF_SetSandBoxPolicy), - KEEP_FUNC(FPDF_LoadDocument), - KEEP_FUNC(FPDF_LoadMemDocument), - KEEP_FUNC(FPDF_LoadMemDocument64), - KEEP_FUNC(FPDF_LoadCustomDocument), - KEEP_FUNC(FPDF_GetFileVersion), - KEEP_FUNC(FPDF_GetLastError), - KEEP_FUNC(FPDF_DocumentHasValidCrossReferenceTable), - KEEP_FUNC(FPDF_GetTrailerEnds), - KEEP_FUNC(FPDF_GetDocPermissions), - KEEP_FUNC(FPDF_GetDocUserPermissions), - KEEP_FUNC(FPDF_GetSecurityHandlerRevision), - KEEP_FUNC(FPDF_GetPageCount), - KEEP_FUNC(FPDF_LoadPage), - KEEP_FUNC(FPDF_GetPageWidthF), - KEEP_FUNC(FPDF_GetPageWidth), - KEEP_FUNC(FPDF_GetPageHeightF), - KEEP_FUNC(FPDF_GetPageHeight), - KEEP_FUNC(FPDF_GetPageBoundingBox), - KEEP_FUNC(FPDF_GetPageSizeByIndexF), - KEEP_FUNC(FPDF_GetPageSizeByIndex), - KEEP_FUNC(FPDF_RenderPageBitmap), - KEEP_FUNC(FPDF_RenderPageBitmapWithMatrix), - KEEP_FUNC(FPDF_ClosePage), - KEEP_FUNC(FPDF_CloseDocument), - KEEP_FUNC(FPDF_DeviceToPage), - KEEP_FUNC(FPDF_PageToDevice), - KEEP_FUNC(FPDFBitmap_Create), - KEEP_FUNC(FPDFBitmap_CreateEx), - KEEP_FUNC(FPDFBitmap_GetFormat), - KEEP_FUNC(FPDFBitmap_FillRect), - KEEP_FUNC(FPDFBitmap_GetBuffer), - KEEP_FUNC(FPDFBitmap_GetWidth), - KEEP_FUNC(FPDFBitmap_GetHeight), - KEEP_FUNC(FPDFBitmap_GetStride), - KEEP_FUNC(FPDFBitmap_Destroy), - KEEP_FUNC(FPDF_VIEWERREF_GetPrintScaling), - KEEP_FUNC(FPDF_VIEWERREF_GetNumCopies), - KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRange), - KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRangeCount), - KEEP_FUNC(FPDF_VIEWERREF_GetPrintPageRangeElement), - KEEP_FUNC(FPDF_VIEWERREF_GetDuplex), - KEEP_FUNC(FPDF_VIEWERREF_GetName), - KEEP_FUNC(FPDF_CountNamedDests), - KEEP_FUNC(FPDF_GetNamedDestByName), - KEEP_FUNC(FPDF_GetNamedDest), - KEEP_FUNC(FPDF_GetXFAPacketCount), - KEEP_FUNC(FPDF_GetXFAPacketName), - KEEP_FUNC(FPDF_GetXFAPacketContent), - KEEP_FUNC(FPDF_GetSignatureCount), - KEEP_FUNC(FPDF_GetSignatureObject), - KEEP_FUNC(FPDFSignatureObj_GetContents), - KEEP_FUNC(FPDFSignatureObj_GetByteRange), - KEEP_FUNC(FPDFSignatureObj_GetSubFilter), - KEEP_FUNC(FPDFSignatureObj_GetReason), - KEEP_FUNC(FPDFSignatureObj_GetTime), - KEEP_FUNC(FPDFSignatureObj_GetDocMDPPermission), - KEEP_FUNC(FPDF_GetDefaultTTFMap), - KEEP_FUNC(FPDF_GetDefaultTTFMapCount), - KEEP_FUNC(FPDF_GetDefaultTTFMapEntry), - KEEP_FUNC(FPDF_AddInstalledFont), - KEEP_FUNC(FPDF_SetSystemFontInfo), - KEEP_FUNC(FPDF_GetDefaultSystemFontInfo), - KEEP_FUNC(FPDF_FreeDefaultSystemFontInfo), - KEEP_FUNC(FPDFDoc_GetJavaScriptActionCount), - KEEP_FUNC(FPDFDoc_GetJavaScriptAction), - KEEP_FUNC(FPDFDoc_CloseJavaScriptAction), - KEEP_FUNC(FPDFJavaScriptAction_GetName), - KEEP_FUNC(FPDFJavaScriptAction_GetScript), - KEEP_FUNC(FPDFText_LoadPage), - KEEP_FUNC(FPDFText_ClosePage), - KEEP_FUNC(FPDFText_CountChars), - KEEP_FUNC(FPDFText_GetUnicode), - KEEP_FUNC(FPDFText_GetTextObject), - KEEP_FUNC(FPDFText_IsGenerated), - KEEP_FUNC(FPDFText_IsHyphen), - KEEP_FUNC(FPDFText_HasUnicodeMapError), - KEEP_FUNC(FPDFText_GetFontSize), - KEEP_FUNC(FPDFText_GetFontInfo), - KEEP_FUNC(FPDFText_GetFontWeight), - KEEP_FUNC(FPDFText_GetFillColor), - KEEP_FUNC(FPDFText_GetStrokeColor), - KEEP_FUNC(FPDFText_GetCharAngle), - KEEP_FUNC(FPDFText_GetCharBox), - KEEP_FUNC(FPDFText_GetLooseCharBox), - KEEP_FUNC(FPDFText_GetMatrix), - KEEP_FUNC(FPDFText_GetCharOrigin), - KEEP_FUNC(FPDFText_GetCharIndexAtPos), - KEEP_FUNC(FPDFText_GetText), - KEEP_FUNC(FPDFText_CountRects), - KEEP_FUNC(FPDFText_GetRect), - KEEP_FUNC(FPDFText_GetBoundedText), - KEEP_FUNC(FPDFText_FindStart), - KEEP_FUNC(FPDFText_FindNext), - KEEP_FUNC(FPDFText_FindPrev), - KEEP_FUNC(FPDFText_GetSchResultIndex), - KEEP_FUNC(FPDFText_GetSchCount), - KEEP_FUNC(FPDFText_FindClose), - KEEP_FUNC(FPDFLink_LoadWebLinks), - KEEP_FUNC(FPDFLink_CountWebLinks), - KEEP_FUNC(FPDFLink_GetURL), - KEEP_FUNC(FPDFLink_CountRects), - KEEP_FUNC(FPDFLink_GetRect), - KEEP_FUNC(FPDFLink_GetTextRange), - KEEP_FUNC(FPDFLink_CloseWebLinks), - KEEP_FUNC(FPDFText_GetCharIndexFromTextIndex), - KEEP_FUNC(FPDFText_GetTextIndexFromCharIndex), - KEEP_FUNC(FPDF_RenderPageBitmapWithColorScheme_Start), - KEEP_FUNC(FPDF_RenderPageBitmap_Start), - KEEP_FUNC(FPDF_RenderPage_Continue), - KEEP_FUNC(FPDF_RenderPage_Close), - KEEP_FUNC(FPDF_CreateNewDocument), - KEEP_FUNC(FPDFPage_New), - KEEP_FUNC(FPDFPage_Delete), - KEEP_FUNC(FPDF_MovePages), - KEEP_FUNC(FPDFPage_GetRotation), - KEEP_FUNC(FPDFPage_SetRotation), - KEEP_FUNC(FPDFPage_InsertObject), - KEEP_FUNC(FPDFPage_InsertObjectAtIndex), - KEEP_FUNC(FPDFPage_RemoveObject), - KEEP_FUNC(FPDFPage_CountObjects), - KEEP_FUNC(FPDFPage_GetObject), - KEEP_FUNC(FPDFPage_HasTransparency), - KEEP_FUNC(FPDFPage_GenerateContent), - KEEP_FUNC(FPDFPageObj_Destroy), - KEEP_FUNC(FPDFPageObj_HasTransparency), - KEEP_FUNC(FPDFPageObj_GetType), - KEEP_FUNC(FPDFPageObj_GetIsActive), - KEEP_FUNC(FPDFPageObj_SetIsActive), - KEEP_FUNC(FPDFPageObj_Transform), - KEEP_FUNC(FPDFPageObj_TransformF), - KEEP_FUNC(FPDFPageObj_GetMatrix), - KEEP_FUNC(FPDFPageObj_SetMatrix), - KEEP_FUNC(FPDFPage_TransformAnnots), - KEEP_FUNC(FPDFPageObj_NewImageObj), - KEEP_FUNC(FPDFPageObj_GetMarkedContentID), - KEEP_FUNC(FPDFPageObj_CountMarks), - KEEP_FUNC(FPDFPageObj_GetMark), - KEEP_FUNC(FPDFPageObj_AddMark), - KEEP_FUNC(FPDFPageObj_RemoveMark), - KEEP_FUNC(FPDFPageObjMark_GetName), - KEEP_FUNC(FPDFPageObjMark_CountParams), - KEEP_FUNC(FPDFPageObjMark_GetParamKey), - KEEP_FUNC(FPDFPageObjMark_GetParamValueType), - KEEP_FUNC(FPDFPageObjMark_GetParamIntValue), - //KEEP_FUNC(FPDFPageObjMark_GetParamFloatValue), - KEEP_FUNC(FPDFPageObjMark_GetParamStringValue), - KEEP_FUNC(FPDFPageObjMark_GetParamBlobValue), - KEEP_FUNC(FPDFPageObjMark_SetIntParam), - //KEEP_FUNC(FPDFPageObjMark_SetFloatParam), - KEEP_FUNC(FPDFPageObjMark_SetStringParam), - KEEP_FUNC(FPDFPageObjMark_SetBlobParam), - KEEP_FUNC(FPDFPageObjMark_RemoveParam), - KEEP_FUNC(FPDFImageObj_LoadJpegFile), - KEEP_FUNC(FPDFImageObj_LoadJpegFileInline), - KEEP_FUNC(FPDFImageObj_SetMatrix), - KEEP_FUNC(FPDFImageObj_SetBitmap), - KEEP_FUNC(FPDFImageObj_GetBitmap), - KEEP_FUNC(FPDFImageObj_GetRenderedBitmap), - KEEP_FUNC(FPDFImageObj_GetImageDataDecoded), - KEEP_FUNC(FPDFImageObj_GetImageDataRaw), - KEEP_FUNC(FPDFImageObj_GetImageFilterCount), - KEEP_FUNC(FPDFImageObj_GetImageFilter), - KEEP_FUNC(FPDFImageObj_GetImageMetadata), - KEEP_FUNC(FPDFImageObj_GetImagePixelSize), - KEEP_FUNC(FPDFImageObj_GetIccProfileDataDecoded), - KEEP_FUNC(FPDFPageObj_CreateNewPath), - KEEP_FUNC(FPDFPageObj_CreateNewRect), - KEEP_FUNC(FPDFPageObj_GetBounds), - KEEP_FUNC(FPDFPageObj_GetRotatedBounds), - KEEP_FUNC(FPDFPageObj_SetBlendMode), - KEEP_FUNC(FPDFPageObj_SetStrokeColor), - KEEP_FUNC(FPDFPageObj_GetStrokeColor), - KEEP_FUNC(FPDFPageObj_SetStrokeWidth), - KEEP_FUNC(FPDFPageObj_GetStrokeWidth), - KEEP_FUNC(FPDFPageObj_GetLineJoin), - KEEP_FUNC(FPDFPageObj_SetLineJoin), - KEEP_FUNC(FPDFPageObj_GetLineCap), - KEEP_FUNC(FPDFPageObj_SetLineCap), - KEEP_FUNC(FPDFPageObj_SetFillColor), - KEEP_FUNC(FPDFPageObj_GetFillColor), - KEEP_FUNC(FPDFPageObj_GetDashPhase), - KEEP_FUNC(FPDFPageObj_SetDashPhase), - KEEP_FUNC(FPDFPageObj_GetDashCount), - KEEP_FUNC(FPDFPageObj_GetDashArray), - KEEP_FUNC(FPDFPageObj_SetDashArray), - KEEP_FUNC(FPDFPath_CountSegments), - KEEP_FUNC(FPDFPath_GetPathSegment), - KEEP_FUNC(FPDFPathSegment_GetPoint), - KEEP_FUNC(FPDFPathSegment_GetType), - KEEP_FUNC(FPDFPathSegment_GetClose), - KEEP_FUNC(FPDFPath_MoveTo), - KEEP_FUNC(FPDFPath_LineTo), - KEEP_FUNC(FPDFPath_BezierTo), - KEEP_FUNC(FPDFPath_Close), - KEEP_FUNC(FPDFPath_SetDrawMode), - KEEP_FUNC(FPDFPath_GetDrawMode), - KEEP_FUNC(FPDFPageObj_NewTextObj), - KEEP_FUNC(FPDFText_SetText), - KEEP_FUNC(FPDFText_SetCharcodes), - KEEP_FUNC(FPDFText_LoadFont), - KEEP_FUNC(FPDFText_LoadStandardFont), - KEEP_FUNC(FPDFText_LoadCidType2Font), - KEEP_FUNC(FPDFTextObj_GetFontSize), - KEEP_FUNC(FPDFFont_Close), - KEEP_FUNC(FPDFPageObj_CreateTextObj), - KEEP_FUNC(FPDFTextObj_GetTextRenderMode), - KEEP_FUNC(FPDFTextObj_SetTextRenderMode), - KEEP_FUNC(FPDFTextObj_GetText), - KEEP_FUNC(FPDFTextObj_GetRenderedBitmap), - KEEP_FUNC(FPDFTextObj_GetFont), - KEEP_FUNC(FPDFFont_GetBaseFontName), - KEEP_FUNC(FPDFFont_GetFamilyName), - KEEP_FUNC(FPDFFont_GetFontData), - KEEP_FUNC(FPDFFont_GetIsEmbedded), - KEEP_FUNC(FPDFFont_GetFlags), - KEEP_FUNC(FPDFFont_GetWeight), - KEEP_FUNC(FPDFFont_GetItalicAngle), - KEEP_FUNC(FPDFFont_GetAscent), - KEEP_FUNC(FPDFFont_GetDescent), - KEEP_FUNC(FPDFFont_GetGlyphWidth), - KEEP_FUNC(FPDFFont_GetGlyphPath), - KEEP_FUNC(FPDFGlyphPath_CountGlyphSegments), - KEEP_FUNC(FPDFGlyphPath_GetGlyphPathSegment), - KEEP_FUNC(FPDFFormObj_CountObjects), - KEEP_FUNC(FPDFFormObj_GetObject), - KEEP_FUNC(FPDFFormObj_RemoveObject), - KEEP_FUNC(FPDFDoc_GetAttachmentCount), - KEEP_FUNC(FPDFDoc_AddAttachment), - KEEP_FUNC(FPDFDoc_GetAttachment), - KEEP_FUNC(FPDFDoc_DeleteAttachment), - KEEP_FUNC(FPDFAttachment_GetName), - KEEP_FUNC(FPDFAttachment_HasKey), - KEEP_FUNC(FPDFAttachment_GetValueType), - KEEP_FUNC(FPDFAttachment_SetStringValue), - KEEP_FUNC(FPDFAttachment_GetStringValue), - KEEP_FUNC(FPDFAttachment_SetFile), - KEEP_FUNC(FPDFAttachment_GetFile), - KEEP_FUNC(FPDFAttachment_GetSubtype), - KEEP_FUNC(FPDFDOC_InitFormFillEnvironment), - KEEP_FUNC(FPDFDOC_ExitFormFillEnvironment), - KEEP_FUNC(FORM_OnAfterLoadPage), - KEEP_FUNC(FORM_OnBeforeClosePage), - KEEP_FUNC(FORM_DoDocumentJSAction), - KEEP_FUNC(FORM_DoDocumentOpenAction), - KEEP_FUNC(FORM_DoDocumentAAction), - KEEP_FUNC(FORM_DoPageAAction), - KEEP_FUNC(FORM_OnMouseMove), - KEEP_FUNC(FORM_OnMouseWheel), - KEEP_FUNC(FORM_OnFocus), - KEEP_FUNC(FORM_OnLButtonDown), - KEEP_FUNC(FORM_OnRButtonDown), - KEEP_FUNC(FORM_OnLButtonUp), - KEEP_FUNC(FORM_OnRButtonUp), - KEEP_FUNC(FORM_OnLButtonDoubleClick), - KEEP_FUNC(FORM_OnKeyDown), - KEEP_FUNC(FORM_OnKeyUp), - KEEP_FUNC(FORM_OnChar), - KEEP_FUNC(FORM_GetFocusedText), - KEEP_FUNC(FORM_GetSelectedText), - KEEP_FUNC(FORM_ReplaceAndKeepSelection), - KEEP_FUNC(FORM_ReplaceSelection), - KEEP_FUNC(FORM_SelectAllText), - KEEP_FUNC(FORM_CanUndo), - KEEP_FUNC(FORM_CanRedo), - KEEP_FUNC(FORM_Undo), - KEEP_FUNC(FORM_Redo), - KEEP_FUNC(FORM_ForceToKillFocus), - KEEP_FUNC(FORM_GetFocusedAnnot), - KEEP_FUNC(FORM_SetFocusedAnnot), - KEEP_FUNC(FPDFPage_HasFormFieldAtPoint), - KEEP_FUNC(FPDFPage_FormFieldZOrderAtPoint), - KEEP_FUNC(FPDF_SetFormFieldHighlightColor), - KEEP_FUNC(FPDF_SetFormFieldHighlightAlpha), - KEEP_FUNC(FPDF_RemoveFormFieldHighlight), - KEEP_FUNC(FPDF_FFLDraw), - KEEP_FUNC(FPDF_GetFormType), - KEEP_FUNC(FORM_SetIndexSelected), - KEEP_FUNC(FORM_IsIndexSelected), - KEEP_FUNC(FPDF_LoadXFA), - KEEP_FUNC(FPDFAnnot_IsSupportedSubtype), - KEEP_FUNC(FPDFPage_CreateAnnot), - KEEP_FUNC(FPDFPage_GetAnnotCount), - KEEP_FUNC(FPDFPage_GetAnnot), - KEEP_FUNC(FPDFPage_GetAnnotIndex), - KEEP_FUNC(FPDFPage_CloseAnnot), - KEEP_FUNC(FPDFPage_RemoveAnnot), - KEEP_FUNC(FPDFAnnot_GetSubtype), - KEEP_FUNC(FPDFAnnot_IsObjectSupportedSubtype), - KEEP_FUNC(FPDFAnnot_UpdateObject), - KEEP_FUNC(FPDFAnnot_AddInkStroke), - KEEP_FUNC(FPDFAnnot_RemoveInkList), - KEEP_FUNC(FPDFAnnot_AppendObject), - KEEP_FUNC(FPDFAnnot_GetObjectCount), - KEEP_FUNC(FPDFAnnot_GetObject), - KEEP_FUNC(FPDFAnnot_RemoveObject), - KEEP_FUNC(FPDFAnnot_SetColor), - KEEP_FUNC(FPDFAnnot_GetColor), - KEEP_FUNC(FPDFAnnot_HasAttachmentPoints), - KEEP_FUNC(FPDFAnnot_SetAttachmentPoints), - KEEP_FUNC(FPDFAnnot_AppendAttachmentPoints), - KEEP_FUNC(FPDFAnnot_CountAttachmentPoints), - KEEP_FUNC(FPDFAnnot_GetAttachmentPoints), - KEEP_FUNC(FPDFAnnot_SetRect), - KEEP_FUNC(FPDFAnnot_GetRect), - KEEP_FUNC(FPDFAnnot_GetVertices), - KEEP_FUNC(FPDFAnnot_GetInkListCount), - KEEP_FUNC(FPDFAnnot_GetInkListPath), - KEEP_FUNC(FPDFAnnot_GetLine), - KEEP_FUNC(FPDFAnnot_SetBorder), - KEEP_FUNC(FPDFAnnot_GetBorder), - KEEP_FUNC(FPDFAnnot_GetFormAdditionalActionJavaScript), - KEEP_FUNC(FPDFAnnot_HasKey), - KEEP_FUNC(FPDFAnnot_GetValueType), - KEEP_FUNC(FPDFAnnot_SetStringValue), - KEEP_FUNC(FPDFAnnot_GetStringValue), - KEEP_FUNC(FPDFAnnot_GetNumberValue), - KEEP_FUNC(FPDFAnnot_SetAP), - KEEP_FUNC(FPDFAnnot_GetAP), - KEEP_FUNC(FPDFAnnot_GetLinkedAnnot), - KEEP_FUNC(FPDFAnnot_GetFlags), - KEEP_FUNC(FPDFAnnot_SetFlags), - KEEP_FUNC(FPDFAnnot_GetFormFieldFlags), - KEEP_FUNC(FPDFAnnot_SetFormFieldFlags), - KEEP_FUNC(FPDFAnnot_GetFormFieldAtPoint), - KEEP_FUNC(FPDFAnnot_GetFormFieldName), - KEEP_FUNC(FPDFAnnot_GetFormFieldAlternateName), - KEEP_FUNC(FPDFAnnot_GetFormFieldType), - KEEP_FUNC(FPDFAnnot_GetFormFieldValue), - KEEP_FUNC(FPDFAnnot_GetOptionCount), - KEEP_FUNC(FPDFAnnot_GetOptionLabel), - KEEP_FUNC(FPDFAnnot_IsOptionSelected), - KEEP_FUNC(FPDFAnnot_GetFontSize), - KEEP_FUNC(FPDFAnnot_SetFontColor), - KEEP_FUNC(FPDFAnnot_GetFontColor), - KEEP_FUNC(FPDFAnnot_IsChecked), - KEEP_FUNC(FPDFAnnot_SetFocusableSubtypes), - KEEP_FUNC(FPDFAnnot_GetFocusableSubtypesCount), - KEEP_FUNC(FPDFAnnot_GetFocusableSubtypes), - KEEP_FUNC(FPDFAnnot_GetLink), - KEEP_FUNC(FPDFAnnot_GetFormControlCount), - KEEP_FUNC(FPDFAnnot_GetFormControlIndex), - KEEP_FUNC(FPDFAnnot_GetFormFieldExportValue), - KEEP_FUNC(FPDFAnnot_SetURI), - KEEP_FUNC(FPDFAnnot_GetFileAttachment), - KEEP_FUNC(FPDFAnnot_AddFileAttachment), - KEEP_FUNC(FPDFCatalog_IsTagged), - KEEP_FUNC(FPDFCatalog_SetLanguage), - KEEP_FUNC(FPDF_ImportPagesByIndex), - KEEP_FUNC(FPDF_ImportPages), - KEEP_FUNC(FPDF_ImportNPagesToOne), - KEEP_FUNC(FPDF_NewXObjectFromPage), - KEEP_FUNC(FPDF_CloseXObject), - KEEP_FUNC(FPDF_NewFormObjectFromXObject), - KEEP_FUNC(FPDF_CopyViewerPreferences), - KEEP_FUNC(FPDF_SaveAsCopy), - KEEP_FUNC(FPDF_SaveWithVersion), - KEEP_FUNC(FPDFBookmark_GetFirstChild), - KEEP_FUNC(FPDFBookmark_GetNextSibling), - KEEP_FUNC(FPDFBookmark_GetTitle), - KEEP_FUNC(FPDFBookmark_GetCount), - KEEP_FUNC(FPDFBookmark_Find), - KEEP_FUNC(FPDFBookmark_GetDest), - KEEP_FUNC(FPDFBookmark_GetAction), - KEEP_FUNC(FPDFAction_GetType), - KEEP_FUNC(FPDFAction_GetDest), - KEEP_FUNC(FPDFAction_GetFilePath), - KEEP_FUNC(FPDFAction_GetURIPath), - KEEP_FUNC(FPDFDest_GetDestPageIndex), - KEEP_FUNC(FPDFDest_GetView), - KEEP_FUNC(FPDFDest_GetLocationInPage), - KEEP_FUNC(FPDFLink_GetLinkAtPoint), - KEEP_FUNC(FPDFLink_GetLinkZOrderAtPoint), - KEEP_FUNC(FPDFLink_GetDest), - KEEP_FUNC(FPDFLink_GetAction), - KEEP_FUNC(FPDFLink_Enumerate), - KEEP_FUNC(FPDFLink_GetAnnot), - KEEP_FUNC(FPDFLink_GetAnnotRect), - KEEP_FUNC(FPDFLink_CountQuadPoints), - KEEP_FUNC(FPDFLink_GetQuadPoints), - KEEP_FUNC(FPDF_GetPageAAction), - KEEP_FUNC(FPDF_GetFileIdentifier), - KEEP_FUNC(FPDF_GetMetaText), - KEEP_FUNC(FPDF_GetPageLabel), - KEEP_FUNC(FPDF_StructTree_GetForPage), - KEEP_FUNC(FPDF_StructTree_Close), - KEEP_FUNC(FPDF_StructTree_CountChildren), - KEEP_FUNC(FPDF_StructTree_GetChildAtIndex), - KEEP_FUNC(FPDF_StructElement_GetAltText), - KEEP_FUNC(FPDF_StructElement_GetActualText), - KEEP_FUNC(FPDF_StructElement_GetID), - KEEP_FUNC(FPDF_StructElement_GetLang), - KEEP_FUNC(FPDF_StructElement_GetStringAttribute), - KEEP_FUNC(FPDF_StructElement_GetMarkedContentID), - KEEP_FUNC(FPDF_StructElement_GetType), - KEEP_FUNC(FPDF_StructElement_GetObjType), - KEEP_FUNC(FPDF_StructElement_GetTitle), - KEEP_FUNC(FPDF_StructElement_CountChildren), - KEEP_FUNC(FPDF_StructElement_GetChildAtIndex), - KEEP_FUNC(FPDF_StructElement_GetChildMarkedContentID), - KEEP_FUNC(FPDF_StructElement_GetParent), - KEEP_FUNC(FPDF_StructElement_GetAttributeCount), - KEEP_FUNC(FPDF_StructElement_GetAttributeAtIndex), - KEEP_FUNC(FPDF_StructElement_Attr_GetCount), - KEEP_FUNC(FPDF_StructElement_Attr_GetName), - KEEP_FUNC(FPDF_StructElement_Attr_GetValue), - KEEP_FUNC(FPDF_StructElement_Attr_GetType), - KEEP_FUNC(FPDF_StructElement_Attr_GetBooleanValue), - KEEP_FUNC(FPDF_StructElement_Attr_GetNumberValue), - KEEP_FUNC(FPDF_StructElement_Attr_GetStringValue), - KEEP_FUNC(FPDF_StructElement_Attr_GetBlobValue), - KEEP_FUNC(FPDF_StructElement_Attr_CountChildren), - KEEP_FUNC(FPDF_StructElement_Attr_GetChildAtIndex), - KEEP_FUNC(FPDF_StructElement_GetMarkedContentIdCount), - KEEP_FUNC(FPDF_StructElement_GetMarkedContentIdAtIndex), - KEEP_FUNC(FPDFAvail_Create), - KEEP_FUNC(FPDFAvail_Destroy), - KEEP_FUNC(FPDFAvail_IsDocAvail), - KEEP_FUNC(FPDFAvail_GetDocument), - KEEP_FUNC(FPDFAvail_GetFirstPageNum), - KEEP_FUNC(FPDFAvail_IsPageAvail), - KEEP_FUNC(FPDFAvail_IsFormAvail), - KEEP_FUNC(FPDFAvail_IsLinearized), - KEEP_FUNC(FSDK_SetUnSpObjProcessHandler), - KEEP_FUNC(FSDK_SetTimeFunction), - KEEP_FUNC(FSDK_SetLocaltimeFunction), - KEEP_FUNC(FPDFDoc_GetPageMode), - KEEP_FUNC(FPDFPage_SetMediaBox), - KEEP_FUNC(FPDFPage_SetCropBox), - KEEP_FUNC(FPDFPage_SetBleedBox), - KEEP_FUNC(FPDFPage_SetTrimBox), - KEEP_FUNC(FPDFPage_SetArtBox), - KEEP_FUNC(FPDFPage_GetMediaBox), - KEEP_FUNC(FPDFPage_GetCropBox), - KEEP_FUNC(FPDFPage_GetBleedBox), - KEEP_FUNC(FPDFPage_GetTrimBox), - KEEP_FUNC(FPDFPage_GetArtBox), - KEEP_FUNC(FPDFPage_TransFormWithClip), - KEEP_FUNC(FPDFPageObj_TransformClipPath), - KEEP_FUNC(FPDFPageObj_GetClipPath), - KEEP_FUNC(FPDFClipPath_CountPaths), - KEEP_FUNC(FPDFClipPath_CountPathSegments), - KEEP_FUNC(FPDFClipPath_GetPathSegment), - KEEP_FUNC(FPDF_CreateClipPath), - KEEP_FUNC(FPDF_DestroyClipPath), - KEEP_FUNC(FPDFPage_InsertClipPath), - KEEP_FUNC(FPDFPage_Flatten), - KEEP_FUNC(FPDFPage_GetDecodedThumbnailData), - KEEP_FUNC(FPDFPage_GetRawThumbnailData), - KEEP_FUNC(FPDFPage_GetThumbnailAsBitmap), - - nullptr, nullptr // End marker - }; - return bindings; -} diff --git a/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift b/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift deleted file mode 100644 index 848e6873..00000000 --- a/packages/pdfrx/darwin/pdfrx/Sources/main/PdfrxPlugin.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -#if os(iOS) - import Flutter -#elseif os(macOS) - import FlutterMacOS -#endif - -// We don't want to strip these symbols out, so we declare them here. -// For the actual implementation, see pdfrx_interop.cpp. -@_silgen_name("pdfrx_binding") -func pdfrx_binding() -> UnsafePointer - -/// The PdfrxPlugin class that is used to keep PDFium exports alive. -public class PdfrxPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "pdfrx", - binaryMessenger: registrar.pdfrxMessenger - ) - let instance = PdfrxPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "loadBindings": - loadBindings(arguments: call.arguments, result: result) - default: - result(FlutterMethodNotImplemented) - } - } - - private func loadBindings(arguments _: Any?, result: @escaping FlutterResult) { - let unsafeBindings = pdfrx_binding() - var bindings: [String: Int64] = [:] - var index = 0 - - while true { - let namePtr = unsafeBindings[index] - let funcPtr = unsafeBindings[index + 1] - - // Check for end marker (nullptr, nullptr) - if namePtr == nil || funcPtr == nil { - break - } - - let functionName = String(cString: namePtr!.assumingMemoryBound(to: CChar.self)) - let functionAddress = Int64(Int(bitPattern: funcPtr!)) - bindings[functionName] = functionAddress - - index += 2 - } - - result(bindings) - } -} - -private extension FlutterPluginRegistrar { - #if os(iOS) - var pdfrxMessenger: FlutterBinaryMessenger { - messenger() - } - - #elseif os(macOS) - var pdfrxMessenger: FlutterBinaryMessenger { - messenger - } - #endif -} diff --git a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift index 1be94b67..d16846ad 100644 --- a/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/pdf_combine/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import desktop_drop import file_selector_macos import path_provider_foundation -import pdfrx import share_plus import url_launcher_macos @@ -16,7 +15,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index fd3b2951..622302ae 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.12): + - pdfrx (0.1.3): - Flutter - FlutterMacOS - url_launcher_ios (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: af3a9815e0f9b28ad40c6bdc8ee56f3d77dc5574 + pdfrx: 958de19403cf8ee766b42d4c95a9ad095a74a44f url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift index cc36c74b..05b351d6 100644 --- a/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/pdfrx/example/viewer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,10 @@ import Foundation import file_selector_macos import path_provider_foundation -import pdfrx import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - PdfrxPlugin.register(with: registry.registrar(forPlugin: "PdfrxPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 432da463..271dfe13 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -5,7 +5,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - pdfrx (0.0.12): + - pdfrx (0.1.3): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: af3a9815e0f9b28ad40c6bdc8ee56f3d77dc5574 + pdfrx: 62dad897080c0dbfc4deadb110eab93fbe50a068 url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index b64200e1..f79abb0a 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -123,7 +123,6 @@ A392714C36B02B18E76DEBDE /* Pods-RunnerTests.release.xcconfig */, E4EF327FA5D50CED341F92A5 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -276,7 +275,6 @@ 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -578,13 +576,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = XRDM278W3T; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 12.4; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -704,13 +706,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = XRDM278W3T; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 12.4; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -724,13 +730,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = XRDM278W3T; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 12.4; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index af6c5853..d20b1092 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -1,10 +1,7 @@ import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; -// ignore: implementation_imports -import 'package:pdfrx_engine/src/native/apple_direct_lookup.dart'; import '../../../pdfrx.dart'; @@ -36,19 +33,9 @@ PdfrxEntryFunctions? get pdfrxEntryFunctionsOverride => null; /// /// This function is here to maintain a consistent API with web and other platforms. Future platformInitialize() async { - if (PdfrxEntryFunctions.instance.backend == PdfrxBackend.pdfium && isApple) { - await _enableAppleDirectBindings(); - } await PdfrxEntryFunctions.instance.init(); } -Future _enableAppleDirectBindings() async { - debugPrint('pdfrx: Enabling direct bindings for iOS/macOS platforms...'); - final channel = MethodChannel('pdfrx'); - Pdfrx.pdfiumNativeBindings = (await channel.invokeMethod('loadBindings') as Map).cast(); - setupAppleDirectLookupIfApplicable(); -} - /// Reports focus changes for the Web platform to handle right-click context menus. /// /// For native platforms, this function does nothing. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index e9ba0f1e..425d7a83 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -47,13 +47,11 @@ flutter: android: ffiPlugin: true ios: - pluginClass: PdfrxPlugin ffiPlugin: true sharedDarwinSource: true linux: ffiPlugin: true macos: - pluginClass: PdfrxPlugin ffiPlugin: true sharedDarwinSource: true windows: diff --git a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart b/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart deleted file mode 100644 index 3ed49645..00000000 --- a/packages/pdfrx_engine/lib/src/native/apple_direct_lookup.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:ffi' as ffi; - -import '../pdfrx.dart'; -import 'pdfium.dart' as pdfium_native; -import 'pdfium_bindings.dart' as pdfium_bindings; - -/// Sets up direct lookup for Apple platforms if applicable. -/// -/// Instead of using dynamic library loading, this function sets up -/// direct symbol lookups for iOS and macOS platforms to workaround link-time function -/// stripping issues. -void setupAppleDirectLookupIfApplicable() { - if (Pdfrx.pdfiumNativeBindings != null) { - final bindings = Pdfrx.pdfiumNativeBindings!; - ffi.Pointer lookup(String symbolName) { - final ptr = bindings[symbolName]; - //print('Lookup symbol: $symbolName -> $ptr'); - if (ptr == null) throw Exception('Failed to find binding for $symbolName'); - return ffi.Pointer.fromAddress(ptr); - } - - //print('Loading PDFium bindings via direct interop...'); - pdfium_native.pdfium = pdfium_bindings.pdfium.fromLookup(lookup); - } -} diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 2cd20cbb..62468605 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -6,7 +6,6 @@ import 'dart:isolate'; import 'package:ffi/ffi.dart'; import '../pdfrx.dart'; -import 'apple_direct_lookup.dart'; typedef PdfrxComputeCallback = FutureOr Function(M message); @@ -28,7 +27,6 @@ class BackgroundWorker { worker.compute((params) { Pdfrx.pdfiumModulePath = params.modulePath; Pdfrx.pdfiumNativeBindings = params.bindings; - setupAppleDirectLookupIfApplicable(); }, (modulePath: Pdfrx.pdfiumModulePath, bindings: Pdfrx.pdfiumNativeBindings)); return worker; From f31107fba5fac782ddc90062f53363879a6a5381 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 03:18:57 +0900 Subject: [PATCH 537/663] Now CocoaPods works correctly. --- packages/pdfrx/darwin/pdfrx.podspec | 25 +++++++++++++++---- .../pdfrx/example/viewer/ios/Podfile.lock | 2 +- .../pdfrx/example/viewer/macos/Podfile.lock | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index f6dc7f64..bba0b56e 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -1,3 +1,8 @@ +# PDFium xcframework configuration +# https://github.com/espresso3389/pdfium-xcframework/releases +PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-180742.xcframework.zip" +PDFIUM_HASH = "0a900bb5b5d66c4caaaaef1cf291dd1ef34639069baa12c565eda296aee878ec" + Pod::Spec.new do |s| s.name = 'pdfrx' s.version = '0.1.3' @@ -24,17 +29,27 @@ Pod::Spec.new do |s| s.osx.deployment_target = '10.13' s.osx.dependency 'FlutterMacOS' s.osx.vendored_frameworks = 'PDFium.xcframework' - s.osx.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - 'OTHER_LDFLAGS' => '-framework PDFium' - } + s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' s.prepare_command = <<-CMD if [ ! -d "PDFium.xcframework" ]; then echo "Downloading PDFium xcframework..." - curl -L -o pdfium.zip "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-174316.xcframework.zip" + curl -L -o pdfium.zip "#{PDFIUM_URL}" + + echo "Verifying ZIP file hash..." + ACTUAL_HASH=$(shasum -a 256 pdfium.zip | awk '{print $1}') + + if [ "$ACTUAL_HASH" != "#{PDFIUM_HASH}" ]; then + echo "Error: Hash mismatch!" + echo "Expected: #{PDFIUM_HASH}" + echo "Actual: $ACTUAL_HASH" + rm pdfium.zip + exit 1 + fi + echo "Hash verification successful" + unzip -q pdfium.zip rm pdfium.zip echo "PDFium xcframework downloaded successfully" diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 622302ae..50883bf5 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 958de19403cf8ee766b42d4c95a9ad095a74a44f + pdfrx: 26ca23d4d98a22be951464ff86f4712023288f7d url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 271dfe13..ca6a4cd0 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 62dad897080c0dbfc4deadb110eab93fbe50a068 + pdfrx: 26ca23d4d98a22be951464ff86f4712023288f7d url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 From 93de47a4dad32cf70fb85a9d9d4a129a7f3299f6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 03:23:33 +0900 Subject: [PATCH 538/663] .pdfium_hash file to detect xcframework updates. --- packages/pdfrx/.gitignore | 2 ++ packages/pdfrx/darwin/pdfrx.podspec | 30 +++++++++++++++++-- .../pdfrx/example/viewer/ios/Podfile.lock | 2 +- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/pdfrx/.gitignore b/packages/pdfrx/.gitignore index 19a190f3..8f44ddfb 100644 --- a/packages/pdfrx/.gitignore +++ b/packages/pdfrx/.gitignore @@ -46,3 +46,5 @@ build/ *.zip doc/api/ + +.pdfium_hash diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index bba0b56e..20c82b3a 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -34,16 +34,36 @@ Pod::Spec.new do |s| s.swift_version = '5.0' s.prepare_command = <<-CMD + HASH_FILE=".pdfium_hash" + EXPECTED_HASH="#{PDFIUM_HASH}" + + # Check if we need to download/update + NEEDS_DOWNLOAD=false if [ ! -d "PDFium.xcframework" ]; then + echo "PDFium xcframework not found" + NEEDS_DOWNLOAD=true + elif [ ! -f "$HASH_FILE" ]; then + echo "Hash file not found, will re-download" + NEEDS_DOWNLOAD=true + elif [ "$(cat $HASH_FILE)" != "$EXPECTED_HASH" ]; then + echo "PDFium version mismatch, will update" + NEEDS_DOWNLOAD=true + fi + + if [ "$NEEDS_DOWNLOAD" = true ]; then + # Clean up old version if exists + rm -rf PDFium.xcframework + rm -f "$HASH_FILE" + echo "Downloading PDFium xcframework..." curl -L -o pdfium.zip "#{PDFIUM_URL}" echo "Verifying ZIP file hash..." ACTUAL_HASH=$(shasum -a 256 pdfium.zip | awk '{print $1}') - if [ "$ACTUAL_HASH" != "#{PDFIUM_HASH}" ]; then + if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then echo "Error: Hash mismatch!" - echo "Expected: #{PDFIUM_HASH}" + echo "Expected: $EXPECTED_HASH" echo "Actual: $ACTUAL_HASH" rm pdfium.zip exit 1 @@ -52,9 +72,13 @@ Pod::Spec.new do |s| unzip -q pdfium.zip rm pdfium.zip + + # Store hash for future version checks + echo "$EXPECTED_HASH" > "$HASH_FILE" + echo "PDFium xcframework downloaded successfully" else - echo "PDFium xcframework already exists, skipping download" + echo "PDFium xcframework is up to date" fi CMD end diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 50883bf5..1fa2a8a1 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -34,7 +34,7 @@ SPEC CHECKSUMS: file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 26ca23d4d98a22be951464ff86f4712023288f7d + pdfrx: eb73539579f736e207752e64c25c060992ce6ff1 url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 From fb0c7d10bc150888323c5c1799f6a19c7b9621c8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 04:10:34 +0900 Subject: [PATCH 539/663] Now SwiftPM support works well --- packages/pdfrx/darwin/pdfrx.podspec | 2 +- .../pdfrx/darwin/{ => pdfrx}/Package.swift | 9 +- .../pdfrx => pdfrx/Sources/main}/pdfrx.swift | 0 .../pdfrx/example/viewer/ios/Podfile.lock | 26 ----- .../ios/Runner.xcodeproj/project.pbxproj | 95 ------------------- .../pdfrx/example/viewer/macos/Podfile.lock | 26 ----- .../macos/Runner.xcodeproj/project.pbxproj | 92 ++++++++---------- 7 files changed, 43 insertions(+), 207 deletions(-) rename packages/pdfrx/darwin/{ => pdfrx}/Package.swift (74%) rename packages/pdfrx/darwin/{Sources/pdfrx => pdfrx/Sources/main}/pdfrx.swift (100%) diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfrx/darwin/pdfrx.podspec index 20c82b3a..a067da51 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfrx/darwin/pdfrx.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = 'Sources/**/*.swift' + s.source_files = 'pdfrx/Sources/**/*.swift' s.preserve_paths = 'PDFium.xcframework/**/*' s.ios.deployment_target = '12.0' diff --git a/packages/pdfrx/darwin/Package.swift b/packages/pdfrx/darwin/pdfrx/Package.swift similarity index 74% rename from packages/pdfrx/darwin/Package.swift rename to packages/pdfrx/darwin/pdfrx/Package.swift index b02d871c..1bf89916 100644 --- a/packages/pdfrx/darwin/Package.swift +++ b/packages/pdfrx/darwin/pdfrx/Package.swift @@ -10,7 +10,7 @@ let package = Package( products: [ .library( name: "pdfrx", - targets: ["PDFium"] + targets: ["pdfrx"] ), ], targets: [ @@ -18,12 +18,13 @@ let package = Package( name: "pdfrx", dependencies: [ .target(name: "PDFium"), - ] + ], + path: "Sources/main" ), .binaryTarget( name: "PDFium", - url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-174316.xcframework.zip", - checksum: "b114cd6b6cc52bff705e9b705b450f83bc014b146f6fa532f37177b5c8f4b030" + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-180742.xcframework.zip", + checksum: "0a900bb5b5d66c4caaaaef1cf291dd1ef34639069baa12c565eda296aee878ec" ), ] ) diff --git a/packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift b/packages/pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift similarity index 100% rename from packages/pdfrx/darwin/Sources/pdfrx/pdfrx.swift rename to packages/pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index 1fa2a8a1..c3dcb254 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,41 +1,15 @@ PODS: - - file_selector_ios (0.0.1): - - Flutter - Flutter (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.1.3): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter DEPENDENCIES: - - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: - file_selector_ios: - :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - pdfrx: - :path: ".symlinks/plugins/pdfrx/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: eb73539579f736e207752e64c25c060992ce6ff1 - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 371bc57d..8b7f4816 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -15,8 +15,6 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - BE975C22D15CF1BBE8071C10 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38B4320811C7EB4CB74C6133 /* Pods_Runner.framework */; }; - E99BBE0FBBBAAC964367C729 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7BEC1FADF343CBFC5B51B1E /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -47,14 +45,11 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 38B4320811C7EB4CB74C6133 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; - 7AB3913973095D5E19AA88E2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 85C9F234D9DF12C65EB18206 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -62,11 +57,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9E247B55F4C64CE7CF913025 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - D5D2308B985CDB250A9F88B7 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - DEBC11A5C1305C8F354ABA41 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E7BEC1FADF343CBFC5B51B1E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EBE521B9258A2D46B34232C9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +64,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E99BBE0FBBBAAC964367C729 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -83,7 +72,6 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - BE975C22D15CF1BBE8071C10 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -93,12 +81,6 @@ 2C0A7AC090CFDFC89385173D /* Pods */ = { isa = PBXGroup; children = ( - 7AB3913973095D5E19AA88E2 /* Pods-Runner.debug.xcconfig */, - 9E247B55F4C64CE7CF913025 /* Pods-Runner.release.xcconfig */, - D5D2308B985CDB250A9F88B7 /* Pods-Runner.profile.xcconfig */, - DEBC11A5C1305C8F354ABA41 /* Pods-RunnerTests.debug.xcconfig */, - EBE521B9258A2D46B34232C9 /* Pods-RunnerTests.release.xcconfig */, - 85C9F234D9DF12C65EB18206 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -131,7 +113,6 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 2C0A7AC090CFDFC89385173D /* Pods */, - F3A93483760A6F9EFD891B53 /* Frameworks */, ); sourceTree = ""; }; @@ -159,15 +140,6 @@ path = Runner; sourceTree = ""; }; - F3A93483760A6F9EFD891B53 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 38B4320811C7EB4CB74C6133 /* Pods_Runner.framework */, - E7BEC1FADF343CBFC5B51B1E /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -175,7 +147,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 938050D037D35BB79276ADD0 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 8CA26D04F87237D4AA4DC2CF /* Frameworks */, @@ -194,14 +165,12 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 475BC99F98491F877D325517 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 167CB5D42EFFE1024C6A90CF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -279,23 +248,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 167CB5D42EFFE1024C6A90CF /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -312,50 +264,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 475BC99F98491F877D325517 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 938050D037D35BB79276ADD0 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -499,7 +407,6 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DEBC11A5C1305C8F354ABA41 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -517,7 +424,6 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EBE521B9258A2D46B34232C9 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -533,7 +439,6 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 85C9F234D9DF12C65EB18206 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index ca6a4cd0..9d359e12 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -1,41 +1,15 @@ PODS: - - file_selector_macos (0.0.1): - - FlutterMacOS - FlutterMacOS (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - pdfrx (0.1.3): - - Flutter - - FlutterMacOS - - url_launcher_macos (0.0.1): - - FlutterMacOS DEPENDENCIES: - - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - pdfrx (from `Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin`) - - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: - file_selector_macos: - :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral - path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - pdfrx: - :path: Flutter/ephemeral/.symlinks/plugins/pdfrx/darwin - url_launcher_macos: - :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - pdfrx: 26ca23d4d98a22be951464ff86f4712023288f7d - url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index f79abb0a..8c12c8e6 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -21,15 +21,15 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 197F95E95681452C2C706E26 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E810B0C266EEE0A56DD759CF /* Pods_Runner.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 4F04007BD71D9E24E68CAA20 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF58CB124081E3D64D45978F /* Pods_Runner.framework */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 7FC10F0346A1C9FEAEC7A56E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A50F6EA4CAA1D0D6E368554 /* Pods_RunnerTests.framework */; }; + A354CD3A0CF63DBE7FA95FE4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,8 +63,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1A50F6EA4CAA1D0D6E368554 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2728056CB87CCEF7E7B9E3AD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 07F88847F7E0AD2AB6A18DBD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; @@ -81,15 +80,16 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 5F27827074D121E8149E1E36 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 5ABE92F86B1527884947EFD9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BB77DD10EE76AAC0ED2FF18 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - A392714C36B02B18E76DEBDE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - D1F1A0FA3EB21E69A5CB6616 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - E4EF327FA5D50CED341F92A5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - E810B0C266EEE0A56DD759CF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F1A3FB6C4BBE46ABE02C84CE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + AF58CB124081E3D64D45978F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,7 +97,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7FC10F0346A1C9FEAEC7A56E /* Pods_RunnerTests.framework in Frameworks */, + A354CD3A0CF63DBE7FA95FE4 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -106,22 +106,31 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 197F95E95681452C2C706E26 /* Pods_Runner.framework in Frameworks */, + 4F04007BD71D9E24E68CAA20 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 067233A8615F413EB50792B3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AF58CB124081E3D64D45978F /* Pods_Runner.framework */, + 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 2549777989FCB1E18D7F6133 /* Pods */ = { isa = PBXGroup; children = ( - 2728056CB87CCEF7E7B9E3AD /* Pods-Runner.debug.xcconfig */, - F1A3FB6C4BBE46ABE02C84CE /* Pods-Runner.release.xcconfig */, - D1F1A0FA3EB21E69A5CB6616 /* Pods-Runner.profile.xcconfig */, - 5F27827074D121E8149E1E36 /* Pods-RunnerTests.debug.xcconfig */, - A392714C36B02B18E76DEBDE /* Pods-RunnerTests.release.xcconfig */, - E4EF327FA5D50CED341F92A5 /* Pods-RunnerTests.profile.xcconfig */, + 8BB77DD10EE76AAC0ED2FF18 /* Pods-Runner.debug.xcconfig */, + 07F88847F7E0AD2AB6A18DBD /* Pods-Runner.release.xcconfig */, + 5ABE92F86B1527884947EFD9 /* Pods-Runner.profile.xcconfig */, + 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */, + 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */, + 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -152,8 +161,8 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, 2549777989FCB1E18D7F6133 /* Pods */, + 067233A8615F413EB50792B3 /* Frameworks */, ); sourceTree = ""; }; @@ -202,15 +211,6 @@ path = Runner; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - E810B0C266EEE0A56DD759CF /* Pods_Runner.framework */, - 1A50F6EA4CAA1D0D6E368554 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -218,7 +218,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 9C1586A419FF5DDDD0DFC35A /* [CP] Check Pods Manifest.lock */, + 95E89CCDE9488642DADD7E81 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -237,13 +237,12 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - AD9238A20634DDAB9D2BD9BF /* [CP] Check Pods Manifest.lock */, + 8DA07DC84C90D2B5192A3EAA /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 9E685165F03DA85483B0D5E9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -368,7 +367,7 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 9C1586A419FF5DDDD0DFC35A /* [CP] Check Pods Manifest.lock */ = { + 8DA07DC84C90D2B5192A3EAA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -383,31 +382,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9E685165F03DA85483B0D5E9 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - AD9238A20634DDAB9D2BD9BF /* [CP] Check Pods Manifest.lock */ = { + 95E89CCDE9488642DADD7E81 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -422,7 +404,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -480,7 +462,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5F27827074D121E8149E1E36 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -495,7 +477,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A392714C36B02B18E76DEBDE /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -510,7 +492,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E4EF327FA5D50CED341F92A5 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; From d202bdcb68ce9bd4c4d59224c2c51b0eb7e01620 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 15:11:12 +0900 Subject: [PATCH 540/663] Add pdfium_dart package. --- packages/pdfium_dart/.gitignore | 2 + packages/pdfium_dart/README.md | 104 + packages/pdfium_dart/lib/pdfium_dart.dart | 9 + .../pdfium_dart/lib/src/pdfium_bindings.dart | 14019 ++++++++++++++++ .../lib/src/pdfium_downloader.dart | 128 + packages/pdfium_dart/pubspec.yaml | 60 + packages/pdfium_dart/test/pdfium_test.dart | 21 + 7 files changed, 14343 insertions(+) create mode 100644 packages/pdfium_dart/.gitignore create mode 100644 packages/pdfium_dart/README.md create mode 100644 packages/pdfium_dart/lib/pdfium_dart.dart create mode 100644 packages/pdfium_dart/lib/src/pdfium_bindings.dart create mode 100644 packages/pdfium_dart/lib/src/pdfium_downloader.dart create mode 100644 packages/pdfium_dart/pubspec.yaml create mode 100644 packages/pdfium_dart/test/pdfium_test.dart diff --git a/packages/pdfium_dart/.gitignore b/packages/pdfium_dart/.gitignore new file mode 100644 index 00000000..58a0dae1 --- /dev/null +++ b/packages/pdfium_dart/.gitignore @@ -0,0 +1,2 @@ +test/.tmp/ + diff --git a/packages/pdfium_dart/README.md b/packages/pdfium_dart/README.md new file mode 100644 index 00000000..29b0967c --- /dev/null +++ b/packages/pdfium_dart/README.md @@ -0,0 +1,104 @@ +# pdfium_dart + +Dart FFI bindings for the PDFium library. This package provides low-level access to PDFium's C API from Dart. + +## Overview + +This package contains auto-generated FFI bindings for PDFium using `ffigen`. It is designed to be a minimal, pure Dart package that other packages can depend on to access PDFium functionality. + +**Key Features:** +- Pure Dart package with no Flutter dependencies +- Auto-generated FFI bindings using `ffigen` +- Provides direct access to PDFium's C API +- Includes `getPdfium()` function for on-demand PDFium binary downloads +- Supports Windows (x64), Linux (x64, ARM64), and macOS (x64, ARM64) + +## Usage + +### Basic Usage + +This package is primarily intended to be used as a dependency by higher-level packages like `pdfium_flutter` and `pdfrx_engine`. Direct usage is possible but not recommended unless you need low-level PDFium access. + +```dart +import 'package:pdfium_dart/pdfium_dart.dart'; +import 'dart:ffi'; + +// If you already have PDFium loaded +final pdfium = pdfium(DynamicLibrary.open('/path/to/libpdfium.so')); +``` + +### On-Demand PDFium Downloads + +The `getPdfium()` function automatically downloads PDFium binaries on demand, making it easy to use PDFium in CLI applications or for testing without bundling binaries: + +```dart +import 'package:pdfium_dart/pdfium_dart.dart'; + +void main() async { + // Downloads PDFium binaries automatically if not cached + final pdfium = await getPdfium(); + + // Use PDFium API + // ... +} +``` + +**Note for macOS:** The downloaded library is not codesigned. If you encounter issues loading the library, you may need to manually codesign it: + +```bash +codesign --force --sign - +``` + +The binaries are downloaded from [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases) and cached in the system temp directory. + +## Generating Bindings + +### Prerequisites + +The `ffigen` process requires LLVM/Clang to be installed for parsing C headers: + +- **macOS**: Install via Homebrew + ```bash + brew install llvm + ``` + +- **Linux**: Install via package manager + ```bash + # Ubuntu/Debian + sudo apt-get install libclang-dev + + # Fedora + sudo dnf install clang-devel + ``` + +- **Windows**: Download and install LLVM from [llvm.org](https://releases.llvm.org/) + +### Regenerating Bindings + +To regenerate the FFI bindings: + +1. Run tests to download PDFium headers: + ```bash + dart test + ``` + +2. Generate bindings: + ```bash + dart run ffigen + ``` + +The bindings are generated from PDFium headers using the configuration in `ffigen.yaml`. + +## Platform Support + +| Platform | Architecture | Support | +|----------|-------------|---------| +| Windows | x64 | ✅ | +| Linux | x64, ARM64 | ✅ | +| macOS | x64, ARM64 | ✅ | + +**Note:** For Flutter applications with bundled PDFium binaries, use the `pdfium_flutter` package instead. + +## License + +This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. diff --git a/packages/pdfium_dart/lib/pdfium_dart.dart b/packages/pdfium_dart/lib/pdfium_dart.dart new file mode 100644 index 00000000..e3a0d400 --- /dev/null +++ b/packages/pdfium_dart/lib/pdfium_dart.dart @@ -0,0 +1,9 @@ +/// Dart FFI bindings for PDFium library. +/// +/// This package provides low-level FFI bindings to the PDFium C API. +/// It is intended to be used by higher-level packages that provide +/// a more user-friendly API for working with PDF documents. +library pdfium_dart; + +export 'src/pdfium_bindings.dart'; +export 'src/pdfium_downloader.dart'; diff --git a/packages/pdfium_dart/lib/src/pdfium_bindings.dart b/packages/pdfium_dart/lib/src/pdfium_bindings.dart new file mode 100644 index 00000000..bb585d08 --- /dev/null +++ b/packages/pdfium_dart/lib/src/pdfium_bindings.dart @@ -0,0 +1,14019 @@ +// ignore_for_file: unused_field +// dart format off + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; +class PDFium{ +/// Holds the symbol lookup function. +final ffi.Pointer Function(String symbolName) _lookup; + +/// The symbols are looked up in [dynamicLibrary]. +PDFium(ffi.DynamicLibrary dynamicLibrary): _lookup = dynamicLibrary.lookup; + +/// The symbols are looked up with [lookup]. +PDFium.fromLookup(ffi.Pointer Function(String symbolName) lookup): _lookup = lookup; + +/// Function: FPDF_InitLibraryWithConfig +/// Initialize the PDFium library and allocate global resources for it. +/// Parameters: +/// config - configuration information as above. +/// Return value: +/// None. +/// Comments: +/// You have to call this function before you can call any PDF +/// processing functions. +void FPDF_InitLibraryWithConfig(ffi.Pointer config, +) { + return _FPDF_InitLibraryWithConfig(config, +); +} + +late final _FPDF_InitLibraryWithConfigPtr = _lookup< + ffi.NativeFunction )>>('FPDF_InitLibraryWithConfig'); +late final _FPDF_InitLibraryWithConfig = _FPDF_InitLibraryWithConfigPtr.asFunction )>(); + +/// Function: FPDF_InitLibrary +/// Initialize the PDFium library (alternative form). +/// Parameters: +/// None +/// Return value: +/// None. +/// Comments: +/// Convenience function to call FPDF_InitLibraryWithConfig() with a +/// default configuration for backwards compatibility purposes. New +/// code should call FPDF_InitLibraryWithConfig() instead. This will +/// be deprecated in the future. +void FPDF_InitLibrary() { + return _FPDF_InitLibrary(); +} + +late final _FPDF_InitLibraryPtr = _lookup< + ffi.NativeFunction>('FPDF_InitLibrary'); +late final _FPDF_InitLibrary = _FPDF_InitLibraryPtr.asFunction(); + +/// Function: FPDF_DestroyLibrary +/// Release global resources allocated to the PDFium library by +/// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). +/// Parameters: +/// None. +/// Return value: +/// None. +/// Comments: +/// After this function is called, you must not call any PDF +/// processing functions. +/// +/// Calling this function does not automatically close other +/// objects. It is recommended to close other objects before +/// closing the library with this function. +void FPDF_DestroyLibrary() { + return _FPDF_DestroyLibrary(); +} + +late final _FPDF_DestroyLibraryPtr = _lookup< + ffi.NativeFunction>('FPDF_DestroyLibrary'); +late final _FPDF_DestroyLibrary = _FPDF_DestroyLibraryPtr.asFunction(); + +/// Function: FPDF_SetSandBoxPolicy +/// Set the policy for the sandbox environment. +/// Parameters: +/// policy - The specified policy for setting, for example: +/// FPDF_POLICY_MACHINETIME_ACCESS. +/// enable - True to enable, false to disable the policy. +/// Return value: +/// None. +void FPDF_SetSandBoxPolicy(int policy, +int enable, +) { + return _FPDF_SetSandBoxPolicy(policy, +enable, +); +} + +late final _FPDF_SetSandBoxPolicyPtr = _lookup< + ffi.NativeFunction>('FPDF_SetSandBoxPolicy'); +late final _FPDF_SetSandBoxPolicy = _FPDF_SetSandBoxPolicyPtr.asFunction(); + +/// Function: FPDF_LoadDocument +/// Open and load a PDF document. +/// Parameters: +/// file_path - Path to the PDF file (including extension). +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// See comments below regarding the encoding. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// Loaded document can be closed by FPDF_CloseDocument(). +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// The encoding for |file_path| is UTF-8. +/// +/// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, +/// depending on the security handler revision, will only accept one or +/// the other encoding. If |password|'s encoding and the PDF's expected +/// encoding do not match, FPDF_LoadDocument() will automatically +/// convert |password| to the other encoding. +FPDF_DOCUMENT FPDF_LoadDocument(FPDF_STRING file_path, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadDocument(file_path, +password, +); +} + +late final _FPDF_LoadDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_LoadDocument'); +late final _FPDF_LoadDocument = _FPDF_LoadDocumentPtr.asFunction(); + +/// Function: FPDF_LoadMemDocument +/// Open and load a PDF document from memory. +/// Parameters: +/// data_buf - Pointer to a buffer containing the PDF document. +/// size - Number of bytes in the PDF document. +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The memory buffer must remain valid when the document is open. +/// The loaded document can be closed by FPDF_CloseDocument. +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadMemDocument(ffi.Pointer data_buf, +int size, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadMemDocument(data_buf, +size, +password, +); +} + +late final _FPDF_LoadMemDocumentPtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument'); +late final _FPDF_LoadMemDocument = _FPDF_LoadMemDocumentPtr.asFunction , int , FPDF_BYTESTRING )>(); + +/// Experimental API. +/// Function: FPDF_LoadMemDocument64 +/// Open and load a PDF document from memory. +/// Parameters: +/// data_buf - Pointer to a buffer containing the PDF document. +/// size - Number of bytes in the PDF document. +/// password - A string used as the password for the PDF file. +/// If no password is needed, empty or NULL can be used. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The memory buffer must remain valid when the document is open. +/// The loaded document can be closed by FPDF_CloseDocument. +/// If this function fails, you can use FPDF_GetLastError() to retrieve +/// the reason why it failed. +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadMemDocument64(ffi.Pointer data_buf, +int size, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadMemDocument64(data_buf, +size, +password, +); +} + +late final _FPDF_LoadMemDocument64Ptr = _lookup< + ffi.NativeFunction , ffi.Size , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument64'); +late final _FPDF_LoadMemDocument64 = _FPDF_LoadMemDocument64Ptr.asFunction , int , FPDF_BYTESTRING )>(); + +/// Function: FPDF_LoadCustomDocument +/// Load PDF document from a custom access descriptor. +/// Parameters: +/// pFileAccess - A structure for accessing the file. +/// password - Optional password for decrypting the PDF file. +/// Return value: +/// A handle to the loaded document, or NULL on failure. +/// Comments: +/// The application must keep the file resources |pFileAccess| points to +/// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| +/// itself does not need to outlive the FPDF_DOCUMENT. +/// +/// The loaded document can be closed with FPDF_CloseDocument(). +/// +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +/// Notes: +/// If PDFium is built with the XFA module, the application should call +/// FPDF_LoadXFA() function after the PDF document loaded to support XFA +/// fields defined in the fpdfformfill.h file. +FPDF_DOCUMENT FPDF_LoadCustomDocument(ffi.Pointer pFileAccess, +FPDF_BYTESTRING password, +) { + return _FPDF_LoadCustomDocument(pFileAccess, +password, +); +} + +late final _FPDF_LoadCustomDocumentPtr = _lookup< + ffi.NativeFunction , FPDF_BYTESTRING )>>('FPDF_LoadCustomDocument'); +late final _FPDF_LoadCustomDocument = _FPDF_LoadCustomDocumentPtr.asFunction , FPDF_BYTESTRING )>(); + +/// Function: FPDF_GetFileVersion +/// Get the file version of the given PDF document. +/// Parameters: +/// doc - Handle to a document. +/// fileVersion - The PDF file version. File version: 14 for 1.4, 15 +/// for 1.5, ... +/// Return value: +/// True if succeeds, false otherwise. +/// Comments: +/// If the document was created by FPDF_CreateNewDocument, +/// then this function will always fail. +int FPDF_GetFileVersion(FPDF_DOCUMENT doc, +ffi.Pointer fileVersion, +) { + return _FPDF_GetFileVersion(doc, +fileVersion, +); +} + +late final _FPDF_GetFileVersionPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetFileVersion'); +late final _FPDF_GetFileVersion = _FPDF_GetFileVersionPtr.asFunction )>(); + +/// Function: FPDF_GetLastError +/// Get last error code when a function fails. +/// Parameters: +/// None. +/// Return value: +/// A 32-bit integer indicating error code as defined above. +/// Comments: +/// If the previous SDK call succeeded, the return value of this +/// function is not defined. This function only works in conjunction +/// with APIs that mention FPDF_GetLastError() in their documentation. +int FPDF_GetLastError() { + return _FPDF_GetLastError(); +} + +late final _FPDF_GetLastErrorPtr = _lookup< + ffi.NativeFunction>('FPDF_GetLastError'); +late final _FPDF_GetLastError = _FPDF_GetLastErrorPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_DocumentHasValidCrossReferenceTable +/// Whether the document's cross reference table is valid or not. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// True if the PDF parser did not encounter problems parsing the cross +/// reference table. False if the parser could not parse the cross +/// reference table and the table had to be rebuild from other data +/// within the document. +/// Comments: +/// The return value can change over time as the PDF parser evolves. +int FPDF_DocumentHasValidCrossReferenceTable(FPDF_DOCUMENT document, +) { + return _FPDF_DocumentHasValidCrossReferenceTable(document, +); +} + +late final _FPDF_DocumentHasValidCrossReferenceTablePtr = _lookup< + ffi.NativeFunction>('FPDF_DocumentHasValidCrossReferenceTable'); +late final _FPDF_DocumentHasValidCrossReferenceTable = _FPDF_DocumentHasValidCrossReferenceTablePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetTrailerEnds +/// Get the byte offsets of trailer ends. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// buffer - The address of a buffer that receives the +/// byte offsets. +/// length - The size, in ints, of |buffer|. +/// Return value: +/// Returns the number of ints in the buffer on success, 0 on error. +/// +/// |buffer| is an array of integers that describes the exact byte offsets of the +/// trailer ends in the document. If |length| is less than the returned length, +/// or |document| or |buffer| is NULL, |buffer| will not be modified. +int FPDF_GetTrailerEnds(FPDF_DOCUMENT document, +ffi.Pointer buffer, +int length, +) { + return _FPDF_GetTrailerEnds(document, +buffer, +length, +); +} + +late final _FPDF_GetTrailerEndsPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetTrailerEnds'); +late final _FPDF_GetTrailerEnds = _FPDF_GetTrailerEndsPtr.asFunction , int )>(); + +/// Function: FPDF_GetDocPermissions +/// Get file permission flags of the document. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// A 32-bit integer indicating permission flags. Please refer to the +/// PDF Reference for detailed descriptions. If the document is not +/// protected or was unlocked by the owner, 0xffffffff will be returned. +int FPDF_GetDocPermissions(FPDF_DOCUMENT document, +) { + return _FPDF_GetDocPermissions(document, +); +} + +late final _FPDF_GetDocPermissionsPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDocPermissions'); +late final _FPDF_GetDocPermissions = _FPDF_GetDocPermissionsPtr.asFunction(); + +/// Function: FPDF_GetDocUserPermissions +/// Get user file permission flags of the document. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// A 32-bit integer indicating permission flags. Please refer to the +/// PDF Reference for detailed descriptions. If the document is not +/// protected, 0xffffffff will be returned. Always returns user +/// permissions, even if the document was unlocked by the owner. +int FPDF_GetDocUserPermissions(FPDF_DOCUMENT document, +) { + return _FPDF_GetDocUserPermissions(document, +); +} + +late final _FPDF_GetDocUserPermissionsPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDocUserPermissions'); +late final _FPDF_GetDocUserPermissions = _FPDF_GetDocUserPermissionsPtr.asFunction(); + +/// Function: FPDF_GetSecurityHandlerRevision +/// Get the revision for the security handler. +/// Parameters: +/// document - Handle to a document. Returned by FPDF_LoadDocument. +/// Return value: +/// The security handler revision number. Please refer to the PDF +/// Reference for a detailed description. If the document is not +/// protected, -1 will be returned. +int FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document, +) { + return _FPDF_GetSecurityHandlerRevision(document, +); +} + +late final _FPDF_GetSecurityHandlerRevisionPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSecurityHandlerRevision'); +late final _FPDF_GetSecurityHandlerRevision = _FPDF_GetSecurityHandlerRevisionPtr.asFunction(); + +/// Function: FPDF_GetPageCount +/// Get total number of pages in the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument. +/// Return value: +/// Total number of pages in the document. +int FPDF_GetPageCount(FPDF_DOCUMENT document, +) { + return _FPDF_GetPageCount(document, +); +} + +late final _FPDF_GetPageCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageCount'); +late final _FPDF_GetPageCount = _FPDF_GetPageCountPtr.asFunction(); + +/// Function: FPDF_LoadPage +/// Load a page inside the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument +/// page_index - Index number of the page. 0 for the first page. +/// Return value: +/// A handle to the loaded page, or NULL if page load fails. +/// Comments: +/// The loaded page can be rendered to devices using FPDF_RenderPage. +/// The loaded page can be closed using FPDF_ClosePage. +FPDF_PAGE FPDF_LoadPage(FPDF_DOCUMENT document, +int page_index, +) { + return _FPDF_LoadPage(document, +page_index, +); +} + +late final _FPDF_LoadPagePtr = _lookup< + ffi.NativeFunction>('FPDF_LoadPage'); +late final _FPDF_LoadPage = _FPDF_LoadPagePtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetPageWidthF +/// Get page width. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// Return value: +/// Page width (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm). +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageWidthF(FPDF_PAGE page, +) { + return _FPDF_GetPageWidthF(page, +); +} + +late final _FPDF_GetPageWidthFPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageWidthF'); +late final _FPDF_GetPageWidthF = _FPDF_GetPageWidthFPtr.asFunction(); + +/// Function: FPDF_GetPageWidth +/// Get page width. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// Return value: +/// Page width (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm). +/// Note: +/// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the +/// future. +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageWidth(FPDF_PAGE page, +) { + return _FPDF_GetPageWidth(page, +); +} + +late final _FPDF_GetPageWidthPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageWidth'); +late final _FPDF_GetPageWidth = _FPDF_GetPageWidthPtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetPageHeightF +/// Get page height. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// Return value: +/// Page height (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm) +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageHeightF(FPDF_PAGE page, +) { + return _FPDF_GetPageHeightF(page, +); +} + +late final _FPDF_GetPageHeightFPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageHeightF'); +late final _FPDF_GetPageHeightF = _FPDF_GetPageHeightFPtr.asFunction(); + +/// Function: FPDF_GetPageHeight +/// Get page height. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// Return value: +/// Page height (excluding non-displayable area) measured in points. +/// One point is 1/72 inch (around 0.3528 mm) +/// Note: +/// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the +/// future. +/// Comments: +/// Changing the rotation of |page| affects the return value. +double FPDF_GetPageHeight(FPDF_PAGE page, +) { + return _FPDF_GetPageHeight(page, +); +} + +late final _FPDF_GetPageHeightPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageHeight'); +late final _FPDF_GetPageHeight = _FPDF_GetPageHeightPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetPageBoundingBox +/// Get the bounding box of the page. This is the intersection between +/// its media box and its crop box. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// rect - Pointer to a rect to receive the page bounding box. +/// On an error, |rect| won't be filled. +/// Return value: +/// True for success. +int FPDF_GetPageBoundingBox(FPDF_PAGE page, +ffi.Pointer rect, +) { + return _FPDF_GetPageBoundingBox(page, +rect, +); +} + +late final _FPDF_GetPageBoundingBoxPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetPageBoundingBox'); +late final _FPDF_GetPageBoundingBox = _FPDF_GetPageBoundingBoxPtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_GetPageSizeByIndexF +/// Get the size of the page at the given index. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// page_index - Page index, zero for the first page. +/// size - Pointer to a FS_SIZEF to receive the page size. +/// (in points). +/// Return value: +/// Non-zero for success. 0 for error (document or page not found). +int FPDF_GetPageSizeByIndexF(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer size, +) { + return _FPDF_GetPageSizeByIndexF(document, +page_index, +size, +); +} + +late final _FPDF_GetPageSizeByIndexFPtr = _lookup< + ffi.NativeFunction )>>('FPDF_GetPageSizeByIndexF'); +late final _FPDF_GetPageSizeByIndexF = _FPDF_GetPageSizeByIndexFPtr.asFunction )>(); + +/// Function: FPDF_GetPageSizeByIndex +/// Get the size of the page at the given index. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument. +/// page_index - Page index, zero for the first page. +/// width - Pointer to a double to receive the page width +/// (in points). +/// height - Pointer to a double to receive the page height +/// (in points). +/// Return value: +/// Non-zero for success. 0 for error (document or page not found). +/// Note: +/// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in +/// the future. +int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer width, +ffi.Pointer height, +) { + return _FPDF_GetPageSizeByIndex(document, +page_index, +width, +height, +); +} + +late final _FPDF_GetPageSizeByIndexPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetPageSizeByIndex'); +late final _FPDF_GetPageSizeByIndex = _FPDF_GetPageSizeByIndexPtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDF_RenderPageBitmap +/// Render contents of a page to a device independent bitmap. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). The bitmap handle can be created +/// by FPDFBitmap_Create or retrieved from an image +/// object by FPDFImageObj_GetBitmap. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// start_x - Left pixel position of the display area in +/// bitmap coordinates. +/// start_y - Top pixel position of the display area in bitmap +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// flags - 0 for normal display, or combination of the Page +/// Rendering flags defined above. With the FPDF_ANNOT +/// flag, it renders all annotations that do not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// Return value: +/// None. +void FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +) { + return _FPDF_RenderPageBitmap(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +); +} + +late final _FPDF_RenderPageBitmapPtr = _lookup< + ffi.NativeFunction>('FPDF_RenderPageBitmap'); +late final _FPDF_RenderPageBitmap = _FPDF_RenderPageBitmapPtr.asFunction(); + +/// Function: FPDF_RenderPageBitmapWithMatrix +/// Render contents of a page to a device independent bitmap. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). The bitmap handle can be created +/// by FPDFBitmap_Create or retrieved by +/// FPDFImageObj_GetBitmap. +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// matrix - The transform matrix, which must be invertible. +/// See PDF Reference 1.7, 4.2.2 Common Transformations. +/// clipping - The rect to clip to in device coords. +/// flags - 0 for normal display, or combination of the Page +/// Rendering flags defined above. With the FPDF_ANNOT +/// flag, it renders all annotations that do not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// Return value: +/// None. Note that behavior is undefined if det of |matrix| is 0. +void FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, +FPDF_PAGE page, +ffi.Pointer matrix, +ffi.Pointer clipping, +int flags, +) { + return _FPDF_RenderPageBitmapWithMatrix(bitmap, +page, +matrix, +clipping, +flags, +); +} + +late final _FPDF_RenderPageBitmapWithMatrixPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_RenderPageBitmapWithMatrix'); +late final _FPDF_RenderPageBitmapWithMatrix = _FPDF_RenderPageBitmapWithMatrixPtr.asFunction , ffi.Pointer , int )>(); + +/// Function: FPDF_ClosePage +/// Close a loaded PDF page. +/// Parameters: +/// page - Handle to the loaded page. +/// Return value: +/// None. +void FPDF_ClosePage(FPDF_PAGE page, +) { + return _FPDF_ClosePage(page, +); +} + +late final _FPDF_ClosePagePtr = _lookup< + ffi.NativeFunction>('FPDF_ClosePage'); +late final _FPDF_ClosePage = _FPDF_ClosePagePtr.asFunction(); + +/// Function: FPDF_CloseDocument +/// Close a loaded PDF document. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// None. +void FPDF_CloseDocument(FPDF_DOCUMENT document, +) { + return _FPDF_CloseDocument(document, +); +} + +late final _FPDF_CloseDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_CloseDocument'); +late final _FPDF_CloseDocument = _FPDF_CloseDocumentPtr.asFunction(); + +/// Function: FPDF_DeviceToPage +/// Convert the screen coordinates of a point to page coordinates. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// start_x - Left pixel position of the display area in +/// device coordinates. +/// start_y - Top pixel position of the display area in device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// device_x - X value in device coordinates to be converted. +/// device_y - Y value in device coordinates to be converted. +/// page_x - A pointer to a double receiving the converted X +/// value in page coordinates. +/// page_y - A pointer to a double receiving the converted Y +/// value in page coordinates. +/// Return value: +/// Returns true if the conversion succeeds, and |page_x| and |page_y| +/// successfully receives the converted coordinates. +/// Comments: +/// The page coordinate system has its origin at the left-bottom corner +/// of the page, with the X-axis on the bottom going to the right, and +/// the Y-axis on the left side going up. +/// +/// NOTE: this coordinate system can be altered when you zoom, scroll, +/// or rotate a page, however, a point on the page should always have +/// the same coordinate values in the page coordinate system. +/// +/// The device coordinate system is device dependent. For screen device, +/// its origin is at the left-top corner of the window. However this +/// origin can be altered by the Windows coordinate transformation +/// utilities. +/// +/// You must make sure the start_x, start_y, size_x, size_y +/// and rotate parameters have exactly same values as you used in +/// the FPDF_RenderPage() function call. +int FPDF_DeviceToPage(FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int device_x, +int device_y, +ffi.Pointer page_x, +ffi.Pointer page_y, +) { + return _FPDF_DeviceToPage(page, +start_x, +start_y, +size_x, +size_y, +rotate, +device_x, +device_y, +page_x, +page_y, +); +} + +late final _FPDF_DeviceToPagePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_DeviceToPage'); +late final _FPDF_DeviceToPage = _FPDF_DeviceToPagePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDF_PageToDevice +/// Convert the page coordinates of a point to screen coordinates. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage. +/// start_x - Left pixel position of the display area in +/// device coordinates. +/// start_y - Top pixel position of the display area in device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: +/// 0 (normal) +/// 1 (rotated 90 degrees clockwise) +/// 2 (rotated 180 degrees) +/// 3 (rotated 90 degrees counter-clockwise) +/// page_x - X value in page coordinates. +/// page_y - Y value in page coordinate. +/// device_x - A pointer to an integer receiving the result X +/// value in device coordinates. +/// device_y - A pointer to an integer receiving the result Y +/// value in device coordinates. +/// Return value: +/// Returns true if the conversion succeeds, and |device_x| and +/// |device_y| successfully receives the converted coordinates. +/// Comments: +/// See comments for FPDF_DeviceToPage(). +int FPDF_PageToDevice(FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +double page_x, +double page_y, +ffi.Pointer device_x, +ffi.Pointer device_y, +) { + return _FPDF_PageToDevice(page, +start_x, +start_y, +size_x, +size_y, +rotate, +page_x, +page_y, +device_x, +device_y, +); +} + +late final _FPDF_PageToDevicePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_PageToDevice'); +late final _FPDF_PageToDevice = _FPDF_PageToDevicePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFBitmap_Create +/// Create a device independent bitmap (FXDIB). +/// Parameters: +/// width - The number of pixels in width for the bitmap. +/// Must be greater than 0. +/// height - The number of pixels in height for the bitmap. +/// Must be greater than 0. +/// alpha - A flag indicating whether the alpha channel is used. +/// Non-zero for using alpha, zero for not using. +/// Return value: +/// The created bitmap handle, or NULL if a parameter error or out of +/// memory. +/// Comments: +/// The bitmap always uses 4 bytes per pixel. The first byte is always +/// double word aligned. +/// +/// The byte order is BGRx (the last byte unused if no alpha channel) or +/// BGRA. +/// +/// The pixels in a horizontal line are stored side by side, with the +/// left most pixel stored first (with lower memory address). +/// Each line uses width * 4 bytes. +/// +/// Lines are stored one after another, with the top most line stored +/// first. There is no gap between adjacent lines. +/// +/// This function allocates enough memory for holding all pixels in the +/// bitmap, but it doesn't initialize the buffer. Applications can use +/// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS +/// allows it, this function can allocate up to 4 GB of memory. +FPDF_BITMAP FPDFBitmap_Create(int width, +int height, +int alpha, +) { + return _FPDFBitmap_Create(width, +height, +alpha, +); +} + +late final _FPDFBitmap_CreatePtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_Create'); +late final _FPDFBitmap_Create = _FPDFBitmap_CreatePtr.asFunction(); + +/// Function: FPDFBitmap_CreateEx +/// Create a device independent bitmap (FXDIB) +/// Parameters: +/// width - The number of pixels in width for the bitmap. +/// Must be greater than 0. +/// height - The number of pixels in height for the bitmap. +/// Must be greater than 0. +/// format - A number indicating for bitmap format, as defined +/// above. +/// first_scan - A pointer to the first byte of the first line if +/// using an external buffer. If this parameter is NULL, +/// then a new buffer will be created. +/// stride - Number of bytes for each scan line. The value must +/// be 0 or greater. When the value is 0, +/// FPDFBitmap_CreateEx() will automatically calculate +/// the appropriate value using |width| and |format|. +/// When using an external buffer, it is recommended for +/// the caller to pass in the value. +/// When not using an external buffer, it is recommended +/// for the caller to pass in 0. +/// Return value: +/// The bitmap handle, or NULL if parameter error or out of memory. +/// Comments: +/// Similar to FPDFBitmap_Create function, but allows for more formats +/// and an external buffer is supported. The bitmap created by this +/// function can be used in any place that a FPDF_BITMAP handle is +/// required. +/// +/// If an external buffer is used, then the caller should destroy the +/// buffer. FPDFBitmap_Destroy() will not destroy the buffer. +/// +/// It is recommended to use FPDFBitmap_GetStride() to get the stride +/// value. +FPDF_BITMAP FPDFBitmap_CreateEx(int width, +int height, +int format, +ffi.Pointer first_scan, +int stride, +) { + return _FPDFBitmap_CreateEx(width, +height, +format, +first_scan, +stride, +); +} + +late final _FPDFBitmap_CreateExPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFBitmap_CreateEx'); +late final _FPDFBitmap_CreateEx = _FPDFBitmap_CreateExPtr.asFunction , int )>(); + +/// Function: FPDFBitmap_GetFormat +/// Get the format of the bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The format of the bitmap. +/// Comments: +/// Only formats supported by FPDFBitmap_CreateEx are supported by this +/// function; see the list of such formats above. +int FPDFBitmap_GetFormat(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetFormat(bitmap, +); +} + +late final _FPDFBitmap_GetFormatPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetFormat'); +late final _FPDFBitmap_GetFormat = _FPDFBitmap_GetFormatPtr.asFunction(); + +/// Function: FPDFBitmap_FillRect +/// Fill a rectangle in a bitmap. +/// Parameters: +/// bitmap - The handle to the bitmap. Returned by +/// FPDFBitmap_Create. +/// left - The left position. Starting from 0 at the +/// left-most pixel. +/// top - The top position. Starting from 0 at the +/// top-most line. +/// width - Width in pixels to be filled. +/// height - Height in pixels to be filled. +/// color - A 32-bit value specifing the color, in 8888 ARGB +/// format. +/// Return value: +/// Returns whether the operation succeeded or not. +/// Comments: +/// This function sets the color and (optionally) alpha value in the +/// specified region of the bitmap. +/// +/// NOTE: If the alpha channel is used, this function does NOT +/// composite the background with the source color, instead the +/// background will be replaced by the source color and the alpha. +/// +/// If the alpha channel is not used, the alpha parameter is ignored. +int FPDFBitmap_FillRect(FPDF_BITMAP bitmap, +int left, +int top, +int width, +int height, +int color, +) { + return _FPDFBitmap_FillRect(bitmap, +left, +top, +width, +height, +color, +); +} + +late final _FPDFBitmap_FillRectPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_FillRect'); +late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction(); + +/// Function: FPDFBitmap_GetBuffer +/// Get data buffer of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The pointer to the first byte of the bitmap buffer. +/// Comments: +/// The stride may be more than width * number of bytes per pixel +/// +/// Applications can use this function to get the bitmap buffer pointer, +/// then manipulate any color and/or alpha values for any pixels in the +/// bitmap. +/// +/// Use FPDFBitmap_GetFormat() to find out the format of the data. +ffi.Pointer FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetBuffer(bitmap, +); +} + +late final _FPDFBitmap_GetBufferPtr = _lookup< + ffi.NativeFunction Function(FPDF_BITMAP )>>('FPDFBitmap_GetBuffer'); +late final _FPDFBitmap_GetBuffer = _FPDFBitmap_GetBufferPtr.asFunction Function(FPDF_BITMAP )>(); + +/// Function: FPDFBitmap_GetWidth +/// Get width of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The width of the bitmap in pixels. +int FPDFBitmap_GetWidth(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetWidth(bitmap, +); +} + +late final _FPDFBitmap_GetWidthPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetWidth'); +late final _FPDFBitmap_GetWidth = _FPDFBitmap_GetWidthPtr.asFunction(); + +/// Function: FPDFBitmap_GetHeight +/// Get height of a bitmap. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The height of the bitmap in pixels. +int FPDFBitmap_GetHeight(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetHeight(bitmap, +); +} + +late final _FPDFBitmap_GetHeightPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetHeight'); +late final _FPDFBitmap_GetHeight = _FPDFBitmap_GetHeightPtr.asFunction(); + +/// Function: FPDFBitmap_GetStride +/// Get number of bytes for each line in the bitmap buffer. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// The number of bytes for each line in the bitmap buffer. +/// Comments: +/// The stride may be more than width * number of bytes per pixel. +int FPDFBitmap_GetStride(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_GetStride(bitmap, +); +} + +late final _FPDFBitmap_GetStridePtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_GetStride'); +late final _FPDFBitmap_GetStride = _FPDFBitmap_GetStridePtr.asFunction(); + +/// Function: FPDFBitmap_Destroy +/// Destroy a bitmap and release all related buffers. +/// Parameters: +/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +/// or FPDFImageObj_GetBitmap. +/// Return value: +/// None. +/// Comments: +/// This function will not destroy any external buffers provided when +/// the bitmap was created. +void FPDFBitmap_Destroy(FPDF_BITMAP bitmap, +) { + return _FPDFBitmap_Destroy(bitmap, +); +} + +late final _FPDFBitmap_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFBitmap_Destroy'); +late final _FPDFBitmap_Destroy = _FPDFBitmap_DestroyPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetPrintScaling +/// Whether the PDF document prefers to be scaled or not. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// None. +int FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetPrintScaling(document, +); +} + +late final _FPDF_VIEWERREF_GetPrintScalingPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintScaling'); +late final _FPDF_VIEWERREF_GetPrintScaling = _FPDF_VIEWERREF_GetPrintScalingPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetNumCopies +/// Returns the number of copies to be printed. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The number of copies to be printed. +int FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetNumCopies(document, +); +} + +late final _FPDF_VIEWERREF_GetNumCopiesPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetNumCopies'); +late final _FPDF_VIEWERREF_GetNumCopies = _FPDF_VIEWERREF_GetNumCopiesPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetPrintPageRange +/// Page numbers to initialize print dialog box when file is printed. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The print page range to be used for printing. +FPDF_PAGERANGE FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document, +) { + return _FPDF_VIEWERREF_GetPrintPageRange(document, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangePtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRange'); +late final _FPDF_VIEWERREF_GetPrintPageRange = _FPDF_VIEWERREF_GetPrintPageRangePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_VIEWERREF_GetPrintPageRangeCount +/// Returns the number of elements in a FPDF_PAGERANGE. +/// Parameters: +/// pagerange - Handle to the page range. +/// Return value: +/// The number of elements in the page range. Returns 0 on error. +int FPDF_VIEWERREF_GetPrintPageRangeCount(FPDF_PAGERANGE pagerange, +) { + return _FPDF_VIEWERREF_GetPrintPageRangeCount(pagerange, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangeCountPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeCount'); +late final _FPDF_VIEWERREF_GetPrintPageRangeCount = _FPDF_VIEWERREF_GetPrintPageRangeCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_VIEWERREF_GetPrintPageRangeElement +/// Returns an element from a FPDF_PAGERANGE. +/// Parameters: +/// pagerange - Handle to the page range. +/// index - Index of the element. +/// Return value: +/// The value of the element in the page range at a given index. +/// Returns -1 on error. +int FPDF_VIEWERREF_GetPrintPageRangeElement(FPDF_PAGERANGE pagerange, +int index, +) { + return _FPDF_VIEWERREF_GetPrintPageRangeElement(pagerange, +index, +); +} + +late final _FPDF_VIEWERREF_GetPrintPageRangeElementPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeElement'); +late final _FPDF_VIEWERREF_GetPrintPageRangeElement = _FPDF_VIEWERREF_GetPrintPageRangeElementPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetDuplex +/// Returns the paper handling option to be used when printing from +/// the print dialog. +/// Parameters: +/// document - Handle to the loaded document. +/// Return value: +/// The paper handling option to be used when printing. +_FPDF_DUPLEXTYPE_ FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document, +) { + return _FPDF_DUPLEXTYPE_.fromValue(_FPDF_VIEWERREF_GetDuplex(document, +)); +} + +late final _FPDF_VIEWERREF_GetDuplexPtr = _lookup< + ffi.NativeFunction>('FPDF_VIEWERREF_GetDuplex'); +late final _FPDF_VIEWERREF_GetDuplex = _FPDF_VIEWERREF_GetDuplexPtr.asFunction(); + +/// Function: FPDF_VIEWERREF_GetName +/// Gets the contents for a viewer ref, with a given key. The value must +/// be of type "name". +/// Parameters: +/// document - Handle to the loaded document. +/// key - Name of the key in the viewer pref dictionary, +/// encoded in UTF-8. +/// buffer - Caller-allocate buffer to receive the key, or NULL +/// - to query the required length. +/// length - Length of the buffer. +/// Return value: +/// The number of bytes in the contents, including the NULL terminator. +/// Thus if the return value is 0, then that indicates an error, such +/// as when |document| is invalid. If |length| is less than the required +/// length, or |buffer| is NULL, |buffer| will not be modified. +int FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int length, +) { + return _FPDF_VIEWERREF_GetName(document, +key, +buffer, +length, +); +} + +late final _FPDF_VIEWERREF_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_VIEWERREF_GetName'); +late final _FPDF_VIEWERREF_GetName = _FPDF_VIEWERREF_GetNamePtr.asFunction , int )>(); + +/// Function: FPDF_CountNamedDests +/// Get the count of named destinations in the PDF document. +/// Parameters: +/// document - Handle to a document +/// Return value: +/// The count of named destinations. +int FPDF_CountNamedDests(FPDF_DOCUMENT document, +) { + return _FPDF_CountNamedDests(document, +); +} + +late final _FPDF_CountNamedDestsPtr = _lookup< + ffi.NativeFunction>('FPDF_CountNamedDests'); +late final _FPDF_CountNamedDests = _FPDF_CountNamedDestsPtr.asFunction(); + +/// Function: FPDF_GetNamedDestByName +/// Get a the destination handle for the given name. +/// Parameters: +/// document - Handle to the loaded document. +/// name - The name of a destination. +/// Return value: +/// The handle to the destination. +FPDF_DEST FPDF_GetNamedDestByName(FPDF_DOCUMENT document, +FPDF_BYTESTRING name, +) { + return _FPDF_GetNamedDestByName(document, +name, +); +} + +late final _FPDF_GetNamedDestByNamePtr = _lookup< + ffi.NativeFunction>('FPDF_GetNamedDestByName'); +late final _FPDF_GetNamedDestByName = _FPDF_GetNamedDestByNamePtr.asFunction(); + +/// Function: FPDF_GetNamedDest +/// Get the named destination by index. +/// Parameters: +/// document - Handle to a document +/// index - The index of a named destination. +/// buffer - The buffer to store the destination name, +/// used as wchar_t*. +/// buflen [in/out] - Size of the buffer in bytes on input, +/// length of the result in bytes on output +/// or -1 if the buffer is too small. +/// Return value: +/// The destination handle for a given index, or NULL if there is no +/// named destination corresponding to |index|. +/// Comments: +/// Call this function twice to get the name of the named destination: +/// 1) First time pass in |buffer| as NULL and get buflen. +/// 2) Second time pass in allocated |buffer| and buflen to retrieve +/// |buffer|, which should be used as wchar_t*. +/// +/// If buflen is not sufficiently large, it will be set to -1 upon +/// return. +FPDF_DEST FPDF_GetNamedDest(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +ffi.Pointer buflen, +) { + return _FPDF_GetNamedDest(document, +index, +buffer, +buflen, +); +} + +late final _FPDF_GetNamedDestPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetNamedDest'); +late final _FPDF_GetNamedDest = _FPDF_GetNamedDestPtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketCount +/// Get the number of valid packets in the XFA entry. +/// Parameters: +/// document - Handle to the document. +/// Return value: +/// The number of valid packets, or -1 on error. +int FPDF_GetXFAPacketCount(FPDF_DOCUMENT document, +) { + return _FPDF_GetXFAPacketCount(document, +); +} + +late final _FPDF_GetXFAPacketCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetXFAPacketCount'); +late final _FPDF_GetXFAPacketCount = _FPDF_GetXFAPacketCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketName +/// Get the name of a packet in the XFA array. +/// Parameters: +/// document - Handle to the document. +/// index - Index number of the packet. 0 for the first packet. +/// buffer - Buffer for holding the name of the XFA packet. +/// buflen - Length of |buffer| in bytes. +/// Return value: +/// The length of the packet name in bytes, or 0 on error. +/// +/// |document| must be valid and |index| must be in the range [0, N), where N is +/// the value returned by FPDF_GetXFAPacketCount(). +/// |buffer| is only modified if it is non-NULL and |buflen| is greater than or +/// equal to the length of the packet name. The packet name includes a +/// terminating NUL character. |buffer| is unmodified on error. +int FPDF_GetXFAPacketName(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetXFAPacketName(document, +index, +buffer, +buflen, +); +} + +late final _FPDF_GetXFAPacketNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetXFAPacketName'); +late final _FPDF_GetXFAPacketName = _FPDF_GetXFAPacketNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_GetXFAPacketContent +/// Get the content of a packet in the XFA array. +/// Parameters: +/// document - Handle to the document. +/// index - Index number of the packet. 0 for the first packet. +/// buffer - Buffer for holding the content of the XFA packet. +/// buflen - Length of |buffer| in bytes. +/// out_buflen - Pointer to the variable that will receive the minimum +/// buffer size needed to contain the content of the XFA +/// packet. +/// Return value: +/// Whether the operation succeeded or not. +/// +/// |document| must be valid and |index| must be in the range [0, N), where N is +/// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be +/// NULL. When the aforementioned arguments are valid, the operation succeeds, +/// and |out_buflen| receives the content size. |buffer| is only modified if +/// |buffer| is non-null and long enough to contain the content. Callers must +/// check both the return value and the input |buflen| is no less than the +/// returned |out_buflen| before using the data in |buffer|. +int FPDF_GetXFAPacketContent(FPDF_DOCUMENT document, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_GetXFAPacketContent(document, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_GetXFAPacketContentPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_GetXFAPacketContent'); +late final _FPDF_GetXFAPacketContent = _FPDF_GetXFAPacketContentPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_GetSignatureCount +/// Get total number of signatures in the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// Return value: +/// Total number of signatures in the document on success, -1 on error. +int FPDF_GetSignatureCount(FPDF_DOCUMENT document, +) { + return _FPDF_GetSignatureCount(document, +); +} + +late final _FPDF_GetSignatureCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSignatureCount'); +late final _FPDF_GetSignatureCount = _FPDF_GetSignatureCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_GetSignatureObject +/// Get the Nth signature of the document. +/// Parameters: +/// document - Handle to document. Returned by FPDF_LoadDocument(). +/// index - Index into the array of signatures of the document. +/// Return value: +/// Returns the handle to the signature, or NULL on failure. The caller +/// does not take ownership of the returned FPDF_SIGNATURE. Instead, it +/// remains valid until FPDF_CloseDocument() is called for the document. +FPDF_SIGNATURE FPDF_GetSignatureObject(FPDF_DOCUMENT document, +int index, +) { + return _FPDF_GetSignatureObject(document, +index, +); +} + +late final _FPDF_GetSignatureObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_GetSignatureObject'); +late final _FPDF_GetSignatureObject = _FPDF_GetSignatureObjectPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetContents +/// Get the contents of a signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the contents. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the contents on success, 0 on error. +/// +/// For public-key signatures, |buffer| is either a DER-encoded PKCS#1 binary or +/// a DER-encoded PKCS#7 binary. If |length| is less than the returned length, or +/// |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetContents(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, +) { + return _FPDFSignatureObj_GetContents(signature, +buffer, +length, +); +} + +late final _FPDFSignatureObj_GetContentsPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetContents'); +late final _FPDFSignatureObj_GetContents = _FPDFSignatureObj_GetContentsPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetByteRange +/// Get the byte range of a signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the +/// byte range. +/// length - The size, in ints, of |buffer|. +/// Return value: +/// Returns the number of ints in the byte range on +/// success, 0 on error. +/// +/// |buffer| is an array of pairs of integers (starting byte offset, +/// length in bytes) that describes the exact byte range for the digest +/// calculation. If |length| is less than the returned length, or +/// |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetByteRange(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, +) { + return _FPDFSignatureObj_GetByteRange(signature, +buffer, +length, +); +} + +late final _FPDFSignatureObj_GetByteRangePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetByteRange'); +late final _FPDFSignatureObj_GetByteRange = _FPDFSignatureObj_GetByteRangePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetSubFilter +/// Get the encoding of the value of a signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the encoding. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the encoding name (including the +/// trailing NUL character) on success, 0 on error. +/// +/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetSubFilter(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, +) { + return _FPDFSignatureObj_GetSubFilter(signature, +buffer, +length, +); +} + +late final _FPDFSignatureObj_GetSubFilterPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetSubFilter'); +late final _FPDFSignatureObj_GetSubFilter = _FPDFSignatureObj_GetSubFilterPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetReason +/// Get the reason (comment) of the signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the reason. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the reason on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The +/// string is terminated by a UTF16 NUL character. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, +) { + return _FPDFSignatureObj_GetReason(signature, +buffer, +length, +); +} + +late final _FPDFSignatureObj_GetReasonPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetReason'); +late final _FPDFSignatureObj_GetReason = _FPDFSignatureObj_GetReasonPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetTime +/// Get the time of signing of a signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// buffer - The address of a buffer that receives the time. +/// length - The size, in bytes, of |buffer|. +/// Return value: +/// Returns the number of bytes in the encoding name (including the +/// trailing NUL character) on success, 0 on error. +/// +/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the +/// returned length, or |buffer| is NULL, |buffer| will not be modified. +/// +/// The format of time is expected to be D:YYYYMMDDHHMMSS+XX'YY', i.e. it's +/// percision is seconds, with timezone information. This value should be used +/// only when the time of signing is not available in the (PKCS#7 binary) +/// signature. +int FPDFSignatureObj_GetTime(FPDF_SIGNATURE signature, +ffi.Pointer buffer, +int length, +) { + return _FPDFSignatureObj_GetTime(signature, +buffer, +length, +); +} + +late final _FPDFSignatureObj_GetTimePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetTime'); +late final _FPDFSignatureObj_GetTime = _FPDFSignatureObj_GetTimePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDFSignatureObj_GetDocMDPPermission +/// Get the DocMDP permission of a signature object. +/// Parameters: +/// signature - Handle to the signature object. Returned by +/// FPDF_GetSignatureObject(). +/// Return value: +/// Returns the permission (1, 2 or 3) on success, 0 on error. +int FPDFSignatureObj_GetDocMDPPermission(FPDF_SIGNATURE signature, +) { + return _FPDFSignatureObj_GetDocMDPPermission(signature, +); +} + +late final _FPDFSignatureObj_GetDocMDPPermissionPtr = _lookup< + ffi.NativeFunction>('FPDFSignatureObj_GetDocMDPPermission'); +late final _FPDFSignatureObj_GetDocMDPPermission = _FPDFSignatureObj_GetDocMDPPermissionPtr.asFunction(); + +/// Function: FPDF_GetDefaultTTFMap +/// Returns a pointer to the default character set to TT Font name map. The +/// map is an array of FPDF_CharsetFontMap structs, with its end indicated +/// by a { -1, NULL } entry. +/// Parameters: +/// None. +/// Return Value: +/// Pointer to the Charset Font Map. +/// Note: +/// Once FPDF_GetDefaultTTFMapCount() and FPDF_GetDefaultTTFMapEntry() are no +/// longer experimental, this API will be marked as deprecated. +/// See https://crbug.com/348468114 +ffi.Pointer FPDF_GetDefaultTTFMap() { + return _FPDF_GetDefaultTTFMap(); +} + +late final _FPDF_GetDefaultTTFMapPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultTTFMap'); +late final _FPDF_GetDefaultTTFMap = _FPDF_GetDefaultTTFMapPtr.asFunction Function()>(); + +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapCount +/// Returns the number of entries in the default character set to TT Font name +/// map. +/// Parameters: +/// None. +/// Return Value: +/// The number of entries in the map. +int FPDF_GetDefaultTTFMapCount() { + return _FPDF_GetDefaultTTFMapCount(); +} + +late final _FPDF_GetDefaultTTFMapCountPtr = _lookup< + ffi.NativeFunction>('FPDF_GetDefaultTTFMapCount'); +late final _FPDF_GetDefaultTTFMapCount = _FPDF_GetDefaultTTFMapCountPtr.asFunction(); + +/// Experimental API. +/// +/// Function: FPDF_GetDefaultTTFMapEntry +/// Returns an entry in the default character set to TT Font name map. +/// Parameters: +/// index - The index to the entry in the map to retrieve. +/// Return Value: +/// A pointer to the entry, if it is in the map, or NULL if the index is out +/// of bounds. +ffi.Pointer FPDF_GetDefaultTTFMapEntry(int index, +) { + return _FPDF_GetDefaultTTFMapEntry(index, +); +} + +late final _FPDF_GetDefaultTTFMapEntryPtr = _lookup< + ffi.NativeFunction Function(ffi.Size )>>('FPDF_GetDefaultTTFMapEntry'); +late final _FPDF_GetDefaultTTFMapEntry = _FPDF_GetDefaultTTFMapEntryPtr.asFunction Function(int )>(); + +/// Function: FPDF_AddInstalledFont +/// Add a system font to the list in PDFium. +/// Comments: +/// This function is only called during the system font list building +/// process. +/// Parameters: +/// mapper - Opaque pointer to Foxit font mapper +/// face - The font face name +/// charset - Font character set. See above defined constants. +/// Return Value: +/// None. +void FPDF_AddInstalledFont(ffi.Pointer mapper, +ffi.Pointer face, +int charset, +) { + return _FPDF_AddInstalledFont(mapper, +face, +charset, +); +} + +late final _FPDF_AddInstalledFontPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_AddInstalledFont'); +late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction , ffi.Pointer , int )>(); + +/// Function: FPDF_SetSystemFontInfo +/// Set the system font info interface into PDFium +/// Parameters: +/// font_info - Pointer to a FPDF_SYSFONTINFO structure +/// Return Value: +/// None +/// Comments: +/// Platform support implementation should implement required methods of +/// FFDF_SYSFONTINFO interface, then call this function during PDFium +/// initialization process. +/// +/// Call this with NULL to tell PDFium to stop using a previously set +/// |FPDF_SYSFONTINFO|. +void FPDF_SetSystemFontInfo(ffi.Pointer font_info, +) { + return _FPDF_SetSystemFontInfo(font_info, +); +} + +late final _FPDF_SetSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_SetSystemFontInfo'); +late final _FPDF_SetSystemFontInfo = _FPDF_SetSystemFontInfoPtr.asFunction )>(); + +/// Function: FPDF_GetDefaultSystemFontInfo +/// Get default system font info interface for current platform +/// Parameters: +/// None +/// Return Value: +/// Pointer to a FPDF_SYSFONTINFO structure describing the default +/// interface, or NULL if the platform doesn't have a default interface. +/// Application should call FPDF_FreeDefaultSystemFontInfo to free the +/// returned pointer. +/// Comments: +/// For some platforms, PDFium implements a default version of system +/// font info interface. The default implementation can be passed to +/// FPDF_SetSystemFontInfo(). +ffi.Pointer FPDF_GetDefaultSystemFontInfo() { + return _FPDF_GetDefaultSystemFontInfo(); +} + +late final _FPDF_GetDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction Function()>>('FPDF_GetDefaultSystemFontInfo'); +late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.asFunction Function()>(); + +/// Function: FPDF_FreeDefaultSystemFontInfo +/// Free a default system font info interface +/// Parameters: +/// font_info - Pointer to a FPDF_SYSFONTINFO structure +/// Return Value: +/// None +/// Comments: +/// This function should be called on the output from +/// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. +void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer font_info, +) { + return _FPDF_FreeDefaultSystemFontInfo(font_info, +); +} + +late final _FPDF_FreeDefaultSystemFontInfoPtr = _lookup< + ffi.NativeFunction )>>('FPDF_FreeDefaultSystemFontInfo'); +late final _FPDF_FreeDefaultSystemFontInfo = _FPDF_FreeDefaultSystemFontInfoPtr.asFunction )>(); + +/// Experimental API. +/// Get the number of JavaScript actions in |document|. +/// +/// document - handle to a document. +/// +/// Returns the number of JavaScript actions in |document| or -1 on error. +int FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document, +) { + return _FPDFDoc_GetJavaScriptActionCount(document, +); +} + +late final _FPDFDoc_GetJavaScriptActionCountPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetJavaScriptActionCount'); +late final _FPDFDoc_GetJavaScriptActionCount = _FPDFDoc_GetJavaScriptActionCountPtr.asFunction(); + +/// Experimental API. +/// Get the JavaScript action at |index| in |document|. +/// +/// document - handle to a document. +/// index - the index of the requested JavaScript action. +/// +/// Returns the handle to the JavaScript action, or NULL on failure. +/// Caller owns the returned handle and must close it with +/// FPDFDoc_CloseJavaScriptAction(). +FPDF_JAVASCRIPT_ACTION FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, +int index, +) { + return _FPDFDoc_GetJavaScriptAction(document, +index, +); +} + +late final _FPDFDoc_GetJavaScriptActionPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetJavaScriptAction'); +late final _FPDFDoc_GetJavaScriptAction = _FPDFDoc_GetJavaScriptActionPtr.asFunction(); + +/// javascript - Handle to a JavaScript action. +void FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript, +) { + return _FPDFDoc_CloseJavaScriptAction(javascript, +); +} + +late final _FPDFDoc_CloseJavaScriptActionPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_CloseJavaScriptAction'); +late final _FPDFDoc_CloseJavaScriptAction = _FPDFDoc_CloseJavaScriptActionPtr.asFunction(); + +/// Experimental API. +/// Get the name from the |javascript| handle. |buffer| is only modified if +/// |buflen| is longer than the length of the name. On errors, |buffer| is +/// unmodified and the returned length is 0. +/// +/// javascript - handle to an JavaScript action. +/// buffer - buffer for holding the name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the JavaScript action name in bytes. +int FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFJavaScriptAction_GetName(javascript, +buffer, +buflen, +); +} + +late final _FPDFJavaScriptAction_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetName'); +late final _FPDFJavaScriptAction_GetName = _FPDFJavaScriptAction_GetNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the script from the |javascript| handle. |buffer| is only modified if +/// |buflen| is longer than the length of the script. On errors, |buffer| is +/// unmodified and the returned length is 0. +/// +/// javascript - handle to an JavaScript action. +/// buffer - buffer for holding the name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the JavaScript action name in bytes. +int FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFJavaScriptAction_GetScript(javascript, +buffer, +buflen, +); +} + +late final _FPDFJavaScriptAction_GetScriptPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetScript'); +late final _FPDFJavaScriptAction_GetScript = _FPDFJavaScriptAction_GetScriptPtr.asFunction , int )>(); + +/// Function: FPDFText_LoadPage +/// Prepare information about all characters in a page. +/// Parameters: +/// page - Handle to the page. Returned by FPDF_LoadPage function +/// (in FPDFVIEW module). +/// Return value: +/// A handle to the text page information structure. +/// NULL if something goes wrong. +/// Comments: +/// Application must call FPDFText_ClosePage to release the text page +/// information. +FPDF_TEXTPAGE FPDFText_LoadPage(FPDF_PAGE page, +) { + return _FPDFText_LoadPage(page, +); +} + +late final _FPDFText_LoadPagePtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadPage'); +late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); + +/// Function: FPDFText_ClosePage +/// Release all resources allocated for a text page information +/// structure. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return Value: +/// None. +void FPDFText_ClosePage(FPDF_TEXTPAGE text_page, +) { + return _FPDFText_ClosePage(text_page, +); +} + +late final _FPDFText_ClosePagePtr = _lookup< + ffi.NativeFunction>('FPDFText_ClosePage'); +late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); + +/// Function: FPDFText_CountChars +/// Get number of characters in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return value: +/// Number of characters in the page. Return -1 for error. +/// Generated characters, like additional space characters, new line +/// characters, are also counted. +/// Comments: +/// Characters in a page form a "stream", inside the stream, each +/// character has an index. +/// We will use the index parameters in many of FPDFTEXT functions. The +/// first character in the page +/// has an index value of zero. +int FPDFText_CountChars(FPDF_TEXTPAGE text_page, +) { + return _FPDFText_CountChars(text_page, +); +} + +late final _FPDFText_CountCharsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountChars'); +late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); + +/// Function: FPDFText_GetUnicode +/// Get Unicode of a character in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The Unicode of the particular character. +/// If a character is not encoded in Unicode and Foxit engine can't +/// convert to Unicode, +/// the return value will be zero. +int FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetUnicode(text_page, +index, +); +} + +late final _FPDFText_GetUnicodePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetUnicode'); +late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetTextObject +/// Get the FPDF_PAGEOBJECT associated with a given character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The associated text object for the character at |index|, or NULL on +/// error. The returned text object, if non-null, is of type +/// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. +FPDF_PAGEOBJECT FPDFText_GetTextObject(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetTextObject(text_page, +index, +); +} + +late final _FPDFText_GetTextObjectPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetTextObject'); +late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_IsGenerated +/// Get if a character in a page is generated by PDFium. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is generated by PDFium. +/// 0 if the character is not generated by PDFium. +/// -1 if there was an error. +int FPDFText_IsGenerated(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_IsGenerated(text_page, +index, +); +} + +late final _FPDFText_IsGeneratedPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsGenerated'); +late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_IsHyphen +/// Get if a character in a page is a hyphen. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character is a hyphen. +/// 0 if the character is not a hyphen. +/// -1 if there was an error. +int FPDFText_IsHyphen(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_IsHyphen(text_page, +index, +); +} + +late final _FPDFText_IsHyphenPtr = _lookup< + ffi.NativeFunction>('FPDFText_IsHyphen'); +late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_HasUnicodeMapError +/// Get if a character in a page has an invalid unicode mapping. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// 1 if the character has an invalid unicode mapping. +/// 0 if the character has no known unicode mapping issues. +/// -1 if there was an error. +int FPDFText_HasUnicodeMapError(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_HasUnicodeMapError(text_page, +index, +); +} + +late final _FPDFText_HasUnicodeMapErrorPtr = _lookup< + ffi.NativeFunction>('FPDFText_HasUnicodeMapError'); +late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr.asFunction(); + +/// Function: FPDFText_GetFontSize +/// Get the font size of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// The font size of the particular character, measured in points (about +/// 1/72 inch). This is the typographic size of the font (so called +/// "em size"). +double FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetFontSize(text_page, +index, +); +} + +late final _FPDFText_GetFontSizePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontSize'); +late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetFontInfo +/// Get the font name and flags of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// buffer - A buffer receiving the font name. +/// buflen - The length of |buffer| in bytes. +/// flags - Optional pointer to an int receiving the font flags. +/// These flags should be interpreted per PDF spec 1.7 +/// Section 5.7.1 Font Descriptor Flags. +/// Return value: +/// On success, return the length of the font name, including the +/// trailing NUL character, in bytes. If this length is less than or +/// equal to |length|, |buffer| is set to the font name, |flags| is +/// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on +/// failure. +int FPDFText_GetFontInfo(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer flags, +) { + return _FPDFText_GetFontInfo(text_page, +index, +buffer, +buflen, +flags, +); +} + +late final _FPDFText_GetFontInfoPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFText_GetFontInfo'); +late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetFontWeight +/// Get the font weight of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return value: +/// On success, return the font weight of the particular character. If +/// |text_page| is invalid, if |index| is out of bounds, or if the +/// character's text object is undefined, return -1. +int FPDFText_GetFontWeight(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetFontWeight(text_page, +index, +); +} + +late final _FPDFText_GetFontWeightPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetFontWeight'); +late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); + +/// Experimental API. +/// Function: FPDFText_GetFillColor +/// Get the fill color of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the fill color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the fill color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the fill color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the fill color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetFillColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFText_GetFillColor(text_page, +index, +R, +G, +B, +A, +); +} + +late final _FPDFText_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetFillColor'); +late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetStrokeColor +/// Get the stroke color of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// R - Pointer to an unsigned int number receiving the +/// red value of the stroke color. +/// G - Pointer to an unsigned int number receiving the +/// green value of the stroke color. +/// B - Pointer to an unsigned int number receiving the +/// blue value of the stroke color. +/// A - Pointer to an unsigned int number receiving the +/// alpha value of the stroke color. +/// Return value: +/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are +/// unchanged. +int FPDFText_GetStrokeColor(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFText_GetStrokeColor(text_page, +index, +R, +G, +B, +A, +); +} + +late final _FPDFText_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetStrokeColor'); +late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetCharAngle +/// Get character rotation angle. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// Return Value: +/// On success, return the angle value in radian. Value will always be +/// greater or equal to 0. If |text_page| is invalid, or if |index| is +/// out of bounds, then return -1. +double FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page, +int index, +) { + return _FPDFText_GetCharAngle(text_page, +index, +); +} + +late final _FPDFText_GetCharAnglePtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharAngle'); +late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction(); + +/// Function: FPDFText_GetCharBox +/// Get bounding box of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// left - Pointer to a double number receiving left position +/// of the character box. +/// right - Pointer to a double number receiving right position +/// of the character box. +/// bottom - Pointer to a double number receiving bottom position +/// of the character box. +/// top - Pointer to a double number receiving top position of +/// the character box. +/// Return Value: +/// On success, return TRUE and fill in |left|, |right|, |bottom|, and +/// |top|. If |text_page| is invalid, or if |index| is out of bounds, +/// then return FALSE, and the out parameters remain unmodified. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer left, +ffi.Pointer right, +ffi.Pointer bottom, +ffi.Pointer top, +) { + return _FPDFText_GetCharBox(text_page, +index, +left, +right, +bottom, +top, +); +} + +late final _FPDFText_GetCharBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetCharBox'); +late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFText_GetLooseCharBox +/// Get a "loose" bounding box of a particular character, i.e., covering +/// the entire glyph bounds, without taking the actual glyph shape into +/// account. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// rect - Pointer to a FS_RECTF receiving the character box. +/// Return Value: +/// On success, return TRUE and fill in |rect|. If |text_page| is +/// invalid, or if |index| is out of bounds, then return FALSE, and the +/// |rect| out parameter remains unmodified. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetLooseCharBox(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer rect, +) { + return _FPDFText_GetLooseCharBox(text_page, +index, +rect, +); +} + +late final _FPDFText_GetLooseCharBoxPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetLooseCharBox'); +late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDFText_GetMatrix +/// Get the effective transformation matrix for a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage(). +/// index - Zero-based index of the character. +/// matrix - Pointer to a FS_MATRIX receiving the transformation +/// matrix. +/// Return Value: +/// On success, return TRUE and fill in |matrix|. If |text_page| is +/// invalid, or if |index| is out of bounds, or if |matrix| is NULL, +/// then return FALSE, and |matrix| remains unmodified. +int FPDFText_GetMatrix(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer matrix, +) { + return _FPDFText_GetMatrix(text_page, +index, +matrix, +); +} + +late final _FPDFText_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetMatrix'); +late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction )>(); + +/// Function: FPDFText_GetCharOrigin +/// Get origin of a particular character. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// index - Zero-based index of the character. +/// x - Pointer to a double number receiving x coordinate of +/// the character origin. +/// y - Pointer to a double number receiving y coordinate of +/// the character origin. +/// Return Value: +/// Whether the call succeeded. If false, x and y are unchanged. +/// Comments: +/// All positions are measured in PDF "user space". +int FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, +int index, +ffi.Pointer x, +ffi.Pointer y, +) { + return _FPDFText_GetCharOrigin(text_page, +index, +x, +y, +); +} + +late final _FPDFText_GetCharOriginPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFText_GetCharOrigin'); +late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFText_GetCharIndexAtPos +/// Get the index of a character at or nearby a certain position on the +/// page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// x - X position in PDF "user space". +/// y - Y position in PDF "user space". +/// xTolerance - An x-axis tolerance value for character hit +/// detection, in point units. +/// yTolerance - A y-axis tolerance value for character hit +/// detection, in point units. +/// Return Value: +/// The zero-based index of the character at, or nearby the point (x,y). +/// If there is no character at or nearby the point, return value will +/// be -1. If an error occurs, -3 will be returned. +int FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, +double x, +double y, +double xTolerance, +double yTolerance, +) { + return _FPDFText_GetCharIndexAtPos(text_page, +x, +y, +xTolerance, +yTolerance, +); +} + +late final _FPDFText_GetCharIndexAtPosPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharIndexAtPos'); +late final _FPDFText_GetCharIndexAtPos = _FPDFText_GetCharIndexAtPosPtr.asFunction(); + +/// Function: FPDFText_GetText +/// Extract unicode text string from the page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start characters. +/// count - Number of UCS-2 values to be extracted. +/// result - A buffer (allocated by application) receiving the +/// extracted UCS-2 values. The buffer must be able to +/// hold `count` UCS-2 values plus a terminator. +/// Return Value: +/// Number of characters written into the result buffer, including the +/// trailing terminator. +/// Comments: +/// This function ignores characters without UCS-2 representations. +/// It considers all characters on the page, even those that are not +/// visible when the page has a cropbox. To filter out the characters +/// outside of the cropbox, use FPDF_GetPageBoundingBox() and +/// FPDFText_GetCharBox(). +int FPDFText_GetText(FPDF_TEXTPAGE text_page, +int start_index, +int count, +ffi.Pointer result, +) { + return _FPDFText_GetText(text_page, +start_index, +count, +result, +); +} + +late final _FPDFText_GetTextPtr = _lookup< + ffi.NativeFunction )>>('FPDFText_GetText'); +late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction )>(); + +/// Function: FPDFText_CountRects +/// Counts number of rectangular areas occupied by a segment of text, +/// and caches the result for subsequent FPDFText_GetRect() calls. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// start_index - Index for the start character. +/// count - Number of characters, or -1 for all remaining. +/// Return value: +/// Number of rectangles, 0 if text_page is null, or -1 on bad +/// start_index. +/// Comments: +/// This function, along with FPDFText_GetRect can be used by +/// applications to detect the position on the page for a text segment, +/// so proper areas can be highlighted. The FPDFText_* functions will +/// automatically merge small character boxes into bigger one if those +/// characters are on the same line and use same font settings. +int FPDFText_CountRects(FPDF_TEXTPAGE text_page, +int start_index, +int count, +) { + return _FPDFText_CountRects(text_page, +start_index, +count, +); +} + +late final _FPDFText_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFText_CountRects'); +late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction(); + +/// Function: FPDFText_GetRect +/// Get a rectangular area from the result generated by +/// FPDFText_CountRects. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// rect_index - Zero-based index for the rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |text_page| is invalid then return FALSE, and the out +/// parameters remain unmodified. If |text_page| is valid but +/// |rect_index| is out of bounds, then return FALSE and set the out +/// parameters to 0. +int FPDFText_GetRect(FPDF_TEXTPAGE text_page, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, +) { + return _FPDFText_GetRect(text_page, +rect_index, +left, +top, +right, +bottom, +); +} + +late final _FPDFText_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetRect'); +late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Function: FPDFText_GetBoundedText +/// Extract unicode text within a rectangular boundary on the page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// left - Left boundary. +/// top - Top boundary. +/// right - Right boundary. +/// bottom - Bottom boundary. +/// buffer - Caller-allocated buffer to receive UTF-16 values. +/// buflen - Number of UTF-16 values (not bytes) that `buffer` +/// is capable of holding. +/// Return Value: +/// If buffer is NULL or buflen is zero, return number of UTF-16 +/// values (not bytes) of text present within the rectangle, excluding +/// a terminating NUL. Generally you should pass a buffer at least one +/// larger than this if you want a terminating NUL, which will be +/// provided if space is available. Otherwise, return number of UTF-16 +/// values copied into the buffer, including the terminating NUL when +/// space for it is available. +/// Comment: +/// If the buffer is too small, as much text as will fit is copied into +/// it. May return a split surrogate in that case. +int FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, +double left, +double top, +double right, +double bottom, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFText_GetBoundedText(text_page, +left, +top, +right, +bottom, +buffer, +buflen, +); +} + +late final _FPDFText_GetBoundedTextPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFText_GetBoundedText'); +late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction , int )>(); + +/// Function: FPDFText_FindStart +/// Start a search. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// findwhat - A unicode match pattern. +/// flags - Option flags. +/// start_index - Start from this character. -1 for end of the page. +/// Return Value: +/// A handle for the search context. FPDFText_FindClose must be called +/// to release this handle. +FPDF_SCHHANDLE FPDFText_FindStart(FPDF_TEXTPAGE text_page, +FPDF_WIDESTRING findwhat, +int flags, +int start_index, +) { + return _FPDFText_FindStart(text_page, +findwhat, +flags, +start_index, +); +} + +late final _FPDFText_FindStartPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindStart'); +late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction(); + +/// Function: FPDFText_FindNext +/// Search in the direction from page start to end. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindNext(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindNext(handle, +); +} + +late final _FPDFText_FindNextPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindNext'); +late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); + +/// Function: FPDFText_FindPrev +/// Search in the direction from page end to start. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Whether a match is found. +int FPDFText_FindPrev(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindPrev(handle, +); +} + +late final _FPDFText_FindPrevPtr = _lookup< + ffi.NativeFunction>('FPDFText_FindPrev'); +late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); + +/// Function: FPDFText_GetSchResultIndex +/// Get the starting character index of the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Index for the starting character. +int FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle, +) { + return _FPDFText_GetSchResultIndex(handle, +); +} + +late final _FPDFText_GetSchResultIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchResultIndex'); +late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); + +/// Function: FPDFText_GetSchCount +/// Get the number of matched characters in the search result. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// Number of matched characters. +int FPDFText_GetSchCount(FPDF_SCHHANDLE handle, +) { + return _FPDFText_GetSchCount(handle, +); +} + +late final _FPDFText_GetSchCountPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetSchCount'); +late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); + +/// Function: FPDFText_FindClose +/// Release a search context. +/// Parameters: +/// handle - A search context handle returned by +/// FPDFText_FindStart. +/// Return Value: +/// None. +void FPDFText_FindClose(FPDF_SCHHANDLE handle, +) { + return _FPDFText_FindClose(handle, +); +} + +late final _FPDFText_FindClosePtr = _lookup< + ffi.NativeFunction>('FPDFText_FindClose'); +late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); + +/// Function: FPDFLink_LoadWebLinks +/// Prepare information about weblinks in a page. +/// Parameters: +/// text_page - Handle to a text page information structure. +/// Returned by FPDFText_LoadPage function. +/// Return Value: +/// A handle to the page's links information structure, or +/// NULL if something goes wrong. +/// Comments: +/// Weblinks are those links implicitly embedded in PDF pages. PDF also +/// has a type of annotation called "link" (FPDFTEXT doesn't deal with +/// that kind of link). FPDFTEXT weblink feature is useful for +/// automatically detecting links in the page contents. For example, +/// things like "https://www.example.com" will be detected, so +/// applications can allow user to click on those characters to activate +/// the link, even the PDF doesn't come with link annotations. +/// +/// FPDFLink_CloseWebLinks must be called to release resources. +FPDF_PAGELINK FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page, +) { + return _FPDFLink_LoadWebLinks(text_page, +); +} + +late final _FPDFLink_LoadWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_LoadWebLinks'); +late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction(); + +/// Function: FPDFLink_CountWebLinks +/// Count number of detected web links. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// Number of detected web links. +int FPDFLink_CountWebLinks(FPDF_PAGELINK link_page, +) { + return _FPDFLink_CountWebLinks(link_page, +); +} + +late final _FPDFLink_CountWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountWebLinks'); +late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); + +/// Function: FPDFLink_GetURL +/// Fetch the URL information for a detected web link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// buffer - A unicode buffer for the result. +/// buflen - Number of 16-bit code units (not bytes) for the +/// buffer, including an additional terminator. +/// Return Value: +/// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit +/// code units (not bytes) needed to buffer the result (an additional +/// terminator is included in this count). +/// Otherwise, copy the result into |buffer|, truncating at |buflen| if +/// the result is too large to fit, and return the number of 16-bit code +/// units actually copied into the buffer (the additional terminator is +/// also included in this count). +/// If |link_index| does not correspond to a valid link, then the result +/// is an empty string. +int FPDFLink_GetURL(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFLink_GetURL(link_page, +link_index, +buffer, +buflen, +); +} + +late final _FPDFLink_GetURLPtr = _lookup< + ffi.NativeFunction , ffi.Int )>>('FPDFLink_GetURL'); +late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction , int )>(); + +/// Function: FPDFLink_CountRects +/// Count number of rectangular areas for the link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// Return Value: +/// Number of rectangular areas for the link. If |link_index| does +/// not correspond to a valid link, then 0 is returned. +int FPDFLink_CountRects(FPDF_PAGELINK link_page, +int link_index, +) { + return _FPDFLink_CountRects(link_page, +link_index, +); +} + +late final _FPDFLink_CountRectsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountRects'); +late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); + +/// Function: FPDFLink_GetRect +/// Fetch the boundaries of a rectangle for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// rect_index - Zero-based index for a rectangle. +/// left - Pointer to a double value receiving the rectangle +/// left boundary. +/// top - Pointer to a double value receiving the rectangle +/// top boundary. +/// right - Pointer to a double value receiving the rectangle +/// right boundary. +/// bottom - Pointer to a double value receiving the rectangle +/// bottom boundary. +/// Return Value: +/// On success, return TRUE and fill in |left|, |top|, |right|, and +/// |bottom|. If |link_page| is invalid or if |link_index| does not +/// correspond to a valid link, then return FALSE, and the out +/// parameters remain unmodified. +int FPDFLink_GetRect(FPDF_PAGELINK link_page, +int link_index, +int rect_index, +ffi.Pointer left, +ffi.Pointer top, +ffi.Pointer right, +ffi.Pointer bottom, +) { + return _FPDFLink_GetRect(link_page, +link_index, +rect_index, +left, +top, +right, +bottom, +); +} + +late final _FPDFLink_GetRectPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFLink_GetRect'); +late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDFLink_GetTextRange +/// Fetch the start char index and char count for a link. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// link_index - Zero-based index for the link. +/// start_char_index - pointer to int receiving the start char index +/// char_count - pointer to int receiving the char count +/// Return Value: +/// On success, return TRUE and fill in |start_char_index| and +/// |char_count|. if |link_page| is invalid or if |link_index| does +/// not correspond to a valid link, then return FALSE and the out +/// parameters remain unmodified. +int FPDFLink_GetTextRange(FPDF_PAGELINK link_page, +int link_index, +ffi.Pointer start_char_index, +ffi.Pointer char_count, +) { + return _FPDFLink_GetTextRange(link_page, +link_index, +start_char_index, +char_count, +); +} + +late final _FPDFLink_GetTextRangePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_GetTextRange'); +late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDFLink_CloseWebLinks +/// Release resources used by weblink feature. +/// Parameters: +/// link_page - Handle returned by FPDFLink_LoadWebLinks. +/// Return Value: +/// None. +void FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page, +) { + return _FPDFLink_CloseWebLinks(link_page, +); +} + +late final _FPDFLink_CloseWebLinksPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CloseWebLinks'); +late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); + +/// Get the character index in |text_page| internal character list. +/// +/// text_page - a text page information structure. +/// nTextIndex - index of the text returned from FPDFText_GetText(). +/// +/// Returns the index of the character in internal character list. -1 for error. +int FPDFText_GetCharIndexFromTextIndex(FPDF_TEXTPAGE text_page, +int nTextIndex, +) { + return _FPDFText_GetCharIndexFromTextIndex(text_page, +nTextIndex, +); +} + +late final _FPDFText_GetCharIndexFromTextIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetCharIndexFromTextIndex'); +late final _FPDFText_GetCharIndexFromTextIndex = _FPDFText_GetCharIndexFromTextIndexPtr.asFunction(); + +/// Get the text index in |text_page| internal character list. +/// +/// text_page - a text page information structure. +/// nCharIndex - index of the character in internal character list. +/// +/// Returns the index of the text returned from FPDFText_GetText(). -1 for error. +int FPDFText_GetTextIndexFromCharIndex(FPDF_TEXTPAGE text_page, +int nCharIndex, +) { + return _FPDFText_GetTextIndexFromCharIndex(text_page, +nCharIndex, +); +} + +late final _FPDFText_GetTextIndexFromCharIndexPtr = _lookup< + ffi.NativeFunction>('FPDFText_GetTextIndexFromCharIndex'); +late final _FPDFText_GetTextIndexFromCharIndex = _FPDFText_GetTextIndexFromCharIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_RenderPageBitmapWithColorScheme_Start +/// Start to render page contents to a device independent bitmap +/// progressively with a specified color scheme for the content. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handle can be created by +/// FPDFBitmap_Create function. +/// page - Handle to the page as returned by FPDF_LoadPage +/// function. +/// start_x - Left pixel position of the display area in the +/// bitmap coordinate. +/// start_y - Top pixel position of the display area in the +/// bitmap coordinate. +/// size_x - Horizontal size (in pixels) for displaying the +/// page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 +/// degrees clockwise), 2 (rotated 180 degrees), +/// 3 (rotated 90 degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined in fpdfview.h. With FPDF_ANNOT flag, it +/// renders all annotations that does not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// color_scheme - Color scheme to be used in rendering the |page|. +/// If null, this function will work similar to +/// FPDF_RenderPageBitmap_Start(). +/// pause - The IFSDK_PAUSE interface. A callback mechanism +/// allowing the page rendering process. +/// Return value: +/// Rendering Status. See flags for progressive process status for the +/// details. +int FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +ffi.Pointer color_scheme, +ffi.Pointer pause, +) { + return _FPDF_RenderPageBitmapWithColorScheme_Start(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +color_scheme, +pause, +); +} + +late final _FPDF_RenderPageBitmapWithColorScheme_StartPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDF_RenderPageBitmapWithColorScheme_Start'); +late final _FPDF_RenderPageBitmapWithColorScheme_Start = _FPDF_RenderPageBitmapWithColorScheme_StartPtr.asFunction , ffi.Pointer )>(); + +/// Function: FPDF_RenderPageBitmap_Start +/// Start to render page contents to a device independent bitmap +/// progressively. +/// Parameters: +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handle can be created by +/// FPDFBitmap_Create(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// start_x - Left pixel position of the display area in the +/// bitmap coordinates. +/// start_y - Top pixel position of the display area in the bitmap +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees +/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 +/// degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined in fpdfview.h. With FPDF_ANNOT flag, it +/// renders all annotations that does not require +/// user-interaction, which are all annotations except +/// widget and popup annotations. +/// pause - The IFSDK_PAUSE interface.A callback mechanism +/// allowing the page rendering process +/// Return value: +/// Rendering Status. See flags for progressive process status for the +/// details. +int FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +ffi.Pointer pause, +) { + return _FPDF_RenderPageBitmap_Start(bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +pause, +); +} + +late final _FPDF_RenderPageBitmap_StartPtr = _lookup< + ffi.NativeFunction )>>('FPDF_RenderPageBitmap_Start'); +late final _FPDF_RenderPageBitmap_Start = _FPDF_RenderPageBitmap_StartPtr.asFunction )>(); + +/// Function: FPDF_RenderPage_Continue +/// Continue rendering a PDF page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// pause - The IFSDK_PAUSE interface (a callback mechanism +/// allowing the page rendering process to be paused +/// before it's finished). This can be NULL if you +/// don't want to pause. +/// Return value: +/// The rendering status. See flags for progressive process status for +/// the details. +int FPDF_RenderPage_Continue(FPDF_PAGE page, +ffi.Pointer pause, +) { + return _FPDF_RenderPage_Continue(page, +pause, +); +} + +late final _FPDF_RenderPage_ContinuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_RenderPage_Continue'); +late final _FPDF_RenderPage_Continue = _FPDF_RenderPage_ContinuePtr.asFunction )>(); + +/// Function: FPDF_RenderPage_Close +/// Release the resource allocate during page rendering. Need to be +/// called after finishing rendering or +/// cancel the rendering. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return value: +/// None. +void FPDF_RenderPage_Close(FPDF_PAGE page, +) { + return _FPDF_RenderPage_Close(page, +); +} + +late final _FPDF_RenderPage_ClosePtr = _lookup< + ffi.NativeFunction>('FPDF_RenderPage_Close'); +late final _FPDF_RenderPage_Close = _FPDF_RenderPage_ClosePtr.asFunction(); + +/// Create a new PDF document. +/// +/// Returns a handle to a new document, or NULL on failure. +FPDF_DOCUMENT FPDF_CreateNewDocument() { + return _FPDF_CreateNewDocument(); +} + +late final _FPDF_CreateNewDocumentPtr = _lookup< + ffi.NativeFunction>('FPDF_CreateNewDocument'); +late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); + +/// Create a new PDF page. +/// +/// document - handle to document. +/// page_index - suggested 0-based index of the page to create. If it is larger +/// than document's current last index(L), the created page index +/// is the next available index -- L+1. +/// width - the page width in points. +/// height - the page height in points. +/// +/// Returns the handle to the new page or NULL on failure. +/// +/// The page should be closed with FPDF_ClosePage() when finished as +/// with any other page in the document. +FPDF_PAGE FPDFPage_New(FPDF_DOCUMENT document, +int page_index, +double width, +double height, +) { + return _FPDFPage_New(document, +page_index, +width, +height, +); +} + +late final _FPDFPage_NewPtr = _lookup< + ffi.NativeFunction>('FPDFPage_New'); +late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction(); + +/// Delete the page at |page_index|. +/// +/// document - handle to document. +/// page_index - the index of the page to delete. +void FPDFPage_Delete(FPDF_DOCUMENT document, +int page_index, +) { + return _FPDFPage_Delete(document, +page_index, +); +} + +late final _FPDFPage_DeletePtr = _lookup< + ffi.NativeFunction>('FPDFPage_Delete'); +late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); + +/// Experimental API. +/// Move the given pages to a new index position. +/// +/// page_indices - the ordered list of pages to move. No duplicates allowed. +/// page_indices_len - the number of elements in |page_indices| +/// dest_page_index - the new index position to which the pages in +/// |page_indices| are moved. +/// +/// Returns TRUE on success. If it returns FALSE, the document may be left in an +/// indeterminate state. +/// +/// Example: The PDF document starts out with pages [A, B, C, D], with indices +/// [0, 1, 2, 3]. +/// +/// > Move(doc, [3, 2], 2, 1); // returns true +/// > // The document has pages [A, D, C, B]. +/// > +/// > Move(doc, [0, 4, 3], 3, 1); // returns false +/// > // Returned false because index 4 is out of range. +/// > +/// > Move(doc, [0, 3, 1], 3, 2); // returns false +/// > // Returned false because index 2 is out of range for 3 page indices. +/// > +/// > Move(doc, [2, 2], 2, 0); // returns false +/// > // Returned false because [2, 2] contains duplicates. +int FPDF_MovePages(FPDF_DOCUMENT document, +ffi.Pointer page_indices, +int page_indices_len, +int dest_page_index, +) { + return _FPDF_MovePages(document, +page_indices, +page_indices_len, +dest_page_index, +); +} + +late final _FPDF_MovePagesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_MovePages'); +late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction , int , int )>(); + +/// Get the rotation of |page|. +/// +/// page - handle to a page +/// +/// Returns one of the following indicating the page rotation: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +int FPDFPage_GetRotation(FPDF_PAGE page, +) { + return _FPDFPage_GetRotation(page, +); +} + +late final _FPDFPage_GetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetRotation'); +late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); + +/// Set rotation for |page|. +/// +/// page - handle to a page. +/// rotate - the rotation value, one of: +/// 0 - No rotation. +/// 1 - Rotated 90 degrees clockwise. +/// 2 - Rotated 180 degrees clockwise. +/// 3 - Rotated 270 degrees clockwise. +void FPDFPage_SetRotation(FPDF_PAGE page, +int rotate, +) { + return _FPDFPage_SetRotation(page, +rotate, +); +} + +late final _FPDFPage_SetRotationPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetRotation'); +late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); + +/// Insert |page_object| into |page|. +/// +/// page - handle to a page +/// page_object - handle to a page object. The |page_object| will be +/// automatically freed. +void FPDFPage_InsertObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFPage_InsertObject(page, +page_object, +); +} + +late final _FPDFPage_InsertObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObject'); +late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); + +/// Insert |page_object| into |page| at the specified |index|. +/// +/// page - handle to a page +/// page_object - handle to a page object as previously obtained by +/// FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). Ownership of the object +/// is transferred back to PDFium. +/// index - the index position to insert the object at. If index equals +/// the current object count, the object will be appended to the +/// end. If index is greater than the object count, the function +/// will fail and return false. +/// +/// Returns true if successful. +int FPDFPage_InsertObjectAtIndex(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +int index, +) { + return _FPDFPage_InsertObjectAtIndex(page, +page_object, +index, +); +} + +late final _FPDFPage_InsertObjectAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertObjectAtIndex'); +late final _FPDFPage_InsertObjectAtIndex = _FPDFPage_InsertObjectAtIndexPtr.asFunction(); + +/// Experimental API. +/// Remove |page_object| from |page|. +/// +/// page - handle to a page +/// page_object - handle to a page object to be removed. +/// +/// Returns TRUE on success. +/// +/// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free +/// it. +/// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all +/// FPDF_TEXTPAGE handles for |page| are no longer valid. +int FPDFPage_RemoveObject(FPDF_PAGE page, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFPage_RemoveObject(page, +page_object, +); +} + +late final _FPDFPage_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveObject'); +late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction(); + +/// Get number of page objects inside |page|. +/// +/// page - handle to a page. +/// +/// Returns the number of objects in |page|. +int FPDFPage_CountObjects(FPDF_PAGE page, +) { + return _FPDFPage_CountObjects(page, +); +} + +late final _FPDFPage_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CountObjects'); +late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); + +/// Get object in |page| at |index|. +/// +/// page - handle to a page. +/// index - the index of a page object. +/// +/// Returns the handle to the page object, or NULL on failed. +FPDF_PAGEOBJECT FPDFPage_GetObject(FPDF_PAGE page, +int index, +) { + return _FPDFPage_GetObject(page, +index, +); +} + +late final _FPDFPage_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetObject'); +late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction(); + +/// Checks if |page| contains transparency. +/// +/// page - handle to a page. +/// +/// Returns TRUE if |page| contains transparency. +int FPDFPage_HasTransparency(FPDF_PAGE page, +) { + return _FPDFPage_HasTransparency(page, +); +} + +late final _FPDFPage_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasTransparency'); +late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); + +/// Generate the content of |page|. +/// +/// page - handle to a page. +/// +/// Returns TRUE on success. +/// +/// Before you save the page to a file, or reload the page, you must call +/// |FPDFPage_GenerateContent| or any changes to |page| will be lost. +int FPDFPage_GenerateContent(FPDF_PAGE page, +) { + return _FPDFPage_GenerateContent(page, +); +} + +late final _FPDFPage_GenerateContentPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GenerateContent'); +late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); + +/// Destroy |page_object| by releasing its resources. |page_object| must have +/// been created by FPDFPageObj_CreateNew{Path|Rect}() or +/// FPDFPageObj_New{Text|Image}Obj(). This function must be called on +/// newly-created objects if they are not added to a page through +/// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). +/// +/// page_object - handle to a page object. +void FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_Destroy(page_object, +); +} + +late final _FPDFPageObj_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Destroy'); +late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); + +/// Checks if |page_object| contains transparency. +/// +/// page_object - handle to a page object. +/// +/// Returns TRUE if |page_object| contains transparency. +int FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_HasTransparency(page_object, +); +} + +late final _FPDFPageObj_HasTransparencyPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_HasTransparency'); +late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr.asFunction(); + +/// Get type of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on +/// error. +int FPDFPageObj_GetType(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetType(page_object, +); +} + +late final _FPDFPageObj_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetType'); +late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); + +/// Experimental API. +/// Gets active state for |page_object| within page. +/// +/// page_object - handle to a page object. +/// active - pointer to variable that will receive if the page object is +/// active. This is a required parameter. Not filled if FALSE +/// is returned. +/// +/// For page objects where |active| is filled with FALSE, the |page_object| is +/// treated as if it wasn't in the document even though it is still held +/// internally. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_GetIsActive(FPDF_PAGEOBJECT page_object, +ffi.Pointer active, +) { + return _FPDFPageObj_GetIsActive(page_object, +active, +); +} + +late final _FPDFPageObj_GetIsActivePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetIsActive'); +late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction )>(); + +/// Experimental API. +/// Sets if |page_object| is active within page. +/// +/// page_object - handle to a page object. +/// active - a boolean specifying if the object is active. +/// +/// Returns TRUE on success. +/// +/// Page objects all start in the active state by default, and remain in that +/// state unless this function is called. +/// +/// When |active| is false, this makes the |page_object| be treated as if it +/// wasn't in the document even though it is still held internally. +int FPDFPageObj_SetIsActive(FPDF_PAGEOBJECT page_object, +int active, +) { + return _FPDFPageObj_SetIsActive(page_object, +active, +); +} + +late final _FPDFPageObj_SetIsActivePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetIsActive'); +late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction(); + +/// Transform |page_object| by the given matrix. +/// +/// page_object - handle to a page object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page_object|. +void FPDFPageObj_Transform(FPDF_PAGEOBJECT page_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPageObj_Transform(page_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPageObj_TransformPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_Transform'); +late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction(); + +/// Experimental API. +/// Transform |page_object| by the given matrix. +/// +/// page_object - handle to a page object. +/// matrix - the transform matrix. +/// +/// Returns TRUE on success. +/// +/// This can be used to scale, rotate, shear and translate the |page_object|. +/// It is an improved version of FPDFPageObj_Transform() that does not do +/// unnecessary double to float conversions, and only uses 1 parameter for the +/// matrix. It also returns whether the operation succeeded or not. +int FPDFPageObj_TransformF(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_TransformF(page_object, +matrix, +); +} + +late final _FPDFPageObj_TransformFPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_TransformF'); +late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction )>(); + +/// Experimental API. +/// Get the transform matrix of a page object. +/// +/// page_object - handle to a page object. +/// matrix - pointer to struct to receive the matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and used to scale, rotate, shear and translate the page object. +/// +/// For page objects outside form objects, the matrix values are relative to the +/// page that contains it. +/// For page objects inside form objects, the matrix values are relative to the +/// form that contains it. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_GetMatrix(page_object, +matrix, +); +} + +late final _FPDFPageObj_GetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetMatrix'); +late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction )>(); + +/// Experimental API. +/// Set the transform matrix of a page object. +/// +/// page_object - handle to a page object. +/// matrix - pointer to struct with the matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the page object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, +ffi.Pointer matrix, +) { + return _FPDFPageObj_SetMatrix(page_object, +matrix, +); +} + +late final _FPDFPageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_SetMatrix'); +late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction )>(); + +/// Transform all annotations in |page|. +/// +/// page - handle to a page. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |page| annotations. +void FPDFPage_TransformAnnots(FPDF_PAGE page, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPage_TransformAnnots(page, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPage_TransformAnnotsPtr = _lookup< + ffi.NativeFunction>('FPDFPage_TransformAnnots'); +late final _FPDFPage_TransformAnnots = _FPDFPage_TransformAnnotsPtr.asFunction(); + +/// Create a new image object. +/// +/// document - handle to a document. +/// +/// Returns a handle to a new image object. +FPDF_PAGEOBJECT FPDFPageObj_NewImageObj(FPDF_DOCUMENT document, +) { + return _FPDFPageObj_NewImageObj(document, +); +} + +late final _FPDFPageObj_NewImageObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewImageObj'); +late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction(); + +/// Experimental API. +/// Get the marked content ID for the object. +/// +/// page_object - handle to a page object. +/// +/// Returns the page object's marked content ID, or -1 on error. +int FPDFPageObj_GetMarkedContentID(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetMarkedContentID(page_object, +); +} + +late final _FPDFPageObj_GetMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMarkedContentID'); +late final _FPDFPageObj_GetMarkedContentID = _FPDFPageObj_GetMarkedContentIDPtr.asFunction(); + +/// Experimental API. +/// Get number of content marks in |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the number of content marks in |page_object|, or -1 in case of +/// failure. +int FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_CountMarks(page_object, +); +} + +late final _FPDFPageObj_CountMarksPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CountMarks'); +late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); + +/// Experimental API. +/// Get content mark in |page_object| at |index|. +/// +/// page_object - handle to a page object. +/// index - the index of a page object. +/// +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, +int index, +) { + return _FPDFPageObj_GetMark(page_object, +index, +); +} + +late final _FPDFPageObj_GetMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetMark'); +late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction(); + +/// Experimental API. +/// Add a new content mark to a |page_object|. +/// +/// page_object - handle to a page object. +/// name - the name (tag) of the mark. +/// +/// Returns the handle to the content mark, or NULL on failure. The handle is +/// still owned by the library, and it should not be freed directly. It becomes +/// invalid if the page object is destroyed, either directly or indirectly by +/// unloading the page. +FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING name, +) { + return _FPDFPageObj_AddMark(page_object, +name, +); +} + +late final _FPDFPageObj_AddMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_AddMark'); +late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction(); + +/// Experimental API. +/// Removes a content |mark| from a |page_object|. +/// The mark handle will be invalid after the removal. +/// +/// page_object - handle to a page object. +/// mark - handle to a content mark in that object to remove. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObj_RemoveMark(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +) { + return _FPDFPageObj_RemoveMark(page_object, +mark, +); +} + +late final _FPDFPageObj_RemoveMarkPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_RemoveMark'); +late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction(); + +/// Experimental API. +/// Get the name of a content mark. +/// +/// mark - handle to a content mark. +/// buffer - buffer for holding the returned name in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the name. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the operation succeeded, FALSE if it failed. +int FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetName(mark, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetName'); +late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the number of key/value pair parameters in |mark|. +/// +/// mark - handle to a content mark. +/// +/// Returns the number of key/value pair parameters |mark|, or -1 in case of +/// failure. +int FPDFPageObjMark_CountParams(FPDF_PAGEOBJECTMARK mark, +) { + return _FPDFPageObjMark_CountParams(mark, +); +} + +late final _FPDFPageObjMark_CountParamsPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_CountParams'); +late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr.asFunction(); + +/// Experimental API. +/// Get the key of a property in a content mark. +/// +/// mark - handle to a content mark. +/// index - index of the property. +/// buffer - buffer for holding the returned key in UTF-16LE. This is only +/// modified if |buflen| is large enough to store the key. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the operation was successful, FALSE otherwise. +int FPDFPageObjMark_GetParamKey(FPDF_PAGEOBJECTMARK mark, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamKey(mark, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamKey'); +late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the type of the value of a property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// +/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. +int FPDFPageObjMark_GetParamValueType(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +) { + return _FPDFPageObjMark_GetParamValueType(mark, +key, +); +} + +late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_GetParamValueType'); +late final _FPDFPageObjMark_GetParamValueType = _FPDFPageObjMark_GetParamValueTypePtr.asFunction(); + +/// Experimental API. +/// Get the value of a number property in a content mark by key as int. +/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER +/// for this property. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// out_value - pointer to variable that will receive the value. Not filled if +/// false is returned. +/// +/// Returns TRUE if the key maps to a number value, FALSE otherwise. +int FPDFPageObjMark_GetParamIntValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer out_value, +) { + return _FPDFPageObjMark_GetParamIntValue(mark, +key, +out_value, +); +} + +late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObjMark_GetParamIntValue'); +late final _FPDFPageObjMark_GetParamIntValue = _FPDFPageObjMark_GetParamIntValuePtr.asFunction )>(); + +/// Experimental API. +/// Get the value of a number property in a content mark by key as float. +/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER +/// for this property. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// out_value - pointer to variable that will receive the value. Not filled if +/// false is returned. +/// +/// Returns TRUE if the key maps to a number value, FALSE otherwise. +int FPDFPageObjMark_GetParamFloatValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer out_value, +) { + return _FPDFPageObjMark_GetParamFloatValue(mark, +key, +out_value, +); +} + +late final _FPDFPageObjMark_GetParamFloatValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObjMark_GetParamFloatValue'); +late final _FPDFPageObjMark_GetParamFloatValue = _FPDFPageObjMark_GetParamFloatValuePtr.asFunction )>(); + +/// Experimental API. +/// Get the value of a string property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value in UTF-16LE. This is +/// only modified if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamStringValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamStringValue(mark, +key, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamStringValue'); +late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the value of a blob property in a content mark by key. +/// +/// mark - handle to a content mark. +/// key - string key of the property. +/// buffer - buffer for holding the returned value. This is only modified +/// if |buflen| is large enough to store the value. +/// Optional, pass null to just retrieve the size of the buffer +/// needed. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to variable that will receive the minimum buffer size +/// in bytes to contain the name. This is a required parameter. +/// Not filled if FALSE is returned. +/// +/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. +int FPDFPageObjMark_GetParamBlobValue(FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFPageObjMark_GetParamBlobValue(mark, +key, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamBlobValue'); +late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Set the value of an int property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - int value to set. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetIntParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +int value, +) { + return _FPDFPageObjMark_SetIntParam(document, +page_object, +mark, +key, +value, +); +} + +late final _FPDFPageObjMark_SetIntParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetIntParam'); +late final _FPDFPageObjMark_SetIntParam = _FPDFPageObjMark_SetIntParamPtr.asFunction(); + +/// Experimental API. +/// Set the value of a float property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - float value to set. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetFloatParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +double value, +) { + return _FPDFPageObjMark_SetFloatParam(document, +page_object, +mark, +key, +value, +); +} + +late final _FPDFPageObjMark_SetFloatParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetFloatParam'); +late final _FPDFPageObjMark_SetFloatParam = _FPDFPageObjMark_SetFloatParamPtr.asFunction(); + +/// Experimental API. +/// Set the value of a string property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - string value to set. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetStringParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +FPDF_BYTESTRING value, +) { + return _FPDFPageObjMark_SetStringParam(document, +page_object, +mark, +key, +value, +); +} + +late final _FPDFPageObjMark_SetStringParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_SetStringParam'); +late final _FPDFPageObjMark_SetStringParam = _FPDFPageObjMark_SetStringParamPtr.asFunction(); + +/// Experimental API. +/// Set the value of a blob property in a content mark by key. If a parameter +/// with key |key| exists, its value is set to |value|. Otherwise, it is added as +/// a new parameter. +/// +/// document - handle to the document. +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// value - pointer to blob value to set. +/// value_len - size in bytes of |value|. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_SetBlobParam(FPDF_DOCUMENT document, +FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +ffi.Pointer value, +int value_len, +) { + return _FPDFPageObjMark_SetBlobParam(document, +page_object, +mark, +key, +value, +value_len, +); +} + +late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPageObjMark_SetBlobParam'); +late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction , int )>(); + +/// Experimental API. +/// Removes a property from a content mark by key. +/// +/// page_object - handle to the page object with the mark. +/// mark - handle to a content mark. +/// key - string key of the property. +/// +/// Returns TRUE if the operation succeeded, FALSE otherwise. +int FPDFPageObjMark_RemoveParam(FPDF_PAGEOBJECT page_object, +FPDF_PAGEOBJECTMARK mark, +FPDF_BYTESTRING key, +) { + return _FPDFPageObjMark_RemoveParam(page_object, +mark, +key, +); +} + +late final _FPDFPageObjMark_RemoveParamPtr = _lookup< + ffi.NativeFunction>('FPDFPageObjMark_RemoveParam'); +late final _FPDFPageObjMark_RemoveParam = _FPDFPageObjMark_RemoveParamPtr.asFunction(); + +/// Load an image from a JPEG image file and then set it into |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. +/// +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. +int FPDFImageObj_LoadJpegFile(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, +) { + return _FPDFImageObj_LoadJpegFile(pages, +count, +image_object, +file_access, +); +} + +late final _FPDFImageObj_LoadJpegFilePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFile'); +late final _FPDFImageObj_LoadJpegFile = _FPDFImageObj_LoadJpegFilePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); + +/// Load an image from a JPEG image file and then set it into |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// file_access - file access handler which specifies the JPEG image file. +/// +/// Returns TRUE on success. +/// +/// The image object might already have an associated image, which is shared and +/// cached by the loaded pages. In that case, we need to clear the cached image +/// for all the loaded pages. Pass |pages| and page count (|count|) to this API +/// to clear the image cache. If the image is not previously shared, or NULL is a +/// valid |pages| value. This function loads the JPEG image inline, so the image +/// content is copied to the file. This allows |file_access| and its associated +/// data to be deleted after this function returns. +int FPDFImageObj_LoadJpegFileInline(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +ffi.Pointer file_access, +) { + return _FPDFImageObj_LoadJpegFileInline(pages, +count, +image_object, +file_access, +); +} + +late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFileInline'); +late final _FPDFImageObj_LoadJpegFileInline = _FPDFImageObj_LoadJpegFileInlinePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); + +/// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. +/// +/// Set the transform matrix of |image_object|. +/// +/// image_object - handle to an image object. +/// a - matrix value. +/// b - matrix value. +/// c - matrix value. +/// d - matrix value. +/// e - matrix value. +/// f - matrix value. +/// +/// The matrix is composed as: +/// |a c e| +/// |b d f| +/// and can be used to scale, rotate, shear and translate the |image_object|. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFImageObj_SetMatrix(image_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFImageObj_SetMatrixPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_SetMatrix'); +late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction(); + +/// Set |bitmap| to |image_object|. +/// +/// pages - pointer to the start of all loaded pages, may be NULL. +/// count - number of |pages|, may be 0. +/// image_object - handle to an image object. +/// bitmap - handle of the bitmap. +/// +/// Returns TRUE on success. +int FPDFImageObj_SetBitmap(ffi.Pointer pages, +int count, +FPDF_PAGEOBJECT image_object, +FPDF_BITMAP bitmap, +) { + return _FPDFImageObj_SetBitmap(pages, +count, +image_object, +bitmap, +); +} + +late final _FPDFImageObj_SetBitmapPtr = _lookup< + ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , FPDF_BITMAP )>>('FPDFImageObj_SetBitmap'); +late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction , int , FPDF_PAGEOBJECT , FPDF_BITMAP )>(); + +/// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only +/// operates on |image_object| and does not take the associated image mask into +/// account. It also ignores the matrix for |image_object|. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// image_object - handle to an image object. +/// +/// Returns the bitmap. +FPDF_BITMAP FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetBitmap(image_object, +); +} + +late final _FPDFImageObj_GetBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetBitmap'); +late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction(); + +/// Experimental API. +/// Get a bitmap rasterization of |image_object| that takes the image mask and +/// image matrix into account. To render correctly, the caller must provide the +/// |document| associated with |image_object|. If there is a |page| associated +/// with |image_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// document - handle to a document associated with |image_object|. +/// page - handle to an optional page associated with |image_object|. +/// image_object - handle to an image object. +/// +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetRenderedBitmap(document, +page, +image_object, +); +} + +late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetRenderedBitmap'); +late final _FPDFImageObj_GetRenderedBitmap = _FPDFImageObj_GetRenderedBitmapPtr.asFunction(); + +/// Get the decoded image data of |image_object|. The decoded data is the +/// uncompressed image data, i.e. the raw image data after having all filters +/// applied. |buffer| is only modified if |buflen| is longer than the length of +/// the decoded image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the decoded image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the decoded image data. +int FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageDataDecoded(image_object, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataDecoded'); +late final _FPDFImageObj_GetImageDataDecoded = _FPDFImageObj_GetImageDataDecodedPtr.asFunction , int )>(); + +/// Get the raw image data of |image_object|. The raw data is the image data as +/// stored in the PDF without applying any filters. |buffer| is only modified if +/// |buflen| is longer than the length of the raw image data. +/// +/// image_object - handle to an image object. +/// buffer - buffer for holding the raw image data. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the raw image data. +int FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageDataRaw(image_object, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageDataRawPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataRaw'); +late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr.asFunction , int )>(); + +/// Get the number of filters (i.e. decoders) of the image in |image_object|. +/// +/// image_object - handle to an image object. +/// +/// Returns the number of |image_object|'s filters. +int FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object, +) { + return _FPDFImageObj_GetImageFilterCount(image_object, +); +} + +late final _FPDFImageObj_GetImageFilterCountPtr = _lookup< + ffi.NativeFunction>('FPDFImageObj_GetImageFilterCount'); +late final _FPDFImageObj_GetImageFilterCount = _FPDFImageObj_GetImageFilterCountPtr.asFunction(); + +/// Get the filter at |index| of |image_object|'s list of filters. Note that the +/// filters need to be applied in order, i.e. the first filter should be applied +/// first, then the second, etc. |buffer| is only modified if |buflen| is longer +/// than the length of the filter string. +/// +/// image_object - handle to an image object. +/// index - the index of the filter requested. +/// buffer - buffer for holding filter string, encoded in UTF-8. +/// buflen - length of the buffer. +/// +/// Returns the length of the filter string. +int FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFImageObj_GetImageFilter(image_object, +index, +buffer, +buflen, +); +} + +late final _FPDFImageObj_GetImageFilterPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageFilter'); +late final _FPDFImageObj_GetImageFilter = _FPDFImageObj_GetImageFilterPtr.asFunction , int )>(); + +/// Get the image metadata of |image_object|, including dimension, DPI, bits per +/// pixel, and colorspace. If the |image_object| is not an image object or if it +/// does not have an image, then the return value will be false. Otherwise, +/// failure to retrieve any specific parameter would result in its value being 0. +/// +/// image_object - handle to an image object. +/// page - handle to the page that |image_object| is on. Required for +/// retrieving the image's bits per pixel and colorspace. +/// metadata - receives the image metadata; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer metadata, +) { + return _FPDFImageObj_GetImageMetadata(image_object, +page, +metadata, +); +} + +late final _FPDFImageObj_GetImageMetadataPtr = _lookup< + ffi.NativeFunction )>>('FPDFImageObj_GetImageMetadata'); +late final _FPDFImageObj_GetImageMetadata = _FPDFImageObj_GetImageMetadataPtr.asFunction )>(); + +/// Experimental API. +/// Get the image size in pixels. Faster method to get only image size. +/// +/// image_object - handle to an image object. +/// width - receives the image width in pixels; must not be NULL. +/// height - receives the image height in pixels; must not be NULL. +/// +/// Returns true if successful. +int FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, +ffi.Pointer width, +ffi.Pointer height, +) { + return _FPDFImageObj_GetImagePixelSize(image_object, +width, +height, +); +} + +late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFImageObj_GetImagePixelSize'); +late final _FPDFImageObj_GetImagePixelSize = _FPDFImageObj_GetImagePixelSizePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Get ICC profile decoded data of |image_object|. If the |image_object| is not +/// an image object or if it does not have an image, then the return value will +/// be false. It also returns false if the |image_object| has no ICC profile. +/// |buffer| is only modified if ICC profile exists and |buflen| is longer than +/// the length of the ICC profile decoded data. +/// +/// image_object - handle to an image object; must not be NULL. +/// page - handle to the page containing |image_object|; must not be +/// NULL. Required for retrieving the image's colorspace. +/// buffer - Buffer to receive ICC profile data; may be NULL if querying +/// required size via |out_buflen|. +/// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. +/// out_buflen - Pointer to receive the ICC profile data size in bytes; must +/// not be NULL. Will be set if this API returns true. +/// +/// Returns true if |out_buflen| is not null and an ICC profile exists for the +/// given |image_object|. +int FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFImageObj_GetIccProfileDataDecoded(image_object, +page, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFImageObj_GetIccProfileDataDecoded'); +late final _FPDFImageObj_GetIccProfileDataDecoded = _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction , int , ffi.Pointer )>(); + +/// Create a new path object at an initial position. +/// +/// x - initial horizontal position. +/// y - initial vertical position. +/// +/// Returns a handle to a new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath(double x, +double y, +) { + return _FPDFPageObj_CreateNewPath(x, +y, +); +} + +late final _FPDFPageObj_CreateNewPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewPath'); +late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr.asFunction(); + +/// Create a closed path consisting of a rectangle. +/// +/// x - horizontal position for the left boundary of the rectangle. +/// y - vertical position for the bottom boundary of the rectangle. +/// w - width of the rectangle. +/// h - height of the rectangle. +/// +/// Returns a handle to the new path object. +FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect(double x, +double y, +double w, +double h, +) { + return _FPDFPageObj_CreateNewRect(x, +y, +w, +h, +); +} + +late final _FPDFPageObj_CreateNewRectPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateNewRect'); +late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr.asFunction(); + +/// Get the bounding box of |page_object|. +/// +/// page_object - handle to a page object. +/// left - pointer where the left coordinate will be stored +/// bottom - pointer where the bottom coordinate will be stored +/// right - pointer where the right coordinate will be stored +/// top - pointer where the top coordinate will be stored +/// +/// On success, returns TRUE and fills in the 4 coordinates. +int FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPageObj_GetBounds(page_object, +left, +bottom, +right, +top, +); +} + +late final _FPDFPageObj_GetBoundsPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetBounds'); +late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the quad points that bounds |page_object|. +/// +/// page_object - handle to a page object. +/// quad_points - pointer where the quadrilateral points will be stored. +/// +/// On success, returns TRUE and fills in |quad_points|. +/// +/// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page +/// object. When the object is rotated by a non-multiple of 90 degrees, this API +/// returns a tighter bound that cannot be represented with just the 4 sides of +/// a rectangle. +/// +/// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and +/// FPDF_PAGEOBJ_IMAGE. +int FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, +ffi.Pointer quad_points, +) { + return _FPDFPageObj_GetRotatedBounds(page_object, +quad_points, +); +} + +late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetRotatedBounds'); +late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr.asFunction )>(); + +/// Set the blend mode of |page_object|. +/// +/// page_object - handle to a page object. +/// blend_mode - string containing the blend mode. +/// +/// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, +/// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, +/// Overlay, Saturation, Screen, SoftLight +void FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, +FPDF_BYTESTRING blend_mode, +) { + return _FPDFPageObj_SetBlendMode(page_object, +blend_mode, +); +} + +late final _FPDFPageObj_SetBlendModePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetBlendMode'); +late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr.asFunction(); + +/// Set the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's stroke color. +/// G - the green component for the object's stroke color. +/// B - the blue component for the object's stroke color. +/// A - the stroke alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, +) { + return _FPDFPageObj_SetStrokeColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_SetStrokeColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeColor'); +late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr.asFunction(); + +/// Get the stroke RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the path stroke color. +/// G - the green component of the object's stroke color. +/// B - the blue component of the object's stroke color. +/// A - the stroke alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetStrokeColor(FPDF_PAGEOBJECT page_object, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFPageObj_GetStrokeColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_GetStrokeColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetStrokeColor'); +late final _FPDFPageObj_GetStrokeColor = _FPDFPageObj_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Set the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_SetStrokeWidth(FPDF_PAGEOBJECT page_object, +double width, +) { + return _FPDFPageObj_SetStrokeWidth(page_object, +width, +); +} + +late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetStrokeWidth'); +late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr.asFunction(); + +/// Get the stroke width of a page object. +/// +/// path - the handle to the page object. +/// width - the width of the stroke. +/// +/// Returns TRUE on success +int FPDFPageObj_GetStrokeWidth(FPDF_PAGEOBJECT page_object, +ffi.Pointer width, +) { + return _FPDFPageObj_GetStrokeWidth(page_object, +width, +); +} + +late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetStrokeWidth'); +late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr.asFunction )>(); + +/// Get the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line join, or -1 on failure. +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_GetLineJoin(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetLineJoin(page_object, +); +} + +late final _FPDFPageObj_GetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineJoin'); +late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); + +/// Set the line join of |page_object|. +/// +/// page_object - handle to a page object. +/// line_join - line join +/// +/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, +/// FPDF_LINEJOIN_BEVEL +int FPDFPageObj_SetLineJoin(FPDF_PAGEOBJECT page_object, +int line_join, +) { + return _FPDFPageObj_SetLineJoin(page_object, +line_join, +); +} + +late final _FPDFPageObj_SetLineJoinPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineJoin'); +late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction(); + +/// Get the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line cap, or -1 on failure. +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_GetLineCap(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetLineCap(page_object, +); +} + +late final _FPDFPageObj_GetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetLineCap'); +late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); + +/// Set the line cap of |page_object|. +/// +/// page_object - handle to a page object. +/// line_cap - line cap +/// +/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, +/// FPDF_LINECAP_PROJECTING_SQUARE +int FPDFPageObj_SetLineCap(FPDF_PAGEOBJECT page_object, +int line_cap, +) { + return _FPDFPageObj_SetLineCap(page_object, +line_cap, +); +} + +late final _FPDFPageObj_SetLineCapPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetLineCap'); +late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction(); + +/// Set the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component for the object's fill color. +/// G - the green component for the object's fill color. +/// B - the blue component for the object's fill color. +/// A - the fill alpha for the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, +int R, +int G, +int B, +int A, +) { + return _FPDFPageObj_SetFillColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_SetFillColorPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetFillColor'); +late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr.asFunction(); + +/// Get the fill RGBA of a page object. Range of values: 0 - 255. +/// +/// page_object - the handle to the page object. +/// R - the red component of the object's fill color. +/// G - the green component of the object's fill color. +/// B - the blue component of the object's fill color. +/// A - the fill alpha of the object. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetFillColor(FPDF_PAGEOBJECT page_object, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFPageObj_GetFillColor(page_object, +R, +G, +B, +A, +); +} + +late final _FPDFPageObj_GetFillColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetFillColor'); +late final _FPDFPageObj_GetFillColor = _FPDFPageObj_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the line dash |phase| of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - pointer where the dashing phase will be stored. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, +ffi.Pointer phase, +) { + return _FPDFPageObj_GetDashPhase(page_object, +phase, +); +} + +late final _FPDFPageObj_GetDashPhasePtr = _lookup< + ffi.NativeFunction )>>('FPDFPageObj_GetDashPhase'); +late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr.asFunction )>(); + +/// Experimental API. +/// Set the line dash phase of |page_object|. +/// +/// page_object - handle to a page object. +/// phase - line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, +double phase, +) { + return _FPDFPageObj_SetDashPhase(page_object, +phase, +); +} + +late final _FPDFPageObj_SetDashPhasePtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_SetDashPhase'); +late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr.asFunction(); + +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// +/// Returns the line dash array size or -1 on failure. +int FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetDashCount(page_object, +); +} + +late final _FPDFPageObj_GetDashCountPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetDashCount'); +late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); + +/// Experimental API. +/// Get the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - pointer where the dashing array will be stored. +/// dash_count - number of elements in |dash_array|. +/// +/// Returns TRUE on success. +int FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, +) { + return _FPDFPageObj_GetDashArray(page_object, +dash_array, +dash_count, +); +} + +late final _FPDFPageObj_GetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFPageObj_GetDashArray'); +late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr.asFunction , int )>(); + +/// Experimental API. +/// Set the line dash array of |page_object|. +/// +/// page_object - handle to a page object. +/// dash_array - the dash array. +/// dash_count - number of elements in |dash_array|. +/// phase - the line dash phase. +/// +/// Returns TRUE on success. +int FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, +ffi.Pointer dash_array, +int dash_count, +double phase, +) { + return _FPDFPageObj_SetDashArray(page_object, +dash_array, +dash_count, +phase, +); +} + +late final _FPDFPageObj_SetDashArrayPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Float )>>('FPDFPageObj_SetDashArray'); +late final _FPDFPageObj_SetDashArray = _FPDFPageObj_SetDashArrayPtr.asFunction , int , double )>(); + +/// Get number of segments inside |path|. +/// +/// path - handle to a path. +/// +/// A segment is a command, created by e.g. FPDFPath_MoveTo(), +/// FPDFPath_LineTo() or FPDFPath_BezierTo(). +/// +/// Returns the number of objects in |path| or -1 on failure. +int FPDFPath_CountSegments(FPDF_PAGEOBJECT path, +) { + return _FPDFPath_CountSegments(path, +); +} + +late final _FPDFPath_CountSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFPath_CountSegments'); +late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); + +/// Get segment in |path| at |index|. +/// +/// path - handle to a path. +/// index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, +int index, +) { + return _FPDFPath_GetPathSegment(path, +index, +); +} + +late final _FPDFPath_GetPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFPath_GetPathSegment'); +late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction(); + +/// Get coordinates of |segment|. +/// +/// segment - handle to a segment. +/// x - the horizontal position of the segment. +/// y - the vertical position of the segment. +/// +/// Returns TRUE on success, otherwise |x| and |y| is not set. +int FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, +ffi.Pointer x, +ffi.Pointer y, +) { + return _FPDFPathSegment_GetPoint(segment, +x, +y, +); +} + +late final _FPDFPathSegment_GetPointPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPathSegment_GetPoint'); +late final _FPDFPathSegment_GetPoint = _FPDFPathSegment_GetPointPtr.asFunction , ffi.Pointer )>(); + +/// Get type of |segment|. +/// +/// segment - handle to a segment. +/// +/// Returns one of the FPDF_SEGMENT_* values on success, +/// FPDF_SEGMENT_UNKNOWN on error. +int FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment, +) { + return _FPDFPathSegment_GetType(segment, +); +} + +late final _FPDFPathSegment_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetType'); +late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); + +/// Gets if the |segment| closes the current subpath of a given path. +/// +/// segment - handle to a segment. +/// +/// Returns close flag for non-NULL segment, FALSE otherwise. +int FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment, +) { + return _FPDFPathSegment_GetClose(segment, +); +} + +late final _FPDFPathSegment_GetClosePtr = _lookup< + ffi.NativeFunction>('FPDFPathSegment_GetClose'); +late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); + +/// Move a path's current point. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new current point. +/// y - the vertical position of the new current point. +/// +/// Note that no line will be created between the previous current point and the +/// new one. +/// +/// Returns TRUE on success +int FPDFPath_MoveTo(FPDF_PAGEOBJECT path, +double x, +double y, +) { + return _FPDFPath_MoveTo(path, +x, +y, +); +} + +late final _FPDFPath_MoveToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_MoveTo'); +late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction(); + +/// Add a line between the current point and a new point in the path. +/// +/// path - the handle to the path object. +/// x - the horizontal position of the new point. +/// y - the vertical position of the new point. +/// +/// The path's current point is changed to (x, y). +/// +/// Returns TRUE on success +int FPDFPath_LineTo(FPDF_PAGEOBJECT path, +double x, +double y, +) { + return _FPDFPath_LineTo(path, +x, +y, +); +} + +late final _FPDFPath_LineToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_LineTo'); +late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction(); + +/// Add a cubic Bezier curve to the given path, starting at the current point. +/// +/// path - the handle to the path object. +/// x1 - the horizontal position of the first Bezier control point. +/// y1 - the vertical position of the first Bezier control point. +/// x2 - the horizontal position of the second Bezier control point. +/// y2 - the vertical position of the second Bezier control point. +/// x3 - the horizontal position of the ending point of the Bezier curve. +/// y3 - the vertical position of the ending point of the Bezier curve. +/// +/// Returns TRUE on success +int FPDFPath_BezierTo(FPDF_PAGEOBJECT path, +double x1, +double y1, +double x2, +double y2, +double x3, +double y3, +) { + return _FPDFPath_BezierTo(path, +x1, +y1, +x2, +y2, +x3, +y3, +); +} + +late final _FPDFPath_BezierToPtr = _lookup< + ffi.NativeFunction>('FPDFPath_BezierTo'); +late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction(); + +/// Close the current subpath of a given path. +/// +/// path - the handle to the path object. +/// +/// This will add a line between the current point and the initial point of the +/// subpath, thus terminating the current subpath. +/// +/// Returns TRUE on success +int FPDFPath_Close(FPDF_PAGEOBJECT path, +) { + return _FPDFPath_Close(path, +); +} + +late final _FPDFPath_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFPath_Close'); +late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); + +/// Set the drawing mode of a path. +/// +/// path - the handle to the path object. +/// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path should be stroked or not. +/// +/// Returns TRUE on success +int FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, +int fillmode, +int stroke, +) { + return _FPDFPath_SetDrawMode(path, +fillmode, +stroke, +); +} + +late final _FPDFPath_SetDrawModePtr = _lookup< + ffi.NativeFunction>('FPDFPath_SetDrawMode'); +late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction(); + +/// Get the drawing mode of a path. +/// +/// path - the handle to the path object. +/// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. +/// stroke - a boolean specifying if the path is stroked or not. +/// +/// Returns TRUE on success +int FPDFPath_GetDrawMode(FPDF_PAGEOBJECT path, +ffi.Pointer fillmode, +ffi.Pointer stroke, +) { + return _FPDFPath_GetDrawMode(path, +fillmode, +stroke, +); +} + +late final _FPDFPath_GetDrawModePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPath_GetDrawMode'); +late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction , ffi.Pointer )>(); + +/// Create a new text object using one of the standard PDF fonts. +/// +/// document - handle to the document. +/// font - string containing the font name, without spaces. +/// font_size - the font size for the new text object. +/// +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, +double font_size, +) { + return _FPDFPageObj_NewTextObj(document, +font, +font_size, +); +} + +late final _FPDFPageObj_NewTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_NewTextObj'); +late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction(); + +/// Set the text for a text object. If it had text, it will be replaced. +/// +/// text_object - handle to the text object. +/// text - the UTF-16LE encoded string containing the text to be added. +/// +/// Returns TRUE on success +int FPDFText_SetText(FPDF_PAGEOBJECT text_object, +FPDF_WIDESTRING text, +) { + return _FPDFText_SetText(text_object, +text, +); +} + +late final _FPDFText_SetTextPtr = _lookup< + ffi.NativeFunction>('FPDFText_SetText'); +late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction(); + +/// Experimental API. +/// Set the text using charcodes for a text object. If it had text, it will be +/// replaced. +/// +/// text_object - handle to the text object. +/// charcodes - pointer to an array of charcodes to be added. +/// count - number of elements in |charcodes|. +/// +/// Returns TRUE on success +int FPDFText_SetCharcodes(FPDF_PAGEOBJECT text_object, +ffi.Pointer charcodes, +int count, +) { + return _FPDFText_SetCharcodes(text_object, +charcodes, +count, +); +} + +late final _FPDFText_SetCharcodesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFText_SetCharcodes'); +late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction , int )>(); + +/// Returns a font object loaded from a stream of data. The font is loaded +/// into the document. Various font data structures, such as the ToUnicode data, +/// are auto-generated based on the inputs. +/// +/// document - handle to the document. +/// data - the stream of font data, which will be copied by the font object. +/// size - the size of the font data, in bytes. +/// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. +/// cid - a boolean specifying if the font is a CID font or not. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure +FPDF_FONT FPDFText_LoadFont(FPDF_DOCUMENT document, +ffi.Pointer data, +int size, +int font_type, +int cid, +) { + return _FPDFText_LoadFont(document, +data, +size, +font_type, +cid, +); +} + +late final _FPDFText_LoadFontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , ffi.Int , FPDF_BOOL )>>('FPDFText_LoadFont'); +late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction , int , int , int )>(); + +/// Experimental API. +/// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred +/// way of using font style is using a dash to separate the name from the style, +/// for example 'Helvetica-BoldItalic'. +/// +/// document - handle to the document. +/// font - string containing the font name, without spaces. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadStandardFont(FPDF_DOCUMENT document, +FPDF_BYTESTRING font, +) { + return _FPDFText_LoadStandardFont(document, +font, +); +} + +late final _FPDFText_LoadStandardFontPtr = _lookup< + ffi.NativeFunction>('FPDFText_LoadStandardFont'); +late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr.asFunction(); + +/// Experimental API. +/// Returns a font object loaded from a stream of data for a type 2 CID font. The +/// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode +/// data and the CIDToGIDMap data are caller provided, instead of auto-generated. +/// +/// document - handle to the document. +/// font_data - the stream of font data, which will be copied by +/// the font object. +/// font_data_size - the size of the font data, in bytes. +/// to_unicode_cmap - the ToUnicode data. +/// cid_to_gid_map_data - the stream of CIDToGIDMap data. +/// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. +/// +/// The loaded font can be closed using FPDFFont_Close(). +/// +/// Returns NULL on failure. +FPDF_FONT FPDFText_LoadCidType2Font(FPDF_DOCUMENT document, +ffi.Pointer font_data, +int font_data_size, +FPDF_BYTESTRING to_unicode_cmap, +ffi.Pointer cid_to_gid_map_data, +int cid_to_gid_map_data_size, +) { + return _FPDFText_LoadCidType2Font(document, +font_data, +font_data_size, +to_unicode_cmap, +cid_to_gid_map_data, +cid_to_gid_map_data_size, +); +} + +late final _FPDFText_LoadCidType2FontPtr = _lookup< + ffi.NativeFunction , ffi.Uint32 , FPDF_BYTESTRING , ffi.Pointer , ffi.Uint32 )>>('FPDFText_LoadCidType2Font'); +late final _FPDFText_LoadCidType2Font = _FPDFText_LoadCidType2FontPtr.asFunction , int , FPDF_BYTESTRING , ffi.Pointer , int )>(); + +/// Get the font size of a text object. +/// +/// text - handle to a text. +/// size - pointer to the font size of the text object, measured in points +/// (about 1/72 inch) +/// +/// Returns TRUE on success. +int FPDFTextObj_GetFontSize(FPDF_PAGEOBJECT text, +ffi.Pointer size, +) { + return _FPDFTextObj_GetFontSize(text, +size, +); +} + +late final _FPDFTextObj_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFTextObj_GetFontSize'); +late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction )>(); + +/// Close a loaded PDF font. +/// +/// font - Handle to the loaded font. +void FPDFFont_Close(FPDF_FONT font, +) { + return _FPDFFont_Close(font, +); +} + +late final _FPDFFont_ClosePtr = _lookup< + ffi.NativeFunction>('FPDFFont_Close'); +late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); + +/// Create a new text object using a loaded font. +/// +/// document - handle to the document. +/// font - handle to the font object. +/// font_size - the font size for the new text object. +/// +/// Returns a handle to a new text object, or NULL on failure +FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, +FPDF_FONT font, +double font_size, +) { + return _FPDFPageObj_CreateTextObj(document, +font, +font_size, +); +} + +late final _FPDFPageObj_CreateTextObjPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_CreateTextObj'); +late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr.asFunction(); + +/// Get the text rendering mode of a text object. +/// +/// text - the handle to the text object. +/// +/// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, +/// FPDF_TEXTRENDERMODE_UNKNOWN on error. +FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode(FPDF_PAGEOBJECT text, +) { + return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode(text, +)); +} + +late final _FPDFTextObj_GetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetTextRenderMode'); +late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr.asFunction(); + +/// Experimental API. +/// Set the text rendering mode of a text object. +/// +/// text - the handle to the text object. +/// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to +/// FPDF_TEXTRENDERMODE_UNKNOWN). +/// +/// Returns TRUE on success. +DartFPDF_BOOL FPDFTextObj_SetTextRenderMode(FPDF_PAGEOBJECT text, +FPDF_TEXT_RENDERMODE render_mode, +) { + return _FPDFTextObj_SetTextRenderMode(text, +render_mode.value, +); +} + +late final _FPDFTextObj_SetTextRenderModePtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_SetTextRenderMode'); +late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr.asFunction(); + +/// Get the text of a text object. +/// +/// text_object - the handle to the text object. +/// text_page - the handle to the text page. +/// buffer - the address of a buffer that receives the text. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the text (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFTextObj_GetText(FPDF_PAGEOBJECT text_object, +FPDF_TEXTPAGE text_page, +ffi.Pointer buffer, +int length, +) { + return _FPDFTextObj_GetText(text_object, +text_page, +buffer, +length, +); +} + +late final _FPDFTextObj_GetTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFTextObj_GetText'); +late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction , int )>(); + +/// Experimental API. +/// Get a bitmap rasterization of |text_object|. To render correctly, the caller +/// must provide the |document| associated with |text_object|. If there is a +/// |page| associated with |text_object|, the caller should provide that as well. +/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() +/// must be called on the returned bitmap when it is no longer needed. +/// +/// document - handle to a document associated with |text_object|. +/// page - handle to an optional page associated with |text_object|. +/// text_object - handle to a text object. +/// scale - the scaling factor, which must be greater than 0. +/// +/// Returns the bitmap or NULL on failure. +FPDF_BITMAP FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, +FPDF_PAGE page, +FPDF_PAGEOBJECT text_object, +double scale, +) { + return _FPDFTextObj_GetRenderedBitmap(document, +page, +text_object, +scale, +); +} + +late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetRenderedBitmap'); +late final _FPDFTextObj_GetRenderedBitmap = _FPDFTextObj_GetRenderedBitmapPtr.asFunction(); + +/// Experimental API. +/// Get the font of a text object. +/// +/// text - the handle to the text object. +/// +/// Returns a handle to the font object held by |text| which retains ownership. +FPDF_FONT FPDFTextObj_GetFont(FPDF_PAGEOBJECT text, +) { + return _FPDFTextObj_GetFont(text, +); +} + +late final _FPDFTextObj_GetFontPtr = _lookup< + ffi.NativeFunction>('FPDFTextObj_GetFont'); +late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); + +/// Experimental API. +/// Get the base name of a font. +/// +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the base font name. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the base name (including the trailing NUL +/// character) on success, 0 on error. The base name is typically the font's +/// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetBaseFontName(FPDF_FONT font, +ffi.Pointer buffer, +int length, +) { + return _FPDFFont_GetBaseFontName(font, +buffer, +length, +); +} + +late final _FPDFFont_GetBaseFontNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetBaseFontName'); +late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the family name of a font. +/// +/// font - the handle to the font object. +/// buffer - the address of a buffer that receives the font name. +/// length - the size, in bytes, of |buffer|. +/// +/// Returns the number of bytes in the family name (including the trailing NUL +/// character) on success, 0 on error. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFFont_GetFamilyName(FPDF_FONT font, +ffi.Pointer buffer, +int length, +) { + return _FPDFFont_GetFamilyName(font, +buffer, +length, +); +} + +late final _FPDFFont_GetFamilyNamePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetFamilyName'); +late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the decoded data from the |font| object. +/// +/// font - The handle to the font object. (Required) +/// buffer - The address of a buffer that receives the font data. +/// buflen - Length of the buffer. +/// out_buflen - Pointer to variable that will receive the minimum buffer size +/// to contain the font data. Not filled if the return value is +/// FALSE. (Required) +/// +/// Returns TRUE on success. In which case, |out_buflen| will be filled, and +/// |buffer| will be filled if it is large enough. Returns FALSE if any of the +/// required parameters are null. +/// +/// The decoded data is the uncompressed font data. i.e. the raw font data after +/// having all stream filters applied, when the data is embedded. +/// +/// If the font is not embedded, then this API will instead return the data for +/// the substitution font it is using. +int FPDFFont_GetFontData(FPDF_FONT font, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFFont_GetFontData(font, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFFont_GetFontDataPtr = _lookup< + ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFFont_GetFontData'); +late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get whether |font| is embedded or not. +/// +/// font - the handle to the font object. +/// +/// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. +int FPDFFont_GetIsEmbedded(FPDF_FONT font, +) { + return _FPDFFont_GetIsEmbedded(font, +); +} + +late final _FPDFFont_GetIsEmbeddedPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetIsEmbedded'); +late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); + +/// Experimental API. +/// Get the descriptor flags of a font. +/// +/// font - the handle to the font object. +/// +/// Returns the bit flags specifying various characteristics of the font as +/// defined in ISO 32000-1:2008, table 123, -1 on failure. +int FPDFFont_GetFlags(FPDF_FONT font, +) { + return _FPDFFont_GetFlags(font, +); +} + +late final _FPDFFont_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetFlags'); +late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); + +/// Experimental API. +/// Get the font weight of a font. +/// +/// font - the handle to the font object. +/// +/// Returns the font weight, -1 on failure. +/// Typical values are 400 (normal) and 700 (bold). +int FPDFFont_GetWeight(FPDF_FONT font, +) { + return _FPDFFont_GetWeight(font, +); +} + +late final _FPDFFont_GetWeightPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetWeight'); +late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); + +/// Experimental API. +/// Get the italic angle of a font. +/// +/// font - the handle to the font object. +/// angle - pointer where the italic angle will be stored +/// +/// The italic angle of a |font| is defined as degrees counterclockwise +/// from vertical. For a font that slopes to the right, this will be negative. +/// +/// Returns TRUE on success; |angle| unmodified on failure. +int FPDFFont_GetItalicAngle(FPDF_FONT font, +ffi.Pointer angle, +) { + return _FPDFFont_GetItalicAngle(font, +angle, +); +} + +late final _FPDFFont_GetItalicAnglePtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetItalicAngle'); +late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction )>(); + +/// Experimental API. +/// Get ascent distance of a font. +/// +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// ascent - pointer where the font ascent will be stored +/// +/// Ascent is the maximum distance in points above the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |ascent| unmodified on failure. +int FPDFFont_GetAscent(FPDF_FONT font, +double font_size, +ffi.Pointer ascent, +) { + return _FPDFFont_GetAscent(font, +font_size, +ascent, +); +} + +late final _FPDFFont_GetAscentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetAscent'); +late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction )>(); + +/// Experimental API. +/// Get descent distance of a font. +/// +/// font - the handle to the font object. +/// font_size - the size of the |font|. +/// descent - pointer where the font descent will be stored +/// +/// Descent is the maximum distance in points below the baseline reached by the +/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). +/// +/// Returns TRUE on success; |descent| unmodified on failure. +int FPDFFont_GetDescent(FPDF_FONT font, +double font_size, +ffi.Pointer descent, +) { + return _FPDFFont_GetDescent(font, +font_size, +descent, +); +} + +late final _FPDFFont_GetDescentPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetDescent'); +late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction )>(); + +/// Experimental API. +/// Get the width of a glyph in a font. +/// +/// font - the handle to the font object. +/// glyph - the glyph. +/// font_size - the size of the font. +/// width - pointer where the glyph width will be stored +/// +/// Glyph width is the distance from the end of the prior glyph to the next +/// glyph. This will be the vertical distance for vertical writing. +/// +/// Returns TRUE on success; |width| unmodified on failure. +int FPDFFont_GetGlyphWidth(FPDF_FONT font, +int glyph, +double font_size, +ffi.Pointer width, +) { + return _FPDFFont_GetGlyphWidth(font, +glyph, +font_size, +width, +); +} + +late final _FPDFFont_GetGlyphWidthPtr = _lookup< + ffi.NativeFunction )>>('FPDFFont_GetGlyphWidth'); +late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction )>(); + +/// Experimental API. +/// Get the glyphpath describing how to draw a font glyph. +/// +/// font - the handle to the font object. +/// glyph - the glyph being drawn. +/// font_size - the size of the font. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_GLYPHPATH FPDFFont_GetGlyphPath(FPDF_FONT font, +int glyph, +double font_size, +) { + return _FPDFFont_GetGlyphPath(font, +glyph, +font_size, +); +} + +late final _FPDFFont_GetGlyphPathPtr = _lookup< + ffi.NativeFunction>('FPDFFont_GetGlyphPath'); +late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction(); + +/// Experimental API. +/// Get number of segments inside glyphpath. +/// +/// glyphpath - handle to a glyph path. +/// +/// Returns the number of objects in |glyphpath| or -1 on failure. +int FPDFGlyphPath_CountGlyphSegments(FPDF_GLYPHPATH glyphpath, +) { + return _FPDFGlyphPath_CountGlyphSegments(glyphpath, +); +} + +late final _FPDFGlyphPath_CountGlyphSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_CountGlyphSegments'); +late final _FPDFGlyphPath_CountGlyphSegments = _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction(); + +/// Experimental API. +/// Get segment in glyphpath at index. +/// +/// glyphpath - handle to a glyph path. +/// index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on faiure. +FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment(FPDF_GLYPHPATH glyphpath, +int index, +) { + return _FPDFGlyphPath_GetGlyphPathSegment(glyphpath, +index, +); +} + +late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFGlyphPath_GetGlyphPathSegment'); +late final _FPDFGlyphPath_GetGlyphPathSegment = _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction(); + +/// Get number of page objects inside |form_object|. +/// +/// form_object - handle to a form object. +/// +/// Returns the number of objects in |form_object| on success, -1 on error. +int FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object, +) { + return _FPDFFormObj_CountObjects(form_object, +); +} + +late final _FPDFFormObj_CountObjectsPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_CountObjects'); +late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); + +/// Get page object in |form_object| at |index|. +/// +/// form_object - handle to a form object. +/// index - the 0-based index of a page object. +/// +/// Returns the handle to the page object, or NULL on error. +FPDF_PAGEOBJECT FPDFFormObj_GetObject(FPDF_PAGEOBJECT form_object, +int index, +) { + return _FPDFFormObj_GetObject(form_object, +index, +); +} + +late final _FPDFFormObj_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_GetObject'); +late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction(); + +/// Experimental API. +/// +/// Remove |page_object| from |form_object|. +/// +/// form_object - handle to a form object. +/// page_object - handle to a page object to be removed from the form. +/// +/// Returns TRUE on success. +/// +/// Ownership of the removed |page_object| is transferred to the caller. +/// Call FPDFPageObj_Destroy() on the removed page_object to free it. +int FPDFFormObj_RemoveObject(FPDF_PAGEOBJECT form_object, +FPDF_PAGEOBJECT page_object, +) { + return _FPDFFormObj_RemoveObject(form_object, +page_object, +); +} + +late final _FPDFFormObj_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFFormObj_RemoveObject'); +late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); + +/// Experimental API. +/// Get the number of embedded files in |document|. +/// +/// document - handle to a document. +/// +/// Returns the number of embedded files in |document|. +int FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document, +) { + return _FPDFDoc_GetAttachmentCount(document, +); +} + +late final _FPDFDoc_GetAttachmentCountPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetAttachmentCount'); +late final _FPDFDoc_GetAttachmentCount = _FPDFDoc_GetAttachmentCountPtr.asFunction(); + +/// Experimental API. +/// Add an embedded file with |name| in |document|. If |name| is empty, or if +/// |name| is the name of a existing embedded file in |document|, or if +/// |document|'s embedded file name tree is too deep (i.e. |document| has too +/// many embedded files already), then a new attachment will not be added. +/// +/// document - handle to a document. +/// name - name of the new attachment. +/// +/// Returns a handle to the new attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFDoc_AddAttachment(FPDF_DOCUMENT document, +FPDF_WIDESTRING name, +) { + return _FPDFDoc_AddAttachment(document, +name, +); +} + +late final _FPDFDoc_AddAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_AddAttachment'); +late final _FPDFDoc_AddAttachment = _FPDFDoc_AddAttachmentPtr.asFunction(); + +/// Experimental API. +/// Get the embedded attachment at |index| in |document|. Note that the returned +/// attachment handle is only valid while |document| is open. +/// +/// document - handle to a document. +/// index - the index of the requested embedded file. +/// +/// Returns the handle to the attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFDoc_GetAttachment(FPDF_DOCUMENT document, +int index, +) { + return _FPDFDoc_GetAttachment(document, +index, +); +} + +late final _FPDFDoc_GetAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetAttachment'); +late final _FPDFDoc_GetAttachment = _FPDFDoc_GetAttachmentPtr.asFunction(); + +/// Experimental API. +/// Delete the embedded attachment at |index| in |document|. Note that this does +/// not remove the attachment data from the PDF file; it simply removes the +/// file's entry in the embedded files name tree so that it does not appear in +/// the attachment list. This behavior may change in the future. +/// +/// document - handle to a document. +/// index - the index of the embedded file to be deleted. +/// +/// Returns true if successful. +int FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, +int index, +) { + return _FPDFDoc_DeleteAttachment(document, +index, +); +} + +late final _FPDFDoc_DeleteAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFDoc_DeleteAttachment'); +late final _FPDFDoc_DeleteAttachment = _FPDFDoc_DeleteAttachmentPtr.asFunction(); + +/// Experimental API. +/// Get the name of the |attachment| file. |buffer| is only modified if |buflen| +/// is longer than the length of the file name. On errors, |buffer| is unmodified +/// and the returned length is 0. +/// +/// attachment - handle to an attachment. +/// buffer - buffer for holding the file name, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the file name in bytes. +int FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAttachment_GetName(attachment, +buffer, +buflen, +); +} + +late final _FPDFAttachment_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetName'); +late final _FPDFAttachment_GetName = _FPDFAttachment_GetNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Check if the params dictionary of |attachment| has |key| as a key. +/// +/// attachment - handle to an attachment. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns true if |key| exists. +int FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +) { + return _FPDFAttachment_HasKey(attachment, +key, +); +} + +late final _FPDFAttachment_HasKeyPtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_HasKey'); +late final _FPDFAttachment_HasKey = _FPDFAttachment_HasKeyPtr.asFunction(); + +/// Experimental API. +/// Get the type of the value corresponding to |key| in the params dictionary of +/// the embedded |attachment|. +/// +/// attachment - handle to an attachment. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns the type of the dictionary value. +int FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +) { + return _FPDFAttachment_GetValueType(attachment, +key, +); +} + +late final _FPDFAttachment_GetValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_GetValueType'); +late final _FPDFAttachment_GetValueType = _FPDFAttachment_GetValueTypePtr.asFunction(); + +/// Experimental API. +/// Set the string value corresponding to |key| in the params dictionary of the +/// embedded file |attachment|, overwriting the existing value if any. The value +/// type should be FPDF_OBJECT_STRING after this function call succeeds. +/// +/// attachment - handle to an attachment. +/// key - the key to the dictionary entry, encoded in UTF-8. +/// value - the string value to be set, encoded in UTF-16LE. +/// +/// Returns true if successful. +int FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +FPDF_WIDESTRING value, +) { + return _FPDFAttachment_SetStringValue(attachment, +key, +value, +); +} + +late final _FPDFAttachment_SetStringValuePtr = _lookup< + ffi.NativeFunction>('FPDFAttachment_SetStringValue'); +late final _FPDFAttachment_SetStringValue = _FPDFAttachment_SetStringValuePtr.asFunction(); + +/// Experimental API. +/// Get the string value corresponding to |key| in the params dictionary of the +/// embedded file |attachment|. |buffer| is only modified if |buflen| is longer +/// than the length of the string value. Note that if |key| does not exist in the +/// dictionary or if |key|'s corresponding value in the dictionary is not a +/// string (i.e. the value is not of type FPDF_OBJECT_STRING or +/// FPDF_OBJECT_NAME), then an empty string would be copied to |buffer| and the +/// return value would be 2. On other errors, nothing would be added to |buffer| +/// and the return value would be 0. +/// +/// attachment - handle to an attachment. +/// key - the key to the requested string value, encoded in UTF-8. +/// buffer - buffer for holding the string value encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the dictionary value string in bytes. +int FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAttachment_GetStringValue(attachment, +key, +buffer, +buflen, +); +} + +late final _FPDFAttachment_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetStringValue'); +late final _FPDFAttachment_GetStringValue = _FPDFAttachment_GetStringValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Set the file data of |attachment|, overwriting the existing file data if any. +/// The creation date and checksum will be updated, while all other dictionary +/// entries will be deleted. Note that only contents with |len| smaller than +/// INT_MAX is supported. +/// +/// attachment - handle to an attachment. +/// contents - buffer holding the file data to write to |attachment|. +/// len - length of file data in bytes. +/// +/// Returns true if successful. +int FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, +FPDF_DOCUMENT document, +ffi.Pointer contents, +int len, +) { + return _FPDFAttachment_SetFile(attachment, +document, +contents, +len, +); +} + +late final _FPDFAttachment_SetFilePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_SetFile'); +late final _FPDFAttachment_SetFile = _FPDFAttachment_SetFilePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the file data of |attachment|. +/// When the attachment file data is readable, true is returned, and |out_buflen| +/// is updated to indicate the file data size. |buffer| is only modified if +/// |buflen| is non-null and long enough to contain the entire file data. Callers +/// must check both the return value and the input |buflen| is no less than the +/// returned |out_buflen| before using the data. +/// +/// Otherwise, when the attachment file data is unreadable or when |out_buflen| +/// is null, false is returned and |buffer| and |out_buflen| remain unmodified. +/// +/// attachment - handle to an attachment. +/// buffer - buffer for holding the file data from |attachment|. +/// buflen - length of the buffer in bytes. +/// out_buflen - pointer to the variable that will receive the minimum buffer +/// size to contain the file data of |attachment|. +/// +/// Returns true on success, false otherwise. +int FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDFAttachment_GetFile(attachment, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDFAttachment_GetFilePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFAttachment_GetFile'); +late final _FPDFAttachment_GetFile = _FPDFAttachment_GetFilePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Get the MIME type (Subtype) of the embedded file |attachment|. |buffer| is +/// only modified if |buflen| is longer than the length of the MIME type string. +/// If the Subtype is not found or if there is no file stream, an empty string +/// would be copied to |buffer| and the return value would be 2. On other errors, +/// nothing would be added to |buffer| and the return value would be 0. +/// +/// attachment - handle to an attachment. +/// buffer - buffer for holding the MIME type string encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the MIME type string in bytes. +int FPDFAttachment_GetSubtype(FPDF_ATTACHMENT attachment, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAttachment_GetSubtype(attachment, +buffer, +buflen, +); +} + +late final _FPDFAttachment_GetSubtypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetSubtype'); +late final _FPDFAttachment_GetSubtype = _FPDFAttachment_GetSubtypePtr.asFunction , int )>(); + +/// Function: FPDFDOC_InitFormFillEnvironment +/// Initialize form fill environment. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// formInfo - Pointer to a FPDF_FORMFILLINFO structure. +/// Return Value: +/// Handle to the form fill module, or NULL on failure. +/// Comments: +/// This function should be called before any form fill operation. +/// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until +/// the returned FPDF_FORMHANDLE is closed. +FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, +ffi.Pointer formInfo, +) { + return _FPDFDOC_InitFormFillEnvironment(document, +formInfo, +); +} + +late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction )>>('FPDFDOC_InitFormFillEnvironment'); +late final _FPDFDOC_InitFormFillEnvironment = _FPDFDOC_InitFormFillEnvironmentPtr.asFunction )>(); + +/// Function: FPDFDOC_ExitFormFillEnvironment +/// Take ownership of |hHandle| and exit form fill environment. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This function is a no-op when |hHandle| is null. +void FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle, +) { + return _FPDFDOC_ExitFormFillEnvironment(hHandle, +); +} + +late final _FPDFDOC_ExitFormFillEnvironmentPtr = _lookup< + ffi.NativeFunction>('FPDFDOC_ExitFormFillEnvironment'); +late final _FPDFDOC_ExitFormFillEnvironment = _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction(); + +/// Function: FORM_OnAfterLoadPage +/// This method is required for implementing all the form related +/// functions. Should be invoked after user successfully loaded a +/// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnAfterLoadPage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +) { + return _FORM_OnAfterLoadPage(page, +hHandle, +); +} + +late final _FORM_OnAfterLoadPagePtr = _lookup< + ffi.NativeFunction>('FORM_OnAfterLoadPage'); +late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction(); + +/// Function: FORM_OnBeforeClosePage +/// This method is required for implementing all the form related +/// functions. Should be invoked before user closes the PDF page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +void FORM_OnBeforeClosePage(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +) { + return _FORM_OnBeforeClosePage(page, +hHandle, +); +} + +late final _FORM_OnBeforeClosePagePtr = _lookup< + ffi.NativeFunction>('FORM_OnBeforeClosePage'); +late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction(); + +/// Function: FORM_DoDocumentJSAction +/// This method is required for performing document-level JavaScript +/// actions. It should be invoked after the PDF document has been loaded. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// If there is document-level JavaScript action embedded in the +/// document, this method will execute the JavaScript action. Otherwise, +/// the method will do nothing. +void FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle, +) { + return _FORM_DoDocumentJSAction(hHandle, +); +} + +late final _FORM_DoDocumentJSActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentJSAction'); +late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); + +/// Function: FORM_DoDocumentOpenAction +/// This method is required for performing open-action when the document +/// is opened. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there are no open-actions embedded +/// in the document. +void FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle, +) { + return _FORM_DoDocumentOpenAction(hHandle, +); +} + +late final _FORM_DoDocumentOpenActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentOpenAction'); +late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr.asFunction(); + +/// Function: FORM_DoDocumentAAction +/// This method is required for performing the document's +/// additional-action. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// aaType - The type of the additional-actions which defined +/// above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if there is no document +/// additional-action corresponding to the specified |aaType|. +void FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, +int aaType, +) { + return _FORM_DoDocumentAAction(hHandle, +aaType, +); +} + +late final _FORM_DoDocumentAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoDocumentAAction'); +late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction(); + +/// Function: FORM_DoPageAAction +/// This method is required for performing the page object's +/// additional-action when opened or closed. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// aaType - The type of the page object's additional-actions +/// which defined above. +/// Return Value: +/// None. +/// Comments: +/// This method will do nothing if no additional-action corresponding +/// to the specified |aaType| exists. +void FORM_DoPageAAction(FPDF_PAGE page, +FPDF_FORMHANDLE hHandle, +int aaType, +) { + return _FORM_DoPageAAction(page, +hHandle, +aaType, +); +} + +late final _FORM_DoPageAActionPtr = _lookup< + ffi.NativeFunction>('FORM_DoPageAAction'); +late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction(); + +/// Function: FORM_OnMouseMove +/// Call this member function when the mouse cursor moves. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnMouseMove(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnMouseMovePtr = _lookup< + ffi.NativeFunction>('FORM_OnMouseMove'); +late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction(); + +/// Experimental API +/// Function: FORM_OnMouseWheel +/// Call this member function when the user scrolls the mouse wheel. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_coord - Specifies the coordinates of the cursor in PDF user +/// space. +/// delta_x - Specifies the amount of wheel movement on the x-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean left. +/// delta_y - Specifies the amount of wheel movement on the y-axis, +/// in units of platform-agnostic wheel deltas. Negative +/// values mean down. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// For |delta_x| and |delta_y|, the caller must normalize +/// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 +/// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines +/// WHEEL_DELTA as 120. +int FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +ffi.Pointer page_coord, +int delta_x, +int delta_y, +) { + return _FORM_OnMouseWheel(hHandle, +page, +modifier, +page_coord, +delta_x, +delta_y, +); +} + +late final _FORM_OnMouseWheelPtr = _lookup< + ffi.NativeFunction , ffi.Int , ffi.Int )>>('FORM_OnMouseWheel'); +late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction , int , int )>(); + +/// Function: FORM_OnFocus +/// This function focuses the form annotation at a given point. If the +/// annotation at the point already has focus, nothing happens. If there +/// is no annotation at the point, removes form focus. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True if there is an annotation at the given point and it has focus. +int FORM_OnFocus(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnFocus(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnFocusPtr = _lookup< + ffi.NativeFunction>('FORM_OnFocus'); +late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction(); + +/// Function: FORM_OnLButtonDown +/// Call this member function when the user presses the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonDown(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDown'); +late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction(); + +/// Function: FORM_OnRButtonDown +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnRButtonDown(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnRButtonDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonDown'); +late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction(); + +/// Function: FORM_OnLButtonUp +/// Call this member function when the user releases the left +/// mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in device. +/// page_y - Specifies the y-coordinate of the cursor in device. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonUp(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonUp'); +late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction(); + +/// Function: FORM_OnRButtonUp +/// Same as above, execpt for the right mouse button. +/// Comments: +/// At the present time, has no effect except in XFA builds, but is +/// included for the sake of symmetry. +int FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnRButtonUp(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnRButtonUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnRButtonUp'); +late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction(); + +/// Function: FORM_OnLButtonDoubleClick +/// Call this member function when the user double clicks the +/// left mouse button. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// modifier - Indicates whether various virtual keys are down. +/// page_x - Specifies the x-coordinate of the cursor in PDF user +/// space. +/// page_y - Specifies the y-coordinate of the cursor in PDF user +/// space. +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnLButtonDoubleClick(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int modifier, +double page_x, +double page_y, +) { + return _FORM_OnLButtonDoubleClick(hHandle, +page, +modifier, +page_x, +page_y, +); +} + +late final _FORM_OnLButtonDoubleClickPtr = _lookup< + ffi.NativeFunction>('FORM_OnLButtonDoubleClick'); +late final _FORM_OnLButtonDoubleClick = _FORM_OnLButtonDoubleClickPtr.asFunction(); + +/// Function: FORM_OnKeyDown +/// Call this member function when a nonsystem key is pressed. +/// Parameters: +/// hHandle - Handle to the form fill module, aseturned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, +) { + return _FORM_OnKeyDown(hHandle, +page, +nKeyCode, +modifier, +); +} + +late final _FORM_OnKeyDownPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyDown'); +late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction(); + +/// Function: FORM_OnKeyUp +/// Call this member function when a nonsystem key is released. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nKeyCode - The virtual-key code of the given key (see +/// fpdf_fwlevent.h for virtual key codes). +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// Currently unimplemented and always returns false. PDFium reserves this +/// API and may implement it in the future on an as-needed basis. +int FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nKeyCode, +int modifier, +) { + return _FORM_OnKeyUp(hHandle, +page, +nKeyCode, +modifier, +); +} + +late final _FORM_OnKeyUpPtr = _lookup< + ffi.NativeFunction>('FORM_OnKeyUp'); +late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction(); + +/// Function: FORM_OnChar +/// Call this member function when a keystroke translates to a +/// nonsystem character. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// nChar - The character code value itself. +/// modifier - Mask of key flags (see fpdf_fwlevent.h for key +/// flag values). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_OnChar(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int nChar, +int modifier, +) { + return _FORM_OnChar(hHandle, +page, +nChar, +modifier, +); +} + +late final _FORM_OnCharPtr = _lookup< + ffi.NativeFunction>('FORM_OnChar'); +late final _FORM_OnChar = _FORM_OnCharPtr.asFunction(); + +/// Experimental API +/// Function: FORM_GetFocusedText +/// Call this function to obtain the text within the current focused +/// field, if any. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the form text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the form text string, |buffer| is +/// not modified. +/// Return Value: +/// Length in bytes for the text in the focused field. +int FORM_GetFocusedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FORM_GetFocusedText(hHandle, +page, +buffer, +buflen, +); +} + +late final _FORM_GetFocusedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetFocusedText'); +late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction , int )>(); + +/// Function: FORM_GetSelectedText +/// Call this function to obtain selected text within a form text +/// field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// buffer - Buffer for holding the selected text, encoded in +/// UTF-16LE. If NULL, |buffer| is not modified. +/// buflen - Length of |buffer| in bytes. If |buflen| is less +/// than the length of the selected text string, +/// |buffer| is not modified. +/// Return Value: +/// Length in bytes of selected text in form text field or form combobox +/// text field. +int FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FORM_GetSelectedText(hHandle, +page, +buffer, +buflen, +); +} + +late final _FORM_GetSelectedTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetSelectedText'); +late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction , int )>(); + +/// Experimental API +/// Function: FORM_ReplaceAndKeepSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the inserted text +/// will be selected. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceAndKeepSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, +) { + return _FORM_ReplaceAndKeepSelection(hHandle, +page, +wsText, +); +} + +late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceAndKeepSelection'); +late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr.asFunction(); + +/// Function: FORM_ReplaceSelection +/// Call this function to replace the selected text in a form +/// text field or user-editable form combobox text field with another +/// text string (which can be empty or non-empty). If there is no +/// selected text, this function will append the replacement text after +/// the current caret position. After the insertion, the selection range +/// will be set to empty. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as Returned by FPDF_LoadPage(). +/// wsText - The text to be inserted, in UTF-16LE format. +/// Return Value: +/// None. +void FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +FPDF_WIDESTRING wsText, +) { + return _FORM_ReplaceSelection(hHandle, +page, +wsText, +); +} + +late final _FORM_ReplaceSelectionPtr = _lookup< + ffi.NativeFunction>('FORM_ReplaceSelection'); +late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction(); + +/// Experimental API +/// Function: FORM_SelectAllText +/// Call this function to select all the text within the currently focused +/// form text field or form combobox text field. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// Whether the operation succeeded or not. +int FORM_SelectAllText(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_SelectAllText(hHandle, +page, +); +} + +late final _FORM_SelectAllTextPtr = _lookup< + ffi.NativeFunction>('FORM_SelectAllText'); +late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction(); + +/// Function: FORM_CanUndo +/// Find out if it is possible for the current focused widget in a given +/// form to perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to undo. +int FORM_CanUndo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_CanUndo(hHandle, +page, +); +} + +late final _FORM_CanUndoPtr = _lookup< + ffi.NativeFunction>('FORM_CanUndo'); +late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); + +/// Function: FORM_CanRedo +/// Find out if it is possible for the current focused widget in a given +/// form to perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if it is possible to redo. +int FORM_CanRedo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_CanRedo(hHandle, +page, +); +} + +late final _FORM_CanRedoPtr = _lookup< + ffi.NativeFunction>('FORM_CanRedo'); +late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); + +/// Function: FORM_Undo +/// Make the current focused widget perform an undo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the undo operation succeeded. +int FORM_Undo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_Undo(hHandle, +page, +); +} + +late final _FORM_UndoPtr = _lookup< + ffi.NativeFunction>('FORM_Undo'); +late final _FORM_Undo = _FORM_UndoPtr.asFunction(); + +/// Function: FORM_Redo +/// Make the current focused widget perform a redo operation. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return Value: +/// True if the redo operation succeeded. +int FORM_Redo(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +) { + return _FORM_Redo(hHandle, +page, +); +} + +late final _FORM_RedoPtr = _lookup< + ffi.NativeFunction>('FORM_Redo'); +late final _FORM_Redo = _FORM_RedoPtr.asFunction(); + +/// Function: FORM_ForceToKillFocus. +/// Call this member function to force to kill the focus of the form +/// field which has focus. If it would kill the focus of a form field, +/// save the value of form field if was changed by theuser. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// True indicates success; otherwise false. +int FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle, +) { + return _FORM_ForceToKillFocus(hHandle, +); +} + +late final _FORM_ForceToKillFocusPtr = _lookup< + ffi.NativeFunction>('FORM_ForceToKillFocus'); +late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); + +/// Experimental API. +/// Function: FORM_GetFocusedAnnot. +/// Call this member function to get the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page_index - Buffer to hold the index number of the page which +/// contains the focused annotation. 0 for the first page. +/// Can't be NULL. +/// annot - Buffer to hold the focused annotation. Can't be NULL. +/// Return Value: +/// On success, return true and write to the out parameters. Otherwise +/// return false and leave the out parameters unmodified. +/// Comments: +/// Not currently supported for XFA forms - will report no focused +/// annotation. +/// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| +/// by this function is no longer needed. +/// This will return true and set |page_index| to -1 and |annot| to NULL, +/// if there is no focused annotation. +int FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle, +ffi.Pointer page_index, +ffi.Pointer annot, +) { + return _FORM_GetFocusedAnnot(handle, +page_index, +annot, +); +} + +late final _FORM_GetFocusedAnnotPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FORM_GetFocusedAnnot'); +late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FORM_SetFocusedAnnot. +/// Call this member function to set the currently focused annotation. +/// Parameters: +/// handle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - Handle to an annotation. +/// Return Value: +/// True indicates success; otherwise false. +/// Comments: +/// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() +/// instead. +int FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +) { + return _FORM_SetFocusedAnnot(handle, +annot, +); +} + +late final _FORM_SetFocusedAnnotPtr = _lookup< + ffi.NativeFunction>('FORM_SetFocusedAnnot'); +late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction(); + +/// Function: FPDFPage_HasFormFieldAtPoint +/// Get the form field type by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the type of the form field; -1 indicates no field. +/// See field types above. +int FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +double page_x, +double page_y, +) { + return _FPDFPage_HasFormFieldAtPoint(hHandle, +page, +page_x, +page_y, +); +} + +late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_HasFormFieldAtPoint'); +late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr.asFunction(); + +/// Function: FPDFPage_FormFieldZOrderAtPoint +/// Get the form field z-order by point. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - Handle to the page. Returned by FPDF_LoadPage(). +/// page_x - X position in PDF "user space". +/// page_y - Y position in PDF "user space". +/// Return Value: +/// Return the z-order of the form field; -1 indicates no field. +/// Higher numbers are closer to the front. +int FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +double page_x, +double page_y, +) { + return _FPDFPage_FormFieldZOrderAtPoint(hHandle, +page, +page_x, +page_y, +); +} + +late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFPage_FormFieldZOrderAtPoint'); +late final _FPDFPage_FormFieldZOrderAtPoint = _FPDFPage_FormFieldZOrderAtPointPtr.asFunction(); + +/// Function: FPDF_SetFormFieldHighlightColor +/// Set the highlight color of the specified (or all) form fields +/// in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returned by +/// FPDF_LoadDocument(). +/// fieldType - A 32-bit integer indicating the type of a form +/// field (defined above). +/// color - The highlight color of the form field. Constructed by +/// 0xxxrrggbb. +/// Return Value: +/// None. +/// Comments: +/// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the +/// highlight color will be applied to all the form fields in the +/// document. +/// Please refresh the client window to show the highlight immediately +/// if necessary. +void FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, +int fieldType, +int color, +) { + return _FPDF_SetFormFieldHighlightColor(hHandle, +fieldType, +color, +); +} + +late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightColor'); +late final _FPDF_SetFormFieldHighlightColor = _FPDF_SetFormFieldHighlightColorPtr.asFunction(); + +/// Function: FPDF_SetFormFieldHighlightAlpha +/// Set the transparency of the form field highlight color in the +/// document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// doc - Handle to the document, as returaned by +/// FPDF_LoadDocument(). +/// alpha - The transparency of the form field highlight color, +/// between 0-255. +/// Return Value: +/// None. +void FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, +int alpha, +) { + return _FPDF_SetFormFieldHighlightAlpha(hHandle, +alpha, +); +} + +late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< + ffi.NativeFunction>('FPDF_SetFormFieldHighlightAlpha'); +late final _FPDF_SetFormFieldHighlightAlpha = _FPDF_SetFormFieldHighlightAlphaPtr.asFunction(); + +/// Function: FPDF_RemoveFormFieldHighlight +/// Remove the form field highlight color in the document. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// Return Value: +/// None. +/// Comments: +/// Please refresh the client window to remove the highlight immediately +/// if necessary. +void FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle, +) { + return _FPDF_RemoveFormFieldHighlight(hHandle, +); +} + +late final _FPDF_RemoveFormFieldHighlightPtr = _lookup< + ffi.NativeFunction>('FPDF_RemoveFormFieldHighlight'); +late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr.asFunction(); + +/// Function: FPDF_FFLDraw +/// Render FormFields and popup window on a page to a device independent +/// bitmap. +/// Parameters: +/// hHandle - Handle to the form fill module, as returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// bitmap - Handle to the device independent bitmap (as the +/// output buffer). Bitmap handles can be created by +/// FPDFBitmap_Create(). +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// start_x - Left pixel position of the display area in the +/// device coordinates. +/// start_y - Top pixel position of the display area in the device +/// coordinates. +/// size_x - Horizontal size (in pixels) for displaying the page. +/// size_y - Vertical size (in pixels) for displaying the page. +/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees +/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 +/// degrees counter-clockwise). +/// flags - 0 for normal display, or combination of flags +/// defined above. +/// Return Value: +/// None. +/// Comments: +/// This function is designed to render annotations that are +/// user-interactive, which are widget annotations (for FormFields) and +/// popup annotations. +/// With the FPDF_ANNOT flag, this function will render a popup annotation +/// when users mouse-hover on a non-widget annotation. Regardless of +/// FPDF_ANNOT flag, this function will always render widget annotations +/// for FormFields. +/// In order to implement the FormFill functions, implementation should +/// call this function after rendering functions, such as +/// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have +/// finished rendering the page contents. +void FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, +FPDF_BITMAP bitmap, +FPDF_PAGE page, +int start_x, +int start_y, +int size_x, +int size_y, +int rotate, +int flags, +) { + return _FPDF_FFLDraw(hHandle, +bitmap, +page, +start_x, +start_y, +size_x, +size_y, +rotate, +flags, +); +} + +late final _FPDF_FFLDrawPtr = _lookup< + ffi.NativeFunction>('FPDF_FFLDraw'); +late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction(); + +/// Experimental API +/// Function: FPDF_GetFormType +/// Returns the type of form contained in the PDF document. +/// Parameters: +/// document - Handle to document. +/// Return Value: +/// Integer value representing one of the FORMTYPE_ values. +/// Comments: +/// If |document| is NULL, then the return value is FORMTYPE_NONE. +int FPDF_GetFormType(FPDF_DOCUMENT document, +) { + return _FPDF_GetFormType(document, +); +} + +late final _FPDF_GetFormTypePtr = _lookup< + ffi.NativeFunction>('FPDF_GetFormType'); +late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); + +/// Experimental API +/// Function: FORM_SetIndexSelected +/// Selects/deselects the value at the given |index| of the focused +/// annotation. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based index of value to be set as +/// selected/unselected +/// selected - true to select, false to deselect +/// Return Value: +/// TRUE if the operation succeeded. +/// FALSE if the operation failed or widget is not a supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Comboboxes +/// have at most a single value selected at a time which cannot be +/// deselected. Deselect on a combobox is a no-op that returns false. +/// Default implementation is a no-op that will return false for +/// other types. +/// Not currently supported for XFA forms - will return false. +int FORM_SetIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, +int selected, +) { + return _FORM_SetIndexSelected(hHandle, +page, +index, +selected, +); +} + +late final _FORM_SetIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_SetIndexSelected'); +late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction(); + +/// Experimental API +/// Function: FORM_IsIndexSelected +/// Returns whether or not the value at |index| of the focused +/// annotation is currently selected. +/// Parameters: +/// hHandle - Handle to the form fill module. Returned by +/// FPDFDOC_InitFormFillEnvironment. +/// page - Handle to the page. Returned by FPDF_LoadPage +/// index - 0-based Index of value to check +/// Return Value: +/// TRUE if value at |index| is currently selected. +/// FALSE if value at |index| is not selected or widget is not a +/// supported type. +/// Comments: +/// Intended for use with listbox/combobox widget types. Default +/// implementation is a no-op that will return false for other types. +/// Not currently supported for XFA forms - will return false. +int FORM_IsIndexSelected(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +int index, +) { + return _FORM_IsIndexSelected(hHandle, +page, +index, +); +} + +late final _FORM_IsIndexSelectedPtr = _lookup< + ffi.NativeFunction>('FORM_IsIndexSelected'); +late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction(); + +/// Function: FPDF_LoadXFA +/// If the document consists of XFA fields, call this method to +/// attempt to load XFA fields. +/// Parameters: +/// document - Handle to document from FPDF_LoadDocument(). +/// Return Value: +/// TRUE upon success, otherwise FALSE. If XFA support is not built +/// into PDFium, performs no action and always returns FALSE. +int FPDF_LoadXFA(FPDF_DOCUMENT document, +) { + return _FPDF_LoadXFA(document, +); +} + +late final _FPDF_LoadXFAPtr = _lookup< + ffi.NativeFunction>('FPDF_LoadXFA'); +late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); + +/// Experimental API. +/// Check if an annotation subtype is currently supported for creation. +/// Currently supported subtypes: +/// - circle +/// - fileattachment +/// - freetext +/// - highlight +/// - ink +/// - link +/// - popup +/// - square, +/// - squiggly +/// - stamp +/// - strikeout +/// - text +/// - underline +/// +/// subtype - the subtype to be checked. +/// +/// Returns true if this subtype supported. +int FPDFAnnot_IsSupportedSubtype(int subtype, +) { + return _FPDFAnnot_IsSupportedSubtype(subtype, +); +} + +late final _FPDFAnnot_IsSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsSupportedSubtype'); +late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); + +/// Experimental API. +/// Create an annotation in |page| of the subtype |subtype|. If the specified +/// subtype is illegal or unsupported, then a new annotation will not be created. +/// Must call FPDFPage_CloseAnnot() when the annotation returned by this +/// function is no longer needed. +/// +/// page - handle to a page. +/// subtype - the subtype of the new annotation. +/// +/// Returns a handle to the new annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_CreateAnnot(FPDF_PAGE page, +int subtype, +) { + return _FPDFPage_CreateAnnot(page, +subtype, +); +} + +late final _FPDFPage_CreateAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CreateAnnot'); +late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the number of annotations in |page|. +/// +/// page - handle to a page. +/// +/// Returns the number of annotations in |page|. +int FPDFPage_GetAnnotCount(FPDF_PAGE page, +) { + return _FPDFPage_GetAnnotCount(page, +); +} + +late final _FPDFPage_GetAnnotCountPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotCount'); +late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); + +/// Experimental API. +/// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the +/// annotation returned by this function is no longer needed. +/// +/// page - handle to a page. +/// index - the index of the annotation. +/// +/// Returns a handle to the annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFPage_GetAnnot(FPDF_PAGE page, +int index, +) { + return _FPDFPage_GetAnnot(page, +index, +); +} + +late final _FPDFPage_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnot'); +late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the index of |annot| in |page|. This is the opposite of +/// FPDFPage_GetAnnot(). +/// +/// page - handle to the page that the annotation is on. +/// annot - handle to an annotation. +/// +/// Returns the index of |annot|, or -1 on failure. +int FPDFPage_GetAnnotIndex(FPDF_PAGE page, +FPDF_ANNOTATION annot, +) { + return _FPDFPage_GetAnnotIndex(page, +annot, +); +} + +late final _FPDFPage_GetAnnotIndexPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetAnnotIndex'); +late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction(); + +/// Experimental API. +/// Close an annotation. Must be called when the annotation returned by +/// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This +/// function does not remove the annotation from the document. +/// +/// annot - handle to an annotation. +void FPDFPage_CloseAnnot(FPDF_ANNOTATION annot, +) { + return _FPDFPage_CloseAnnot(annot, +); +} + +late final _FPDFPage_CloseAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_CloseAnnot'); +late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); + +/// Experimental API. +/// Remove the annotation in |page| at |index|. +/// +/// page - handle to a page. +/// index - the index of the annotation. +/// +/// Returns true if successful. +int FPDFPage_RemoveAnnot(FPDF_PAGE page, +int index, +) { + return _FPDFPage_RemoveAnnot(page, +index, +); +} + +late final _FPDFPage_RemoveAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFPage_RemoveAnnot'); +late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the subtype of an annotation. +/// +/// annot - handle to an annotation. +/// +/// Returns the annotation subtype. +int FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetSubtype(annot, +); +} + +late final _FPDFAnnot_GetSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetSubtype'); +late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); + +/// Experimental API. +/// Check if an annotation subtype is currently supported for object extraction, +/// update, and removal. +/// Currently supported subtypes: ink and stamp. +/// +/// subtype - the subtype to be checked. +/// +/// Returns true if this subtype supported. +int FPDFAnnot_IsObjectSupportedSubtype(int subtype, +) { + return _FPDFAnnot_IsObjectSupportedSubtype(subtype, +); +} + +late final _FPDFAnnot_IsObjectSupportedSubtypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsObjectSupportedSubtype'); +late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); + +/// Experimental API. +/// Update |obj| in |annot|. |obj| must be in |annot| already and must have +/// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp +/// annotations are supported by this API. Also note that only path, image, and +/// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and +/// FPDFImageObj_*(). +/// +/// annot - handle to an annotation. +/// obj - handle to the object that |annot| needs to update. +/// +/// Return true if successful. +int FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, +) { + return _FPDFAnnot_UpdateObject(annot, +obj, +); +} + +late final _FPDFAnnot_UpdateObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_UpdateObject'); +late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction(); + +/// Experimental API. +/// Add a new InkStroke, represented by an array of points, to the InkList of +/// |annot|. The API creates an InkList if one doesn't already exist in |annot|. +/// This API works only for ink annotations. Please refer to ISO 32000-1:2008 +/// spec, section 12.5.6.13. +/// +/// annot - handle to an annotation. +/// points - pointer to a FS_POINTF array representing input points. +/// point_count - number of elements in |points| array. This should not exceed +/// the maximum value that can be represented by an int32_t). +/// +/// Returns the 0-based index at which the new InkStroke is added in the InkList +/// of the |annot|. Returns -1 on failure. +int FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot, +ffi.Pointer points, +int point_count, +) { + return _FPDFAnnot_AddInkStroke(annot, +points, +point_count, +); +} + +late final _FPDFAnnot_AddInkStrokePtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_AddInkStroke'); +late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction , int )>(); + +/// Experimental API. +/// Removes an InkList in |annot|. +/// This API works only for ink annotations. +/// +/// annot - handle to an annotation. +/// +/// Return true on successful removal of /InkList entry from context of the +/// non-null ink |annot|. Returns false on failure. +int FPDFAnnot_RemoveInkList(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_RemoveInkList(annot, +); +} + +late final _FPDFAnnot_RemoveInkListPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveInkList'); +late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); + +/// Experimental API. +/// Add |obj| to |annot|. |obj| must have been created by +/// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and +/// will be owned by |annot|. Note that an |obj| cannot belong to more than one +/// |annot|. Currently, only ink and stamp annotations are supported by this API. +/// Also note that only path, image, and text objects have APIs for creation. +/// +/// annot - handle to an annotation. +/// obj - handle to the object that is to be added to |annot|. +/// +/// Return true if successful. +int FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, +FPDF_PAGEOBJECT obj, +) { + return _FPDFAnnot_AppendObject(annot, +obj, +); +} + +late final _FPDFAnnot_AppendObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AppendObject'); +late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction(); + +/// Experimental API. +/// Get the total number of objects in |annot|, including path objects, text +/// objects, external objects, image objects, and shading objects. +/// +/// annot - handle to an annotation. +/// +/// Returns the number of objects in |annot|. +int FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetObjectCount(annot, +); +} + +late final _FPDFAnnot_GetObjectCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObjectCount'); +late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); + +/// Experimental API. +/// Get the object in |annot| at |index|. +/// +/// annot - handle to an annotation. +/// index - the index of the object. +/// +/// Return a handle to the object, or NULL on failure. +FPDF_PAGEOBJECT FPDFAnnot_GetObject(FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_GetObject(annot, +index, +); +} + +late final _FPDFAnnot_GetObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetObject'); +late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction(); + +/// Experimental API. +/// Remove the object in |annot| at |index|. +/// +/// annot - handle to an annotation. +/// index - the index of the object to be removed. +/// +/// Return true if successful. +int FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_RemoveObject(annot, +index, +); +} + +late final _FPDFAnnot_RemoveObjectPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_RemoveObject'); +late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction(); + +/// Experimental API. +/// Set the color of an annotation. Fails when called on annotations with +/// appearance streams already defined; instead use +/// FPDFPageObj_Set{Stroke|Fill}Color(). +/// +/// annot - handle to an annotation. +/// type - type of the color to be set. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. +/// +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +int R, +int G, +int B, +int A, +) { + return _FPDFAnnot_SetColor(annot, +type.value, +R, +G, +B, +A, +); +} + +late final _FPDFAnnot_SetColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetColor'); +late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction(); + +/// Experimental API. +/// Get the color of an annotation. If no color is specified, default to yellow +/// for highlight annotation, black for all else. Fails when called on +/// annotations with appearance streams already defined; instead use +/// FPDFPageObj_Get{Stroke|Fill}Color(). +/// +/// annot - handle to an annotation. +/// type - type of the color requested. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// A - buffer to hold the opacity. Ranges from 0 to 255. +/// +/// Returns true if successful. +DartFPDF_BOOL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, +FPDFANNOT_COLORTYPE type, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +ffi.Pointer A, +) { + return _FPDFAnnot_GetColor(annot, +type.value, +R, +G, +B, +A, +); +} + +late final _FPDFAnnot_GetColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetColor'); +late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Check if the annotation is of a type that has attachment points +/// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that +/// encompasses the texts affected by the annotation. They provide the +/// coordinates in the page where the annotation is attached. Only text markup +/// annotations (i.e. highlight, strikeout, squiggly, and underline) and link +/// annotations have quadpoints. +/// +/// annot - handle to an annotation. +/// +/// Returns true if the annotation is of a type that has quadpoints, false +/// otherwise. +int FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_HasAttachmentPoints(annot, +); +} + +late final _FPDFAnnot_HasAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasAttachmentPoints'); +late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr.asFunction(); + +/// Experimental API. +/// Replace the attachment points (i.e. quadpoints) set of an annotation at +/// |quad_index|. This index needs to be within the result of +/// FPDFAnnot_CountAttachmentPoints(). +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. +/// +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - the quadpoints to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_SetAttachmentPoints(annot, +quad_index, +quad_points, +); +} + +late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetAttachmentPoints'); +late final _FPDFAnnot_SetAttachmentPoints = _FPDFAnnot_SetAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Append to the list of attachment points (i.e. quadpoints) of an annotation. +/// If the annotation's appearance stream is defined and this annotation is of a +/// type with quadpoints, then update the bounding box too if the new quadpoints +/// define a bigger one. +/// +/// annot - handle to an annotation. +/// quad_points - the quadpoints to be set. +/// +/// Returns true if successful. +int FPDFAnnot_AppendAttachmentPoints(FPDF_ANNOTATION annot, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_AppendAttachmentPoints(annot, +quad_points, +); +} + +late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_AppendAttachmentPoints'); +late final _FPDFAnnot_AppendAttachmentPoints = _FPDFAnnot_AppendAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Get the number of sets of quadpoints of an annotation. +/// +/// annot - handle to an annotation. +/// +/// Returns the number of sets of quadpoints, or 0 on failure. +int FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_CountAttachmentPoints(annot, +); +} + +late final _FPDFAnnot_CountAttachmentPointsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_CountAttachmentPoints'); +late final _FPDFAnnot_CountAttachmentPoints = _FPDFAnnot_CountAttachmentPointsPtr.asFunction(); + +/// Experimental API. +/// Get the attachment points (i.e. quadpoints) of an annotation. +/// +/// annot - handle to an annotation. +/// quad_index - index of the set of quadpoints. +/// quad_points - receives the quadpoints; must not be NULL. +/// +/// Returns true if successful. +int FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFAnnot_GetAttachmentPoints(annot, +quad_index, +quad_points, +); +} + +late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetAttachmentPoints'); +late final _FPDFAnnot_GetAttachmentPoints = _FPDFAnnot_GetAttachmentPointsPtr.asFunction )>(); + +/// Experimental API. +/// Set the annotation rectangle defining the location of the annotation. If the +/// annotation's appearance stream is defined and this annotation is of a type +/// without quadpoints, then update the bounding box too if the new rectangle +/// defines a bigger one. +/// +/// annot - handle to an annotation. +/// rect - the annotation rectangle to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, +) { + return _FPDFAnnot_SetRect(annot, +rect, +); +} + +late final _FPDFAnnot_SetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetRect'); +late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction )>(); + +/// Experimental API. +/// Get the annotation rectangle defining the location of the annotation. +/// +/// annot - handle to an annotation. +/// rect - receives the rectangle; must not be NULL. +/// +/// Returns true if successful. +int FPDFAnnot_GetRect(FPDF_ANNOTATION annot, +ffi.Pointer rect, +) { + return _FPDFAnnot_GetRect(annot, +rect, +); +} + +late final _FPDFAnnot_GetRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetRect'); +late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction )>(); + +/// Experimental API. +/// Get the vertices of a polygon or polyline annotation. |buffer| is an array of +/// points of the annotation. If |length| is less than the returned length, or +/// |annot| or |buffer| is NULL, |buffer| will not be modified. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. +/// +/// Returns the number of points if the annotation is of type polygon or +/// polyline, 0 otherwise. +int FPDFAnnot_GetVertices(FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int length, +) { + return _FPDFAnnot_GetVertices(annot, +buffer, +length, +); +} + +late final _FPDFAnnot_GetVerticesPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetVertices'); +late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of paths in the ink list of an ink annotation. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// +/// Returns the number of paths in the ink list if the annotation is of type ink, +/// 0 otherwise. +int FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetInkListCount(annot, +); +} + +late final _FPDFAnnot_GetInkListCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetInkListCount'); +late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); + +/// Experimental API. +/// Get a path in the ink list of an ink annotation. |buffer| is an array of +/// points of the path. If |length| is less than the returned length, or |annot| +/// or |buffer| is NULL, |buffer| will not be modified. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// path_index - index of the path +/// buffer - buffer for holding the points. +/// length - length of the buffer in points. +/// +/// Returns the number of points of the path if the annotation is of type ink, 0 +/// otherwise. +int FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot, +int path_index, +ffi.Pointer buffer, +int length, +) { + return _FPDFAnnot_GetInkListPath(annot, +path_index, +buffer, +length, +); +} + +late final _FPDFAnnot_GetInkListPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetInkListPath'); +late final _FPDFAnnot_GetInkListPath = _FPDFAnnot_GetInkListPathPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the starting and ending coordinates of a line annotation. +/// +/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() +/// start - starting point +/// end - ending point +/// +/// Returns true if the annotation is of type line, |start| and |end| are not +/// NULL, false otherwise. +int FPDFAnnot_GetLine(FPDF_ANNOTATION annot, +ffi.Pointer start, +ffi.Pointer end, +) { + return _FPDFAnnot_GetLine(annot, +start, +end, +); +} + +late final _FPDFAnnot_GetLinePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFAnnot_GetLine'); +late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Set the characteristics of the annotation's border (rounded rectangle). +/// +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units +/// +/// Returns true if setting the border for |annot| succeeds, false otherwise. +/// +/// If |annot| contains an appearance stream that overrides the border values, +/// then the appearance stream will be removed on success. +int FPDFAnnot_SetBorder(FPDF_ANNOTATION annot, +double horizontal_radius, +double vertical_radius, +double border_width, +) { + return _FPDFAnnot_SetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, +); +} + +late final _FPDFAnnot_SetBorderPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetBorder'); +late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction(); + +/// Experimental API. +/// Get the characteristics of the annotation's border (rounded rectangle). +/// +/// annot - handle to an annotation +/// horizontal_radius - horizontal corner radius, in default user space units +/// vertical_radius - vertical corner radius, in default user space units +/// border_width - border width, in default user space units +/// +/// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are +/// not NULL, false otherwise. +int FPDFAnnot_GetBorder(FPDF_ANNOTATION annot, +ffi.Pointer horizontal_radius, +ffi.Pointer vertical_radius, +ffi.Pointer border_width, +) { + return _FPDFAnnot_GetBorder(annot, +horizontal_radius, +vertical_radius, +border_width, +); +} + +late final _FPDFAnnot_GetBorderPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetBorder'); +late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Get the JavaScript of an event of the annotation's additional actions. +/// |buffer| is only modified if |buflen| is large enough to hold the whole +/// JavaScript string. If |buflen| is smaller, the total size of the JavaScript +/// is still returned, but nothing is copied. If there is no JavaScript for +/// |event| in |annot|, an empty string is written to |buf| and 2 is returned, +/// denoting the size of the null terminator in the buffer. On other errors, +/// nothing is written to |buffer| and 0 is returned. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// event - event type, one of the FPDF_ANNOT_AACTION_* values. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes, including the 2-byte +/// null terminator. +int FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +int event, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormAdditionalActionJavaScript(hHandle, +annot, +event, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormAdditionalActionJavaScript'); +late final _FPDFAnnot_GetFormAdditionalActionJavaScript = _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction , int )>(); + +/// Experimental API. +/// Check if |annot|'s dictionary has |key| as a key. +/// +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns true if |key| exists. +int FPDFAnnot_HasKey(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_HasKey(annot, +key, +); +} + +late final _FPDFAnnot_HasKeyPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_HasKey'); +late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction(); + +/// Experimental API. +/// Get the type of the value corresponding to |key| in |annot|'s dictionary. +/// +/// annot - handle to an annotation. +/// key - the key to look for, encoded in UTF-8. +/// +/// Returns the type of the dictionary value. +int FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_GetValueType(annot, +key, +); +} + +late final _FPDFAnnot_GetValueTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetValueType'); +late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction(); + +/// Experimental API. +/// Set the string value corresponding to |key| in |annot|'s dictionary, +/// overwriting the existing value if any. The value type would be +/// FPDF_OBJECT_STRING after this function call succeeds. +/// +/// annot - handle to an annotation. +/// key - the key to the dictionary entry to be set, encoded in UTF-8. +/// value - the string value to be set, encoded in UTF-16LE. +/// +/// Returns true if successful. +int FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +FPDF_WIDESTRING value, +) { + return _FPDFAnnot_SetStringValue(annot, +key, +value, +); +} + +late final _FPDFAnnot_SetStringValuePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetStringValue'); +late final _FPDFAnnot_SetStringValue = _FPDFAnnot_SetStringValuePtr.asFunction(); + +/// Experimental API. +/// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| +/// is only modified if |buflen| is longer than the length of contents. Note that +/// if |key| does not exist in the dictionary or if |key|'s corresponding value +/// in the dictionary is not a string (i.e. the value is not of type +/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied +/// to |buffer| and the return value would be 2. On other errors, nothing would +/// be added to |buffer| and the return value would be 0. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetStringValue(annot, +key, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetStringValue'); +late final _FPDFAnnot_GetStringValue = _FPDFAnnot_GetStringValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the float value corresponding to |key| in |annot|'s dictionary. Writes +/// value to |value| and returns True if |key| exists in the dictionary and +/// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False +/// otherwise. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// value - receives the value, must not be NULL. +/// +/// Returns True if value found, False otherwise. +int FPDFAnnot_GetNumberValue(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +ffi.Pointer value, +) { + return _FPDFAnnot_GetNumberValue(annot, +key, +value, +); +} + +late final _FPDFAnnot_GetNumberValuePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetNumberValue'); +late final _FPDFAnnot_GetNumberValue = _FPDFAnnot_GetNumberValuePtr.asFunction )>(); + +/// Experimental API. +/// Set the AP (appearance string) in |annot|'s dictionary for a given +/// |appearanceMode|. +/// +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// value - the string value to be set, encoded in UTF-16LE. If +/// nullptr is passed, the AP is cleared for that mode. If the +/// mode is Normal, APs for all modes are cleared. +/// +/// Returns true if successful. +int FPDFAnnot_SetAP(FPDF_ANNOTATION annot, +int appearanceMode, +FPDF_WIDESTRING value, +) { + return _FPDFAnnot_SetAP(annot, +appearanceMode, +value, +); +} + +late final _FPDFAnnot_SetAPPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetAP'); +late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction(); + +/// Experimental API. +/// Get the AP (appearance string) from |annot|'s dictionary for a given +/// |appearanceMode|. +/// |buffer| is only modified if |buflen| is large enough to hold the whole AP +/// string. If |buflen| is smaller, the total size of the AP is still returned, +/// but nothing is copied. +/// If there is no appearance stream for |annot| in |appearanceMode|, an empty +/// string is written to |buf| and 2 is returned. +/// On other errors, nothing is written to |buffer| and 0 is returned. +/// +/// annot - handle to an annotation. +/// appearanceMode - the appearance mode (normal, rollover or down) for which +/// to get the AP. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetAP(FPDF_ANNOTATION annot, +int appearanceMode, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetAP(annot, +appearanceMode, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetAPPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetAP'); +late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the annotation corresponding to |key| in |annot|'s dictionary. Common +/// keys for linking annotations include "IRT" and "Popup". Must call +/// FPDFPage_CloseAnnot() when the annotation returned by this function is no +/// longer needed. +/// +/// annot - handle to an annotation. +/// key - the key to the requested dictionary entry, encoded in UTF-8. +/// +/// Returns a handle to the linked annotation object, or NULL on failure. +FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, +FPDF_BYTESTRING key, +) { + return _FPDFAnnot_GetLinkedAnnot(annot, +key, +); +} + +late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLinkedAnnot'); +late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr.asFunction(); + +/// Experimental API. +/// Get the annotation flags of |annot|. +/// +/// annot - handle to an annotation. +/// +/// Returns the annotation flags. +int FPDFAnnot_GetFlags(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFlags(annot, +); +} + +late final _FPDFAnnot_GetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFlags'); +late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); + +/// Experimental API. +/// Set the |annot|'s flags to be of the value |flags|. +/// +/// annot - handle to an annotation. +/// flags - the flag values to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, +int flags, +) { + return _FPDFAnnot_SetFlags(annot, +flags, +); +} + +late final _FPDFAnnot_SetFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFlags'); +late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); + +/// Experimental API. +/// Get the annotation flags of |annot|. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// +/// Returns the annotation flags specific to interactive forms. +int FPDFAnnot_GetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormFieldFlags(handle, +annot, +); +} + +late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldFlags'); +late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr.asFunction(); + +/// Experimental API. +/// Sets the form field flags for an interactive form annotation. +/// +/// handle - the handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// flags - the form field flags to be set. +/// +/// Returns true if successful. +int FPDFAnnot_SetFormFieldFlags(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int flags, +) { + return _FPDFAnnot_SetFormFieldFlags(handle, +annot, +flags, +); +} + +late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFormFieldFlags'); +late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr.asFunction(); + +/// Experimental API. +/// Retrieves an interactive form annotation whose rectangle contains a given +/// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned +/// is no longer needed. +/// +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// page - handle to the page, returned by FPDF_LoadPage function. +/// point - position in PDF "user space". +/// +/// Returns the interactive form annotation whose rectangle contains the given +/// coordinates on the page. If there is no such annotation, return NULL. +FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, +FPDF_PAGE page, +ffi.Pointer point, +) { + return _FPDFAnnot_GetFormFieldAtPoint(hHandle, +page, +point, +); +} + +late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFormFieldAtPoint'); +late final _FPDFAnnot_GetFormFieldAtPoint = _FPDFAnnot_GetFormFieldAtPointPtr.asFunction )>(); + +/// Experimental API. +/// Gets the name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the name string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldName(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldName'); +late final _FPDFAnnot_GetFormFieldName = _FPDFAnnot_GetFormFieldNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the alternate name of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the alternate name string, encoded in +/// UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldAlternateName(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldAlternateName(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldAlternateName'); +late final _FPDFAnnot_GetFormFieldAlternateName = _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the form field type of |annot|, which is an interactive form annotation. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// +/// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on +/// success. Returns -1 on error. +/// See field types in fpdf_formfill.h. +int FPDFAnnot_GetFormFieldType(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormFieldType(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormFieldType'); +late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr.asFunction(); + +/// Experimental API. +/// Gets the value of |annot|, which is an interactive form annotation. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value will +/// be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldValue(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldValue'); +late final _FPDFAnnot_GetFormFieldValue = _FPDFAnnot_GetFormFieldValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of options in the |annot|'s "Opt" dictionary. Intended for +/// use with listbox and combobox widget annotations. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns the number of options in "Opt" dictionary on success. Return value +/// will be -1 if annotation does not have an "Opt" dictionary or other error. +int FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetOptionCount(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetOptionCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetOptionCount'); +late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr.asFunction(); + +/// Experimental API. +/// Get the string value for the label of the option at |index| in |annot|'s +/// "Opt" dictionary. Intended for use with listbox and combobox widget +/// annotations. |buffer| is only modified if |buflen| is longer than the length +/// of contents. If index is out of range or in case of other error, nothing +/// will be added to |buffer| and the return value will be 0. Note that +/// return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +/// If |annot| does not have an "Opt" array, |index| is out of range or if any +/// other error occurs, returns 0. +int FPDFAnnot_GetOptionLabel(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +int index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetOptionLabel(hHandle, +annot, +index, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetOptionLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetOptionLabel'); +late final _FPDFAnnot_GetOptionLabel = _FPDFAnnot_GetOptionLabelPtr.asFunction , int )>(); + +/// Experimental API. +/// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary +/// is selected. Intended for use with listbox and combobox widget annotations. +/// +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// index - numeric index of the option in the "Opt" array. +/// +/// Returns true if the option at |index| in |annot|'s "Opt" dictionary is +/// selected, false otherwise. +int FPDFAnnot_IsOptionSelected(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int index, +) { + return _FPDFAnnot_IsOptionSelected(handle, +annot, +index, +); +} + +late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsOptionSelected'); +late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr.asFunction(); + +/// Experimental API. +/// Get the float value of the font size for an |annot| with variable text. +/// If 0, the font is to be auto-sized: its size is computed as a function of +/// the height of the annotation rectangle. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// value - Required. Float which will be set to font size on success. +/// +/// Returns true if the font size was set in |value|, false on error or if +/// |value| not provided. +int FPDFAnnot_GetFontSize(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer value, +) { + return _FPDFAnnot_GetFontSize(hHandle, +annot, +value, +); +} + +late final _FPDFAnnot_GetFontSizePtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_GetFontSize'); +late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction )>(); + +/// Experimental API. +/// Set the text color of an annotation. +/// +/// handle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R - the red component for the text color. +/// G - the green component for the text color. +/// B - the blue component for the text color. +/// +/// Returns true if successful. +/// +/// Currently supported subtypes: freetext. +/// The range for the color components is 0 to 255. +int FPDFAnnot_SetFontColor(FPDF_FORMHANDLE handle, +FPDF_ANNOTATION annot, +int R, +int G, +int B, +) { + return _FPDFAnnot_SetFontColor(handle, +annot, +R, +G, +B, +); +} + +late final _FPDFAnnot_SetFontColorPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_SetFontColor'); +late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction(); + +/// Experimental API. +/// Get the RGB value of the font color for an |annot| with variable text. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. +/// +/// Returns true if the font color was set, false on error or if the font +/// color was not provided. +int FPDFAnnot_GetFontColor(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer R, +ffi.Pointer G, +ffi.Pointer B, +) { + return _FPDFAnnot_GetFontColor(hHandle, +annot, +R, +G, +B, +); +} + +late final _FPDFAnnot_GetFontColorPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetFontColor'); +late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); + +/// Experimental API. +/// Determine if |annot| is a form widget that is checked. Intended for use with +/// checkbox and radio button widgets. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns true if |annot| is a form widget and is checked, false otherwise. +int FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_IsChecked(hHandle, +annot, +); +} + +late final _FPDFAnnot_IsCheckedPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_IsChecked'); +late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction(); + +/// Experimental API. +/// Set the list of focusable annotation subtypes. Annotations of subtype +/// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API +/// will override the existing subtypes. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - list of annotation subtype which can be tabbed over. +/// count - total number of annotation subtype in list. +/// Returns true if list of annotation subtype is set successfully, false +/// otherwise. +int FPDFAnnot_SetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, +) { + return _FPDFAnnot_SetFocusableSubtypes(hHandle, +subtypes, +count, +); +} + +late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_SetFocusableSubtypes'); +late final _FPDFAnnot_SetFocusableSubtypes = _FPDFAnnot_SetFocusableSubtypesPtr.asFunction , int )>(); + +/// Experimental API. +/// Get the count of focusable annotation subtypes as set by host +/// for a |hHandle|. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// Returns the count of focusable annotation subtypes or -1 on error. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypesCount(FPDF_FORMHANDLE hHandle, +) { + return _FPDFAnnot_GetFocusableSubtypesCount(hHandle, +); +} + +late final _FPDFAnnot_GetFocusableSubtypesCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFocusableSubtypesCount'); +late final _FPDFAnnot_GetFocusableSubtypesCount = _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction(); + +/// Experimental API. +/// Get the list of focusable annotation subtype as set by host. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// subtypes - receives the list of annotation subtype which can be tabbed +/// over. Caller must have allocated |subtypes| more than or +/// equal to the count obtained from +/// FPDFAnnot_GetFocusableSubtypesCount() API. +/// count - size of |subtypes|. +/// Returns true on success and set list of annotation subtype to |subtypes|, +/// false otherwise. +/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. +int FPDFAnnot_GetFocusableSubtypes(FPDF_FORMHANDLE hHandle, +ffi.Pointer subtypes, +int count, +) { + return _FPDFAnnot_GetFocusableSubtypes(hHandle, +subtypes, +count, +); +} + +late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< + ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_GetFocusableSubtypes'); +late final _FPDFAnnot_GetFocusableSubtypes = _FPDFAnnot_GetFocusableSubtypesPtr.asFunction , int )>(); + +/// Experimental API. +/// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. +/// +/// annot - handle to an annotation. +/// +/// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, +/// if the input annot is NULL or input annot's subtype is not link. +FPDF_LINK FPDFAnnot_GetLink(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetLink(annot, +); +} + +late final _FPDFAnnot_GetLinkPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetLink'); +late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); + +/// Experimental API. +/// Gets the count of annotations in the |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns number of controls in its control group or -1 on error. +int FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlCount(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormControlCountPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlCount'); +late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr.asFunction(); + +/// Experimental API. +/// Gets the index of |annot| in |annot|'s control group. +/// A group of interactive form annotations is collectively called a form +/// control group. Here, |annot|, an interactive form annotation, should be +/// either a radio button or a checkbox. +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment. +/// annot - handle to an annotation. +/// +/// Returns index of a given |annot| in its control group or -1 on error. +int FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFormControlIndex(hHandle, +annot, +); +} + +late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFormControlIndex'); +late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr.asFunction(); + +/// Experimental API. +/// Gets the export value of |annot| which is an interactive form annotation. +/// Intended for use with radio button and checkbox widget annotations. +/// |buffer| is only modified if |buflen| is longer than the length of contents. +/// In case of error, nothing will be added to |buffer| and the return value +/// will be 0. Note that return value of empty string is 2 for "\0\0". +/// +/// hHandle - handle to the form fill module, returned by +/// FPDFDOC_InitFormFillEnvironment(). +/// annot - handle to an interactive form annotation. +/// buffer - buffer for holding the value string, encoded in UTF-16LE. +/// buflen - length of the buffer in bytes. +/// +/// Returns the length of the string value in bytes. +int FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle, +FPDF_ANNOTATION annot, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAnnot_GetFormFieldExportValue(hHandle, +annot, +buffer, +buflen, +); +} + +late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldExportValue'); +late final _FPDFAnnot_GetFormFieldExportValue = _FPDFAnnot_GetFormFieldExportValuePtr.asFunction , int )>(); + +/// Experimental API. +/// Add a URI action to |annot|, overwriting the existing action, if any. +/// +/// annot - handle to a link annotation. +/// uri - the URI to be set, encoded in 7-bit ASCII. +/// +/// Returns true if successful. +int FPDFAnnot_SetURI(FPDF_ANNOTATION annot, +ffi.Pointer uri, +) { + return _FPDFAnnot_SetURI(annot, +uri, +); +} + +late final _FPDFAnnot_SetURIPtr = _lookup< + ffi.NativeFunction )>>('FPDFAnnot_SetURI'); +late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction )>(); + +/// Experimental API. +/// Get the attachment from |annot|. +/// +/// annot - handle to a file annotation. +/// +/// Returns the handle to the attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot, +) { + return _FPDFAnnot_GetFileAttachment(annot, +); +} + +late final _FPDFAnnot_GetFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_GetFileAttachment'); +late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr.asFunction(); + +/// Experimental API. +/// Add an embedded file with |name| to |annot|. +/// +/// annot - handle to a file annotation. +/// name - name of the new attachment. +/// +/// Returns a handle to the new attachment object, or NULL on failure. +FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, +FPDF_WIDESTRING name, +) { + return _FPDFAnnot_AddFileAttachment(annot, +name, +); +} + +late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< + ffi.NativeFunction>('FPDFAnnot_AddFileAttachment'); +late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr.asFunction(); + +/// Experimental API. +/// +/// Determine if |document| represents a tagged PDF. +/// +/// For the definition of tagged PDF, See (see 10.7 "Tagged PDF" in PDF +/// Reference 1.7). +/// +/// document - handle to a document. +/// +/// Returns |true| iff |document| is a tagged PDF. +int FPDFCatalog_IsTagged(FPDF_DOCUMENT document, +) { + return _FPDFCatalog_IsTagged(document, +); +} + +late final _FPDFCatalog_IsTaggedPtr = _lookup< + ffi.NativeFunction>('FPDFCatalog_IsTagged'); +late final _FPDFCatalog_IsTagged = _FPDFCatalog_IsTaggedPtr.asFunction(); + +/// Experimental API. +/// Sets the language of |document| to |language|. +/// +/// document - handle to a document. +/// language - the language to set to. +/// +/// Returns TRUE on success. +int FPDFCatalog_SetLanguage(FPDF_DOCUMENT document, +FPDF_BYTESTRING language, +) { + return _FPDFCatalog_SetLanguage(document, +language, +); +} + +late final _FPDFCatalog_SetLanguagePtr = _lookup< + ffi.NativeFunction>('FPDFCatalog_SetLanguage'); +late final _FPDFCatalog_SetLanguage = _FPDFCatalog_SetLanguagePtr.asFunction(); + +/// Experimental API. +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// page_indices - An array of page indices to be imported. The first page is +/// zero. If |page_indices| is NULL, all pages from |src_doc| +/// are imported. +/// length - The length of the |page_indices| array. +/// index - The page index at which to insert the first imported page +/// into |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is +/// invalid. +int FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +ffi.Pointer page_indices, +int length, +int index, +) { + return _FPDF_ImportPagesByIndex(dest_doc, +src_doc, +page_indices, +length, +index, +); +} + +late final _FPDF_ImportPagesByIndexPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_ImportPagesByIndex'); +late final _FPDF_ImportPagesByIndex = _FPDF_ImportPagesByIndexPtr.asFunction , int , int )>(); + +/// Import pages to a FPDF_DOCUMENT. +/// +/// dest_doc - The destination document for the pages. +/// src_doc - The document to be imported. +/// pagerange - A page range string, Such as "1,3,5-7". The first page is one. +/// If |pagerange| is NULL, all pages from |src_doc| are imported. +/// index - The page index at which to insert the first imported page into +/// |dest_doc|. The first page is zero. +/// +/// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is +/// invalid or if |pagerange| cannot be read. +int FPDF_ImportPages(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +FPDF_BYTESTRING pagerange, +int index, +) { + return _FPDF_ImportPages(dest_doc, +src_doc, +pagerange, +index, +); +} + +late final _FPDF_ImportPagesPtr = _lookup< + ffi.NativeFunction>('FPDF_ImportPages'); +late final _FPDF_ImportPages = _FPDF_ImportPagesPtr.asFunction(); + +/// Experimental API. +/// Create a new document from |src_doc|. The pages of |src_doc| will be +/// combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per +/// |output_doc| page. +/// +/// src_doc - The document to be imported. +/// output_width - The output page width in PDF "user space" units. +/// output_height - The output page height in PDF "user space" units. +/// num_pages_on_x_axis - The number of pages on X Axis. +/// num_pages_on_y_axis - The number of pages on Y Axis. +/// +/// Return value: +/// A handle to the created document, or NULL on failure. +/// +/// Comments: +/// number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis +FPDF_DOCUMENT FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, +double output_width, +double output_height, +int num_pages_on_x_axis, +int num_pages_on_y_axis, +) { + return _FPDF_ImportNPagesToOne(src_doc, +output_width, +output_height, +num_pages_on_x_axis, +num_pages_on_y_axis, +); +} + +late final _FPDF_ImportNPagesToOnePtr = _lookup< + ffi.NativeFunction>('FPDF_ImportNPagesToOne'); +late final _FPDF_ImportNPagesToOne = _FPDF_ImportNPagesToOnePtr.asFunction(); + +/// Experimental API. +/// Create a template to generate form xobjects from |src_doc|'s page at +/// |src_page_index|, for use in |dest_doc|. +/// +/// Returns a handle on success, or NULL on failure. Caller owns the newly +/// created object. +FPDF_XOBJECT FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +int src_page_index, +) { + return _FPDF_NewXObjectFromPage(dest_doc, +src_doc, +src_page_index, +); +} + +late final _FPDF_NewXObjectFromPagePtr = _lookup< + ffi.NativeFunction>('FPDF_NewXObjectFromPage'); +late final _FPDF_NewXObjectFromPage = _FPDF_NewXObjectFromPagePtr.asFunction(); + +/// Experimental API. +/// Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage(). +/// FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected. +void FPDF_CloseXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_CloseXObject(xobject, +); +} + +late final _FPDF_CloseXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_CloseXObject'); +late final _FPDF_CloseXObject = _FPDF_CloseXObjectPtr.asFunction(); + +/// Experimental API. +/// Create a new form object from an FPDF_XOBJECT object. +/// +/// Returns a new form object on success, or NULL on failure. Caller owns the +/// newly created object. +FPDF_PAGEOBJECT FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject, +) { + return _FPDF_NewFormObjectFromXObject(xobject, +); +} + +late final _FPDF_NewFormObjectFromXObjectPtr = _lookup< + ffi.NativeFunction>('FPDF_NewFormObjectFromXObject'); +late final _FPDF_NewFormObjectFromXObject = _FPDF_NewFormObjectFromXObjectPtr.asFunction(); + +/// Copy the viewer preferences from |src_doc| into |dest_doc|. +/// +/// dest_doc - Document to write the viewer preferences into. +/// src_doc - Document to read the viewer preferences from. +/// +/// Returns TRUE on success. +int FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, +FPDF_DOCUMENT src_doc, +) { + return _FPDF_CopyViewerPreferences(dest_doc, +src_doc, +); +} + +late final _FPDF_CopyViewerPreferencesPtr = _lookup< + ffi.NativeFunction>('FPDF_CopyViewerPreferences'); +late final _FPDF_CopyViewerPreferences = _FPDF_CopyViewerPreferencesPtr.asFunction(); + +/// Function: FPDF_SaveAsCopy +/// Saves the copy of specified document in custom way. +/// Parameters: +/// document - Handle to document, as returned by +/// FPDF_LoadDocument() or FPDF_CreateNewDocument(). +/// pFileWrite - A pointer to a custom file write structure. +/// flags - Flags above that affect how the PDF gets saved. +/// Pass in 0 when there are no flags. +/// Return value: +/// TRUE for succeed, FALSE for failed. +int FPDF_SaveAsCopy(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +) { + return _FPDF_SaveAsCopy(document, +pFileWrite, +flags, +); +} + +late final _FPDF_SaveAsCopyPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD )>>('FPDF_SaveAsCopy'); +late final _FPDF_SaveAsCopy = _FPDF_SaveAsCopyPtr.asFunction , int )>(); + +/// Function: FPDF_SaveWithVersion +/// Same as FPDF_SaveAsCopy(), except the file version of the +/// saved document can be specified by the caller. +/// Parameters: +/// document - Handle to document. +/// pFileWrite - A pointer to a custom file write structure. +/// flags - The creating flags. +/// fileVersion - The PDF file version. File version: 14 for 1.4, +/// 15 for 1.5, ... +/// Return value: +/// TRUE if succeed, FALSE if failed. +int FPDF_SaveWithVersion(FPDF_DOCUMENT document, +ffi.Pointer pFileWrite, +int flags, +int fileVersion, +) { + return _FPDF_SaveWithVersion(document, +pFileWrite, +flags, +fileVersion, +); +} + +late final _FPDF_SaveWithVersionPtr = _lookup< + ffi.NativeFunction , FPDF_DWORD , ffi.Int )>>('FPDF_SaveWithVersion'); +late final _FPDF_SaveWithVersion = _FPDF_SaveWithVersionPtr.asFunction , int , int )>(); + +/// Get the first child of |bookmark|, or the first top-level bookmark item. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. Pass NULL for the first top +/// level item. +/// +/// Returns a handle to the first child of |bookmark| or the first top-level +/// bookmark item. NULL if no child or top-level bookmark found. +/// Note that another name for the bookmarks is the document outline, as +/// described in ISO 32000-1:2008, section 12.3.3. +FPDF_BOOKMARK FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetFirstChild(document, +bookmark, +); +} + +late final _FPDFBookmark_GetFirstChildPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetFirstChild'); +late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr.asFunction(); + +/// Get the next sibling of |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the current bookmark. +/// +/// Returns a handle to the next sibling of |bookmark|, or NULL if this is the +/// last bookmark at this level. +/// +/// Note that the caller is responsible for handling circular bookmark +/// references, as may arise from malformed documents. +FPDF_BOOKMARK FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetNextSibling(document, +bookmark, +); +} + +late final _FPDFBookmark_GetNextSiblingPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetNextSibling'); +late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr.asFunction(); + +/// Get the title of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// buffer - buffer for the title. May be NULL. +/// buflen - the length of the buffer in bytes. May be 0. +/// +/// Returns the number of bytes in the title, including the terminating NUL +/// character. The number of bytes is returned regardless of the |buffer| and +/// |buflen| parameters. +/// +/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The +/// string is terminated by a UTF16 NUL character. If |buflen| is less than the +/// required length, or |buffer| is NULL, |buffer| will not be modified. +int FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFBookmark_GetTitle(bookmark, +buffer, +buflen, +); +} + +late final _FPDFBookmark_GetTitlePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFBookmark_GetTitle'); +late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction , int )>(); + +/// Experimental API. +/// Get the number of chlidren of |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns a signed integer that represents the number of sub-items the given +/// bookmark has. If the value is positive, child items shall be shown by default +/// (open state). If the value is negative, child items shall be hidden by +/// default (closed state). Please refer to PDF 32000-1:2008, Table 153. +/// Returns 0 if the bookmark has no children or is invalid. +int FPDFBookmark_GetCount(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetCount(bookmark, +); +} + +late final _FPDFBookmark_GetCountPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetCount'); +late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); + +/// Find the bookmark with |title| in |document|. +/// +/// document - handle to the document. +/// title - the UTF-16LE encoded Unicode title for which to search. +/// +/// Returns the handle to the bookmark, or NULL if |title| can't be found. +/// +/// FPDFBookmark_Find() will always return the first bookmark found even if +/// multiple bookmarks have the same |title|. +FPDF_BOOKMARK FPDFBookmark_Find(FPDF_DOCUMENT document, +FPDF_WIDESTRING title, +) { + return _FPDFBookmark_Find(document, +title, +); +} + +late final _FPDFBookmark_FindPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_Find'); +late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction(); + +/// Get the destination associated with |bookmark|. +/// +/// document - handle to the document. +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the destination data, or NULL if no destination is +/// associated with |bookmark|. +FPDF_DEST FPDFBookmark_GetDest(FPDF_DOCUMENT document, +FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetDest(document, +bookmark, +); +} + +late final _FPDFBookmark_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetDest'); +late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction(); + +/// Get the action associated with |bookmark|. +/// +/// bookmark - handle to the bookmark. +/// +/// Returns the handle to the action data, or NULL if no action is associated +/// with |bookmark|. +/// If this function returns a valid handle, it is valid as long as |bookmark| is +/// valid. +/// If this function returns NULL, FPDFBookmark_GetDest() should be called to get +/// the |bookmark| destination data. +FPDF_ACTION FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark, +) { + return _FPDFBookmark_GetAction(bookmark, +); +} + +late final _FPDFBookmark_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFBookmark_GetAction'); +late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction(); + +/// Get the type of |action|. +/// +/// action - handle to the action. +/// +/// Returns one of: +/// PDFACTION_UNSUPPORTED +/// PDFACTION_GOTO +/// PDFACTION_REMOTEGOTO +/// PDFACTION_URI +/// PDFACTION_LAUNCH +int FPDFAction_GetType(FPDF_ACTION action, +) { + return _FPDFAction_GetType(action, +); +} + +late final _FPDFAction_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetType'); +late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); + +/// Get the destination of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. |action| must be a |PDFACTION_GOTO| or +/// |PDFACTION_REMOTEGOTO|. +/// +/// Returns a handle to the destination data, or NULL on error, typically +/// because the arguments were bad or the action was of the wrong type. +/// +/// In the case of |PDFACTION_REMOTEGOTO|, you must first call +/// FPDFAction_GetFilePath(), then load the document at that path, then pass +/// the document handle from that document as |document| to FPDFAction_GetDest(). +FPDF_DEST FPDFAction_GetDest(FPDF_DOCUMENT document, +FPDF_ACTION action, +) { + return _FPDFAction_GetDest(document, +action, +); +} + +late final _FPDFAction_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFAction_GetDest'); +late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction(); + +/// Get the file path of |action|. +/// +/// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or +/// |PDFACTION_REMOTEGOTO|. +/// buffer - a buffer for output the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +int FPDFAction_GetFilePath(FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetFilePath(action, +buffer, +buflen, +); +} + +late final _FPDFAction_GetFilePathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetFilePath'); +late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction , int )>(); + +/// Get the URI path of |action|. +/// +/// document - handle to the document. +/// action - handle to the action. Must be a |PDFACTION_URI|. +/// buffer - a buffer for the path string. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the URI path, including the trailing NUL +/// character, or 0 on error, typically because the arguments were bad or the +/// action was of the wrong type. +/// +/// The |buffer| may contain badly encoded data. The caller should validate the +/// output. e.g. Check to see if it is UTF-8. +/// +/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| +/// will not be modified. +/// +/// Historically, the documentation for this API claimed |buffer| is always +/// encoded in 7-bit ASCII, but did not actually enforce it. +/// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 +/// added that enforcement, but that did not work well for real world PDFs that +/// used UTF-8. As of this writing, this API reverted back to its original +/// behavior prior to commit d609e84cee. +int FPDFAction_GetURIPath(FPDF_DOCUMENT document, +FPDF_ACTION action, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFAction_GetURIPath(document, +action, +buffer, +buflen, +); +} + +late final _FPDFAction_GetURIPathPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetURIPath'); +late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction , int )>(); + +/// Get the page index of |dest|. +/// +/// document - handle to the document. +/// dest - handle to the destination. +/// +/// Returns the 0-based page index containing |dest|. Returns -1 on error. +int FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, +FPDF_DEST dest, +) { + return _FPDFDest_GetDestPageIndex(document, +dest, +); +} + +late final _FPDFDest_GetDestPageIndexPtr = _lookup< + ffi.NativeFunction>('FPDFDest_GetDestPageIndex'); +late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr.asFunction(); + +/// Experimental API. +/// Get the view (fit type) specified by |dest|. +/// +/// dest - handle to the destination. +/// pNumParams - receives the number of view parameters, which is at most 4. +/// pParams - buffer to write the view parameters. Must be at least 4 +/// FS_FLOATs long. +/// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if +/// |dest| does not specify a view. +int FPDFDest_GetView(FPDF_DEST dest, +ffi.Pointer pNumParams, +ffi.Pointer pParams, +) { + return _FPDFDest_GetView(dest, +pNumParams, +pParams, +); +} + +late final _FPDFDest_GetViewPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFDest_GetView'); +late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction , ffi.Pointer )>(); + +/// Get the (x, y, zoom) location of |dest| in the destination page, if the +/// destination is in [page /XYZ x y zoom] syntax. +/// +/// dest - handle to the destination. +/// hasXVal - out parameter; true if the x value is not null +/// hasYVal - out parameter; true if the y value is not null +/// hasZoomVal - out parameter; true if the zoom value is not null +/// x - out parameter; the x coordinate, in page coordinates. +/// y - out parameter; the y coordinate, in page coordinates. +/// zoom - out parameter; the zoom value. +/// Returns TRUE on successfully reading the /XYZ value. +/// +/// Note the [x, y, zoom] values are only set if the corresponding hasXVal, +/// hasYVal or hasZoomVal flags are true. +int FPDFDest_GetLocationInPage(FPDF_DEST dest, +ffi.Pointer hasXVal, +ffi.Pointer hasYVal, +ffi.Pointer hasZoomVal, +ffi.Pointer x, +ffi.Pointer y, +ffi.Pointer zoom, +) { + return _FPDFDest_GetLocationInPage(dest, +hasXVal, +hasYVal, +hasZoomVal, +x, +y, +zoom, +); +} + +late final _FPDFDest_GetLocationInPagePtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFDest_GetLocationInPage'); +late final _FPDFDest_GetLocationInPage = _FPDFDest_GetLocationInPagePtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Find a link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns a handle to the link, or NULL if no link found at the given point. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +FPDF_LINK FPDFLink_GetLinkAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkAtPoint(page, +x, +y, +); +} + +late final _FPDFLink_GetLinkAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkAtPoint'); +late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction(); + +/// Find the Z-order of link at point (|x|,|y|) on |page|. +/// +/// page - handle to the document page. +/// x - the x coordinate, in the page coordinate system. +/// y - the y coordinate, in the page coordinate system. +/// +/// Returns the Z-order of the link, or -1 if no link found at the given point. +/// Larger Z-order numbers are closer to the front. +/// +/// You can convert coordinates from screen coordinates to page coordinates using +/// FPDF_DeviceToPage(). +int FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, +double x, +double y, +) { + return _FPDFLink_GetLinkZOrderAtPoint(page, +x, +y, +); +} + +late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetLinkZOrderAtPoint'); +late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr.asFunction(); + +/// Get destination info for |link|. +/// +/// document - handle to the document. +/// link - handle to the link. +/// +/// Returns a handle to the destination, or NULL if there is no destination +/// associated with the link. In this case, you should call FPDFLink_GetAction() +/// to retrieve the action associated with |link|. +FPDF_DEST FPDFLink_GetDest(FPDF_DOCUMENT document, +FPDF_LINK link, +) { + return _FPDFLink_GetDest(document, +link, +); +} + +late final _FPDFLink_GetDestPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetDest'); +late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction(); + +/// Get action info for |link|. +/// +/// link - handle to the link. +/// +/// Returns a handle to the action associated to |link|, or NULL if no action. +/// If this function returns a valid handle, it is valid as long as |link| is +/// valid. +FPDF_ACTION FPDFLink_GetAction(FPDF_LINK link, +) { + return _FPDFLink_GetAction(link, +); +} + +late final _FPDFLink_GetActionPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAction'); +late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); + +/// Enumerates all the link annotations in |page|. +/// +/// page - handle to the page. +/// start_pos - the start position, should initially be 0 and is updated with +/// the next start position on return. +/// link_annot - the link handle for |startPos|. +/// +/// Returns TRUE on success. +int FPDFLink_Enumerate(FPDF_PAGE page, +ffi.Pointer start_pos, +ffi.Pointer link_annot, +) { + return _FPDFLink_Enumerate(page, +start_pos, +link_annot, +); +} + +late final _FPDFLink_EnumeratePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_Enumerate'); +late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction , ffi.Pointer )>(); + +/// Experimental API. +/// Gets FPDF_ANNOTATION object for |link_annot|. +/// +/// page - handle to the page in which FPDF_LINK object is present. +/// link_annot - handle to link annotation. +/// +/// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, +/// if the input link annot or page is NULL. +FPDF_ANNOTATION FPDFLink_GetAnnot(FPDF_PAGE page, +FPDF_LINK link_annot, +) { + return _FPDFLink_GetAnnot(page, +link_annot, +); +} + +late final _FPDFLink_GetAnnotPtr = _lookup< + ffi.NativeFunction>('FPDFLink_GetAnnot'); +late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction(); + +/// Get the rectangle for |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// rect - the annotation rectangle. +/// +/// Returns true on success. +int FPDFLink_GetAnnotRect(FPDF_LINK link_annot, +ffi.Pointer rect, +) { + return _FPDFLink_GetAnnotRect(link_annot, +rect, +); +} + +late final _FPDFLink_GetAnnotRectPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetAnnotRect'); +late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction )>(); + +/// Get the count of quadrilateral points to the |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// +/// Returns the count of quadrilateral points. +int FPDFLink_CountQuadPoints(FPDF_LINK link_annot, +) { + return _FPDFLink_CountQuadPoints(link_annot, +); +} + +late final _FPDFLink_CountQuadPointsPtr = _lookup< + ffi.NativeFunction>('FPDFLink_CountQuadPoints'); +late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); + +/// Get the quadrilateral points for the specified |quad_index| in |link_annot|. +/// +/// link_annot - handle to the link annotation. +/// quad_index - the specified quad point index. +/// quad_points - receives the quadrilateral points. +/// +/// Returns true on success. +int FPDFLink_GetQuadPoints(FPDF_LINK link_annot, +int quad_index, +ffi.Pointer quad_points, +) { + return _FPDFLink_GetQuadPoints(link_annot, +quad_index, +quad_points, +); +} + +late final _FPDFLink_GetQuadPointsPtr = _lookup< + ffi.NativeFunction )>>('FPDFLink_GetQuadPoints'); +late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction )>(); + +/// Experimental API +/// Gets an additional-action from |page|. +/// +/// page - handle to the page, as returned by FPDF_LoadPage(). +/// aa_type - the type of the page object's addtional-action, defined +/// in public/fpdf_formfill.h +/// +/// Returns the handle to the action data, or NULL if there is no +/// additional-action of type |aa_type|. +/// If this function returns a valid handle, it is valid as long as |page| is +/// valid. +FPDF_ACTION FPDF_GetPageAAction(FPDF_PAGE page, +int aa_type, +) { + return _FPDF_GetPageAAction(page, +aa_type, +); +} + +late final _FPDF_GetPageAActionPtr = _lookup< + ffi.NativeFunction>('FPDF_GetPageAAction'); +late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction(); + +/// Experimental API. +/// Get the file identifer defined in the trailer of |document|. +/// +/// document - handle to the document. +/// id_type - the file identifier type to retrieve. +/// buffer - a buffer for the file identifier. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the file identifier, including the NUL +/// terminator. +/// +/// The |buffer| is always a byte string. The |buffer| is followed by a NUL +/// terminator. If |buflen| is less than the returned length, or |buffer| is +/// NULL, |buffer| will not be modified. +int FPDF_GetFileIdentifier(FPDF_DOCUMENT document, +FPDF_FILEIDTYPE id_type, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetFileIdentifier(document, +id_type.value, +buffer, +buflen, +); +} + +late final _FPDF_GetFileIdentifierPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetFileIdentifier'); +late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction , int )>(); + +/// Get meta-data |tag| content from |document|. +/// +/// document - handle to the document. +/// tag - the tag to retrieve. The tag can be one of: +/// Title, Author, Subject, Keywords, Creator, Producer, +/// CreationDate, or ModDate. +/// For detailed explanations of these tags and their respective +/// values, please refer to PDF Reference 1.6, section 10.2.1, +/// 'Document Information Dictionary'. +/// buffer - a buffer for the tag. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the tag, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +/// +/// For linearized files, FPDFAvail_IsFormAvail must be called before this, and +/// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there +/// is no guarantee the metadata has been loaded. +int FPDF_GetMetaText(FPDF_DOCUMENT document, +FPDF_BYTESTRING tag, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetMetaText(document, +tag, +buffer, +buflen, +); +} + +late final _FPDF_GetMetaTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetMetaText'); +late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction , int )>(); + +/// Get the page label for |page_index| from |document|. +/// +/// document - handle to the document. +/// page_index - the 0-based index of the page. +/// buffer - a buffer for the page label. May be NULL. +/// buflen - the length of the buffer, in bytes. May be 0. +/// +/// Returns the number of bytes in the page label, including trailing zeros. +/// +/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two +/// bytes of zeros indicating the end of the string. If |buflen| is less than +/// the returned length, or |buffer| is NULL, |buffer| will not be modified. +int FPDF_GetPageLabel(FPDF_DOCUMENT document, +int page_index, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_GetPageLabel(document, +page_index, +buffer, +buflen, +); +} + +late final _FPDF_GetPageLabelPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetPageLabel'); +late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction , int )>(); + +/// Function: FPDF_StructTree_GetForPage +/// Get the structure tree for a page. +/// Parameters: +/// page - Handle to the page, as returned by FPDF_LoadPage(). +/// Return value: +/// A handle to the structure tree or NULL on error. The caller owns the +/// returned handle and must use FPDF_StructTree_Close() to release it. +/// The handle should be released before |page| gets released. +FPDF_STRUCTTREE FPDF_StructTree_GetForPage(FPDF_PAGE page, +) { + return _FPDF_StructTree_GetForPage(page, +); +} + +late final _FPDF_StructTree_GetForPagePtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_GetForPage'); +late final _FPDF_StructTree_GetForPage = _FPDF_StructTree_GetForPagePtr.asFunction(); + +/// Function: FPDF_StructTree_Close +/// Release a resource allocated by FPDF_StructTree_GetForPage(). +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// Return value: +/// None. +void FPDF_StructTree_Close(FPDF_STRUCTTREE struct_tree, +) { + return _FPDF_StructTree_Close(struct_tree, +); +} + +late final _FPDF_StructTree_ClosePtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_Close'); +late final _FPDF_StructTree_Close = _FPDF_StructTree_ClosePtr.asFunction(); + +/// Function: FPDF_StructTree_CountChildren +/// Count the number of children for the structure tree. +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructTree_CountChildren(FPDF_STRUCTTREE struct_tree, +) { + return _FPDF_StructTree_CountChildren(struct_tree, +); +} + +late final _FPDF_StructTree_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_CountChildren'); +late final _FPDF_StructTree_CountChildren = _FPDF_StructTree_CountChildrenPtr.asFunction(); + +/// Function: FPDF_StructTree_GetChildAtIndex +/// Get a child in the structure tree. +/// Parameters: +/// struct_tree - Handle to the structure tree, as returned by +/// FPDF_StructTree_LoadPage(). +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. The caller does not +/// own the handle. The handle remains valid as long as |struct_tree| +/// remains valid. +/// Comments: +/// The |index| must be less than the FPDF_StructTree_CountChildren() +/// return value. +FPDF_STRUCTELEMENT FPDF_StructTree_GetChildAtIndex(FPDF_STRUCTTREE struct_tree, +int index, +) { + return _FPDF_StructTree_GetChildAtIndex(struct_tree, +index, +); +} + +late final _FPDF_StructTree_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructTree_GetChildAtIndex'); +late final _FPDF_StructTree_GetChildAtIndex = _FPDF_StructTree_GetChildAtIndexPtr.asFunction(); + +/// Function: FPDF_StructElement_GetAltText +/// Get the alt text for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the alt text. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the alt text, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetAltText(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetAltText(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetAltTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetAltText'); +late final _FPDF_StructElement_GetAltText = _FPDF_StructElement_GetAltTextPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetActualText +/// Get the actual text for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the actual text. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the actual text, including the terminating +/// NUL character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetActualText(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetActualText(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetActualTextPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetActualText'); +late final _FPDF_StructElement_GetActualText = _FPDF_StructElement_GetActualTextPtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_GetID +/// Get the ID for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the ID string. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the ID string, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetID(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetID(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetIDPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetID'); +late final _FPDF_StructElement_GetID = _FPDF_StructElement_GetIDPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetLang +/// Get the case-insensitive IETF BCP 47 language code for an element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output the lang string. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the ID string, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetLang(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetLang(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetLangPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetLang'); +late final _FPDF_StructElement_GetLang = _FPDF_StructElement_GetLangPtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetStringAttribute +/// Get a struct element attribute of type "name" or "string". +/// Parameters: +/// struct_element - Handle to the struct element. +/// attr_name - The name of the attribute to retrieve. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the attribute value, including the +/// terminating NUL character. The number of bytes is returned +/// regardless of the |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetStringAttribute(FPDF_STRUCTELEMENT struct_element, +FPDF_BYTESTRING attr_name, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetStringAttribute(struct_element, +attr_name, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetStringAttributePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetStringAttribute'); +late final _FPDF_StructElement_GetStringAttribute = _FPDF_StructElement_GetStringAttributePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_GetMarkedContentID +/// Get the marked content ID for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The marked content ID of the element. If no ID exists, returns +/// -1. +/// Comments: +/// FPDF_StructElement_GetMarkedContentIdAtIndex() may be able to +/// extract more marked content IDs out of |struct_element|. This API +/// may be deprecated in the future. +int FPDF_StructElement_GetMarkedContentID(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetMarkedContentID(struct_element, +); +} + +late final _FPDF_StructElement_GetMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentID'); +late final _FPDF_StructElement_GetMarkedContentID = _FPDF_StructElement_GetMarkedContentIDPtr.asFunction(); + +/// Function: FPDF_StructElement_GetType +/// Get the type (/S) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the type, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetType(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetType(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetTypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetType'); +late final _FPDF_StructElement_GetType = _FPDF_StructElement_GetTypePtr.asFunction , int )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetObjType +/// Get the object type (/Type) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the object type, including the terminating +/// NUL character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetObjType(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetObjType(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetObjTypePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetObjType'); +late final _FPDF_StructElement_GetObjType = _FPDF_StructElement_GetObjTypePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_GetTitle +/// Get the title (/T) for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// buffer - A buffer for output. May be NULL. +/// buflen - The length of the buffer, in bytes. May be 0. +/// Return value: +/// The number of bytes in the title, including the terminating NUL +/// character. The number of bytes is returned regardless of the +/// |buffer| and |buflen| parameters. +/// Comments: +/// Regardless of the platform, the |buffer| is always in UTF-16LE +/// encoding. The string is terminated by a UTF16 NUL character. If +/// |buflen| is less than the required length, or |buffer| is NULL, +/// |buffer| will not be modified. +int FPDF_StructElement_GetTitle(FPDF_STRUCTELEMENT struct_element, +ffi.Pointer buffer, +int buflen, +) { + return _FPDF_StructElement_GetTitle(struct_element, +buffer, +buflen, +); +} + +late final _FPDF_StructElement_GetTitlePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetTitle'); +late final _FPDF_StructElement_GetTitle = _FPDF_StructElement_GetTitlePtr.asFunction , int )>(); + +/// Function: FPDF_StructElement_CountChildren +/// Count the number of children for the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructElement_CountChildren(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_CountChildren(struct_element, +); +} + +late final _FPDF_StructElement_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_CountChildren'); +late final _FPDF_StructElement_CountChildren = _FPDF_StructElement_CountChildrenPtr.asFunction(); + +/// Function: FPDF_StructElement_GetChildAtIndex +/// Get a child in the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. +/// Comments: +/// If the child exists but is not an element, then this function will +/// return NULL. This will also return NULL for out of bounds indices. +/// The |index| must be less than the FPDF_StructElement_CountChildren() +/// return value. +FPDF_STRUCTELEMENT FPDF_StructElement_GetChildAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetChildAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetChildAtIndex'); +late final _FPDF_StructElement_GetChildAtIndex = _FPDF_StructElement_GetChildAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetChildMarkedContentID +/// Get the child's content id +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the child, 0-based. +/// Return value: +/// The marked content ID of the child. If no ID exists, returns -1. +/// Comments: +/// If the child exists but is not a stream or object, then this +/// function will return -1. This will also return -1 for out of bounds +/// indices. Compared to FPDF_StructElement_GetMarkedContentIdAtIndex, +/// it is scoped to the current page. +/// The |index| must be less than the FPDF_StructElement_CountChildren() +/// return value. +int FPDF_StructElement_GetChildMarkedContentID(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetChildMarkedContentID(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetChildMarkedContentIDPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetChildMarkedContentID'); +late final _FPDF_StructElement_GetChildMarkedContentID = _FPDF_StructElement_GetChildMarkedContentIDPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetParent +/// Get the parent of the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The parent structure element or NULL on error. +/// Comments: +/// If structure element is StructTreeRoot, then this function will +/// return NULL. +FPDF_STRUCTELEMENT FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetParent(struct_element, +); +} + +late final _FPDF_StructElement_GetParentPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetParent'); +late final _FPDF_StructElement_GetParent = _FPDF_StructElement_GetParentPtr.asFunction(); + +/// Function: FPDF_StructElement_GetAttributeCount +/// Count the number of attributes for the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The number of attributes, or -1 on error. +int FPDF_StructElement_GetAttributeCount(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetAttributeCount(struct_element, +); +} + +late final _FPDF_StructElement_GetAttributeCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetAttributeCount'); +late final _FPDF_StructElement_GetAttributeCount = _FPDF_StructElement_GetAttributeCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetAttributeAtIndex +/// Get an attribute object in the structure element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index for the attribute object, 0-based. +/// Return value: +/// The attribute object at the n-th index or NULL on error. +/// Comments: +/// If the attribute object exists but is not a dict, then this +/// function will return NULL. This will also return NULL for out of +/// bounds indices. The caller does not own the handle. The handle +/// remains valid as long as |struct_element| remains valid. +/// The |index| must be less than the +/// FPDF_StructElement_GetAttributeCount() return value. +FPDF_STRUCTELEMENT_ATTR FPDF_StructElement_GetAttributeAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetAttributeAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetAttributeAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetAttributeAtIndex'); +late final _FPDF_StructElement_GetAttributeAtIndex = _FPDF_StructElement_GetAttributeAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetCount +/// Count the number of attributes in a structure element attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// Return value: +/// The number of attributes, or -1 on error. +int FPDF_StructElement_Attr_GetCount(FPDF_STRUCTELEMENT_ATTR struct_attribute, +) { + return _FPDF_StructElement_Attr_GetCount(struct_attribute, +); +} + +late final _FPDF_StructElement_Attr_GetCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetCount'); +late final _FPDF_StructElement_Attr_GetCount = _FPDF_StructElement_Attr_GetCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetName +/// Get the name of an attribute in a structure element attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// index - The index of attribute in the map. +/// buffer - A buffer for output. May be NULL. This is only +/// modified if |buflen| is longer than the length +/// of the key. Optional, pass null to just +/// retrieve the size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the +/// minimum buffer size to contain the key. Not +/// filled if FALSE is returned. +/// Return value: +/// TRUE if the operation was successful, FALSE otherwise. +int FPDF_StructElement_Attr_GetName(FPDF_STRUCTELEMENT_ATTR struct_attribute, +int index, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetName(struct_attribute, +index, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetNamePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetName'); +late final _FPDF_StructElement_Attr_GetName = _FPDF_StructElement_Attr_GetNamePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetValue +/// Get a handle to a value for an attribute in a structure element +/// attribute map. +/// Parameters: +/// struct_attribute - Handle to the struct element attribute. +/// name - The attribute name. +/// Return value: +/// Returns a handle to the value associated with the input, if any. +/// Returns NULL on failure. The caller does not own the handle. +/// The handle remains valid as long as |struct_attribute| remains +/// valid. +FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetValue(FPDF_STRUCTELEMENT_ATTR struct_attribute, +FPDF_BYTESTRING name, +) { + return _FPDF_StructElement_Attr_GetValue(struct_attribute, +name, +); +} + +late final _FPDF_StructElement_Attr_GetValuePtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetValue'); +late final _FPDF_StructElement_Attr_GetValue = _FPDF_StructElement_Attr_GetValuePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetType +/// Get the type of an attribute in a structure element attribute map. +/// Parameters: +/// value - Handle to the value. +/// Return value: +/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of +/// failure. Note that this will never return FPDF_OBJECT_REFERENCE, as +/// references are always dereferenced. +int FPDF_StructElement_Attr_GetType(FPDF_STRUCTELEMENT_ATTR_VALUE value, +) { + return _FPDF_StructElement_Attr_GetType(value, +); +} + +late final _FPDF_StructElement_Attr_GetTypePtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetType'); +late final _FPDF_StructElement_Attr_GetType = _FPDF_StructElement_Attr_GetTypePtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetBooleanValue +/// Get the value of a boolean attribute in an attribute map as +/// FPDF_BOOL. FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_BOOLEAN for this property. +/// Parameters: +/// value - Handle to the value. +/// out_value - A pointer to variable that will receive the value. Not +/// filled if false is returned. +/// Return value: +/// Returns TRUE if the attribute maps to a boolean value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetBooleanValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer out_value, +) { + return _FPDF_StructElement_Attr_GetBooleanValue(value, +out_value, +); +} + +late final _FPDF_StructElement_Attr_GetBooleanValuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetBooleanValue'); +late final _FPDF_StructElement_Attr_GetBooleanValue = _FPDF_StructElement_Attr_GetBooleanValuePtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetNumberValue +/// Get the value of a number attribute in an attribute map as float. +/// FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_NUMBER for this property. +/// Parameters: +/// value - Handle to the value. +/// out_value - A pointer to variable that will receive the value. Not +/// filled if false is returned. +/// Return value: +/// Returns TRUE if the attribute maps to a number value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetNumberValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer out_value, +) { + return _FPDF_StructElement_Attr_GetNumberValue(value, +out_value, +); +} + +late final _FPDF_StructElement_Attr_GetNumberValuePtr = _lookup< + ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetNumberValue'); +late final _FPDF_StructElement_Attr_GetNumberValue = _FPDF_StructElement_Attr_GetNumberValuePtr.asFunction )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetStringValue +/// Get the value of a string attribute in an attribute map as string. +/// FPDF_StructElement_Attr_GetType() should have returned +/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME for this property. +/// Parameters: +/// value - Handle to the value. +/// buffer - A buffer for holding the returned key in UTF-16LE. +/// This is only modified if |buflen| is longer than the +/// length of the key. Optional, pass null to just +/// retrieve the size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the minimum +/// buffer size to contain the key. Not filled if FALSE is +/// returned. +/// Return value: +/// Returns TRUE if the attribute maps to a string value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetStringValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetStringValue(value, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetStringValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetStringValue'); +late final _FPDF_StructElement_Attr_GetStringValue = _FPDF_StructElement_Attr_GetStringValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetBlobValue +/// Get the value of a blob attribute in an attribute map as string. +/// Parameters: +/// value - Handle to the value. +/// buffer - A buffer for holding the returned value. This is only +/// modified if |buflen| is at least as long as the length +/// of the value. Optional, pass null to just retrieve the +/// size of the buffer needed. +/// buflen - The length of the buffer. +/// out_buflen - A pointer to variable that will receive the minimum +/// buffer size to contain the key. Not filled if FALSE is +/// returned. +/// Return value: +/// Returns TRUE if the attribute maps to a string value, FALSE +/// otherwise. +int FPDF_StructElement_Attr_GetBlobValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, +ffi.Pointer buffer, +int buflen, +ffi.Pointer out_buflen, +) { + return _FPDF_StructElement_Attr_GetBlobValue(value, +buffer, +buflen, +out_buflen, +); +} + +late final _FPDF_StructElement_Attr_GetBlobValuePtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetBlobValue'); +late final _FPDF_StructElement_Attr_GetBlobValue = _FPDF_StructElement_Attr_GetBlobValuePtr.asFunction , int , ffi.Pointer )>(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_CountChildren +/// Count the number of children values in an attribute. +/// Parameters: +/// value - Handle to the value. +/// Return value: +/// The number of children, or -1 on error. +int FPDF_StructElement_Attr_CountChildren(FPDF_STRUCTELEMENT_ATTR_VALUE value, +) { + return _FPDF_StructElement_Attr_CountChildren(value, +); +} + +late final _FPDF_StructElement_Attr_CountChildrenPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_CountChildren'); +late final _FPDF_StructElement_Attr_CountChildren = _FPDF_StructElement_Attr_CountChildrenPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_Attr_GetChildAtIndex +/// Get a child from an attribute. +/// Parameters: +/// value - Handle to the value. +/// index - The index for the child, 0-based. +/// Return value: +/// The child at the n-th index or NULL on error. +/// Comments: +/// The |index| must be less than the +/// FPDF_StructElement_Attr_CountChildren() return value. +FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetChildAtIndex(FPDF_STRUCTELEMENT_ATTR_VALUE value, +int index, +) { + return _FPDF_StructElement_Attr_GetChildAtIndex(value, +index, +); +} + +late final _FPDF_StructElement_Attr_GetChildAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_Attr_GetChildAtIndex'); +late final _FPDF_StructElement_Attr_GetChildAtIndex = _FPDF_StructElement_Attr_GetChildAtIndexPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetMarkedContentIdCount +/// Get the count of marked content ids for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// Return value: +/// The count of marked content ids or -1 if none exists. +int FPDF_StructElement_GetMarkedContentIdCount(FPDF_STRUCTELEMENT struct_element, +) { + return _FPDF_StructElement_GetMarkedContentIdCount(struct_element, +); +} + +late final _FPDF_StructElement_GetMarkedContentIdCountPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdCount'); +late final _FPDF_StructElement_GetMarkedContentIdCount = _FPDF_StructElement_GetMarkedContentIdCountPtr.asFunction(); + +/// Experimental API. +/// Function: FPDF_StructElement_GetMarkedContentIdAtIndex +/// Get the marked content id at a given index for a given element. +/// Parameters: +/// struct_element - Handle to the struct element. +/// index - The index of the marked content id, 0-based. +/// Return value: +/// The marked content ID of the element. If no ID exists, returns +/// -1. +/// Comments: +/// The |index| must be less than the +/// FPDF_StructElement_GetMarkedContentIdCount() return value. +/// This will likely supersede FPDF_StructElement_GetMarkedContentID(). +int FPDF_StructElement_GetMarkedContentIdAtIndex(FPDF_STRUCTELEMENT struct_element, +int index, +) { + return _FPDF_StructElement_GetMarkedContentIdAtIndex(struct_element, +index, +); +} + +late final _FPDF_StructElement_GetMarkedContentIdAtIndexPtr = _lookup< + ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdAtIndex'); +late final _FPDF_StructElement_GetMarkedContentIdAtIndex = _FPDF_StructElement_GetMarkedContentIdAtIndexPtr.asFunction(); + +/// Create a document availability provider. +/// +/// file_avail - pointer to file availability interface. +/// file - pointer to a file access interface. +/// +/// Returns a handle to the document availability provider, or NULL on error. +/// +/// FPDFAvail_Destroy() must be called when done with the availability provider. +FPDF_AVAIL FPDFAvail_Create(ffi.Pointer file_avail, +ffi.Pointer file, +) { + return _FPDFAvail_Create(file_avail, +file, +); +} + +late final _FPDFAvail_CreatePtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFAvail_Create'); +late final _FPDFAvail_Create = _FPDFAvail_CreatePtr.asFunction , ffi.Pointer )>(); + +/// Destroy the |avail| document availability provider. +/// +/// avail - handle to document availability provider to be destroyed. +void FPDFAvail_Destroy(FPDF_AVAIL avail, +) { + return _FPDFAvail_Destroy(avail, +); +} + +late final _FPDFAvail_DestroyPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_Destroy'); +late final _FPDFAvail_Destroy = _FPDFAvail_DestroyPtr.asFunction(); + +/// Checks if the document is ready for loading, if not, gets download hints. +/// +/// avail - handle to document availability provider. +/// hints - pointer to a download hints interface. +/// +/// Returns one of: +/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. +/// PDF_DATA_NOTAVAIL: Data not yet available. +/// PDF_DATA_AVAIL: Data available. +/// +/// Applications should call this function whenever new data arrives, and process +/// all the generated download hints, if any, until the function returns +/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. +/// if hints is nullptr, the function just check current document availability. +/// +/// Once all data is available, call FPDFAvail_GetDocument() to get a document +/// handle. +int FPDFAvail_IsDocAvail(FPDF_AVAIL avail, +ffi.Pointer hints, +) { + return _FPDFAvail_IsDocAvail(avail, +hints, +); +} + +late final _FPDFAvail_IsDocAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsDocAvail'); +late final _FPDFAvail_IsDocAvail = _FPDFAvail_IsDocAvailPtr.asFunction )>(); + +/// Get document from the availability provider. +/// +/// avail - handle to document availability provider. +/// password - password for decrypting the PDF file. Optional. +/// +/// Returns a handle to the document. +/// +/// When FPDFAvail_IsDocAvail() returns TRUE, call FPDFAvail_GetDocument() to +/// retrieve the document handle. +/// See the comments for FPDF_LoadDocument() regarding the encoding for +/// |password|. +FPDF_DOCUMENT FPDFAvail_GetDocument(FPDF_AVAIL avail, +FPDF_BYTESTRING password, +) { + return _FPDFAvail_GetDocument(avail, +password, +); +} + +late final _FPDFAvail_GetDocumentPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_GetDocument'); +late final _FPDFAvail_GetDocument = _FPDFAvail_GetDocumentPtr.asFunction(); + +/// Get the page number for the first available page in a linearized PDF. +/// +/// doc - document handle. +/// +/// Returns the zero-based index for the first available page. +/// +/// For most linearized PDFs, the first available page will be the first page, +/// however, some PDFs might make another page the first available page. +/// For non-linearized PDFs, this function will always return zero. +int FPDFAvail_GetFirstPageNum(FPDF_DOCUMENT doc, +) { + return _FPDFAvail_GetFirstPageNum(doc, +); +} + +late final _FPDFAvail_GetFirstPageNumPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_GetFirstPageNum'); +late final _FPDFAvail_GetFirstPageNum = _FPDFAvail_GetFirstPageNumPtr.asFunction(); + +/// Check if |page_index| is ready for loading, if not, get the +/// |FX_DOWNLOADHINTS|. +/// +/// avail - handle to document availability provider. +/// page_index - index number of the page. Zero for the first page. +/// hints - pointer to a download hints interface. Populated if +/// |page_index| is not available. +/// +/// Returns one of: +/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. +/// PDF_DATA_NOTAVAIL: Data not yet available. +/// PDF_DATA_AVAIL: Data available. +/// +/// This function can be called only after FPDFAvail_GetDocument() is called. +/// Applications should call this function whenever new data arrives and process +/// all the generated download |hints|, if any, until this function returns +/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. Applications can then perform page +/// loading. +/// if hints is nullptr, the function just check current availability of +/// specified page. +int FPDFAvail_IsPageAvail(FPDF_AVAIL avail, +int page_index, +ffi.Pointer hints, +) { + return _FPDFAvail_IsPageAvail(avail, +page_index, +hints, +); +} + +late final _FPDFAvail_IsPageAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsPageAvail'); +late final _FPDFAvail_IsPageAvail = _FPDFAvail_IsPageAvailPtr.asFunction )>(); + +/// Check if form data is ready for initialization, if not, get the +/// |FX_DOWNLOADHINTS|. +/// +/// avail - handle to document availability provider. +/// hints - pointer to a download hints interface. Populated if form is not +/// ready for initialization. +/// +/// Returns one of: +/// PDF_FORM_ERROR: A common eror, in general incorrect parameters. +/// PDF_FORM_NOTAVAIL: Data not available. +/// PDF_FORM_AVAIL: Data available. +/// PDF_FORM_NOTEXIST: No form data. +/// +/// This function can be called only after FPDFAvail_GetDocument() is called. +/// The application should call this function whenever new data arrives and +/// process all the generated download |hints|, if any, until the function +/// |PDF_FORM_ERROR|, |PDF_FORM_AVAIL| or |PDF_FORM_NOTEXIST|. +/// if hints is nullptr, the function just check current form availability. +/// +/// Applications can then perform page loading. It is recommend to call +/// FPDFDOC_InitFormFillEnvironment() when |PDF_FORM_AVAIL| is returned. +int FPDFAvail_IsFormAvail(FPDF_AVAIL avail, +ffi.Pointer hints, +) { + return _FPDFAvail_IsFormAvail(avail, +hints, +); +} + +late final _FPDFAvail_IsFormAvailPtr = _lookup< + ffi.NativeFunction )>>('FPDFAvail_IsFormAvail'); +late final _FPDFAvail_IsFormAvail = _FPDFAvail_IsFormAvailPtr.asFunction )>(); + +/// Check whether a document is a linearized PDF. +/// +/// avail - handle to document availability provider. +/// +/// Returns one of: +/// PDF_LINEARIZED +/// PDF_NOT_LINEARIZED +/// PDF_LINEARIZATION_UNKNOWN +/// +/// FPDFAvail_IsLinearized() will return |PDF_LINEARIZED| or |PDF_NOT_LINEARIZED| +/// when we have 1k of data. If the files size less than 1k, it returns +/// |PDF_LINEARIZATION_UNKNOWN| as there is insufficient information to determine +/// if the PDF is linearlized. +int FPDFAvail_IsLinearized(FPDF_AVAIL avail, +) { + return _FPDFAvail_IsLinearized(avail, +); +} + +late final _FPDFAvail_IsLinearizedPtr = _lookup< + ffi.NativeFunction>('FPDFAvail_IsLinearized'); +late final _FPDFAvail_IsLinearized = _FPDFAvail_IsLinearizedPtr.asFunction(); + +/// Setup an unsupported object handler. +/// +/// unsp_info - Pointer to an UNSUPPORT_INFO structure. +/// +/// Returns TRUE on success. +int FSDK_SetUnSpObjProcessHandler(ffi.Pointer unsp_info, +) { + return _FSDK_SetUnSpObjProcessHandler(unsp_info, +); +} + +late final _FSDK_SetUnSpObjProcessHandlerPtr = _lookup< + ffi.NativeFunction )>>('FSDK_SetUnSpObjProcessHandler'); +late final _FSDK_SetUnSpObjProcessHandler = _FSDK_SetUnSpObjProcessHandlerPtr.asFunction )>(); + +/// Set replacement function for calls to time(). +/// +/// This API is intended to be used only for testing, thus may cause PDFium to +/// behave poorly in production environments. +/// +/// func - Function pointer to alternate implementation of time(), or +/// NULL to restore to actual time() call itself. +void FSDK_SetTimeFunction(ffi.Pointer> func, +) { + return _FSDK_SetTimeFunction(func, +); +} + +late final _FSDK_SetTimeFunctionPtr = _lookup< + ffi.NativeFunction> )>>('FSDK_SetTimeFunction'); +late final _FSDK_SetTimeFunction = _FSDK_SetTimeFunctionPtr.asFunction> )>(); + +/// Set replacement function for calls to localtime(). +/// +/// This API is intended to be used only for testing, thus may cause PDFium to +/// behave poorly in production environments. +/// +/// func - Function pointer to alternate implementation of localtime(), or +/// NULL to restore to actual localtime() call itself. +void FSDK_SetLocaltimeFunction(ffi.Pointer Function(ffi.Pointer )>> func, +) { + return _FSDK_SetLocaltimeFunction(func, +); +} + +late final _FSDK_SetLocaltimeFunctionPtr = _lookup< + ffi.NativeFunction Function(ffi.Pointer )>> )>>('FSDK_SetLocaltimeFunction'); +late final _FSDK_SetLocaltimeFunction = _FSDK_SetLocaltimeFunctionPtr.asFunction Function(ffi.Pointer )>> )>(); + +/// Get the document's PageMode. +/// +/// doc - Handle to document. +/// +/// Returns one of the |PAGEMODE_*| flags defined above. +/// +/// The page mode defines how the document should be initially displayed. +int FPDFDoc_GetPageMode(FPDF_DOCUMENT document, +) { + return _FPDFDoc_GetPageMode(document, +); +} + +late final _FPDFDoc_GetPageModePtr = _lookup< + ffi.NativeFunction>('FPDFDoc_GetPageMode'); +late final _FPDFDoc_GetPageMode = _FPDFDoc_GetPageModePtr.asFunction(); + +/// Set "MediaBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetMediaBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetMediaBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetMediaBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetMediaBox'); +late final _FPDFPage_SetMediaBox = _FPDFPage_SetMediaBoxPtr.asFunction(); + +/// Set "CropBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetCropBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetCropBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetCropBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetCropBox'); +late final _FPDFPage_SetCropBox = _FPDFPage_SetCropBoxPtr.asFunction(); + +/// Set "BleedBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetBleedBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetBleedBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetBleedBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetBleedBox'); +late final _FPDFPage_SetBleedBox = _FPDFPage_SetBleedBoxPtr.asFunction(); + +/// Set "TrimBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetTrimBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetTrimBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetTrimBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetTrimBox'); +late final _FPDFPage_SetTrimBox = _FPDFPage_SetTrimBoxPtr.asFunction(); + +/// Set "ArtBox" entry to the page dictionary. +/// +/// page - Handle to a page. +/// left - The left of the rectangle. +/// bottom - The bottom of the rectangle. +/// right - The right of the rectangle. +/// top - The top of the rectangle. +void FPDFPage_SetArtBox(FPDF_PAGE page, +double left, +double bottom, +double right, +double top, +) { + return _FPDFPage_SetArtBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_SetArtBoxPtr = _lookup< + ffi.NativeFunction>('FPDFPage_SetArtBox'); +late final _FPDFPage_SetArtBox = _FPDFPage_SetArtBoxPtr.asFunction(); + +/// Get "MediaBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetMediaBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetMediaBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetMediaBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetMediaBox'); +late final _FPDFPage_GetMediaBox = _FPDFPage_GetMediaBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "CropBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetCropBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetCropBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetCropBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetCropBox'); +late final _FPDFPage_GetCropBox = _FPDFPage_GetCropBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "BleedBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetBleedBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetBleedBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetBleedBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetBleedBox'); +late final _FPDFPage_GetBleedBox = _FPDFPage_GetBleedBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "TrimBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetTrimBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetTrimBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetTrimBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetTrimBox'); +late final _FPDFPage_GetTrimBox = _FPDFPage_GetTrimBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Get "ArtBox" entry from the page dictionary. +/// +/// page - Handle to a page. +/// left - Pointer to a float value receiving the left of the rectangle. +/// bottom - Pointer to a float value receiving the bottom of the rectangle. +/// right - Pointer to a float value receiving the right of the rectangle. +/// top - Pointer to a float value receiving the top of the rectangle. +/// +/// On success, return true and write to the out parameters. Otherwise return +/// false and leave the out parameters unmodified. +int FPDFPage_GetArtBox(FPDF_PAGE page, +ffi.Pointer left, +ffi.Pointer bottom, +ffi.Pointer right, +ffi.Pointer top, +) { + return _FPDFPage_GetArtBox(page, +left, +bottom, +right, +top, +); +} + +late final _FPDFPage_GetArtBoxPtr = _lookup< + ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetArtBox'); +late final _FPDFPage_GetArtBox = _FPDFPage_GetArtBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); + +/// Apply transforms to |page|. +/// +/// If |matrix| is provided it will be applied to transform the page. +/// If |clipRect| is provided it will be used to clip the resulting page. +/// If neither |matrix| or |clipRect| are provided this method returns |false|. +/// Returns |true| if transforms are applied. +/// +/// This function will transform the whole page, and would take effect to all the +/// objects in the page. +/// +/// page - Page handle. +/// matrix - Transform matrix. +/// clipRect - Clipping rectangle. +int FPDFPage_TransFormWithClip(FPDF_PAGE page, +ffi.Pointer matrix, +ffi.Pointer clipRect, +) { + return _FPDFPage_TransFormWithClip(page, +matrix, +clipRect, +); +} + +late final _FPDFPage_TransFormWithClipPtr = _lookup< + ffi.NativeFunction , ffi.Pointer )>>('FPDFPage_TransFormWithClip'); +late final _FPDFPage_TransFormWithClip = _FPDFPage_TransFormWithClipPtr.asFunction , ffi.Pointer )>(); + +/// Transform (scale, rotate, shear, move) the clip path of page object. +/// page_object - Handle to a page object. Returned by +/// FPDFPageObj_NewImageObj(). +/// +/// a - The coefficient "a" of the matrix. +/// b - The coefficient "b" of the matrix. +/// c - The coefficient "c" of the matrix. +/// d - The coefficient "d" of the matrix. +/// e - The coefficient "e" of the matrix. +/// f - The coefficient "f" of the matrix. +void FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object, +double a, +double b, +double c, +double d, +double e, +double f, +) { + return _FPDFPageObj_TransformClipPath(page_object, +a, +b, +c, +d, +e, +f, +); +} + +late final _FPDFPageObj_TransformClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_TransformClipPath'); +late final _FPDFPageObj_TransformClipPath = _FPDFPageObj_TransformClipPathPtr.asFunction(); + +/// Experimental API. +/// Get the clip path of the page object. +/// +/// page object - Handle to a page object. Returned by e.g. +/// FPDFPage_GetObject(). +/// +/// Returns the handle to the clip path, or NULL on failure. The caller does not +/// take ownership of the returned FPDF_CLIPPATH. Instead, it remains valid until +/// FPDF_ClosePage() is called for the page containing |page_object|. +FPDF_CLIPPATH FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object, +) { + return _FPDFPageObj_GetClipPath(page_object, +); +} + +late final _FPDFPageObj_GetClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPageObj_GetClipPath'); +late final _FPDFPageObj_GetClipPath = _FPDFPageObj_GetClipPathPtr.asFunction(); + +/// Experimental API. +/// Get number of paths inside |clip_path|. +/// +/// clip_path - handle to a clip_path. +/// +/// Returns the number of objects in |clip_path| or -1 on failure. +int FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path, +) { + return _FPDFClipPath_CountPaths(clip_path, +); +} + +late final _FPDFClipPath_CountPathsPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_CountPaths'); +late final _FPDFClipPath_CountPaths = _FPDFClipPath_CountPathsPtr.asFunction(); + +/// Experimental API. +/// Get number of segments inside one path of |clip_path|. +/// +/// clip_path - handle to a clip_path. +/// path_index - index into the array of paths of the clip path. +/// +/// Returns the number of segments or -1 on failure. +int FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, +int path_index, +) { + return _FPDFClipPath_CountPathSegments(clip_path, +path_index, +); +} + +late final _FPDFClipPath_CountPathSegmentsPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_CountPathSegments'); +late final _FPDFClipPath_CountPathSegments = _FPDFClipPath_CountPathSegmentsPtr.asFunction(); + +/// Experimental API. +/// Get segment in one specific path of |clip_path| at index. +/// +/// clip_path - handle to a clip_path. +/// path_index - the index of a path. +/// segment_index - the index of a segment. +/// +/// Returns the handle to the segment, or NULL on failure. The caller does not +/// take ownership of the returned FPDF_PATHSEGMENT. Instead, it remains valid +/// until FPDF_ClosePage() is called for the page containing |clip_path|. +FPDF_PATHSEGMENT FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path, +int path_index, +int segment_index, +) { + return _FPDFClipPath_GetPathSegment(clip_path, +path_index, +segment_index, +); +} + +late final _FPDFClipPath_GetPathSegmentPtr = _lookup< + ffi.NativeFunction>('FPDFClipPath_GetPathSegment'); +late final _FPDFClipPath_GetPathSegment = _FPDFClipPath_GetPathSegmentPtr.asFunction(); + +/// Create a new clip path, with a rectangle inserted. +/// +/// Caller takes ownership of the returned FPDF_CLIPPATH. It should be freed with +/// FPDF_DestroyClipPath(). +/// +/// left - The left of the clip box. +/// bottom - The bottom of the clip box. +/// right - The right of the clip box. +/// top - The top of the clip box. +FPDF_CLIPPATH FPDF_CreateClipPath(double left, +double bottom, +double right, +double top, +) { + return _FPDF_CreateClipPath(left, +bottom, +right, +top, +); +} + +late final _FPDF_CreateClipPathPtr = _lookup< + ffi.NativeFunction>('FPDF_CreateClipPath'); +late final _FPDF_CreateClipPath = _FPDF_CreateClipPathPtr.asFunction(); + +/// Destroy the clip path. +/// +/// clipPath - A handle to the clip path. It will be invalid after this call. +void FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath, +) { + return _FPDF_DestroyClipPath(clipPath, +); +} + +late final _FPDF_DestroyClipPathPtr = _lookup< + ffi.NativeFunction>('FPDF_DestroyClipPath'); +late final _FPDF_DestroyClipPath = _FPDF_DestroyClipPathPtr.asFunction(); + +/// Clip the page content, the page content that outside the clipping region +/// become invisible. +/// +/// A clip path will be inserted before the page content stream or content array. +/// In this way, the page content will be clipped by this clip path. +/// +/// page - A page handle. +/// clipPath - A handle to the clip path. (Does not take ownership.) +void FPDFPage_InsertClipPath(FPDF_PAGE page, +FPDF_CLIPPATH clipPath, +) { + return _FPDFPage_InsertClipPath(page, +clipPath, +); +} + +late final _FPDFPage_InsertClipPathPtr = _lookup< + ffi.NativeFunction>('FPDFPage_InsertClipPath'); +late final _FPDFPage_InsertClipPath = _FPDFPage_InsertClipPathPtr.asFunction(); + +/// Flatten annotations and form fields into the page contents. +/// +/// page - handle to the page. +/// nFlag - One of the |FLAT_*| values denoting the page usage. +/// +/// Returns one of the |FLATTEN_*| values. +/// +/// Currently, all failures return |FLATTEN_FAIL| with no indication of the +/// cause. +int FPDFPage_Flatten(FPDF_PAGE page, +int nFlag, +) { + return _FPDFPage_Flatten(page, +nFlag, +); +} + +late final _FPDFPage_FlattenPtr = _lookup< + ffi.NativeFunction>('FPDFPage_Flatten'); +late final _FPDFPage_Flatten = _FPDFPage_FlattenPtr.asFunction(); + +/// Experimental API. +/// Gets the decoded data from the thumbnail of |page| if it exists. +/// This only modifies |buffer| if |buflen| less than or equal to the +/// size of the decoded data. Returns the size of the decoded +/// data or 0 if thumbnail DNE. Optional, pass null to just retrieve +/// the size of the buffer needed. +/// +/// page - handle to a page. +/// buffer - buffer for holding the decoded image data. +/// buflen - length of the buffer in bytes. +int FPDFPage_GetDecodedThumbnailData(FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFPage_GetDecodedThumbnailData(page, +buffer, +buflen, +); +} + +late final _FPDFPage_GetDecodedThumbnailDataPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetDecodedThumbnailData'); +late final _FPDFPage_GetDecodedThumbnailData = _FPDFPage_GetDecodedThumbnailDataPtr.asFunction , int )>(); + +/// Experimental API. +/// Gets the raw data from the thumbnail of |page| if it exists. +/// This only modifies |buffer| if |buflen| is less than or equal to +/// the size of the raw data. Returns the size of the raw data or 0 +/// if thumbnail DNE. Optional, pass null to just retrieve the size +/// of the buffer needed. +/// +/// page - handle to a page. +/// buffer - buffer for holding the raw image data. +/// buflen - length of the buffer in bytes. +int FPDFPage_GetRawThumbnailData(FPDF_PAGE page, +ffi.Pointer buffer, +int buflen, +) { + return _FPDFPage_GetRawThumbnailData(page, +buffer, +buflen, +); +} + +late final _FPDFPage_GetRawThumbnailDataPtr = _lookup< + ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetRawThumbnailData'); +late final _FPDFPage_GetRawThumbnailData = _FPDFPage_GetRawThumbnailDataPtr.asFunction , int )>(); + +/// Experimental API. +/// Returns the thumbnail of |page| as a FPDF_BITMAP. Returns a nullptr +/// if unable to access the thumbnail's stream. +/// +/// page - handle to a page. +FPDF_BITMAP FPDFPage_GetThumbnailAsBitmap(FPDF_PAGE page, +) { + return _FPDFPage_GetThumbnailAsBitmap(page, +); +} + +late final _FPDFPage_GetThumbnailAsBitmapPtr = _lookup< + ffi.NativeFunction>('FPDFPage_GetThumbnailAsBitmap'); +late final _FPDFPage_GetThumbnailAsBitmap = _FPDFPage_GetThumbnailAsBitmapPtr.asFunction(); + +} + +/// PDF text rendering modes +enum FPDF_TEXT_RENDERMODE { + FPDF_TEXTRENDERMODE_UNKNOWN(-1), + FPDF_TEXTRENDERMODE_FILL(0), + FPDF_TEXTRENDERMODE_STROKE(1), + FPDF_TEXTRENDERMODE_FILL_STROKE(2), + FPDF_TEXTRENDERMODE_INVISIBLE(3), + FPDF_TEXTRENDERMODE_FILL_CLIP(4), + FPDF_TEXTRENDERMODE_STROKE_CLIP(5), + FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP(6), + FPDF_TEXTRENDERMODE_CLIP(7); + + static const FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP; + + final int value; + const FPDF_TEXT_RENDERMODE(this.value); + + static FPDF_TEXT_RENDERMODE fromValue(int value) => switch (value) { + -1 => FPDF_TEXTRENDERMODE_UNKNOWN, + 0 => FPDF_TEXTRENDERMODE_FILL, + 1 => FPDF_TEXTRENDERMODE_STROKE, + 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, + 3 => FPDF_TEXTRENDERMODE_INVISIBLE, + 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, + 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, + 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, + 7 => FPDF_TEXTRENDERMODE_CLIP, + _ => throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), + }; + + @override + String toString() { + if (this == FPDF_TEXTRENDERMODE_CLIP) return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; + return super.toString(); + }} + +final class fpdf_action_t__ extends ffi.Opaque{ +} + +/// PDF types - use incomplete types (never completed) to force API type safety. +typedef FPDF_ACTION = ffi.Pointer; +final class fpdf_annotation_t__ extends ffi.Opaque{ +} + +typedef FPDF_ANNOTATION = ffi.Pointer; +final class fpdf_attachment_t__ extends ffi.Opaque{ +} + +typedef FPDF_ATTACHMENT = ffi.Pointer; +final class fpdf_avail_t__ extends ffi.Opaque{ +} + +typedef FPDF_AVAIL = ffi.Pointer; +final class fpdf_bitmap_t__ extends ffi.Opaque{ +} + +typedef FPDF_BITMAP = ffi.Pointer; +final class fpdf_bookmark_t__ extends ffi.Opaque{ +} + +typedef FPDF_BOOKMARK = ffi.Pointer; +final class fpdf_clippath_t__ extends ffi.Opaque{ +} + +typedef FPDF_CLIPPATH = ffi.Pointer; +final class fpdf_dest_t__ extends ffi.Opaque{ +} + +typedef FPDF_DEST = ffi.Pointer; +final class fpdf_document_t__ extends ffi.Opaque{ +} + +typedef FPDF_DOCUMENT = ffi.Pointer; +final class fpdf_font_t__ extends ffi.Opaque{ +} + +typedef FPDF_FONT = ffi.Pointer; +final class fpdf_form_handle_t__ extends ffi.Opaque{ +} + +typedef FPDF_FORMHANDLE = ffi.Pointer; +final class fpdf_glyphpath_t__ extends ffi.Opaque{ +} + +typedef FPDF_GLYPHPATH = ffi.Pointer; +final class fpdf_javascript_action_t extends ffi.Opaque{ +} + +typedef FPDF_JAVASCRIPT_ACTION = ffi.Pointer; +final class fpdf_link_t__ extends ffi.Opaque{ +} + +typedef FPDF_LINK = ffi.Pointer; +final class fpdf_page_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGE = ffi.Pointer; +final class fpdf_pagelink_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGELINK = ffi.Pointer; +final class fpdf_pageobject_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGEOBJECT = ffi.Pointer; +final class fpdf_pageobjectmark_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGEOBJECTMARK = ffi.Pointer; +final class fpdf_pagerange_t__ extends ffi.Opaque{ +} + +typedef FPDF_PAGERANGE = ffi.Pointer; +final class fpdf_pathsegment_t extends ffi.Opaque{ +} + +typedef FPDF_PATHSEGMENT = ffi.Pointer; +final class fpdf_schhandle_t__ extends ffi.Opaque{ +} + +typedef FPDF_SCHHANDLE = ffi.Pointer; +final class fpdf_signature_t__ extends ffi.Opaque{ +} + +typedef FPDF_SIGNATURE = ffi.Pointer; +typedef FPDF_SKIA_CANVAS = ffi.Pointer; +final class fpdf_structelement_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT = ffi.Pointer; +final class fpdf_structelement_attr_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT_ATTR = ffi.Pointer; +final class fpdf_structelement_attr_value_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTELEMENT_ATTR_VALUE = ffi.Pointer; +final class fpdf_structtree_t__ extends ffi.Opaque{ +} + +typedef FPDF_STRUCTTREE = ffi.Pointer; +final class fpdf_textpage_t__ extends ffi.Opaque{ +} + +typedef FPDF_TEXTPAGE = ffi.Pointer; +final class fpdf_widget_t__ extends ffi.Opaque{ +} + +typedef FPDF_WIDGET = ffi.Pointer; +final class fpdf_xobject_t__ extends ffi.Opaque{ +} + +typedef FPDF_XOBJECT = ffi.Pointer; +/// Basic data types +typedef FPDF_BOOL = ffi.Int; +typedef DartFPDF_BOOL = int; +typedef FPDF_RESULT = ffi.Int; +typedef DartFPDF_RESULT = int; +typedef FPDF_DWORD = ffi.UnsignedLong; +typedef DartFPDF_DWORD = int; +typedef FS_FLOAT = ffi.Float; +typedef DartFS_FLOAT = double; +/// Duplex types +enum _FPDF_DUPLEXTYPE_ { + DuplexUndefined(0), + Simplex(1), + DuplexFlipShortEdge(2), + DuplexFlipLongEdge(3); + + + final int value; + const _FPDF_DUPLEXTYPE_(this.value); + + static _FPDF_DUPLEXTYPE_ fromValue(int value) => switch (value) { + 0 => DuplexUndefined, + 1 => Simplex, + 2 => DuplexFlipShortEdge, + 3 => DuplexFlipLongEdge, + _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), + }; + +} + +/// String types +typedef FPDF_WCHAR = ffi.UnsignedShort; +typedef DartFPDF_WCHAR = int; +/// Public PDFium API type for byte strings. +typedef FPDF_BYTESTRING = ffi.Pointer; +/// The public PDFium API always uses UTF-16LE encoded wide strings, each +/// character uses 2 bytes (except surrogation), with the low byte first. +typedef FPDF_WIDESTRING = ffi.Pointer; +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. +final class FPDF_BSTR_ extends ffi.Struct{ + /// String buffer, manipulate only with FPDF_BStr_* methods. + external ffi.Pointer str; + + /// Length of the string, in bytes. + @ffi.Int() + external int len; + +} + +/// Structure for persisting a string beyond the duration of a callback. +/// Note: although represented as a char*, string may be interpreted as +/// a UTF-16LE formated string. Used only by XFA callbacks. +typedef FPDF_BSTR = FPDF_BSTR_; +/// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a +/// Windows unicode string, however, special care needs to be taken if you +/// expect to process Unicode larger than 0xffff. +/// +/// For Linux/Unix programmers: most compiler/library environments use 4 bytes +/// for a Unicode character, and you have to convert between FPDF_WIDESTRING and +/// system wide string by yourself. +typedef FPDF_STRING = ffi.Pointer; +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. +final class _FS_MATRIX_ extends ffi.Struct{ + @ffi.Float() + external double a; + + @ffi.Float() + external double b; + + @ffi.Float() + external double c; + + @ffi.Float() + external double d; + + @ffi.Float() + external double e; + + @ffi.Float() + external double f; + +} + +/// Matrix for transformation, in the form [a b c d e f], equivalent to: +/// | a b 0 | +/// | c d 0 | +/// | e f 1 | +/// +/// Translation is performed with [1 0 0 1 tx ty]. +/// Scaling is performed with [sx 0 0 sy 0 0]. +/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. +typedef FS_MATRIX = _FS_MATRIX_; +/// Rectangle area(float) in device or page coordinate system. +final class _FS_RECTF_ extends ffi.Struct{ + /// The x-coordinate of the left-top corner. + @ffi.Float() + external double left; + + /// The y-coordinate of the left-top corner. + @ffi.Float() + external double top; + + /// The x-coordinate of the right-bottom corner. + @ffi.Float() + external double right; + + /// The y-coordinate of the right-bottom corner. + @ffi.Float() + external double bottom; + +} + +/// Rectangle area(float) in device or page coordinate system. +typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; +typedef FS_RECTF = _FS_RECTF_; +/// Const Pointer to FS_RECTF structure. +typedef FS_LPCRECTF = ffi.Pointer; +/// Rectangle size. Coordinate system agnostic. +final class FS_SIZEF_ extends ffi.Struct{ + @ffi.Float() + external double width; + + @ffi.Float() + external double height; + +} + +/// Rectangle size. Coordinate system agnostic. +typedef FS_LPSIZEF = ffi.Pointer; +typedef FS_SIZEF = FS_SIZEF_; +/// Const Pointer to FS_SIZEF structure. +typedef FS_LPCSIZEF = ffi.Pointer; +/// 2D Point. Coordinate system agnostic. +final class FS_POINTF_ extends ffi.Struct{ + @ffi.Float() + external double x; + + @ffi.Float() + external double y; + +} + +/// 2D Point. Coordinate system agnostic. +typedef FS_LPPOINTF = ffi.Pointer; +typedef FS_POINTF = FS_POINTF_; +/// Const Pointer to FS_POINTF structure. +typedef FS_LPCPOINTF = ffi.Pointer; +final class _FS_QUADPOINTSF extends ffi.Struct{ + @FS_FLOAT() + external double x1; + + @FS_FLOAT() + external double y1; + + @FS_FLOAT() + external double x2; + + @FS_FLOAT() + external double y2; + + @FS_FLOAT() + external double x3; + + @FS_FLOAT() + external double y3; + + @FS_FLOAT() + external double x4; + + @FS_FLOAT() + external double y4; + +} + +typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; +/// Annotation enums. +typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; +typedef DartFPDF_ANNOTATION_SUBTYPE = int; +typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; +typedef DartFPDF_ANNOT_APPEARANCEMODE = int; +/// Dictionary value types. +typedef FPDF_OBJECT_TYPE = ffi.Int; +typedef DartFPDF_OBJECT_TYPE = int; +/// PDF renderer types - Experimental. +/// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. +enum FPDF_RENDERER_TYPE { + /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ + FPDF_RENDERERTYPE_AGG(0), + /// Skia - https://skia.org/ + FPDF_RENDERERTYPE_SKIA(1); + + + final int value; + const FPDF_RENDERER_TYPE(this.value); + + static FPDF_RENDERER_TYPE fromValue(int value) => switch (value) { + 0 => FPDF_RENDERERTYPE_AGG, + 1 => FPDF_RENDERERTYPE_SKIA, + _ => throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), + }; + +} + +/// Process-wide options for initializing the library. +final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct{ + /// Version number of the interface. Currently must be 2. + /// Support for version 1 will be deprecated in the future. + @ffi.Int() + external int version; + + /// Array of paths to scan in place of the defaults when using built-in + /// FXGE font loading code. The array is terminated by a NULL pointer. + /// The Array may be NULL itself to use the default paths. May be ignored + /// entirely depending upon the platform. + external ffi.Pointer> m_pUserFontPaths; + + /// Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. + external ffi.Pointer m_pIsolate; + + /// The embedder data slot to use in the v8::Isolate to store PDFium's + /// per-isolate data. The value needs to be in the range + /// [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most + /// embedders. + @ffi.UnsignedInt() + external int m_v8EmbedderSlot; + + /// Pointer to the V8::Platform to use. + external ffi.Pointer m_pPlatform; + + /// Explicit specification of core renderer to use. |m_RendererType| must be + /// a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, + /// or else the initialization will fail with an immediate crash. + /// Note that use of a specified |FPDF_RENDERER_TYPE| value for which the + /// corresponding render library is not included in the build will similarly + /// fail with an immediate crash. + @ffi.UnsignedInt() + external int m_RendererTypeAsInt; + +FPDF_RENDERER_TYPE get m_RendererType => FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); + +} + +/// Process-wide options for initializing the library. +typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; +/// Structure for custom file access. +final class FPDF_FILEACCESS extends ffi.Struct{ + /// File length, in bytes. + @ffi.UnsignedLong() + external int m_FileLen; + + /// A function pointer for getting a block of data from a specific position. + /// Position is specified by byte offset from the beginning of the file. + /// The pointer to the buffer is never NULL and the size is never 0. + /// The position and size will never go out of range of the file length. + /// It may be possible for PDFium to call this function multiple times for + /// the same position. + /// Return value: should be non-zero if successful, zero for error. + external ffi.Pointer param, ffi.UnsignedLong position, ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; + + /// A custom pointer for all implementation specific data. This pointer will + /// be used as the first parameter to the m_GetBlock callback. + external ffi.Pointer m_Param; + +} + +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. +final class FPDF_FILEHANDLER_ extends ffi.Struct{ + /// User-defined data. + /// Note: Callers can use this field to track controls. + external ffi.Pointer clientData; + + /// Callback function to release the current file stream object. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// None. + external ffi.Pointer clientData)>> Release; + + /// Callback function to retrieve the current file stream size. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// Size of file stream. + external ffi.Pointer clientData)>> GetSize; + + /// Callback function to read data from the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates reading position. + /// buffer - Memory buffer to store data which are read from + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be read from file stream, + /// in bytes. The buffer indicated by |buffer| must be + /// large enough to store specified data. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; + + /// Callback function to write data into the current file stream. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// offset - Offset position starts from the beginning of file + /// stream. This parameter indicates writing position. + /// buffer - Memory buffer contains data which is written into + /// file stream. This parameter should not be NULL. + /// size - Size of data which should be written into file + /// stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; + + /// Callback function to flush all internal accessing buffers. + /// + /// Parameters: + /// clientData - Pointer to user-defined data. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData)>> Flush; + + /// Callback function to change file size. + /// + /// Description: + /// This function is called under writing mode usually. Implementer + /// can determine whether to realize it based on application requests. + /// Parameters: + /// clientData - Pointer to user-defined data. + /// size - New size of file stream, in bytes. + /// Returns: + /// 0 for success, other value for failure. + external ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; + +} + +/// Structure for file reading or writing (I/O). +/// +/// Note: This is a handler and should be implemented by callers, +/// and is only used from XFA. +typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. +final class FPDF_COLORSCHEME_ extends ffi.Struct{ + @FPDF_DWORD() + external int path_fill_color; + + @FPDF_DWORD() + external int path_stroke_color; + + @FPDF_DWORD() + external int text_fill_color; + + @FPDF_DWORD() + external int text_stroke_color; + +} + +/// Struct for color scheme. +/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. +typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +final class _FPDF_SYSFONTINFO extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: Release + /// Give implementation a chance to release any data after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None + /// Comments: + /// Called by PDFium during the final cleanup process. + external ffi.Pointer pThis)>> Release; + + /// Method: EnumFonts + /// Enumerate all fonts installed on the system + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// pMapper - An opaque pointer to internal font mapper, used + /// when calling FPDF_AddInstalledFont(). + /// Return Value: + /// None + /// Comments: + /// Implementations should call FPDF_AddInstalledFont() function for + /// each font found. Only TrueType/OpenType and Type1 fonts are + /// accepted by PDFium. + external ffi.Pointer pThis, ffi.Pointer pMapper)>> EnumFonts; + + /// Method: MapFont + /// Use the system font mapper to get a font handle from requested + /// parameters. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if GetFont method is not implemented. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// weight - Weight of the requested font. 400 is normal and + /// 700 is bold. + /// bItalic - Italic option of the requested font, TRUE or + /// FALSE. + /// charset - Character set identifier for the requested font. + /// See above defined constants. + /// pitch_family - A combination of flags. See above defined + /// constants. + /// face - Typeface name. Currently use system local encoding + /// only. + /// bExact - Obsolete: this parameter is now ignored. + /// Return Value: + /// An opaque pointer for font handle, or NULL if system mapping is + /// not supported. + /// Comments: + /// If the system supports native font mapper (like Windows), + /// implementation can implement this method to get a font handle. + /// Otherwise, PDFium will do the mapping and then call GetFont + /// method. Only TrueType/OpenType and Type1 fonts are accepted + /// by PDFium. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Int weight, FPDF_BOOL bItalic, ffi.Int charset, ffi.Int pitch_family, ffi.Pointer face, ffi.Pointer bExact)>> MapFont; + + /// Method: GetFont + /// Get a handle to a particular font by its internal ID + /// Interface Version: + /// 1 + /// Implementation Required: + /// Required if MapFont method is not implemented. + /// Return Value: + /// An opaque pointer for font handle. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// face - Typeface name in system local encoding. + /// Comments: + /// If the system mapping not supported, PDFium will do the font + /// mapping and use this method to get a font handle. + external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Pointer face)>> GetFont; + + /// Method: GetFontData + /// Get font data from a font + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// table - TrueType/OpenType table identifier (refer to + /// TrueType specification), or 0 for the whole file. + /// buffer - The buffer receiving the font data. Can be NULL if + /// not provided. + /// buf_size - Buffer size, can be zero if not provided. + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + /// Comments: + /// Can read either the full font file, or a particular + /// TrueType/OpenType table. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.UnsignedInt table, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFontData; + + /// Method: GetFaceName + /// Get face name from a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// buffer - The buffer receiving the face name. Can be NULL if + /// not provided + /// buf_size - Buffer size, can be zero if not provided + /// Return Value: + /// Number of bytes needed, if buffer not provided or not large + /// enough, or number of bytes written into buffer otherwise. + external ffi.Pointer pThis, ffi.Pointer hFont, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFaceName; + + /// Method: GetFontCharset + /// Get character set information for a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// Character set identifier. See defined constants above. + external ffi.Pointer pThis, ffi.Pointer hFont)>> GetFontCharset; + + /// Method: DeleteFont + /// Delete a font handle + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// hFont - Font handle returned by MapFont or GetFont method + /// Return Value: + /// None + external ffi.Pointer pThis, ffi.Pointer hFont)>> DeleteFont; + +} + +/// Interface: FPDF_SYSFONTINFO +/// Interface for getting system font information and font mapping +typedef FPDF_SYSFONTINFO = _FPDF_SYSFONTINFO; +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +final class FPDF_CharsetFontMap_ extends ffi.Struct{ + /// Character Set Enum value, see FXFONT_*_CHARSET above. + @ffi.Int() + external int charset; + + /// Name of default font to use with that charset. + external ffi.Pointer fontname; + +} + +/// Struct: FPDF_CharsetFontMap +/// Provides the name of a font to use for a given charset value. +typedef FPDF_CharsetFontMap = FPDF_CharsetFontMap_; +/// IFPDF_RENDERINFO interface. +final class _IFSDK_PAUSE extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: NeedToPauseNow + /// Check if we need to pause a progressive process now. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// Non-zero for pause now, 0 for continue. + external ffi.Pointer pThis)>> NeedToPauseNow; + + /// A user defined data pointer, used by user's application. Can be NULL. + external ffi.Pointer user; + +} + +/// IFPDF_RENDERINFO interface. +typedef IFSDK_PAUSE = _IFSDK_PAUSE; +final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ + /// The image width in pixels. + @ffi.UnsignedInt() + external int width; + + /// The image height in pixels. + @ffi.UnsignedInt() + external int height; + + /// The image's horizontal pixel-per-inch. + @ffi.Float() + external double horizontal_dpi; + + /// The image's vertical pixel-per-inch. + @ffi.Float() + external double vertical_dpi; + + /// The number of bits used to represent each pixel. + @ffi.UnsignedInt() + external int bits_per_pixel; + + /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. + @ffi.Int() + external int colorspace; + + /// The image's marked content ID. Useful for pairing with associated alt-text. + /// A value of -1 indicates no ID. + @ffi.Int() + external int marked_content_id; + +} + +final class _IPDF_JsPlatform extends ffi.Struct{ + /// Version number of the interface. Currently must be 2. + @ffi.Int() + external int version; + + /// Method: app_alert + /// Pop up a dialog to show warning or hint. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Msg - A string containing the message to be displayed. + /// Title - The title of the dialog. + /// Type - The type of button group, one of the + /// JSPLATFORM_ALERT_BUTTON_* values above. + /// nIcon - The type of the icon, one of the + /// JSPLATFORM_ALERT_ICON_* above. + /// Return Value: + /// Option selected by user in dialogue, one of the + /// JSPLATFORM_ALERT_RETURN_* values above. + external ffi.Pointer pThis, FPDF_WIDESTRING Msg, FPDF_WIDESTRING Title, ffi.Int Type, ffi.Int Icon)>> app_alert; + + /// Method: app_beep + /// Causes the system to play a sound. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nType - The sound type, see JSPLATFORM_BEEP_TYPE_* + /// above. + /// Return Value: + /// None + external ffi.Pointer pThis, ffi.Int nType)>> app_beep; + + /// Method: app_response + /// Displays a dialog box containing a question and an entry field for + /// the user to reply to the question. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Question - The question to be posed to the user. + /// Title - The title of the dialog box. + /// Default - A default value for the answer to the question. If + /// not specified, no default value is presented. + /// cLabel - A short string to appear in front of and on the + /// same line as the edit text field. + /// bPassword - If true, indicates that the user's response should + /// be shown as asterisks (*) or bullets (?) to mask + /// the response, which might be sensitive information. + /// response - A string buffer allocated by PDFium, to receive the + /// user's response. + /// length - The length of the buffer in bytes. Currently, it is + /// always 2048. + /// Return Value: + /// Number of bytes the complete user input would actually require, not + /// including trailing zeros, regardless of the value of the length + /// parameter or the presence of the response buffer. + /// Comments: + /// No matter on what platform, the response buffer should be always + /// written using UTF-16LE encoding. If a response buffer is + /// present and the size of the user input exceeds the capacity of the + /// buffer as specified by the length parameter, only the + /// first "length" bytes of the user input are to be written to the + /// buffer. + external ffi.Pointer pThis, FPDF_WIDESTRING Question, FPDF_WIDESTRING Title, FPDF_WIDESTRING Default, FPDF_WIDESTRING cLabel, FPDF_BOOL bPassword, ffi.Pointer response, ffi.Int length)>> app_response; + + /// Method: Doc_getFilePath + /// Get the file path of the current document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// filePath - The string buffer to receive the file path. Can + /// be NULL. + /// length - The length of the buffer, number of bytes. Can + /// be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in the local encoding. + /// The return value always indicated number of bytes required for + /// the buffer, even when there is no buffer specified, or the buffer + /// size is less than required. In this case, the buffer will not + /// be modified. + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; + + /// Method: Doc_mail + /// Mails the data buffer as an attachment to all recipients, with or + /// without user interaction. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// mailData - Pointer to the data buffer to be sent. Can be NULL. + /// length - The size,in bytes, of the buffer pointed by + /// mailData parameter. Can be 0. + /// bUI - If true, the rest of the parameters are used in a + /// compose-new-message window that is displayed to the + /// user. If false, the cTo parameter is required and + /// all others are optional. + /// To - A semicolon-delimited list of recipients for the + /// message. + /// Subject - The subject of the message. The length limit is + /// 64 KB. + /// CC - A semicolon-delimited list of CC recipients for + /// the message. + /// BCC - A semicolon-delimited list of BCC recipients for + /// the message. + /// Msg - The content of the message. The length limit is + /// 64 KB. + /// Return Value: + /// None. + /// Comments: + /// If the parameter mailData is NULL or length is 0, the current + /// document will be mailed as an attachment to all recipients. + external ffi.Pointer pThis, ffi.Pointer mailData, ffi.Int length, FPDF_BOOL bUI, FPDF_WIDESTRING To, FPDF_WIDESTRING Subject, FPDF_WIDESTRING CC, FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; + + /// Method: Doc_print + /// Prints all or a specific number of pages of the document. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// bUI - If true, will cause a UI to be presented to the + /// user to obtain printing information and confirm + /// the action. + /// nStart - A 0-based index that defines the start of an + /// inclusive range of pages. + /// nEnd - A 0-based index that defines the end of an + /// inclusive page range. + /// bSilent - If true, suppresses the cancel dialog box while + /// the document is printing. The default is false. + /// bShrinkToFit - If true, the page is shrunk (if necessary) to + /// fit within the imageable area of the printed page. + /// bPrintAsImage - If true, print pages as an image. + /// bReverse - If true, print from nEnd to nStart. + /// bAnnotations - If true (the default), annotations are + /// printed. + /// Return Value: + /// None. + external ffi.Pointer pThis, FPDF_BOOL bUI, ffi.Int nStart, ffi.Int nEnd, FPDF_BOOL bSilent, FPDF_BOOL bShrinkToFit, FPDF_BOOL bPrintAsImage, FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; + + /// Method: Doc_submitForm + /// Send the form data to a specified URL. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// formData - Pointer to the data buffer to be sent. + /// length - The size,in bytes, of the buffer pointed by + /// formData parameter. + /// URL - The URL to send to. + /// Return Value: + /// None. + external ffi.Pointer pThis, ffi.Pointer formData, ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; + + /// Method: Doc_gotoPage + /// Jump to a specified page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// nPageNum - The specified page number, zero for the first page. + /// Return Value: + /// None. + external ffi.Pointer pThis, ffi.Int nPageNum)>> Doc_gotoPage; + + /// Method: Field_browse + /// Show a file selection dialog, and return the selected file path. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// filePath - Pointer to the data buffer to receive the file + /// path. Can be NULL. + /// length - The length of the buffer, in bytes. Can be 0. + /// Return Value: + /// Number of bytes the filePath consumes, including trailing zeros. + /// Comments: + /// The filePath should always be provided in local encoding. + external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; + + /// Pointer for embedder-specific data. Unused by PDFium, and despite + /// its name, can be any data the embedder desires, though traditionally + /// a FPDF_FORMFILLINFO interface. + external ffi.Pointer m_pFormfillinfo; + + /// Unused in v3, retain for compatibility. + external ffi.Pointer m_isolate; + + /// Unused in v3, retain for compatibility. + @ffi.UnsignedInt() + external int m_v8EmbedderSlot; + +} + +typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; +typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); +typedef DartTimerCallbackFunction = void Function(int idEvent); +/// Function signature for the callback function passed to the FFI_SetTimer +/// method. +/// Parameters: +/// idEvent - Identifier of the timer. +/// Return value: +/// None. +typedef TimerCallback = ffi.Pointer>; +/// Declares of a struct type to the local system time. +final class _FPDF_SYSTEMTIME extends ffi.Struct{ + /// years since 1900 + @ffi.UnsignedShort() + external int wYear; + + /// months since January - [0,11] + @ffi.UnsignedShort() + external int wMonth; + + /// days since Sunday - [0,6] + @ffi.UnsignedShort() + external int wDayOfWeek; + + /// day of the month - [1,31] + @ffi.UnsignedShort() + external int wDay; + + /// hours since midnight - [0,23] + @ffi.UnsignedShort() + external int wHour; + + /// minutes after the hour - [0,59] + @ffi.UnsignedShort() + external int wMinute; + + /// seconds after the minute - [0,59] + @ffi.UnsignedShort() + external int wSecond; + + /// milliseconds after the second - [0,999] + @ffi.UnsignedShort() + external int wMilliseconds; + +} + +/// Declares of a struct type to the local system time. +typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; +final class _FPDF_FORMFILLINFO extends ffi.Struct{ + /// Version number of the interface. + /// Version 1 contains stable interfaces. Version 2 has additional + /// experimental interfaces. + /// When PDFium is built without the XFA module, version can be 1 or 2. + /// With version 1, only stable interfaces are called. With version 2, + /// additional experimental interfaces are also called. + /// When PDFium is built with the XFA module, version must be 2. + /// All the XFA related interfaces are experimental. If PDFium is built with + /// the XFA module and version 1 then none of the XFA related interfaces + /// would be called. When PDFium is built with XFA module then the version + /// must be 2. + @ffi.Int() + external int version; + + /// Method: Release + /// Give the implementation a chance to release any resources after the + /// interface is no longer used. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Comments: + /// Called by PDFium during the final cleanup process. + /// Parameters: + /// pThis - Pointer to the interface structure itself + /// Return Value: + /// None + external ffi.Pointer pThis)>> Release; + + /// Method: FFI_Invalidate + /// Invalidate the client area within the specified rectangle. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// All positions are measured in PDF "user space". + /// Implementation should call FPDF_RenderPageBitmap() for repainting + /// the specified page area. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; + + /// Method: FFI_OutputSelectedRect + /// When the user selects text in form fields with the mouse, this + /// callback function will be invoked with the selected areas. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to the page. Returned by FPDF_LoadPage()/ + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return Value: + /// None. + /// Comments: + /// This callback function is useful for implementing special text + /// selection effects. An implementation should first record the + /// returned rectangles, then draw them one by one during the next + /// painting period. Lastly, it should remove all the recorded + /// rectangles when finished painting. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; + + /// Method: FFI_SetCursor + /// Set the Cursor shape. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nCursorType - Cursor type, see Flags for Cursor type for details. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Int nCursorType)>> FFI_SetCursor; + + /// Method: FFI_SetTimer + /// This method installs a system timer. An interval value is specified, + /// and every time that interval elapses, the system must call into the + /// callback function with the timer ID as returned by this function. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// uElapse - Specifies the time-out value, in milliseconds. + /// lpTimerFunc - A pointer to the callback function-TimerCallback. + /// Return value: + /// The timer identifier of the new timer if the function is successful. + /// An application passes this value to the FFI_KillTimer method to kill + /// the timer. Nonzero if it is successful; otherwise, it is zero. + external ffi.Pointer pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; + + /// Method: FFI_KillTimer + /// This method uninstalls a system timer, as set by an earlier call to + /// FFI_SetTimer. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nTimerID - The timer ID returned by FFI_SetTimer function. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Int nTimerID)>> FFI_KillTimer; + + /// Method: FFI_GetLocalTime + /// This method receives the current local time on the system. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// The local time. See FPDF_SYSTEMTIME above for details. + /// Note: Unused. + external ffi.Pointer pThis)>> FFI_GetLocalTime; + + /// Method: FFI_OnChange + /// This method will be invoked to notify the implementation when the + /// value of any FormField on the document had been changed. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// Return value: + /// None. + external ffi.Pointer pThis)>> FFI_OnChange; + + /// Method: FFI_GetPage + /// This method receives the page handle associated with a specified + /// page index. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// nPageIndex - Index number of the page. 0 for the first page. + /// Return value: + /// Handle to the page, as previously returned to the implementation by + /// FPDF_LoadPage(). + /// Comments: + /// The implementation is expected to keep track of the page handles it + /// receives from PDFium, and their mappings to page numbers. In some + /// cases, the document-level JavaScript action may refer to a page + /// which hadn't been loaded yet. To successfully run the Javascript + /// action, the implementation needs to load the page. + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; + + /// Method: FFI_GetCurrentPage + /// This method receives the handle to the current page. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes when V8 support is present, otherwise unused. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document. Returned by FPDF_LoadDocument(). + /// Return value: + /// Handle to the page. Returned by FPDF_LoadPage(). + /// Comments: + /// PDFium doesn't keep keep track of the "current page" (e.g. the one + /// that is most visible on screen), so it must ask the embedder for + /// this information. + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; + + /// Method: FFI_GetRotation + /// This method receives currently rotation of the page view. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page, as returned by FPDF_LoadPage(). + /// Return value: + /// A number to indicate the page rotation in 90 degree increments + /// in a clockwise direction: + /// 0 - 0 degrees + /// 1 - 90 degrees + /// 2 - 180 degrees + /// 3 - 270 degrees + /// Note: Unused. + external ffi.Pointer pThis, FPDF_PAGE page)>> FFI_GetRotation; + + /// Method: FFI_ExecuteNamedAction + /// This method will execute a named action. + /// Interface Version: + /// 1 + /// Implementation Required: + /// yes + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// namedAction - A byte string which indicates the named action, + /// terminated by 0. + /// Return value: + /// None. + /// Comments: + /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the + /// standard named actions, but note that a document may supply any + /// name of its choosing. + external ffi.Pointer pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; + + /// Method: FFI_SetTextFieldFocus + /// Called when a text field is getting or losing focus. + /// Interface Version: + /// 1 + /// Implementation Required: + /// no + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// value - The string value of the form field, in UTF-16LE + /// format. + /// valueLen - The length of the string value. This is the + /// number of characters, not bytes. + /// is_focus - True if the form field is getting focus, false + /// if the form field is losing focus. + /// Return value: + /// None. + /// Comments: + /// Only supports text fields and combobox fields. + external ffi.Pointer pThis, FPDF_WIDESTRING value, FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; + + /// Method: FFI_DoURIAction + /// Ask the implementation to navigate to a uniform resource identifier. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// bsURI - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// Return value: + /// None. + /// Comments: + /// If the embedder is version 2 or higher and have implementation for + /// FFI_DoURIActionWithKeyboardModifier, then + /// FFI_DoURIActionWithKeyboardModifier takes precedence over + /// FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. + external ffi.Pointer pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; + + /// Method: FFI_DoGoToAction + /// This action changes the view to a specified destination. + /// Interface Version: + /// 1 + /// Implementation Required: + /// No + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// nPageIndex - The index of the PDF page. + /// zoomMode - The zoom mode for viewing page. See below. + /// fPosArray - The float array which carries the position info. + /// sizeofArray - The size of float array. + /// PDFZoom values: + /// - XYZ = 1 + /// - FITPAGE = 2 + /// - FITHORZ = 3 + /// - FITVERT = 4 + /// - FITRECT = 5 + /// - FITBBOX = 6 + /// - FITBHORZ = 7 + /// - FITBVERT = 8 + /// Return value: + /// None. + /// Comments: + /// See the Destinations description of <> + /// in 8.2.1 for more details. + external ffi.Pointer pThis, ffi.Int nPageIndex, ffi.Int zoomMode, ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; + + /// Pointer to IPDF_JSPLATFORM interface. + /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then + /// JavaScript will be prevented from executing while rendering the document. + external ffi.Pointer m_pJsPlatform; + + /// Whether the XFA module is disabled when built with the XFA module. + /// Interface Version: + /// Ignored if |version| < 2. + @FPDF_BOOL() + external int xfa_disabled; + + /// Method: FFI_DisplayCaret + /// This method will show the caret at specified position. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - Left position of the client area in PDF page + /// coordinates. + /// top - Top position of the client area in PDF page + /// coordinates. + /// right - Right position of the client area in PDF page + /// coordinates. + /// bottom - Bottom position of the client area in PDF page + /// coordinates. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_BOOL bVisible, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; + + /// Method: FFI_GetCurrentPageIndex + /// This method will get the current page index. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// Return value: + /// The index of current page. + external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; + + /// Method: FFI_SetCurrentPage + /// This method will set the current page. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// iCurPage - The index of the PDF page. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; + + /// Method: FFI_GotoURL + /// This method will navigate to the specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// document - Handle to document from FPDF_LoadDocument(). + /// wsURL - The string value of the URL, in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; + + /// Method: FFI_GetPageViewRect + /// This method will get the current page view rectangle. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// left - The pointer to receive left position of the page + /// view area in PDF page coordinates. + /// top - The pointer to receive top position of the page + /// view area in PDF page coordinates. + /// right - The pointer to receive right position of the + /// page view area in PDF page coordinates. + /// bottom - The pointer to receive bottom position of the + /// page view area in PDF page coordinates. + /// Return value: + /// None. + external ffi.Pointer pThis, FPDF_PAGE page, ffi.Pointer left, ffi.Pointer top, ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; + + /// Method: FFI_PageEvent + /// This method fires when pages have been added to or deleted from + /// the XFA document. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page_count - The number of pages to be added or deleted. + /// event_type - See FXFA_PAGEVIEWEVENT_* above. + /// Return value: + /// None. + /// Comments: + /// The pages to be added or deleted always start from the last page + /// of document. This means that if parameter page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTADDED, 2 new pages have been + /// appended to the tail of document; If page_count is 2 and + /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages + /// have been deleted. + external ffi.Pointer pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; + + /// Method: FFI_PopupMenu + /// This method will track the right context menu for XFA fields. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// page - Handle to page. Returned by FPDF_LoadPage(). + /// hWidget - Always null, exists for compatibility. + /// menuFlag - The menu flags. Please refer to macro definition + /// of FXFA_MENU_XXX and this can be one or a + /// combination of these macros. + /// x - X position of the client area in PDF page + /// coordinates. + /// y - Y position of the client area in PDF page + /// coordinates. + /// Return value: + /// TRUE indicates success; otherwise false. + external ffi.Pointer pThis, FPDF_PAGE page, FPDF_WIDGET hWidget, ffi.Int menuFlag, ffi.Float x, ffi.Float y)>> FFI_PopupMenu; + + /// Method: FFI_OpenFile + /// This method will open the specified file with the specified mode. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// wsURL - The string value of the file URL, in UTF-16LE + /// format. + /// mode - The mode for open file, e.g. "rb" or "wb". + /// Return value: + /// The handle to FPDF_FILEHANDLER. + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int fileFlag, FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; + + /// Method: FFI_EmailTo + /// This method will email the specified file stream to the specified + /// contact. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// pTo - A semicolon-delimited list of recipients for the + /// message,in UTF-16LE format. + /// pSubject - The subject of the message,in UTF-16LE format. + /// pCC - A semicolon-delimited list of CC recipients for + /// the message,in UTF-16LE format. + /// pBcc - A semicolon-delimited list of BCC recipients for + /// the message,in UTF-16LE format. + /// pMsg - Pointer to the data buffer to be sent.Can be + /// NULL,in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Pointer fileHandler, FPDF_WIDESTRING pTo, FPDF_WIDESTRING pSubject, FPDF_WIDESTRING pCC, FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; + + /// Method: FFI_UploadTo + /// This method will upload the specified file stream to the + /// specified URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// pFileHandler - Handle to the FPDF_FILEHANDLER. + /// fileFlag - The file flag. Please refer to macro definition + /// of FXFA_SAVEAS_XXX and use one of these macros. + /// uploadTo - Pointer to the URL path, in UTF-16LE format. + /// Return value: + /// None. + external ffi.Pointer pThis, ffi.Pointer fileHandler, ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; + + /// Method: FFI_GetPlatform + /// This method will get the current platform. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// platform - Pointer to the data buffer to receive the + /// platform,in UTF-16LE format. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. + external ffi.Pointer pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; + + /// Method: FFI_GetLanguage + /// This method will get the current language. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// language - Pointer to the data buffer to receive the + /// current language. Can be NULL. + /// length - The length of the buffer in bytes. Can be + /// 0 to query the required size. + /// Return value: + /// The length of the buffer, number of bytes. + external ffi.Pointer pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; + + /// Method: FFI_DownloadFromURL + /// This method will download the specified file from the URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// URL - The string value of the file URL, in UTF-16LE + /// format. + /// Return value: + /// The handle to FPDF_FILEHANDLER. + external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; + + /// Method: FFI_PostRequestURL + /// This method will post the request to the server URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The post data,in UTF-16LE format. + /// wsContentType - The content type of the request data, in + /// UTF-16LE format. + /// wsEncode - The encode type, in UTF-16LE format. + /// wsHeader - The request header,in UTF-16LE format. + /// response - Pointer to the FPDF_BSTR to receive the response + /// data from the server, in UTF-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsContentType, FPDF_WIDESTRING wsEncode, FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; + + /// Method: FFI_PutRequestURL + /// This method will put the request to the server URL. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// Required for XFA, otherwise set to NULL. + /// Parameters: + /// pThis - Pointer to the interface structure itself. + /// wsURL - The string value of the server URL, in UTF-16LE + /// format. + /// wsData - The put data, in UTF-16LE format. + /// wsEncode - The encode type, in UTR-16LE format. + /// Return value: + /// TRUE indicates success, otherwise FALSE. + external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; + + /// Method: FFI_OnFocusChange + /// Called when the focused annotation is updated. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// No + /// Parameters: + /// param - Pointer to the interface structure itself. + /// annot - The focused annotation. + /// page_index - Index number of the page which contains the + /// focused annotation. 0 for the first page. + /// Return value: + /// None. + /// Comments: + /// This callback function is useful for implementing any view based + /// action such as scrolling the annotation rect into view. The + /// embedder should not copy and store the annot as its scope is + /// limited to this call only. + external ffi.Pointer param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; + + /// Method: FFI_DoURIActionWithKeyboardModifier + /// Ask the implementation to navigate to a uniform resource identifier + /// with the specified modifiers. + /// Interface Version: + /// Ignored if |version| < 2. + /// Implementation Required: + /// No + /// Parameters: + /// param - Pointer to the interface structure itself. + /// uri - A byte string which indicates the uniform + /// resource identifier, terminated by 0. + /// modifiers - Keyboard modifier that indicates which of + /// the virtual keys are down, if any. + /// Return value: + /// None. + /// Comments: + /// If the embedder who is version 2 and does not implement this API, + /// then a call will be redirected to FFI_DoURIAction. + /// See the URI actions description of <> + /// for more details. + external ffi.Pointer param, FPDF_BYTESTRING uri, ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; + +} + +typedef FPDF_FORMFILLINFO = _FPDF_FORMFILLINFO; +enum FPDFANNOT_COLORTYPE { + FPDFANNOT_COLORTYPE_Color(0), + FPDFANNOT_COLORTYPE_InteriorColor(1); + + + final int value; + const FPDFANNOT_COLORTYPE(this.value); + + static FPDFANNOT_COLORTYPE fromValue(int value) => switch (value) { + 0 => FPDFANNOT_COLORTYPE_Color, + 1 => FPDFANNOT_COLORTYPE_InteriorColor, + _ => throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), + }; + +} + +/// Structure for custom file write +final class FPDF_FILEWRITE_ extends ffi.Struct{ + /// Version number of the interface. Currently must be 1. + @ffi.Int() + external int version; + + /// Method: WriteBlock + /// Output a block of data in your custom way. + /// Interface Version: + /// 1 + /// Implementation Required: + /// Yes + /// Comments: + /// Called by function FPDF_SaveDocument + /// Parameters: + /// pThis - Pointer to the structure itself + /// pData - Pointer to a buffer to output + /// size - The size of the buffer. + /// Return value: + /// Should be non-zero if successful, zero for error. + external ffi.Pointer pThis, ffi.Pointer pData, ffi.UnsignedLong size)>> WriteBlock; + +} + +/// Structure for custom file write +typedef FPDF_FILEWRITE = FPDF_FILEWRITE_; +/// The file identifier entry type. See section 14.4 "File Identifiers" of the +/// ISO 32000-1:2008 spec. +enum FPDF_FILEIDTYPE { + FILEIDTYPE_PERMANENT(0), + FILEIDTYPE_CHANGING(1); + + + final int value; + const FPDF_FILEIDTYPE(this.value); + + static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { + 0 => FILEIDTYPE_PERMANENT, + 1 => FILEIDTYPE_CHANGING, + _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), + }; + +} + +/// Interface for checking whether sections of the file are available. +final class _FX_FILEAVAIL extends ffi.Struct{ + /// Version number of the interface. Must be 1. + @ffi.Int() + external int version; + + /// Reports if the specified data section is currently available. A section is + /// available if all bytes in the section are available. + /// + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// offset - the offset of the data section in the file. + /// size - the size of the data section. + /// + /// Returns true if the specified data section at |offset| of |size| + /// is available. + external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> IsDataAvail; + +} + +/// Interface for checking whether sections of the file are available. +typedef FX_FILEAVAIL = _FX_FILEAVAIL; +/// Download hints interface. Used to receive hints for further downloading. +final class _FX_DOWNLOADHINTS extends ffi.Struct{ + /// Version number of the interface. Must be 1. + @ffi.Int() + external int version; + + /// Add a section to be downloaded. + /// + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// offset - the offset of the hint reported to be downloaded. + /// size - the size of the hint reported to be downloaded. + /// + /// The |offset| and |size| of the section may not be unique. Part of the + /// section might be already available. The download manager must deal with + /// overlapping sections. + external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> AddSegment; + +} + +/// Download hints interface. Used to receive hints for further downloading. +typedef FX_DOWNLOADHINTS = _FX_DOWNLOADHINTS; +/// Key flags. +enum FWL_EVENTFLAG { + FWL_EVENTFLAG_ShiftKey(1), + FWL_EVENTFLAG_ControlKey(2), + FWL_EVENTFLAG_AltKey(4), + FWL_EVENTFLAG_MetaKey(8), + FWL_EVENTFLAG_KeyPad(16), + FWL_EVENTFLAG_AutoRepeat(32), + FWL_EVENTFLAG_LeftButtonDown(64), + FWL_EVENTFLAG_MiddleButtonDown(128), + FWL_EVENTFLAG_RightButtonDown(256); + + + final int value; + const FWL_EVENTFLAG(this.value); + + static FWL_EVENTFLAG fromValue(int value) => switch (value) { + 1 => FWL_EVENTFLAG_ShiftKey, + 2 => FWL_EVENTFLAG_ControlKey, + 4 => FWL_EVENTFLAG_AltKey, + 8 => FWL_EVENTFLAG_MetaKey, + 16 => FWL_EVENTFLAG_KeyPad, + 32 => FWL_EVENTFLAG_AutoRepeat, + 64 => FWL_EVENTFLAG_LeftButtonDown, + 128 => FWL_EVENTFLAG_MiddleButtonDown, + 256 => FWL_EVENTFLAG_RightButtonDown, + _ => throw ArgumentError('Unknown value for FWL_EVENTFLAG: $value'), + }; + +} + +/// Virtual keycodes. +enum FWL_VKEYCODE { + FWL_VKEY_Back(8), + FWL_VKEY_Tab(9), + FWL_VKEY_NewLine(10), + FWL_VKEY_Clear(12), + FWL_VKEY_Return(13), + FWL_VKEY_Shift(16), + FWL_VKEY_Control(17), + FWL_VKEY_Menu(18), + FWL_VKEY_Pause(19), + FWL_VKEY_Capital(20), + FWL_VKEY_Kana(21), + FWL_VKEY_Junja(23), + FWL_VKEY_Final(24), + FWL_VKEY_Hanja(25), + FWL_VKEY_Escape(27), + FWL_VKEY_Convert(28), + FWL_VKEY_NonConvert(29), + FWL_VKEY_Accept(30), + FWL_VKEY_ModeChange(31), + FWL_VKEY_Space(32), + FWL_VKEY_Prior(33), + FWL_VKEY_Next(34), + FWL_VKEY_End(35), + FWL_VKEY_Home(36), + FWL_VKEY_Left(37), + FWL_VKEY_Up(38), + FWL_VKEY_Right(39), + FWL_VKEY_Down(40), + FWL_VKEY_Select(41), + FWL_VKEY_Print(42), + FWL_VKEY_Execute(43), + FWL_VKEY_Snapshot(44), + FWL_VKEY_Insert(45), + FWL_VKEY_Delete(46), + FWL_VKEY_Help(47), + FWL_VKEY_0(48), + FWL_VKEY_1(49), + FWL_VKEY_2(50), + FWL_VKEY_3(51), + FWL_VKEY_4(52), + FWL_VKEY_5(53), + FWL_VKEY_6(54), + FWL_VKEY_7(55), + FWL_VKEY_8(56), + FWL_VKEY_9(57), + FWL_VKEY_A(65), + FWL_VKEY_B(66), + FWL_VKEY_C(67), + FWL_VKEY_D(68), + FWL_VKEY_E(69), + FWL_VKEY_F(70), + FWL_VKEY_G(71), + FWL_VKEY_H(72), + FWL_VKEY_I(73), + FWL_VKEY_J(74), + FWL_VKEY_K(75), + FWL_VKEY_L(76), + FWL_VKEY_M(77), + FWL_VKEY_N(78), + FWL_VKEY_O(79), + FWL_VKEY_P(80), + FWL_VKEY_Q(81), + FWL_VKEY_R(82), + FWL_VKEY_S(83), + FWL_VKEY_T(84), + FWL_VKEY_U(85), + FWL_VKEY_V(86), + FWL_VKEY_W(87), + FWL_VKEY_X(88), + FWL_VKEY_Y(89), + FWL_VKEY_Z(90), + FWL_VKEY_LWin(91), + FWL_VKEY_RWin(92), + FWL_VKEY_Apps(93), + FWL_VKEY_Sleep(95), + FWL_VKEY_NumPad0(96), + FWL_VKEY_NumPad1(97), + FWL_VKEY_NumPad2(98), + FWL_VKEY_NumPad3(99), + FWL_VKEY_NumPad4(100), + FWL_VKEY_NumPad5(101), + FWL_VKEY_NumPad6(102), + FWL_VKEY_NumPad7(103), + FWL_VKEY_NumPad8(104), + FWL_VKEY_NumPad9(105), + FWL_VKEY_Multiply(106), + FWL_VKEY_Add(107), + FWL_VKEY_Separator(108), + FWL_VKEY_Subtract(109), + FWL_VKEY_Decimal(110), + FWL_VKEY_Divide(111), + FWL_VKEY_F1(112), + FWL_VKEY_F2(113), + FWL_VKEY_F3(114), + FWL_VKEY_F4(115), + FWL_VKEY_F5(116), + FWL_VKEY_F6(117), + FWL_VKEY_F7(118), + FWL_VKEY_F8(119), + FWL_VKEY_F9(120), + FWL_VKEY_F10(121), + FWL_VKEY_F11(122), + FWL_VKEY_F12(123), + FWL_VKEY_F13(124), + FWL_VKEY_F14(125), + FWL_VKEY_F15(126), + FWL_VKEY_F16(127), + FWL_VKEY_F17(128), + FWL_VKEY_F18(129), + FWL_VKEY_F19(130), + FWL_VKEY_F20(131), + FWL_VKEY_F21(132), + FWL_VKEY_F22(133), + FWL_VKEY_F23(134), + FWL_VKEY_F24(135), + FWL_VKEY_NunLock(144), + FWL_VKEY_Scroll(145), + FWL_VKEY_LShift(160), + FWL_VKEY_RShift(161), + FWL_VKEY_LControl(162), + FWL_VKEY_RControl(163), + FWL_VKEY_LMenu(164), + FWL_VKEY_RMenu(165), + FWL_VKEY_BROWSER_Back(166), + FWL_VKEY_BROWSER_Forward(167), + FWL_VKEY_BROWSER_Refresh(168), + FWL_VKEY_BROWSER_Stop(169), + FWL_VKEY_BROWSER_Search(170), + FWL_VKEY_BROWSER_Favorites(171), + FWL_VKEY_BROWSER_Home(172), + FWL_VKEY_VOLUME_Mute(173), + FWL_VKEY_VOLUME_Down(174), + FWL_VKEY_VOLUME_Up(175), + FWL_VKEY_MEDIA_NEXT_Track(176), + FWL_VKEY_MEDIA_PREV_Track(177), + FWL_VKEY_MEDIA_Stop(178), + FWL_VKEY_MEDIA_PLAY_Pause(179), + FWL_VKEY_MEDIA_LAUNCH_Mail(180), + FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select(181), + FWL_VKEY_MEDIA_LAUNCH_APP1(182), + FWL_VKEY_MEDIA_LAUNCH_APP2(183), + FWL_VKEY_OEM_1(186), + FWL_VKEY_OEM_Plus(187), + FWL_VKEY_OEM_Comma(188), + FWL_VKEY_OEM_Minus(189), + FWL_VKEY_OEM_Period(190), + FWL_VKEY_OEM_2(191), + FWL_VKEY_OEM_3(192), + FWL_VKEY_OEM_4(219), + FWL_VKEY_OEM_5(220), + FWL_VKEY_OEM_6(221), + FWL_VKEY_OEM_7(222), + FWL_VKEY_OEM_8(223), + FWL_VKEY_OEM_102(226), + FWL_VKEY_ProcessKey(229), + FWL_VKEY_Packet(231), + FWL_VKEY_Attn(246), + FWL_VKEY_Crsel(247), + FWL_VKEY_Exsel(248), + FWL_VKEY_Ereof(249), + FWL_VKEY_Play(250), + FWL_VKEY_Zoom(251), + FWL_VKEY_NoName(252), + FWL_VKEY_PA1(253), + FWL_VKEY_OEM_Clear(254), + FWL_VKEY_Unknown(0); + + static const FWL_VKEY_Hangul = FWL_VKEY_Kana; + static const FWL_VKEY_Kanji = FWL_VKEY_Hanja; + static const FWL_VKEY_Command = FWL_VKEY_LWin; + + final int value; + const FWL_VKEYCODE(this.value); + + static FWL_VKEYCODE fromValue(int value) => switch (value) { + 8 => FWL_VKEY_Back, + 9 => FWL_VKEY_Tab, + 10 => FWL_VKEY_NewLine, + 12 => FWL_VKEY_Clear, + 13 => FWL_VKEY_Return, + 16 => FWL_VKEY_Shift, + 17 => FWL_VKEY_Control, + 18 => FWL_VKEY_Menu, + 19 => FWL_VKEY_Pause, + 20 => FWL_VKEY_Capital, + 21 => FWL_VKEY_Kana, + 23 => FWL_VKEY_Junja, + 24 => FWL_VKEY_Final, + 25 => FWL_VKEY_Hanja, + 27 => FWL_VKEY_Escape, + 28 => FWL_VKEY_Convert, + 29 => FWL_VKEY_NonConvert, + 30 => FWL_VKEY_Accept, + 31 => FWL_VKEY_ModeChange, + 32 => FWL_VKEY_Space, + 33 => FWL_VKEY_Prior, + 34 => FWL_VKEY_Next, + 35 => FWL_VKEY_End, + 36 => FWL_VKEY_Home, + 37 => FWL_VKEY_Left, + 38 => FWL_VKEY_Up, + 39 => FWL_VKEY_Right, + 40 => FWL_VKEY_Down, + 41 => FWL_VKEY_Select, + 42 => FWL_VKEY_Print, + 43 => FWL_VKEY_Execute, + 44 => FWL_VKEY_Snapshot, + 45 => FWL_VKEY_Insert, + 46 => FWL_VKEY_Delete, + 47 => FWL_VKEY_Help, + 48 => FWL_VKEY_0, + 49 => FWL_VKEY_1, + 50 => FWL_VKEY_2, + 51 => FWL_VKEY_3, + 52 => FWL_VKEY_4, + 53 => FWL_VKEY_5, + 54 => FWL_VKEY_6, + 55 => FWL_VKEY_7, + 56 => FWL_VKEY_8, + 57 => FWL_VKEY_9, + 65 => FWL_VKEY_A, + 66 => FWL_VKEY_B, + 67 => FWL_VKEY_C, + 68 => FWL_VKEY_D, + 69 => FWL_VKEY_E, + 70 => FWL_VKEY_F, + 71 => FWL_VKEY_G, + 72 => FWL_VKEY_H, + 73 => FWL_VKEY_I, + 74 => FWL_VKEY_J, + 75 => FWL_VKEY_K, + 76 => FWL_VKEY_L, + 77 => FWL_VKEY_M, + 78 => FWL_VKEY_N, + 79 => FWL_VKEY_O, + 80 => FWL_VKEY_P, + 81 => FWL_VKEY_Q, + 82 => FWL_VKEY_R, + 83 => FWL_VKEY_S, + 84 => FWL_VKEY_T, + 85 => FWL_VKEY_U, + 86 => FWL_VKEY_V, + 87 => FWL_VKEY_W, + 88 => FWL_VKEY_X, + 89 => FWL_VKEY_Y, + 90 => FWL_VKEY_Z, + 91 => FWL_VKEY_LWin, + 92 => FWL_VKEY_RWin, + 93 => FWL_VKEY_Apps, + 95 => FWL_VKEY_Sleep, + 96 => FWL_VKEY_NumPad0, + 97 => FWL_VKEY_NumPad1, + 98 => FWL_VKEY_NumPad2, + 99 => FWL_VKEY_NumPad3, + 100 => FWL_VKEY_NumPad4, + 101 => FWL_VKEY_NumPad5, + 102 => FWL_VKEY_NumPad6, + 103 => FWL_VKEY_NumPad7, + 104 => FWL_VKEY_NumPad8, + 105 => FWL_VKEY_NumPad9, + 106 => FWL_VKEY_Multiply, + 107 => FWL_VKEY_Add, + 108 => FWL_VKEY_Separator, + 109 => FWL_VKEY_Subtract, + 110 => FWL_VKEY_Decimal, + 111 => FWL_VKEY_Divide, + 112 => FWL_VKEY_F1, + 113 => FWL_VKEY_F2, + 114 => FWL_VKEY_F3, + 115 => FWL_VKEY_F4, + 116 => FWL_VKEY_F5, + 117 => FWL_VKEY_F6, + 118 => FWL_VKEY_F7, + 119 => FWL_VKEY_F8, + 120 => FWL_VKEY_F9, + 121 => FWL_VKEY_F10, + 122 => FWL_VKEY_F11, + 123 => FWL_VKEY_F12, + 124 => FWL_VKEY_F13, + 125 => FWL_VKEY_F14, + 126 => FWL_VKEY_F15, + 127 => FWL_VKEY_F16, + 128 => FWL_VKEY_F17, + 129 => FWL_VKEY_F18, + 130 => FWL_VKEY_F19, + 131 => FWL_VKEY_F20, + 132 => FWL_VKEY_F21, + 133 => FWL_VKEY_F22, + 134 => FWL_VKEY_F23, + 135 => FWL_VKEY_F24, + 144 => FWL_VKEY_NunLock, + 145 => FWL_VKEY_Scroll, + 160 => FWL_VKEY_LShift, + 161 => FWL_VKEY_RShift, + 162 => FWL_VKEY_LControl, + 163 => FWL_VKEY_RControl, + 164 => FWL_VKEY_LMenu, + 165 => FWL_VKEY_RMenu, + 166 => FWL_VKEY_BROWSER_Back, + 167 => FWL_VKEY_BROWSER_Forward, + 168 => FWL_VKEY_BROWSER_Refresh, + 169 => FWL_VKEY_BROWSER_Stop, + 170 => FWL_VKEY_BROWSER_Search, + 171 => FWL_VKEY_BROWSER_Favorites, + 172 => FWL_VKEY_BROWSER_Home, + 173 => FWL_VKEY_VOLUME_Mute, + 174 => FWL_VKEY_VOLUME_Down, + 175 => FWL_VKEY_VOLUME_Up, + 176 => FWL_VKEY_MEDIA_NEXT_Track, + 177 => FWL_VKEY_MEDIA_PREV_Track, + 178 => FWL_VKEY_MEDIA_Stop, + 179 => FWL_VKEY_MEDIA_PLAY_Pause, + 180 => FWL_VKEY_MEDIA_LAUNCH_Mail, + 181 => FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select, + 182 => FWL_VKEY_MEDIA_LAUNCH_APP1, + 183 => FWL_VKEY_MEDIA_LAUNCH_APP2, + 186 => FWL_VKEY_OEM_1, + 187 => FWL_VKEY_OEM_Plus, + 188 => FWL_VKEY_OEM_Comma, + 189 => FWL_VKEY_OEM_Minus, + 190 => FWL_VKEY_OEM_Period, + 191 => FWL_VKEY_OEM_2, + 192 => FWL_VKEY_OEM_3, + 219 => FWL_VKEY_OEM_4, + 220 => FWL_VKEY_OEM_5, + 221 => FWL_VKEY_OEM_6, + 222 => FWL_VKEY_OEM_7, + 223 => FWL_VKEY_OEM_8, + 226 => FWL_VKEY_OEM_102, + 229 => FWL_VKEY_ProcessKey, + 231 => FWL_VKEY_Packet, + 246 => FWL_VKEY_Attn, + 247 => FWL_VKEY_Crsel, + 248 => FWL_VKEY_Exsel, + 249 => FWL_VKEY_Ereof, + 250 => FWL_VKEY_Play, + 251 => FWL_VKEY_Zoom, + 252 => FWL_VKEY_NoName, + 253 => FWL_VKEY_PA1, + 254 => FWL_VKEY_OEM_Clear, + 0 => FWL_VKEY_Unknown, + _ => throw ArgumentError('Unknown value for FWL_VKEYCODE: $value'), + }; + + @override + String toString() { + if (this == FWL_VKEY_Kana) return "FWL_VKEYCODE.FWL_VKEY_Kana, FWL_VKEYCODE.FWL_VKEY_Hangul"; + if (this == FWL_VKEY_Hanja) return "FWL_VKEYCODE.FWL_VKEY_Hanja, FWL_VKEYCODE.FWL_VKEY_Kanji"; + if (this == FWL_VKEY_LWin) return "FWL_VKEYCODE.FWL_VKEY_LWin, FWL_VKEYCODE.FWL_VKEY_Command"; + return super.toString(); + }} + +/// Interface for unsupported feature notifications. +final class _UNSUPPORT_INFO extends ffi.Struct{ + /// Version number of the interface. Must be 1. + @ffi.Int() + external int version; + + /// Unsupported object notification function. + /// Interface Version: 1 + /// Implementation Required: Yes + /// + /// pThis - pointer to the interface structure. + /// nType - the type of unsupported object. One of the |FPDF_UNSP_*| entries. + external ffi.Pointer pThis, ffi.Int nType)>> FSDK_UnSupport_Handler; + +} + +/// Interface for unsupported feature notifications. +typedef UNSUPPORT_INFO = _UNSUPPORT_INFO; +typedef __darwin_time_t = ffi.Long; +typedef Dart__darwin_time_t = int; +typedef time_t = __darwin_time_t; +final class tm extends ffi.Struct{ + /// seconds after the minute [0-60] + @ffi.Int() + external int tm_sec; + + /// minutes after the hour [0-59] + @ffi.Int() + external int tm_min; + + /// hours since midnight [0-23] + @ffi.Int() + external int tm_hour; + + /// day of the month [1-31] + @ffi.Int() + external int tm_mday; + + /// months since January [0-11] + @ffi.Int() + external int tm_mon; + + /// years since 1900 + @ffi.Int() + external int tm_year; + + /// days since Sunday [0-6] + @ffi.Int() + external int tm_wday; + + /// days since January 1 [0-365] + @ffi.Int() + external int tm_yday; + + /// Daylight Savings Time flag + @ffi.Int() + external int tm_isdst; + + /// offset from UTC in seconds + @ffi.Long() + external int tm_gmtoff; + + /// timezone abbreviation + external ffi.Pointer tm_zone; + +} + + +const int FPDF_OBJECT_UNKNOWN = 0; + + +const int FPDF_OBJECT_BOOLEAN = 1; + + +const int FPDF_OBJECT_NUMBER = 2; + + +const int FPDF_OBJECT_STRING = 3; + + +const int FPDF_OBJECT_NAME = 4; + + +const int FPDF_OBJECT_ARRAY = 5; + + +const int FPDF_OBJECT_DICTIONARY = 6; + + +const int FPDF_OBJECT_STREAM = 7; + + +const int FPDF_OBJECT_NULLOBJ = 8; + + +const int FPDF_OBJECT_REFERENCE = 9; + + +const int FPDF_POLICY_MACHINETIME_ACCESS = 0; + + +const int FPDF_ERR_SUCCESS = 0; + + +const int FPDF_ERR_UNKNOWN = 1; + + +const int FPDF_ERR_FILE = 2; + + +const int FPDF_ERR_FORMAT = 3; + + +const int FPDF_ERR_PASSWORD = 4; + + +const int FPDF_ERR_SECURITY = 5; + + +const int FPDF_ERR_PAGE = 6; + + +const int FPDF_ANNOT = 1; + + +const int FPDF_LCD_TEXT = 2; + + +const int FPDF_NO_NATIVETEXT = 4; + + +const int FPDF_GRAYSCALE = 8; + + +const int FPDF_DEBUG_INFO = 128; + + +const int FPDF_NO_CATCH = 256; + + +const int FPDF_RENDER_LIMITEDIMAGECACHE = 512; + + +const int FPDF_RENDER_FORCEHALFTONE = 1024; + + +const int FPDF_PRINTING = 2048; + + +const int FPDF_RENDER_NO_SMOOTHTEXT = 4096; + + +const int FPDF_RENDER_NO_SMOOTHIMAGE = 8192; + + +const int FPDF_RENDER_NO_SMOOTHPATH = 16384; + + +const int FPDF_REVERSE_BYTE_ORDER = 16; + + +const int FPDF_CONVERT_FILL_TO_STROKE = 32; + + +const int FPDFBitmap_Unknown = 0; + + +const int FPDFBitmap_Gray = 1; + + +const int FPDFBitmap_BGR = 2; + + +const int FPDFBitmap_BGRx = 3; + + +const int FPDFBitmap_BGRA = 4; + + +const int FPDFBitmap_BGRA_Premul = 5; + + +const int FXFONT_ANSI_CHARSET = 0; + + +const int FXFONT_DEFAULT_CHARSET = 1; + + +const int FXFONT_SYMBOL_CHARSET = 2; + + +const int FXFONT_SHIFTJIS_CHARSET = 128; + + +const int FXFONT_HANGEUL_CHARSET = 129; + + +const int FXFONT_GB2312_CHARSET = 134; + + +const int FXFONT_CHINESEBIG5_CHARSET = 136; + + +const int FXFONT_GREEK_CHARSET = 161; + + +const int FXFONT_VIETNAMESE_CHARSET = 163; + + +const int FXFONT_HEBREW_CHARSET = 177; + + +const int FXFONT_ARABIC_CHARSET = 178; + + +const int FXFONT_CYRILLIC_CHARSET = 204; + + +const int FXFONT_THAI_CHARSET = 222; + + +const int FXFONT_EASTERNEUROPEAN_CHARSET = 238; + + +const int FXFONT_FF_FIXEDPITCH = 1; + + +const int FXFONT_FF_ROMAN = 16; + + +const int FXFONT_FF_SCRIPT = 64; + + +const int FXFONT_FW_NORMAL = 400; + + +const int FXFONT_FW_BOLD = 700; + + +const int FPDF_MATCHCASE = 1; + + +const int FPDF_MATCHWHOLEWORD = 2; + + +const int FPDF_CONSECUTIVE = 4; + + +const int FPDF_RENDER_READY = 0; + + +const int FPDF_RENDER_TOBECONTINUED = 1; + + +const int FPDF_RENDER_DONE = 2; + + +const int FPDF_RENDER_FAILED = 3; + + +const int FPDF_COLORSPACE_UNKNOWN = 0; + + +const int FPDF_COLORSPACE_DEVICEGRAY = 1; + + +const int FPDF_COLORSPACE_DEVICERGB = 2; + + +const int FPDF_COLORSPACE_DEVICECMYK = 3; + + +const int FPDF_COLORSPACE_CALGRAY = 4; + + +const int FPDF_COLORSPACE_CALRGB = 5; + + +const int FPDF_COLORSPACE_LAB = 6; + + +const int FPDF_COLORSPACE_ICCBASED = 7; + + +const int FPDF_COLORSPACE_SEPARATION = 8; + + +const int FPDF_COLORSPACE_DEVICEN = 9; + + +const int FPDF_COLORSPACE_INDEXED = 10; + + +const int FPDF_COLORSPACE_PATTERN = 11; + + +const int FPDF_PAGEOBJ_UNKNOWN = 0; + + +const int FPDF_PAGEOBJ_TEXT = 1; + + +const int FPDF_PAGEOBJ_PATH = 2; + + +const int FPDF_PAGEOBJ_IMAGE = 3; + + +const int FPDF_PAGEOBJ_SHADING = 4; + + +const int FPDF_PAGEOBJ_FORM = 5; + + +const int FPDF_SEGMENT_UNKNOWN = -1; + + +const int FPDF_SEGMENT_LINETO = 0; + + +const int FPDF_SEGMENT_BEZIERTO = 1; + + +const int FPDF_SEGMENT_MOVETO = 2; + + +const int FPDF_FILLMODE_NONE = 0; + + +const int FPDF_FILLMODE_ALTERNATE = 1; + + +const int FPDF_FILLMODE_WINDING = 2; + + +const int FPDF_FONT_TYPE1 = 1; + + +const int FPDF_FONT_TRUETYPE = 2; + + +const int FPDF_LINECAP_BUTT = 0; + + +const int FPDF_LINECAP_ROUND = 1; + + +const int FPDF_LINECAP_PROJECTING_SQUARE = 2; + + +const int FPDF_LINEJOIN_MITER = 0; + + +const int FPDF_LINEJOIN_ROUND = 1; + + +const int FPDF_LINEJOIN_BEVEL = 2; + + +const int FPDF_PRINTMODE_EMF = 0; + + +const int FPDF_PRINTMODE_TEXTONLY = 1; + + +const int FPDF_PRINTMODE_POSTSCRIPT2 = 2; + + +const int FPDF_PRINTMODE_POSTSCRIPT3 = 3; + + +const int FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5; + + +const int FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; + + +const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; + + +const int FORMTYPE_NONE = 0; + + +const int FORMTYPE_ACRO_FORM = 1; + + +const int FORMTYPE_XFA_FULL = 2; + + +const int FORMTYPE_XFA_FOREGROUND = 3; + + +const int FORMTYPE_COUNT = 4; + + +const int JSPLATFORM_ALERT_BUTTON_OK = 0; + + +const int JSPLATFORM_ALERT_BUTTON_OKCANCEL = 1; + + +const int JSPLATFORM_ALERT_BUTTON_YESNO = 2; + + +const int JSPLATFORM_ALERT_BUTTON_YESNOCANCEL = 3; + + +const int JSPLATFORM_ALERT_BUTTON_DEFAULT = 0; + + +const int JSPLATFORM_ALERT_ICON_ERROR = 0; + + +const int JSPLATFORM_ALERT_ICON_WARNING = 1; + + +const int JSPLATFORM_ALERT_ICON_QUESTION = 2; + + +const int JSPLATFORM_ALERT_ICON_STATUS = 3; + + +const int JSPLATFORM_ALERT_ICON_ASTERISK = 4; + + +const int JSPLATFORM_ALERT_ICON_DEFAULT = 0; + + +const int JSPLATFORM_ALERT_RETURN_OK = 1; + + +const int JSPLATFORM_ALERT_RETURN_CANCEL = 2; + + +const int JSPLATFORM_ALERT_RETURN_NO = 3; + + +const int JSPLATFORM_ALERT_RETURN_YES = 4; + + +const int JSPLATFORM_BEEP_ERROR = 0; + + +const int JSPLATFORM_BEEP_WARNING = 1; + + +const int JSPLATFORM_BEEP_QUESTION = 2; + + +const int JSPLATFORM_BEEP_STATUS = 3; + + +const int JSPLATFORM_BEEP_DEFAULT = 4; + + +const int FXCT_ARROW = 0; + + +const int FXCT_NESW = 1; + + +const int FXCT_NWSE = 2; + + +const int FXCT_VBEAM = 3; + + +const int FXCT_HBEAM = 4; + + +const int FXCT_HAND = 5; + + +const int FPDFDOC_AACTION_WC = 16; + + +const int FPDFDOC_AACTION_WS = 17; + + +const int FPDFDOC_AACTION_DS = 18; + + +const int FPDFDOC_AACTION_WP = 19; + + +const int FPDFDOC_AACTION_DP = 20; + + +const int FPDFPAGE_AACTION_OPEN = 0; + + +const int FPDFPAGE_AACTION_CLOSE = 1; + + +const int FPDF_FORMFIELD_UNKNOWN = 0; + + +const int FPDF_FORMFIELD_PUSHBUTTON = 1; + + +const int FPDF_FORMFIELD_CHECKBOX = 2; + + +const int FPDF_FORMFIELD_RADIOBUTTON = 3; + + +const int FPDF_FORMFIELD_COMBOBOX = 4; + + +const int FPDF_FORMFIELD_LISTBOX = 5; + + +const int FPDF_FORMFIELD_TEXTFIELD = 6; + + +const int FPDF_FORMFIELD_SIGNATURE = 7; + + +const int FPDF_FORMFIELD_COUNT = 8; + + +const int FPDF_ANNOT_UNKNOWN = 0; + + +const int FPDF_ANNOT_TEXT = 1; + + +const int FPDF_ANNOT_LINK = 2; + + +const int FPDF_ANNOT_FREETEXT = 3; + + +const int FPDF_ANNOT_LINE = 4; + + +const int FPDF_ANNOT_SQUARE = 5; + + +const int FPDF_ANNOT_CIRCLE = 6; + + +const int FPDF_ANNOT_POLYGON = 7; + + +const int FPDF_ANNOT_POLYLINE = 8; + + +const int FPDF_ANNOT_HIGHLIGHT = 9; + + +const int FPDF_ANNOT_UNDERLINE = 10; + + +const int FPDF_ANNOT_SQUIGGLY = 11; + + +const int FPDF_ANNOT_STRIKEOUT = 12; + + +const int FPDF_ANNOT_STAMP = 13; + + +const int FPDF_ANNOT_CARET = 14; + + +const int FPDF_ANNOT_INK = 15; + + +const int FPDF_ANNOT_POPUP = 16; + + +const int FPDF_ANNOT_FILEATTACHMENT = 17; + + +const int FPDF_ANNOT_SOUND = 18; + + +const int FPDF_ANNOT_MOVIE = 19; + + +const int FPDF_ANNOT_WIDGET = 20; + + +const int FPDF_ANNOT_SCREEN = 21; + + +const int FPDF_ANNOT_PRINTERMARK = 22; + + +const int FPDF_ANNOT_TRAPNET = 23; + + +const int FPDF_ANNOT_WATERMARK = 24; + + +const int FPDF_ANNOT_THREED = 25; + + +const int FPDF_ANNOT_RICHMEDIA = 26; + + +const int FPDF_ANNOT_XFAWIDGET = 27; + + +const int FPDF_ANNOT_REDACT = 28; + + +const int FPDF_ANNOT_FLAG_NONE = 0; + + +const int FPDF_ANNOT_FLAG_INVISIBLE = 1; + + +const int FPDF_ANNOT_FLAG_HIDDEN = 2; + + +const int FPDF_ANNOT_FLAG_PRINT = 4; + + +const int FPDF_ANNOT_FLAG_NOZOOM = 8; + + +const int FPDF_ANNOT_FLAG_NOROTATE = 16; + + +const int FPDF_ANNOT_FLAG_NOVIEW = 32; + + +const int FPDF_ANNOT_FLAG_READONLY = 64; + + +const int FPDF_ANNOT_FLAG_LOCKED = 128; + + +const int FPDF_ANNOT_FLAG_TOGGLENOVIEW = 256; + + +const int FPDF_ANNOT_APPEARANCEMODE_NORMAL = 0; + + +const int FPDF_ANNOT_APPEARANCEMODE_ROLLOVER = 1; + + +const int FPDF_ANNOT_APPEARANCEMODE_DOWN = 2; + + +const int FPDF_ANNOT_APPEARANCEMODE_COUNT = 3; + + +const int FPDF_FORMFLAG_NONE = 0; + + +const int FPDF_FORMFLAG_READONLY = 1; + + +const int FPDF_FORMFLAG_REQUIRED = 2; + + +const int FPDF_FORMFLAG_NOEXPORT = 4; + + +const int FPDF_FORMFLAG_TEXT_MULTILINE = 4096; + + +const int FPDF_FORMFLAG_TEXT_PASSWORD = 8192; + + +const int FPDF_FORMFLAG_CHOICE_COMBO = 131072; + + +const int FPDF_FORMFLAG_CHOICE_EDIT = 262144; + + +const int FPDF_FORMFLAG_CHOICE_MULTI_SELECT = 2097152; + + +const int FPDF_ANNOT_AACTION_KEY_STROKE = 12; + + +const int FPDF_ANNOT_AACTION_FORMAT = 13; + + +const int FPDF_ANNOT_AACTION_VALIDATE = 14; + + +const int FPDF_ANNOT_AACTION_CALCULATE = 15; + + +const int FPDF_INCREMENTAL = 1; + + +const int FPDF_NO_INCREMENTAL = 2; + + +const int FPDF_REMOVE_SECURITY = 3; + + +const int PDFACTION_UNSUPPORTED = 0; + + +const int PDFACTION_GOTO = 1; + + +const int PDFACTION_REMOTEGOTO = 2; + + +const int PDFACTION_URI = 3; + + +const int PDFACTION_LAUNCH = 4; + + +const int PDFACTION_EMBEDDEDGOTO = 5; + + +const int PDFDEST_VIEW_UNKNOWN_MODE = 0; + + +const int PDFDEST_VIEW_XYZ = 1; + + +const int PDFDEST_VIEW_FIT = 2; + + +const int PDFDEST_VIEW_FITH = 3; + + +const int PDFDEST_VIEW_FITV = 4; + + +const int PDFDEST_VIEW_FITR = 5; + + +const int PDFDEST_VIEW_FITB = 6; + + +const int PDFDEST_VIEW_FITBH = 7; + + +const int PDFDEST_VIEW_FITBV = 8; + + +const int PDF_LINEARIZATION_UNKNOWN = -1; + + +const int PDF_NOT_LINEARIZED = 0; + + +const int PDF_LINEARIZED = 1; + + +const int PDF_DATA_ERROR = -1; + + +const int PDF_DATA_NOTAVAIL = 0; + + +const int PDF_DATA_AVAIL = 1; + + +const int PDF_FORM_ERROR = -1; + + +const int PDF_FORM_NOTAVAIL = 0; + + +const int PDF_FORM_AVAIL = 1; + + +const int PDF_FORM_NOTEXIST = 2; + + +const int FPDF_UNSP_DOC_XFAFORM = 1; + + +const int FPDF_UNSP_DOC_PORTABLECOLLECTION = 2; + + +const int FPDF_UNSP_DOC_ATTACHMENT = 3; + + +const int FPDF_UNSP_DOC_SECURITY = 4; + + +const int FPDF_UNSP_DOC_SHAREDREVIEW = 5; + + +const int FPDF_UNSP_DOC_SHAREDFORM_ACROBAT = 6; + + +const int FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM = 7; + + +const int FPDF_UNSP_DOC_SHAREDFORM_EMAIL = 8; + + +const int FPDF_UNSP_ANNOT_3DANNOT = 11; + + +const int FPDF_UNSP_ANNOT_MOVIE = 12; + + +const int FPDF_UNSP_ANNOT_SOUND = 13; + + +const int FPDF_UNSP_ANNOT_SCREEN_MEDIA = 14; + + +const int FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA = 15; + + +const int FPDF_UNSP_ANNOT_ATTACHMENT = 16; + + +const int FPDF_UNSP_ANNOT_SIG = 17; + + +const int PAGEMODE_UNKNOWN = -1; + + +const int PAGEMODE_USENONE = 0; + + +const int PAGEMODE_USEOUTLINES = 1; + + +const int PAGEMODE_USETHUMBS = 2; + + +const int PAGEMODE_FULLSCREEN = 3; + + +const int PAGEMODE_USEOC = 4; + + +const int PAGEMODE_USEATTACHMENTS = 5; + + +const int FLATTEN_FAIL = 0; + + +const int FLATTEN_SUCCESS = 1; + + +const int FLATTEN_NOTHINGTODO = 2; + + +const int FLAT_NORMALDISPLAY = 0; + + +const int FLAT_PRINT = 1; + diff --git a/packages/pdfium_dart/lib/src/pdfium_downloader.dart b/packages/pdfium_dart/lib/src/pdfium_downloader.dart new file mode 100644 index 00000000..3bd9c7b6 --- /dev/null +++ b/packages/pdfium_dart/lib/src/pdfium_downloader.dart @@ -0,0 +1,128 @@ +import 'dart:ffi'; +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; + +import 'pdfium_bindings.dart' as pdfium_bindings; + +/// The release of pdfium to download. +/// +/// The actual binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. +const currentPdfiumRelease = 'chromium%2F7506'; + +/// Helper function to get Pdfium instance. +/// +/// This function downloads the Pdfium module if necessary. +/// +/// For macOS, the downloaded library is not codesigned. If you encounter issues loading the library, +/// you may need to manually codesign it using the following command: +/// +/// ``` +/// codesign --force --sign - +/// ``` +Future getPdfium({ + String? tmpPath, + String? pdfiumRelease = currentPdfiumRelease, +}) async { + tmpPath ??= path.join( + Directory.systemTemp.path, + 'pdfium_dart', + 'cache', + pdfiumRelease, + ); + + if (!await File(tmpPath).exists()) { + await Directory(tmpPath).create(recursive: true); + } + final modulePath = await PDFiumDownloader.downloadAndGetPdfiumModulePath( + tmpPath, + pdfiumRelease: pdfiumRelease, + ); + + try { + return pdfium_bindings.PDFium(DynamicLibrary.open(modulePath)); + } catch (e) { + throw Exception('Failed to load PDFium module at $modulePath: $e'); + } +} + +/// PdfiumDownloader is a utility class to download the PDFium module for various platforms. +class PDFiumDownloader { + PDFiumDownloader._(); + + /// Downloads the pdfium module for the current platform and architecture. + /// + /// Currently, the following platforms are supported: + /// - Windows x64 + /// - Linux x64, arm64 + /// - macOS x64, arm64 + /// + /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. + static Future downloadAndGetPdfiumModulePath( + String tmpPath, { + String? pdfiumRelease = currentPdfiumRelease, + }) async { + final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; + final platform = pa[1]!; + final arch = pa[2]!; + if (platform == 'windows' && arch == 'x64') { + return await downloadPdfium( + tmpPath, + 'win', + arch, + 'bin/pdfium.dll', + pdfiumRelease, + ); + } + if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { + return await downloadPdfium( + tmpPath, + platform, + arch, + 'lib/libpdfium.so', + pdfiumRelease, + ); + } + if (platform == 'macos') { + return await downloadPdfium( + tmpPath, + 'mac', + arch, + 'lib/libpdfium.dylib', + pdfiumRelease, + ); + } else { + throw Exception('Unsupported platform: $platform-$arch'); + } + } + + /// Downloads the pdfium module for the given platform and architecture. + static Future downloadPdfium( + String tmpRoot, + String platform, + String arch, + String modulePath, + String? pdfiumRelease, + ) async { + final tmpDir = Directory('$tmpRoot/$platform-$arch'); + final targetPath = '${tmpDir.path}/$modulePath'; + if (await File(targetPath).exists()) return targetPath; + + final uri = + 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; + final tgz = await http.Client().get(Uri.parse(uri)); + if (tgz.statusCode != 200) { + throw Exception('Failed to download pdfium: $uri'); + } + final archive = TarDecoder().decodeBytes( + GZipDecoder().decodeBytes(tgz.bodyBytes), + ); + try { + await tmpDir.delete(recursive: true); + } catch (_) {} + await extractArchiveToDisk(archive, tmpDir.path); + return targetPath; + } +} diff --git a/packages/pdfium_dart/pubspec.yaml b/packages/pdfium_dart/pubspec.yaml new file mode 100644 index 00000000..f8e73dac --- /dev/null +++ b/packages/pdfium_dart/pubspec.yaml @@ -0,0 +1,60 @@ +name: pdfium_dart +description: Dart FFI bindings for PDFium library. Provides low-level access to PDFium's C API from Dart. +version: 0.1.0 +homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_dart +issue_tracker: https://github.com/espresso3389/pdfrx/issues + +environment: + sdk: ^3.9.0 +resolution: workspace + +dependencies: + ffi: + path: + +dev_dependencies: + ffigen: ^19.1.0 + lints: ^6.0.0 + test: ^1.26.2 + +# To generate the bindings, firstly you must run the test once to download PDFium headers: +# dart run test +# then run the following command: +# dart run ffigen +ffigen: + output: + bindings: 'lib/src/pdfium_bindings.dart' + headers: + entry-points: + - 'test/.tmp/**/fpdf_signature.h' + - 'test/.tmp/**/fpdf_sysfontinfo.h' + - 'test/.tmp/**/fpdf_javascript.h' + - 'test/.tmp/**/fpdf_text.h' + - 'test/.tmp/**/fpdf_searchex.h' + - 'test/.tmp/**/fpdf_progressive.h' + - 'test/.tmp/**/fpdfview.h' + - 'test/.tmp/**/fpdf_edit.h' + - 'test/.tmp/**/fpdf_attachment.h' + - 'test/.tmp/**/fpdf_annot.h' + - 'test/.tmp/**/fpdf_catalog.h' + - 'test/.tmp/**/fpdf_ppo.h' + - 'test/.tmp/**/fpdf_formfill.h' + - 'test/.tmp/**/fpdf_save.h' + - 'test/.tmp/**/fpdf_doc.h' + - 'test/.tmp/**/fpdf_structtree.h' + - 'test/.tmp/**/fpdf_dataavail.h' + - 'test/.tmp/**/fpdf_fwlevent.h' + - 'test/.tmp/**/fpdf_ext.h' + - 'test/.tmp/**/fpdf_transformpage.h' + - 'test/.tmp/**/fpdf_flatten.h' + - 'test/.tmp/**/fpdf_thumbnail.h' + include-directives: + - 'test/.tmp/**/**' + preamble: | + // ignore_for_file: unused_field + // dart format off + name: 'PDFium' + comments: + style: any + length: full diff --git a/packages/pdfium_dart/test/pdfium_test.dart b/packages/pdfium_dart/test/pdfium_test.dart new file mode 100644 index 00000000..ef2b8bf3 --- /dev/null +++ b/packages/pdfium_dart/test/pdfium_test.dart @@ -0,0 +1,21 @@ +import 'dart:io'; + +import 'package:test/test.dart'; + +import '../lib/pdfium_dart.dart'; + +final tmpRoot = Directory('${Directory.current.path}/test/.tmp'); +final testPdfFile = File('../pdfrx/example/viewer/assets/hello.pdf'); + +PDFium? _pdfium; + +void main() { + setUp(() async { + _pdfium = await getPdfium(tmpPath: tmpRoot.path); + }); + + test('PDFium Initialization', () { + _pdfium!.FPDF_InitLibrary(); + _pdfium!.FPDF_DestroyLibrary(); + }); +} From 4a6b04ac2c61d8d956621ed31cfc546aefea5bc5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 15:21:12 +0900 Subject: [PATCH 541/663] Update pdfrx_engine to use pdfium_dart package --- .../lib/src/native/pdf_file_cache.dart | 2 +- .../pdfrx_engine/lib/src/native/pdfium.dart | 11 +- .../lib/src/native/pdfium_bindings.dart | 14008 ---------------- .../lib/src/native/pdfrx_pdfium.dart | 2 +- .../lib/src/native/pthread/file_access.dart | 2 +- .../lib/src/native/pthread/file_write.dart | 2 +- .../lib/src/native/win32/file_access.dart | 2 +- .../lib/src/native/win32/file_write.dart | 2 +- .../lib/src/pdfrx_initialize_dart.dart | 72 +- packages/pdfrx_engine/pubspec.yaml | 43 +- 10 files changed, 16 insertions(+), 14130 deletions(-) delete mode 100644 packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart diff --git a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart index f5c7c8d9..e8bed9f0 100644 --- a/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart +++ b/packages/pdfrx_engine/lib/src/native/pdf_file_cache.dart @@ -16,7 +16,7 @@ import '../pdfrx_entry_functions.dart'; import '../pdfrx_initialize_dart.dart'; import 'http_cache_control.dart'; import 'native_utils.dart'; -import 'pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; final _rafFinalizer = Finalizer((raf) { // Attempt to close the file if it hasn't been closed explicitly. diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index cd50b23b..e00bd046 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -3,8 +3,9 @@ import 'dart:ffi' as ffi; import 'dart:ffi'; import 'dart:io'; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; + import '../pdfrx.dart'; -import 'pdfium_bindings.dart' as pdfium_bindings; /// Get the module file name for pdfium. String _getModuleFileName() { @@ -28,15 +29,15 @@ DynamicLibrary _getModule() { return DynamicLibrary.open(_getModuleFileName()); } -pdfium_bindings.pdfium? _pdfium; +pdfium_bindings.PDFium? _pdfium; /// Loaded PDFium module. -pdfium_bindings.pdfium get pdfium { - _pdfium ??= pdfium_bindings.pdfium(_getModule()); +pdfium_bindings.PDFium get pdfium { + _pdfium ??= pdfium_bindings.PDFium(_getModule()); return _pdfium!; } -set pdfium(pdfium_bindings.pdfium value) { +set pdfium(pdfium_bindings.PDFium value) { _pdfium = value; } diff --git a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart b/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart deleted file mode 100644 index 9a0ede1e..00000000 --- a/packages/pdfrx_engine/lib/src/native/pdfium_bindings.dart +++ /dev/null @@ -1,14008 +0,0 @@ -// ignore_for_file: unused_field -// dart format off - -// AUTO GENERATED FILE, DO NOT EDIT. -// -// Generated by `package:ffigen`. -// ignore_for_file: type=lint -import 'dart:ffi' as ffi; -class pdfium{ -/// Holds the symbol lookup function. -final ffi.Pointer Function(String symbolName) _lookup; - -/// The symbols are looked up in [dynamicLibrary]. -pdfium(ffi.DynamicLibrary dynamicLibrary): _lookup = dynamicLibrary.lookup; - -/// The symbols are looked up with [lookup]. -pdfium.fromLookup(ffi.Pointer Function(String symbolName) lookup): _lookup = lookup; - -/// Function: FPDF_InitLibraryWithConfig -/// Initialize the PDFium library and allocate global resources for it. -/// Parameters: -/// config - configuration information as above. -/// Return value: -/// None. -/// Comments: -/// You have to call this function before you can call any PDF -/// processing functions. -void FPDF_InitLibraryWithConfig(ffi.Pointer config, -) { - return _FPDF_InitLibraryWithConfig(config, -); -} - -late final _FPDF_InitLibraryWithConfigPtr = _lookup< - ffi.NativeFunction )>>('FPDF_InitLibraryWithConfig'); -late final _FPDF_InitLibraryWithConfig = _FPDF_InitLibraryWithConfigPtr.asFunction )>(); - -/// Function: FPDF_InitLibrary -/// Initialize the PDFium library (alternative form). -/// Parameters: -/// None -/// Return value: -/// None. -/// Comments: -/// Convenience function to call FPDF_InitLibraryWithConfig() with a -/// default configuration for backwards compatibility purposes. New -/// code should call FPDF_InitLibraryWithConfig() instead. This will -/// be deprecated in the future. -void FPDF_InitLibrary() { - return _FPDF_InitLibrary(); -} - -late final _FPDF_InitLibraryPtr = _lookup< - ffi.NativeFunction>('FPDF_InitLibrary'); -late final _FPDF_InitLibrary = _FPDF_InitLibraryPtr.asFunction(); - -/// Function: FPDF_DestroyLibrary -/// Release global resources allocated to the PDFium library by -/// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). -/// Parameters: -/// None. -/// Return value: -/// None. -/// Comments: -/// After this function is called, you must not call any PDF -/// processing functions. -/// -/// Calling this function does not automatically close other -/// objects. It is recommended to close other objects before -/// closing the library with this function. -void FPDF_DestroyLibrary() { - return _FPDF_DestroyLibrary(); -} - -late final _FPDF_DestroyLibraryPtr = _lookup< - ffi.NativeFunction>('FPDF_DestroyLibrary'); -late final _FPDF_DestroyLibrary = _FPDF_DestroyLibraryPtr.asFunction(); - -/// Function: FPDF_SetSandBoxPolicy -/// Set the policy for the sandbox environment. -/// Parameters: -/// policy - The specified policy for setting, for example: -/// FPDF_POLICY_MACHINETIME_ACCESS. -/// enable - True to enable, false to disable the policy. -/// Return value: -/// None. -void FPDF_SetSandBoxPolicy(int policy, -int enable, -) { - return _FPDF_SetSandBoxPolicy(policy, -enable, -); -} - -late final _FPDF_SetSandBoxPolicyPtr = _lookup< - ffi.NativeFunction>('FPDF_SetSandBoxPolicy'); -late final _FPDF_SetSandBoxPolicy = _FPDF_SetSandBoxPolicyPtr.asFunction(); - -/// Function: FPDF_LoadDocument -/// Open and load a PDF document. -/// Parameters: -/// file_path - Path to the PDF file (including extension). -/// password - A string used as the password for the PDF file. -/// If no password is needed, empty or NULL can be used. -/// See comments below regarding the encoding. -/// Return value: -/// A handle to the loaded document, or NULL on failure. -/// Comments: -/// Loaded document can be closed by FPDF_CloseDocument(). -/// If this function fails, you can use FPDF_GetLastError() to retrieve -/// the reason why it failed. -/// -/// The encoding for |file_path| is UTF-8. -/// -/// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, -/// depending on the security handler revision, will only accept one or -/// the other encoding. If |password|'s encoding and the PDF's expected -/// encoding do not match, FPDF_LoadDocument() will automatically -/// convert |password| to the other encoding. -FPDF_DOCUMENT FPDF_LoadDocument(FPDF_STRING file_path, -FPDF_BYTESTRING password, -) { - return _FPDF_LoadDocument(file_path, -password, -); -} - -late final _FPDF_LoadDocumentPtr = _lookup< - ffi.NativeFunction>('FPDF_LoadDocument'); -late final _FPDF_LoadDocument = _FPDF_LoadDocumentPtr.asFunction(); - -/// Function: FPDF_LoadMemDocument -/// Open and load a PDF document from memory. -/// Parameters: -/// data_buf - Pointer to a buffer containing the PDF document. -/// size - Number of bytes in the PDF document. -/// password - A string used as the password for the PDF file. -/// If no password is needed, empty or NULL can be used. -/// Return value: -/// A handle to the loaded document, or NULL on failure. -/// Comments: -/// The memory buffer must remain valid when the document is open. -/// The loaded document can be closed by FPDF_CloseDocument. -/// If this function fails, you can use FPDF_GetLastError() to retrieve -/// the reason why it failed. -/// -/// See the comments for FPDF_LoadDocument() regarding the encoding for -/// |password|. -/// Notes: -/// If PDFium is built with the XFA module, the application should call -/// FPDF_LoadXFA() function after the PDF document loaded to support XFA -/// fields defined in the fpdfformfill.h file. -FPDF_DOCUMENT FPDF_LoadMemDocument(ffi.Pointer data_buf, -int size, -FPDF_BYTESTRING password, -) { - return _FPDF_LoadMemDocument(data_buf, -size, -password, -); -} - -late final _FPDF_LoadMemDocumentPtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument'); -late final _FPDF_LoadMemDocument = _FPDF_LoadMemDocumentPtr.asFunction , int , FPDF_BYTESTRING )>(); - -/// Experimental API. -/// Function: FPDF_LoadMemDocument64 -/// Open and load a PDF document from memory. -/// Parameters: -/// data_buf - Pointer to a buffer containing the PDF document. -/// size - Number of bytes in the PDF document. -/// password - A string used as the password for the PDF file. -/// If no password is needed, empty or NULL can be used. -/// Return value: -/// A handle to the loaded document, or NULL on failure. -/// Comments: -/// The memory buffer must remain valid when the document is open. -/// The loaded document can be closed by FPDF_CloseDocument. -/// If this function fails, you can use FPDF_GetLastError() to retrieve -/// the reason why it failed. -/// -/// See the comments for FPDF_LoadDocument() regarding the encoding for -/// |password|. -/// Notes: -/// If PDFium is built with the XFA module, the application should call -/// FPDF_LoadXFA() function after the PDF document loaded to support XFA -/// fields defined in the fpdfformfill.h file. -FPDF_DOCUMENT FPDF_LoadMemDocument64(ffi.Pointer data_buf, -int size, -FPDF_BYTESTRING password, -) { - return _FPDF_LoadMemDocument64(data_buf, -size, -password, -); -} - -late final _FPDF_LoadMemDocument64Ptr = _lookup< - ffi.NativeFunction , ffi.Size , FPDF_BYTESTRING )>>('FPDF_LoadMemDocument64'); -late final _FPDF_LoadMemDocument64 = _FPDF_LoadMemDocument64Ptr.asFunction , int , FPDF_BYTESTRING )>(); - -/// Function: FPDF_LoadCustomDocument -/// Load PDF document from a custom access descriptor. -/// Parameters: -/// pFileAccess - A structure for accessing the file. -/// password - Optional password for decrypting the PDF file. -/// Return value: -/// A handle to the loaded document, or NULL on failure. -/// Comments: -/// The application must keep the file resources |pFileAccess| points to -/// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| -/// itself does not need to outlive the FPDF_DOCUMENT. -/// -/// The loaded document can be closed with FPDF_CloseDocument(). -/// -/// See the comments for FPDF_LoadDocument() regarding the encoding for -/// |password|. -/// Notes: -/// If PDFium is built with the XFA module, the application should call -/// FPDF_LoadXFA() function after the PDF document loaded to support XFA -/// fields defined in the fpdfformfill.h file. -FPDF_DOCUMENT FPDF_LoadCustomDocument(ffi.Pointer pFileAccess, -FPDF_BYTESTRING password, -) { - return _FPDF_LoadCustomDocument(pFileAccess, -password, -); -} - -late final _FPDF_LoadCustomDocumentPtr = _lookup< - ffi.NativeFunction , FPDF_BYTESTRING )>>('FPDF_LoadCustomDocument'); -late final _FPDF_LoadCustomDocument = _FPDF_LoadCustomDocumentPtr.asFunction , FPDF_BYTESTRING )>(); - -/// Function: FPDF_GetFileVersion -/// Get the file version of the given PDF document. -/// Parameters: -/// doc - Handle to a document. -/// fileVersion - The PDF file version. File version: 14 for 1.4, 15 -/// for 1.5, ... -/// Return value: -/// True if succeeds, false otherwise. -/// Comments: -/// If the document was created by FPDF_CreateNewDocument, -/// then this function will always fail. -int FPDF_GetFileVersion(FPDF_DOCUMENT doc, -ffi.Pointer fileVersion, -) { - return _FPDF_GetFileVersion(doc, -fileVersion, -); -} - -late final _FPDF_GetFileVersionPtr = _lookup< - ffi.NativeFunction )>>('FPDF_GetFileVersion'); -late final _FPDF_GetFileVersion = _FPDF_GetFileVersionPtr.asFunction )>(); - -/// Function: FPDF_GetLastError -/// Get last error code when a function fails. -/// Parameters: -/// None. -/// Return value: -/// A 32-bit integer indicating error code as defined above. -/// Comments: -/// If the previous SDK call succeeded, the return value of this -/// function is not defined. This function only works in conjunction -/// with APIs that mention FPDF_GetLastError() in their documentation. -int FPDF_GetLastError() { - return _FPDF_GetLastError(); -} - -late final _FPDF_GetLastErrorPtr = _lookup< - ffi.NativeFunction>('FPDF_GetLastError'); -late final _FPDF_GetLastError = _FPDF_GetLastErrorPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_DocumentHasValidCrossReferenceTable -/// Whether the document's cross reference table is valid or not. -/// Parameters: -/// document - Handle to a document. Returned by FPDF_LoadDocument. -/// Return value: -/// True if the PDF parser did not encounter problems parsing the cross -/// reference table. False if the parser could not parse the cross -/// reference table and the table had to be rebuild from other data -/// within the document. -/// Comments: -/// The return value can change over time as the PDF parser evolves. -int FPDF_DocumentHasValidCrossReferenceTable(FPDF_DOCUMENT document, -) { - return _FPDF_DocumentHasValidCrossReferenceTable(document, -); -} - -late final _FPDF_DocumentHasValidCrossReferenceTablePtr = _lookup< - ffi.NativeFunction>('FPDF_DocumentHasValidCrossReferenceTable'); -late final _FPDF_DocumentHasValidCrossReferenceTable = _FPDF_DocumentHasValidCrossReferenceTablePtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_GetTrailerEnds -/// Get the byte offsets of trailer ends. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument(). -/// buffer - The address of a buffer that receives the -/// byte offsets. -/// length - The size, in ints, of |buffer|. -/// Return value: -/// Returns the number of ints in the buffer on success, 0 on error. -/// -/// |buffer| is an array of integers that describes the exact byte offsets of the -/// trailer ends in the document. If |length| is less than the returned length, -/// or |document| or |buffer| is NULL, |buffer| will not be modified. -int FPDF_GetTrailerEnds(FPDF_DOCUMENT document, -ffi.Pointer buffer, -int length, -) { - return _FPDF_GetTrailerEnds(document, -buffer, -length, -); -} - -late final _FPDF_GetTrailerEndsPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetTrailerEnds'); -late final _FPDF_GetTrailerEnds = _FPDF_GetTrailerEndsPtr.asFunction , int )>(); - -/// Function: FPDF_GetDocPermissions -/// Get file permission flags of the document. -/// Parameters: -/// document - Handle to a document. Returned by FPDF_LoadDocument. -/// Return value: -/// A 32-bit integer indicating permission flags. Please refer to the -/// PDF Reference for detailed descriptions. If the document is not -/// protected or was unlocked by the owner, 0xffffffff will be returned. -int FPDF_GetDocPermissions(FPDF_DOCUMENT document, -) { - return _FPDF_GetDocPermissions(document, -); -} - -late final _FPDF_GetDocPermissionsPtr = _lookup< - ffi.NativeFunction>('FPDF_GetDocPermissions'); -late final _FPDF_GetDocPermissions = _FPDF_GetDocPermissionsPtr.asFunction(); - -/// Function: FPDF_GetDocUserPermissions -/// Get user file permission flags of the document. -/// Parameters: -/// document - Handle to a document. Returned by FPDF_LoadDocument. -/// Return value: -/// A 32-bit integer indicating permission flags. Please refer to the -/// PDF Reference for detailed descriptions. If the document is not -/// protected, 0xffffffff will be returned. Always returns user -/// permissions, even if the document was unlocked by the owner. -int FPDF_GetDocUserPermissions(FPDF_DOCUMENT document, -) { - return _FPDF_GetDocUserPermissions(document, -); -} - -late final _FPDF_GetDocUserPermissionsPtr = _lookup< - ffi.NativeFunction>('FPDF_GetDocUserPermissions'); -late final _FPDF_GetDocUserPermissions = _FPDF_GetDocUserPermissionsPtr.asFunction(); - -/// Function: FPDF_GetSecurityHandlerRevision -/// Get the revision for the security handler. -/// Parameters: -/// document - Handle to a document. Returned by FPDF_LoadDocument. -/// Return value: -/// The security handler revision number. Please refer to the PDF -/// Reference for a detailed description. If the document is not -/// protected, -1 will be returned. -int FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document, -) { - return _FPDF_GetSecurityHandlerRevision(document, -); -} - -late final _FPDF_GetSecurityHandlerRevisionPtr = _lookup< - ffi.NativeFunction>('FPDF_GetSecurityHandlerRevision'); -late final _FPDF_GetSecurityHandlerRevision = _FPDF_GetSecurityHandlerRevisionPtr.asFunction(); - -/// Function: FPDF_GetPageCount -/// Get total number of pages in the document. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument. -/// Return value: -/// Total number of pages in the document. -int FPDF_GetPageCount(FPDF_DOCUMENT document, -) { - return _FPDF_GetPageCount(document, -); -} - -late final _FPDF_GetPageCountPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageCount'); -late final _FPDF_GetPageCount = _FPDF_GetPageCountPtr.asFunction(); - -/// Function: FPDF_LoadPage -/// Load a page inside the document. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument -/// page_index - Index number of the page. 0 for the first page. -/// Return value: -/// A handle to the loaded page, or NULL if page load fails. -/// Comments: -/// The loaded page can be rendered to devices using FPDF_RenderPage. -/// The loaded page can be closed using FPDF_ClosePage. -FPDF_PAGE FPDF_LoadPage(FPDF_DOCUMENT document, -int page_index, -) { - return _FPDF_LoadPage(document, -page_index, -); -} - -late final _FPDF_LoadPagePtr = _lookup< - ffi.NativeFunction>('FPDF_LoadPage'); -late final _FPDF_LoadPage = _FPDF_LoadPagePtr.asFunction(); - -/// Experimental API -/// Function: FPDF_GetPageWidthF -/// Get page width. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// Return value: -/// Page width (excluding non-displayable area) measured in points. -/// One point is 1/72 inch (around 0.3528 mm). -/// Comments: -/// Changing the rotation of |page| affects the return value. -double FPDF_GetPageWidthF(FPDF_PAGE page, -) { - return _FPDF_GetPageWidthF(page, -); -} - -late final _FPDF_GetPageWidthFPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageWidthF'); -late final _FPDF_GetPageWidthF = _FPDF_GetPageWidthFPtr.asFunction(); - -/// Function: FPDF_GetPageWidth -/// Get page width. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// Return value: -/// Page width (excluding non-displayable area) measured in points. -/// One point is 1/72 inch (around 0.3528 mm). -/// Note: -/// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the -/// future. -/// Comments: -/// Changing the rotation of |page| affects the return value. -double FPDF_GetPageWidth(FPDF_PAGE page, -) { - return _FPDF_GetPageWidth(page, -); -} - -late final _FPDF_GetPageWidthPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageWidth'); -late final _FPDF_GetPageWidth = _FPDF_GetPageWidthPtr.asFunction(); - -/// Experimental API -/// Function: FPDF_GetPageHeightF -/// Get page height. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// Return value: -/// Page height (excluding non-displayable area) measured in points. -/// One point is 1/72 inch (around 0.3528 mm) -/// Comments: -/// Changing the rotation of |page| affects the return value. -double FPDF_GetPageHeightF(FPDF_PAGE page, -) { - return _FPDF_GetPageHeightF(page, -); -} - -late final _FPDF_GetPageHeightFPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageHeightF'); -late final _FPDF_GetPageHeightF = _FPDF_GetPageHeightFPtr.asFunction(); - -/// Function: FPDF_GetPageHeight -/// Get page height. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// Return value: -/// Page height (excluding non-displayable area) measured in points. -/// One point is 1/72 inch (around 0.3528 mm) -/// Note: -/// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the -/// future. -/// Comments: -/// Changing the rotation of |page| affects the return value. -double FPDF_GetPageHeight(FPDF_PAGE page, -) { - return _FPDF_GetPageHeight(page, -); -} - -late final _FPDF_GetPageHeightPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageHeight'); -late final _FPDF_GetPageHeight = _FPDF_GetPageHeightPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_GetPageBoundingBox -/// Get the bounding box of the page. This is the intersection between -/// its media box and its crop box. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// rect - Pointer to a rect to receive the page bounding box. -/// On an error, |rect| won't be filled. -/// Return value: -/// True for success. -int FPDF_GetPageBoundingBox(FPDF_PAGE page, -ffi.Pointer rect, -) { - return _FPDF_GetPageBoundingBox(page, -rect, -); -} - -late final _FPDF_GetPageBoundingBoxPtr = _lookup< - ffi.NativeFunction )>>('FPDF_GetPageBoundingBox'); -late final _FPDF_GetPageBoundingBox = _FPDF_GetPageBoundingBoxPtr.asFunction )>(); - -/// Experimental API. -/// Function: FPDF_GetPageSizeByIndexF -/// Get the size of the page at the given index. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument(). -/// page_index - Page index, zero for the first page. -/// size - Pointer to a FS_SIZEF to receive the page size. -/// (in points). -/// Return value: -/// Non-zero for success. 0 for error (document or page not found). -int FPDF_GetPageSizeByIndexF(FPDF_DOCUMENT document, -int page_index, -ffi.Pointer size, -) { - return _FPDF_GetPageSizeByIndexF(document, -page_index, -size, -); -} - -late final _FPDF_GetPageSizeByIndexFPtr = _lookup< - ffi.NativeFunction )>>('FPDF_GetPageSizeByIndexF'); -late final _FPDF_GetPageSizeByIndexF = _FPDF_GetPageSizeByIndexFPtr.asFunction )>(); - -/// Function: FPDF_GetPageSizeByIndex -/// Get the size of the page at the given index. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument. -/// page_index - Page index, zero for the first page. -/// width - Pointer to a double to receive the page width -/// (in points). -/// height - Pointer to a double to receive the page height -/// (in points). -/// Return value: -/// Non-zero for success. 0 for error (document or page not found). -/// Note: -/// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in -/// the future. -int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, -int page_index, -ffi.Pointer width, -ffi.Pointer height, -) { - return _FPDF_GetPageSizeByIndex(document, -page_index, -width, -height, -); -} - -late final _FPDF_GetPageSizeByIndexPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetPageSizeByIndex'); -late final _FPDF_GetPageSizeByIndex = _FPDF_GetPageSizeByIndexPtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDF_RenderPageBitmap -/// Render contents of a page to a device independent bitmap. -/// Parameters: -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). The bitmap handle can be created -/// by FPDFBitmap_Create or retrieved from an image -/// object by FPDFImageObj_GetBitmap. -/// page - Handle to the page. Returned by FPDF_LoadPage -/// start_x - Left pixel position of the display area in -/// bitmap coordinates. -/// start_y - Top pixel position of the display area in bitmap -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: -/// 0 (normal) -/// 1 (rotated 90 degrees clockwise) -/// 2 (rotated 180 degrees) -/// 3 (rotated 90 degrees counter-clockwise) -/// flags - 0 for normal display, or combination of the Page -/// Rendering flags defined above. With the FPDF_ANNOT -/// flag, it renders all annotations that do not require -/// user-interaction, which are all annotations except -/// widget and popup annotations. -/// Return value: -/// None. -void FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, -FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int flags, -) { - return _FPDF_RenderPageBitmap(bitmap, -page, -start_x, -start_y, -size_x, -size_y, -rotate, -flags, -); -} - -late final _FPDF_RenderPageBitmapPtr = _lookup< - ffi.NativeFunction>('FPDF_RenderPageBitmap'); -late final _FPDF_RenderPageBitmap = _FPDF_RenderPageBitmapPtr.asFunction(); - -/// Function: FPDF_RenderPageBitmapWithMatrix -/// Render contents of a page to a device independent bitmap. -/// Parameters: -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). The bitmap handle can be created -/// by FPDFBitmap_Create or retrieved by -/// FPDFImageObj_GetBitmap. -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// matrix - The transform matrix, which must be invertible. -/// See PDF Reference 1.7, 4.2.2 Common Transformations. -/// clipping - The rect to clip to in device coords. -/// flags - 0 for normal display, or combination of the Page -/// Rendering flags defined above. With the FPDF_ANNOT -/// flag, it renders all annotations that do not require -/// user-interaction, which are all annotations except -/// widget and popup annotations. -/// Return value: -/// None. Note that behavior is undefined if det of |matrix| is 0. -void FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, -FPDF_PAGE page, -ffi.Pointer matrix, -ffi.Pointer clipping, -int flags, -) { - return _FPDF_RenderPageBitmapWithMatrix(bitmap, -page, -matrix, -clipping, -flags, -); -} - -late final _FPDF_RenderPageBitmapWithMatrixPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_RenderPageBitmapWithMatrix'); -late final _FPDF_RenderPageBitmapWithMatrix = _FPDF_RenderPageBitmapWithMatrixPtr.asFunction , ffi.Pointer , int )>(); - -/// Function: FPDF_ClosePage -/// Close a loaded PDF page. -/// Parameters: -/// page - Handle to the loaded page. -/// Return value: -/// None. -void FPDF_ClosePage(FPDF_PAGE page, -) { - return _FPDF_ClosePage(page, -); -} - -late final _FPDF_ClosePagePtr = _lookup< - ffi.NativeFunction>('FPDF_ClosePage'); -late final _FPDF_ClosePage = _FPDF_ClosePagePtr.asFunction(); - -/// Function: FPDF_CloseDocument -/// Close a loaded PDF document. -/// Parameters: -/// document - Handle to the loaded document. -/// Return value: -/// None. -void FPDF_CloseDocument(FPDF_DOCUMENT document, -) { - return _FPDF_CloseDocument(document, -); -} - -late final _FPDF_CloseDocumentPtr = _lookup< - ffi.NativeFunction>('FPDF_CloseDocument'); -late final _FPDF_CloseDocument = _FPDF_CloseDocumentPtr.asFunction(); - -/// Function: FPDF_DeviceToPage -/// Convert the screen coordinates of a point to page coordinates. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// start_x - Left pixel position of the display area in -/// device coordinates. -/// start_y - Top pixel position of the display area in device -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: -/// 0 (normal) -/// 1 (rotated 90 degrees clockwise) -/// 2 (rotated 180 degrees) -/// 3 (rotated 90 degrees counter-clockwise) -/// device_x - X value in device coordinates to be converted. -/// device_y - Y value in device coordinates to be converted. -/// page_x - A pointer to a double receiving the converted X -/// value in page coordinates. -/// page_y - A pointer to a double receiving the converted Y -/// value in page coordinates. -/// Return value: -/// Returns true if the conversion succeeds, and |page_x| and |page_y| -/// successfully receives the converted coordinates. -/// Comments: -/// The page coordinate system has its origin at the left-bottom corner -/// of the page, with the X-axis on the bottom going to the right, and -/// the Y-axis on the left side going up. -/// -/// NOTE: this coordinate system can be altered when you zoom, scroll, -/// or rotate a page, however, a point on the page should always have -/// the same coordinate values in the page coordinate system. -/// -/// The device coordinate system is device dependent. For screen device, -/// its origin is at the left-top corner of the window. However this -/// origin can be altered by the Windows coordinate transformation -/// utilities. -/// -/// You must make sure the start_x, start_y, size_x, size_y -/// and rotate parameters have exactly same values as you used in -/// the FPDF_RenderPage() function call. -int FPDF_DeviceToPage(FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int device_x, -int device_y, -ffi.Pointer page_x, -ffi.Pointer page_y, -) { - return _FPDF_DeviceToPage(page, -start_x, -start_y, -size_x, -size_y, -rotate, -device_x, -device_y, -page_x, -page_y, -); -} - -late final _FPDF_DeviceToPagePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDF_DeviceToPage'); -late final _FPDF_DeviceToPage = _FPDF_DeviceToPagePtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDF_PageToDevice -/// Convert the page coordinates of a point to screen coordinates. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage. -/// start_x - Left pixel position of the display area in -/// device coordinates. -/// start_y - Top pixel position of the display area in device -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: -/// 0 (normal) -/// 1 (rotated 90 degrees clockwise) -/// 2 (rotated 180 degrees) -/// 3 (rotated 90 degrees counter-clockwise) -/// page_x - X value in page coordinates. -/// page_y - Y value in page coordinate. -/// device_x - A pointer to an integer receiving the result X -/// value in device coordinates. -/// device_y - A pointer to an integer receiving the result Y -/// value in device coordinates. -/// Return value: -/// Returns true if the conversion succeeds, and |device_x| and -/// |device_y| successfully receives the converted coordinates. -/// Comments: -/// See comments for FPDF_DeviceToPage(). -int FPDF_PageToDevice(FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -double page_x, -double page_y, -ffi.Pointer device_x, -ffi.Pointer device_y, -) { - return _FPDF_PageToDevice(page, -start_x, -start_y, -size_x, -size_y, -rotate, -page_x, -page_y, -device_x, -device_y, -); -} - -late final _FPDF_PageToDevicePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDF_PageToDevice'); -late final _FPDF_PageToDevice = _FPDF_PageToDevicePtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDFBitmap_Create -/// Create a device independent bitmap (FXDIB). -/// Parameters: -/// width - The number of pixels in width for the bitmap. -/// Must be greater than 0. -/// height - The number of pixels in height for the bitmap. -/// Must be greater than 0. -/// alpha - A flag indicating whether the alpha channel is used. -/// Non-zero for using alpha, zero for not using. -/// Return value: -/// The created bitmap handle, or NULL if a parameter error or out of -/// memory. -/// Comments: -/// The bitmap always uses 4 bytes per pixel. The first byte is always -/// double word aligned. -/// -/// The byte order is BGRx (the last byte unused if no alpha channel) or -/// BGRA. -/// -/// The pixels in a horizontal line are stored side by side, with the -/// left most pixel stored first (with lower memory address). -/// Each line uses width * 4 bytes. -/// -/// Lines are stored one after another, with the top most line stored -/// first. There is no gap between adjacent lines. -/// -/// This function allocates enough memory for holding all pixels in the -/// bitmap, but it doesn't initialize the buffer. Applications can use -/// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS -/// allows it, this function can allocate up to 4 GB of memory. -FPDF_BITMAP FPDFBitmap_Create(int width, -int height, -int alpha, -) { - return _FPDFBitmap_Create(width, -height, -alpha, -); -} - -late final _FPDFBitmap_CreatePtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_Create'); -late final _FPDFBitmap_Create = _FPDFBitmap_CreatePtr.asFunction(); - -/// Function: FPDFBitmap_CreateEx -/// Create a device independent bitmap (FXDIB) -/// Parameters: -/// width - The number of pixels in width for the bitmap. -/// Must be greater than 0. -/// height - The number of pixels in height for the bitmap. -/// Must be greater than 0. -/// format - A number indicating for bitmap format, as defined -/// above. -/// first_scan - A pointer to the first byte of the first line if -/// using an external buffer. If this parameter is NULL, -/// then a new buffer will be created. -/// stride - Number of bytes for each scan line. The value must -/// be 0 or greater. When the value is 0, -/// FPDFBitmap_CreateEx() will automatically calculate -/// the appropriate value using |width| and |format|. -/// When using an external buffer, it is recommended for -/// the caller to pass in the value. -/// When not using an external buffer, it is recommended -/// for the caller to pass in 0. -/// Return value: -/// The bitmap handle, or NULL if parameter error or out of memory. -/// Comments: -/// Similar to FPDFBitmap_Create function, but allows for more formats -/// and an external buffer is supported. The bitmap created by this -/// function can be used in any place that a FPDF_BITMAP handle is -/// required. -/// -/// If an external buffer is used, then the caller should destroy the -/// buffer. FPDFBitmap_Destroy() will not destroy the buffer. -/// -/// It is recommended to use FPDFBitmap_GetStride() to get the stride -/// value. -FPDF_BITMAP FPDFBitmap_CreateEx(int width, -int height, -int format, -ffi.Pointer first_scan, -int stride, -) { - return _FPDFBitmap_CreateEx(width, -height, -format, -first_scan, -stride, -); -} - -late final _FPDFBitmap_CreateExPtr = _lookup< - ffi.NativeFunction , ffi.Int )>>('FPDFBitmap_CreateEx'); -late final _FPDFBitmap_CreateEx = _FPDFBitmap_CreateExPtr.asFunction , int )>(); - -/// Function: FPDFBitmap_GetFormat -/// Get the format of the bitmap. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// The format of the bitmap. -/// Comments: -/// Only formats supported by FPDFBitmap_CreateEx are supported by this -/// function; see the list of such formats above. -int FPDFBitmap_GetFormat(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_GetFormat(bitmap, -); -} - -late final _FPDFBitmap_GetFormatPtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_GetFormat'); -late final _FPDFBitmap_GetFormat = _FPDFBitmap_GetFormatPtr.asFunction(); - -/// Function: FPDFBitmap_FillRect -/// Fill a rectangle in a bitmap. -/// Parameters: -/// bitmap - The handle to the bitmap. Returned by -/// FPDFBitmap_Create. -/// left - The left position. Starting from 0 at the -/// left-most pixel. -/// top - The top position. Starting from 0 at the -/// top-most line. -/// width - Width in pixels to be filled. -/// height - Height in pixels to be filled. -/// color - A 32-bit value specifing the color, in 8888 ARGB -/// format. -/// Return value: -/// Returns whether the operation succeeded or not. -/// Comments: -/// This function sets the color and (optionally) alpha value in the -/// specified region of the bitmap. -/// -/// NOTE: If the alpha channel is used, this function does NOT -/// composite the background with the source color, instead the -/// background will be replaced by the source color and the alpha. -/// -/// If the alpha channel is not used, the alpha parameter is ignored. -int FPDFBitmap_FillRect(FPDF_BITMAP bitmap, -int left, -int top, -int width, -int height, -int color, -) { - return _FPDFBitmap_FillRect(bitmap, -left, -top, -width, -height, -color, -); -} - -late final _FPDFBitmap_FillRectPtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_FillRect'); -late final _FPDFBitmap_FillRect = _FPDFBitmap_FillRectPtr.asFunction(); - -/// Function: FPDFBitmap_GetBuffer -/// Get data buffer of a bitmap. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// The pointer to the first byte of the bitmap buffer. -/// Comments: -/// The stride may be more than width * number of bytes per pixel -/// -/// Applications can use this function to get the bitmap buffer pointer, -/// then manipulate any color and/or alpha values for any pixels in the -/// bitmap. -/// -/// Use FPDFBitmap_GetFormat() to find out the format of the data. -ffi.Pointer FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_GetBuffer(bitmap, -); -} - -late final _FPDFBitmap_GetBufferPtr = _lookup< - ffi.NativeFunction Function(FPDF_BITMAP )>>('FPDFBitmap_GetBuffer'); -late final _FPDFBitmap_GetBuffer = _FPDFBitmap_GetBufferPtr.asFunction Function(FPDF_BITMAP )>(); - -/// Function: FPDFBitmap_GetWidth -/// Get width of a bitmap. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// The width of the bitmap in pixels. -int FPDFBitmap_GetWidth(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_GetWidth(bitmap, -); -} - -late final _FPDFBitmap_GetWidthPtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_GetWidth'); -late final _FPDFBitmap_GetWidth = _FPDFBitmap_GetWidthPtr.asFunction(); - -/// Function: FPDFBitmap_GetHeight -/// Get height of a bitmap. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// The height of the bitmap in pixels. -int FPDFBitmap_GetHeight(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_GetHeight(bitmap, -); -} - -late final _FPDFBitmap_GetHeightPtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_GetHeight'); -late final _FPDFBitmap_GetHeight = _FPDFBitmap_GetHeightPtr.asFunction(); - -/// Function: FPDFBitmap_GetStride -/// Get number of bytes for each line in the bitmap buffer. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// The number of bytes for each line in the bitmap buffer. -/// Comments: -/// The stride may be more than width * number of bytes per pixel. -int FPDFBitmap_GetStride(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_GetStride(bitmap, -); -} - -late final _FPDFBitmap_GetStridePtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_GetStride'); -late final _FPDFBitmap_GetStride = _FPDFBitmap_GetStridePtr.asFunction(); - -/// Function: FPDFBitmap_Destroy -/// Destroy a bitmap and release all related buffers. -/// Parameters: -/// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create -/// or FPDFImageObj_GetBitmap. -/// Return value: -/// None. -/// Comments: -/// This function will not destroy any external buffers provided when -/// the bitmap was created. -void FPDFBitmap_Destroy(FPDF_BITMAP bitmap, -) { - return _FPDFBitmap_Destroy(bitmap, -); -} - -late final _FPDFBitmap_DestroyPtr = _lookup< - ffi.NativeFunction>('FPDFBitmap_Destroy'); -late final _FPDFBitmap_Destroy = _FPDFBitmap_DestroyPtr.asFunction(); - -/// Function: FPDF_VIEWERREF_GetPrintScaling -/// Whether the PDF document prefers to be scaled or not. -/// Parameters: -/// document - Handle to the loaded document. -/// Return value: -/// None. -int FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document, -) { - return _FPDF_VIEWERREF_GetPrintScaling(document, -); -} - -late final _FPDF_VIEWERREF_GetPrintScalingPtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintScaling'); -late final _FPDF_VIEWERREF_GetPrintScaling = _FPDF_VIEWERREF_GetPrintScalingPtr.asFunction(); - -/// Function: FPDF_VIEWERREF_GetNumCopies -/// Returns the number of copies to be printed. -/// Parameters: -/// document - Handle to the loaded document. -/// Return value: -/// The number of copies to be printed. -int FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document, -) { - return _FPDF_VIEWERREF_GetNumCopies(document, -); -} - -late final _FPDF_VIEWERREF_GetNumCopiesPtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetNumCopies'); -late final _FPDF_VIEWERREF_GetNumCopies = _FPDF_VIEWERREF_GetNumCopiesPtr.asFunction(); - -/// Function: FPDF_VIEWERREF_GetPrintPageRange -/// Page numbers to initialize print dialog box when file is printed. -/// Parameters: -/// document - Handle to the loaded document. -/// Return value: -/// The print page range to be used for printing. -FPDF_PAGERANGE FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document, -) { - return _FPDF_VIEWERREF_GetPrintPageRange(document, -); -} - -late final _FPDF_VIEWERREF_GetPrintPageRangePtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRange'); -late final _FPDF_VIEWERREF_GetPrintPageRange = _FPDF_VIEWERREF_GetPrintPageRangePtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_VIEWERREF_GetPrintPageRangeCount -/// Returns the number of elements in a FPDF_PAGERANGE. -/// Parameters: -/// pagerange - Handle to the page range. -/// Return value: -/// The number of elements in the page range. Returns 0 on error. -int FPDF_VIEWERREF_GetPrintPageRangeCount(FPDF_PAGERANGE pagerange, -) { - return _FPDF_VIEWERREF_GetPrintPageRangeCount(pagerange, -); -} - -late final _FPDF_VIEWERREF_GetPrintPageRangeCountPtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeCount'); -late final _FPDF_VIEWERREF_GetPrintPageRangeCount = _FPDF_VIEWERREF_GetPrintPageRangeCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_VIEWERREF_GetPrintPageRangeElement -/// Returns an element from a FPDF_PAGERANGE. -/// Parameters: -/// pagerange - Handle to the page range. -/// index - Index of the element. -/// Return value: -/// The value of the element in the page range at a given index. -/// Returns -1 on error. -int FPDF_VIEWERREF_GetPrintPageRangeElement(FPDF_PAGERANGE pagerange, -int index, -) { - return _FPDF_VIEWERREF_GetPrintPageRangeElement(pagerange, -index, -); -} - -late final _FPDF_VIEWERREF_GetPrintPageRangeElementPtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetPrintPageRangeElement'); -late final _FPDF_VIEWERREF_GetPrintPageRangeElement = _FPDF_VIEWERREF_GetPrintPageRangeElementPtr.asFunction(); - -/// Function: FPDF_VIEWERREF_GetDuplex -/// Returns the paper handling option to be used when printing from -/// the print dialog. -/// Parameters: -/// document - Handle to the loaded document. -/// Return value: -/// The paper handling option to be used when printing. -_FPDF_DUPLEXTYPE_ FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document, -) { - return _FPDF_DUPLEXTYPE_.fromValue(_FPDF_VIEWERREF_GetDuplex(document, -)); -} - -late final _FPDF_VIEWERREF_GetDuplexPtr = _lookup< - ffi.NativeFunction>('FPDF_VIEWERREF_GetDuplex'); -late final _FPDF_VIEWERREF_GetDuplex = _FPDF_VIEWERREF_GetDuplexPtr.asFunction(); - -/// Function: FPDF_VIEWERREF_GetName -/// Gets the contents for a viewer ref, with a given key. The value must -/// be of type "name". -/// Parameters: -/// document - Handle to the loaded document. -/// key - Name of the key in the viewer pref dictionary, -/// encoded in UTF-8. -/// buffer - Caller-allocate buffer to receive the key, or NULL -/// - to query the required length. -/// length - Length of the buffer. -/// Return value: -/// The number of bytes in the contents, including the NULL terminator. -/// Thus if the return value is 0, then that indicates an error, such -/// as when |document| is invalid. If |length| is less than the required -/// length, or |buffer| is NULL, |buffer| will not be modified. -int FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int length, -) { - return _FPDF_VIEWERREF_GetName(document, -key, -buffer, -length, -); -} - -late final _FPDF_VIEWERREF_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_VIEWERREF_GetName'); -late final _FPDF_VIEWERREF_GetName = _FPDF_VIEWERREF_GetNamePtr.asFunction , int )>(); - -/// Function: FPDF_CountNamedDests -/// Get the count of named destinations in the PDF document. -/// Parameters: -/// document - Handle to a document -/// Return value: -/// The count of named destinations. -int FPDF_CountNamedDests(FPDF_DOCUMENT document, -) { - return _FPDF_CountNamedDests(document, -); -} - -late final _FPDF_CountNamedDestsPtr = _lookup< - ffi.NativeFunction>('FPDF_CountNamedDests'); -late final _FPDF_CountNamedDests = _FPDF_CountNamedDestsPtr.asFunction(); - -/// Function: FPDF_GetNamedDestByName -/// Get a the destination handle for the given name. -/// Parameters: -/// document - Handle to the loaded document. -/// name - The name of a destination. -/// Return value: -/// The handle to the destination. -FPDF_DEST FPDF_GetNamedDestByName(FPDF_DOCUMENT document, -FPDF_BYTESTRING name, -) { - return _FPDF_GetNamedDestByName(document, -name, -); -} - -late final _FPDF_GetNamedDestByNamePtr = _lookup< - ffi.NativeFunction>('FPDF_GetNamedDestByName'); -late final _FPDF_GetNamedDestByName = _FPDF_GetNamedDestByNamePtr.asFunction(); - -/// Function: FPDF_GetNamedDest -/// Get the named destination by index. -/// Parameters: -/// document - Handle to a document -/// index - The index of a named destination. -/// buffer - The buffer to store the destination name, -/// used as wchar_t*. -/// buflen [in/out] - Size of the buffer in bytes on input, -/// length of the result in bytes on output -/// or -1 if the buffer is too small. -/// Return value: -/// The destination handle for a given index, or NULL if there is no -/// named destination corresponding to |index|. -/// Comments: -/// Call this function twice to get the name of the named destination: -/// 1) First time pass in |buffer| as NULL and get buflen. -/// 2) Second time pass in allocated |buffer| and buflen to retrieve -/// |buffer|, which should be used as wchar_t*. -/// -/// If buflen is not sufficiently large, it will be set to -1 upon -/// return. -FPDF_DEST FPDF_GetNamedDest(FPDF_DOCUMENT document, -int index, -ffi.Pointer buffer, -ffi.Pointer buflen, -) { - return _FPDF_GetNamedDest(document, -index, -buffer, -buflen, -); -} - -late final _FPDF_GetNamedDestPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDF_GetNamedDest'); -late final _FPDF_GetNamedDest = _FPDF_GetNamedDestPtr.asFunction , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDF_GetXFAPacketCount -/// Get the number of valid packets in the XFA entry. -/// Parameters: -/// document - Handle to the document. -/// Return value: -/// The number of valid packets, or -1 on error. -int FPDF_GetXFAPacketCount(FPDF_DOCUMENT document, -) { - return _FPDF_GetXFAPacketCount(document, -); -} - -late final _FPDF_GetXFAPacketCountPtr = _lookup< - ffi.NativeFunction>('FPDF_GetXFAPacketCount'); -late final _FPDF_GetXFAPacketCount = _FPDF_GetXFAPacketCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_GetXFAPacketName -/// Get the name of a packet in the XFA array. -/// Parameters: -/// document - Handle to the document. -/// index - Index number of the packet. 0 for the first packet. -/// buffer - Buffer for holding the name of the XFA packet. -/// buflen - Length of |buffer| in bytes. -/// Return value: -/// The length of the packet name in bytes, or 0 on error. -/// -/// |document| must be valid and |index| must be in the range [0, N), where N is -/// the value returned by FPDF_GetXFAPacketCount(). -/// |buffer| is only modified if it is non-NULL and |buflen| is greater than or -/// equal to the length of the packet name. The packet name includes a -/// terminating NUL character. |buffer| is unmodified on error. -int FPDF_GetXFAPacketName(FPDF_DOCUMENT document, -int index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_GetXFAPacketName(document, -index, -buffer, -buflen, -); -} - -late final _FPDF_GetXFAPacketNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetXFAPacketName'); -late final _FPDF_GetXFAPacketName = _FPDF_GetXFAPacketNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDF_GetXFAPacketContent -/// Get the content of a packet in the XFA array. -/// Parameters: -/// document - Handle to the document. -/// index - Index number of the packet. 0 for the first packet. -/// buffer - Buffer for holding the content of the XFA packet. -/// buflen - Length of |buffer| in bytes. -/// out_buflen - Pointer to the variable that will receive the minimum -/// buffer size needed to contain the content of the XFA -/// packet. -/// Return value: -/// Whether the operation succeeded or not. -/// -/// |document| must be valid and |index| must be in the range [0, N), where N is -/// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be -/// NULL. When the aforementioned arguments are valid, the operation succeeds, -/// and |out_buflen| receives the content size. |buffer| is only modified if -/// |buffer| is non-null and long enough to contain the content. Callers must -/// check both the return value and the input |buflen| is no less than the -/// returned |out_buflen| before using the data in |buffer|. -int FPDF_GetXFAPacketContent(FPDF_DOCUMENT document, -int index, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDF_GetXFAPacketContent(document, -index, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDF_GetXFAPacketContentPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_GetXFAPacketContent'); -late final _FPDF_GetXFAPacketContent = _FPDF_GetXFAPacketContentPtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDF_GetSignatureCount -/// Get total number of signatures in the document. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument(). -/// Return value: -/// Total number of signatures in the document on success, -1 on error. -int FPDF_GetSignatureCount(FPDF_DOCUMENT document, -) { - return _FPDF_GetSignatureCount(document, -); -} - -late final _FPDF_GetSignatureCountPtr = _lookup< - ffi.NativeFunction>('FPDF_GetSignatureCount'); -late final _FPDF_GetSignatureCount = _FPDF_GetSignatureCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_GetSignatureObject -/// Get the Nth signature of the document. -/// Parameters: -/// document - Handle to document. Returned by FPDF_LoadDocument(). -/// index - Index into the array of signatures of the document. -/// Return value: -/// Returns the handle to the signature, or NULL on failure. The caller -/// does not take ownership of the returned FPDF_SIGNATURE. Instead, it -/// remains valid until FPDF_CloseDocument() is called for the document. -FPDF_SIGNATURE FPDF_GetSignatureObject(FPDF_DOCUMENT document, -int index, -) { - return _FPDF_GetSignatureObject(document, -index, -); -} - -late final _FPDF_GetSignatureObjectPtr = _lookup< - ffi.NativeFunction>('FPDF_GetSignatureObject'); -late final _FPDF_GetSignatureObject = _FPDF_GetSignatureObjectPtr.asFunction(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetContents -/// Get the contents of a signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// buffer - The address of a buffer that receives the contents. -/// length - The size, in bytes, of |buffer|. -/// Return value: -/// Returns the number of bytes in the contents on success, 0 on error. -/// -/// For public-key signatures, |buffer| is either a DER-encoded PKCS#1 binary or -/// a DER-encoded PKCS#7 binary. If |length| is less than the returned length, or -/// |buffer| is NULL, |buffer| will not be modified. -int FPDFSignatureObj_GetContents(FPDF_SIGNATURE signature, -ffi.Pointer buffer, -int length, -) { - return _FPDFSignatureObj_GetContents(signature, -buffer, -length, -); -} - -late final _FPDFSignatureObj_GetContentsPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetContents'); -late final _FPDFSignatureObj_GetContents = _FPDFSignatureObj_GetContentsPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetByteRange -/// Get the byte range of a signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// buffer - The address of a buffer that receives the -/// byte range. -/// length - The size, in ints, of |buffer|. -/// Return value: -/// Returns the number of ints in the byte range on -/// success, 0 on error. -/// -/// |buffer| is an array of pairs of integers (starting byte offset, -/// length in bytes) that describes the exact byte range for the digest -/// calculation. If |length| is less than the returned length, or -/// |buffer| is NULL, |buffer| will not be modified. -int FPDFSignatureObj_GetByteRange(FPDF_SIGNATURE signature, -ffi.Pointer buffer, -int length, -) { - return _FPDFSignatureObj_GetByteRange(signature, -buffer, -length, -); -} - -late final _FPDFSignatureObj_GetByteRangePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetByteRange'); -late final _FPDFSignatureObj_GetByteRange = _FPDFSignatureObj_GetByteRangePtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetSubFilter -/// Get the encoding of the value of a signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// buffer - The address of a buffer that receives the encoding. -/// length - The size, in bytes, of |buffer|. -/// Return value: -/// Returns the number of bytes in the encoding name (including the -/// trailing NUL character) on success, 0 on error. -/// -/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the -/// returned length, or |buffer| is NULL, |buffer| will not be modified. -int FPDFSignatureObj_GetSubFilter(FPDF_SIGNATURE signature, -ffi.Pointer buffer, -int length, -) { - return _FPDFSignatureObj_GetSubFilter(signature, -buffer, -length, -); -} - -late final _FPDFSignatureObj_GetSubFilterPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetSubFilter'); -late final _FPDFSignatureObj_GetSubFilter = _FPDFSignatureObj_GetSubFilterPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetReason -/// Get the reason (comment) of the signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// buffer - The address of a buffer that receives the reason. -/// length - The size, in bytes, of |buffer|. -/// Return value: -/// Returns the number of bytes in the reason on success, 0 on error. -/// -/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The -/// string is terminated by a UTF16 NUL character. If |length| is less than the -/// returned length, or |buffer| is NULL, |buffer| will not be modified. -int FPDFSignatureObj_GetReason(FPDF_SIGNATURE signature, -ffi.Pointer buffer, -int length, -) { - return _FPDFSignatureObj_GetReason(signature, -buffer, -length, -); -} - -late final _FPDFSignatureObj_GetReasonPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetReason'); -late final _FPDFSignatureObj_GetReason = _FPDFSignatureObj_GetReasonPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetTime -/// Get the time of signing of a signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// buffer - The address of a buffer that receives the time. -/// length - The size, in bytes, of |buffer|. -/// Return value: -/// Returns the number of bytes in the encoding name (including the -/// trailing NUL character) on success, 0 on error. -/// -/// The |buffer| is always encoded in 7-bit ASCII. If |length| is less than the -/// returned length, or |buffer| is NULL, |buffer| will not be modified. -/// -/// The format of time is expected to be D:YYYYMMDDHHMMSS+XX'YY', i.e. it's -/// percision is seconds, with timezone information. This value should be used -/// only when the time of signing is not available in the (PKCS#7 binary) -/// signature. -int FPDFSignatureObj_GetTime(FPDF_SIGNATURE signature, -ffi.Pointer buffer, -int length, -) { - return _FPDFSignatureObj_GetTime(signature, -buffer, -length, -); -} - -late final _FPDFSignatureObj_GetTimePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFSignatureObj_GetTime'); -late final _FPDFSignatureObj_GetTime = _FPDFSignatureObj_GetTimePtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDFSignatureObj_GetDocMDPPermission -/// Get the DocMDP permission of a signature object. -/// Parameters: -/// signature - Handle to the signature object. Returned by -/// FPDF_GetSignatureObject(). -/// Return value: -/// Returns the permission (1, 2 or 3) on success, 0 on error. -int FPDFSignatureObj_GetDocMDPPermission(FPDF_SIGNATURE signature, -) { - return _FPDFSignatureObj_GetDocMDPPermission(signature, -); -} - -late final _FPDFSignatureObj_GetDocMDPPermissionPtr = _lookup< - ffi.NativeFunction>('FPDFSignatureObj_GetDocMDPPermission'); -late final _FPDFSignatureObj_GetDocMDPPermission = _FPDFSignatureObj_GetDocMDPPermissionPtr.asFunction(); - -/// Function: FPDF_GetDefaultTTFMap -/// Returns a pointer to the default character set to TT Font name map. The -/// map is an array of FPDF_CharsetFontMap structs, with its end indicated -/// by a { -1, NULL } entry. -/// Parameters: -/// None. -/// Return Value: -/// Pointer to the Charset Font Map. -/// Note: -/// Once FPDF_GetDefaultTTFMapCount() and FPDF_GetDefaultTTFMapEntry() are no -/// longer experimental, this API will be marked as deprecated. -/// See https://crbug.com/348468114 -ffi.Pointer FPDF_GetDefaultTTFMap() { - return _FPDF_GetDefaultTTFMap(); -} - -late final _FPDF_GetDefaultTTFMapPtr = _lookup< - ffi.NativeFunction Function()>>('FPDF_GetDefaultTTFMap'); -late final _FPDF_GetDefaultTTFMap = _FPDF_GetDefaultTTFMapPtr.asFunction Function()>(); - -/// Experimental API. -/// -/// Function: FPDF_GetDefaultTTFMapCount -/// Returns the number of entries in the default character set to TT Font name -/// map. -/// Parameters: -/// None. -/// Return Value: -/// The number of entries in the map. -int FPDF_GetDefaultTTFMapCount() { - return _FPDF_GetDefaultTTFMapCount(); -} - -late final _FPDF_GetDefaultTTFMapCountPtr = _lookup< - ffi.NativeFunction>('FPDF_GetDefaultTTFMapCount'); -late final _FPDF_GetDefaultTTFMapCount = _FPDF_GetDefaultTTFMapCountPtr.asFunction(); - -/// Experimental API. -/// -/// Function: FPDF_GetDefaultTTFMapEntry -/// Returns an entry in the default character set to TT Font name map. -/// Parameters: -/// index - The index to the entry in the map to retrieve. -/// Return Value: -/// A pointer to the entry, if it is in the map, or NULL if the index is out -/// of bounds. -ffi.Pointer FPDF_GetDefaultTTFMapEntry(int index, -) { - return _FPDF_GetDefaultTTFMapEntry(index, -); -} - -late final _FPDF_GetDefaultTTFMapEntryPtr = _lookup< - ffi.NativeFunction Function(ffi.Size )>>('FPDF_GetDefaultTTFMapEntry'); -late final _FPDF_GetDefaultTTFMapEntry = _FPDF_GetDefaultTTFMapEntryPtr.asFunction Function(int )>(); - -/// Function: FPDF_AddInstalledFont -/// Add a system font to the list in PDFium. -/// Comments: -/// This function is only called during the system font list building -/// process. -/// Parameters: -/// mapper - Opaque pointer to Foxit font mapper -/// face - The font face name -/// charset - Font character set. See above defined constants. -/// Return Value: -/// None. -void FPDF_AddInstalledFont(ffi.Pointer mapper, -ffi.Pointer face, -int charset, -) { - return _FPDF_AddInstalledFont(mapper, -face, -charset, -); -} - -late final _FPDF_AddInstalledFontPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Int )>>('FPDF_AddInstalledFont'); -late final _FPDF_AddInstalledFont = _FPDF_AddInstalledFontPtr.asFunction , ffi.Pointer , int )>(); - -/// Function: FPDF_SetSystemFontInfo -/// Set the system font info interface into PDFium -/// Parameters: -/// font_info - Pointer to a FPDF_SYSFONTINFO structure -/// Return Value: -/// None -/// Comments: -/// Platform support implementation should implement required methods of -/// FFDF_SYSFONTINFO interface, then call this function during PDFium -/// initialization process. -/// -/// Call this with NULL to tell PDFium to stop using a previously set -/// |FPDF_SYSFONTINFO|. -void FPDF_SetSystemFontInfo(ffi.Pointer font_info, -) { - return _FPDF_SetSystemFontInfo(font_info, -); -} - -late final _FPDF_SetSystemFontInfoPtr = _lookup< - ffi.NativeFunction )>>('FPDF_SetSystemFontInfo'); -late final _FPDF_SetSystemFontInfo = _FPDF_SetSystemFontInfoPtr.asFunction )>(); - -/// Function: FPDF_GetDefaultSystemFontInfo -/// Get default system font info interface for current platform -/// Parameters: -/// None -/// Return Value: -/// Pointer to a FPDF_SYSFONTINFO structure describing the default -/// interface, or NULL if the platform doesn't have a default interface. -/// Application should call FPDF_FreeDefaultSystemFontInfo to free the -/// returned pointer. -/// Comments: -/// For some platforms, PDFium implements a default version of system -/// font info interface. The default implementation can be passed to -/// FPDF_SetSystemFontInfo(). -ffi.Pointer FPDF_GetDefaultSystemFontInfo() { - return _FPDF_GetDefaultSystemFontInfo(); -} - -late final _FPDF_GetDefaultSystemFontInfoPtr = _lookup< - ffi.NativeFunction Function()>>('FPDF_GetDefaultSystemFontInfo'); -late final _FPDF_GetDefaultSystemFontInfo = _FPDF_GetDefaultSystemFontInfoPtr.asFunction Function()>(); - -/// Function: FPDF_FreeDefaultSystemFontInfo -/// Free a default system font info interface -/// Parameters: -/// font_info - Pointer to a FPDF_SYSFONTINFO structure -/// Return Value: -/// None -/// Comments: -/// This function should be called on the output from -/// FPDF_GetDefaultSystemFontInfo() once it is no longer needed. -void FPDF_FreeDefaultSystemFontInfo(ffi.Pointer font_info, -) { - return _FPDF_FreeDefaultSystemFontInfo(font_info, -); -} - -late final _FPDF_FreeDefaultSystemFontInfoPtr = _lookup< - ffi.NativeFunction )>>('FPDF_FreeDefaultSystemFontInfo'); -late final _FPDF_FreeDefaultSystemFontInfo = _FPDF_FreeDefaultSystemFontInfoPtr.asFunction )>(); - -/// Experimental API. -/// Get the number of JavaScript actions in |document|. -/// -/// document - handle to a document. -/// -/// Returns the number of JavaScript actions in |document| or -1 on error. -int FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document, -) { - return _FPDFDoc_GetJavaScriptActionCount(document, -); -} - -late final _FPDFDoc_GetJavaScriptActionCountPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_GetJavaScriptActionCount'); -late final _FPDFDoc_GetJavaScriptActionCount = _FPDFDoc_GetJavaScriptActionCountPtr.asFunction(); - -/// Experimental API. -/// Get the JavaScript action at |index| in |document|. -/// -/// document - handle to a document. -/// index - the index of the requested JavaScript action. -/// -/// Returns the handle to the JavaScript action, or NULL on failure. -/// Caller owns the returned handle and must close it with -/// FPDFDoc_CloseJavaScriptAction(). -FPDF_JAVASCRIPT_ACTION FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, -int index, -) { - return _FPDFDoc_GetJavaScriptAction(document, -index, -); -} - -late final _FPDFDoc_GetJavaScriptActionPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_GetJavaScriptAction'); -late final _FPDFDoc_GetJavaScriptAction = _FPDFDoc_GetJavaScriptActionPtr.asFunction(); - -/// javascript - Handle to a JavaScript action. -void FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript, -) { - return _FPDFDoc_CloseJavaScriptAction(javascript, -); -} - -late final _FPDFDoc_CloseJavaScriptActionPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_CloseJavaScriptAction'); -late final _FPDFDoc_CloseJavaScriptAction = _FPDFDoc_CloseJavaScriptActionPtr.asFunction(); - -/// Experimental API. -/// Get the name from the |javascript| handle. |buffer| is only modified if -/// |buflen| is longer than the length of the name. On errors, |buffer| is -/// unmodified and the returned length is 0. -/// -/// javascript - handle to an JavaScript action. -/// buffer - buffer for holding the name, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the JavaScript action name in bytes. -int FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFJavaScriptAction_GetName(javascript, -buffer, -buflen, -); -} - -late final _FPDFJavaScriptAction_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetName'); -late final _FPDFJavaScriptAction_GetName = _FPDFJavaScriptAction_GetNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the script from the |javascript| handle. |buffer| is only modified if -/// |buflen| is longer than the length of the script. On errors, |buffer| is -/// unmodified and the returned length is 0. -/// -/// javascript - handle to an JavaScript action. -/// buffer - buffer for holding the name, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the JavaScript action name in bytes. -int FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFJavaScriptAction_GetScript(javascript, -buffer, -buflen, -); -} - -late final _FPDFJavaScriptAction_GetScriptPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFJavaScriptAction_GetScript'); -late final _FPDFJavaScriptAction_GetScript = _FPDFJavaScriptAction_GetScriptPtr.asFunction , int )>(); - -/// Function: FPDFText_LoadPage -/// Prepare information about all characters in a page. -/// Parameters: -/// page - Handle to the page. Returned by FPDF_LoadPage function -/// (in FPDFVIEW module). -/// Return value: -/// A handle to the text page information structure. -/// NULL if something goes wrong. -/// Comments: -/// Application must call FPDFText_ClosePage to release the text page -/// information. -FPDF_TEXTPAGE FPDFText_LoadPage(FPDF_PAGE page, -) { - return _FPDFText_LoadPage(page, -); -} - -late final _FPDFText_LoadPagePtr = _lookup< - ffi.NativeFunction>('FPDFText_LoadPage'); -late final _FPDFText_LoadPage = _FPDFText_LoadPagePtr.asFunction(); - -/// Function: FPDFText_ClosePage -/// Release all resources allocated for a text page information -/// structure. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return Value: -/// None. -void FPDFText_ClosePage(FPDF_TEXTPAGE text_page, -) { - return _FPDFText_ClosePage(text_page, -); -} - -late final _FPDFText_ClosePagePtr = _lookup< - ffi.NativeFunction>('FPDFText_ClosePage'); -late final _FPDFText_ClosePage = _FPDFText_ClosePagePtr.asFunction(); - -/// Function: FPDFText_CountChars -/// Get number of characters in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return value: -/// Number of characters in the page. Return -1 for error. -/// Generated characters, like additional space characters, new line -/// characters, are also counted. -/// Comments: -/// Characters in a page form a "stream", inside the stream, each -/// character has an index. -/// We will use the index parameters in many of FPDFTEXT functions. The -/// first character in the page -/// has an index value of zero. -int FPDFText_CountChars(FPDF_TEXTPAGE text_page, -) { - return _FPDFText_CountChars(text_page, -); -} - -late final _FPDFText_CountCharsPtr = _lookup< - ffi.NativeFunction>('FPDFText_CountChars'); -late final _FPDFText_CountChars = _FPDFText_CountCharsPtr.asFunction(); - -/// Function: FPDFText_GetUnicode -/// Get Unicode of a character in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The Unicode of the particular character. -/// If a character is not encoded in Unicode and Foxit engine can't -/// convert to Unicode, -/// the return value will be zero. -int FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_GetUnicode(text_page, -index, -); -} - -late final _FPDFText_GetUnicodePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetUnicode'); -late final _FPDFText_GetUnicode = _FPDFText_GetUnicodePtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_GetTextObject -/// Get the FPDF_PAGEOBJECT associated with a given character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The associated text object for the character at |index|, or NULL on -/// error. The returned text object, if non-null, is of type -/// |FPDF_PAGEOBJ_TEXT|. The caller does not own the returned object. -FPDF_PAGEOBJECT FPDFText_GetTextObject(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_GetTextObject(text_page, -index, -); -} - -late final _FPDFText_GetTextObjectPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetTextObject'); -late final _FPDFText_GetTextObject = _FPDFText_GetTextObjectPtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_IsGenerated -/// Get if a character in a page is generated by PDFium. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character is generated by PDFium. -/// 0 if the character is not generated by PDFium. -/// -1 if there was an error. -int FPDFText_IsGenerated(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_IsGenerated(text_page, -index, -); -} - -late final _FPDFText_IsGeneratedPtr = _lookup< - ffi.NativeFunction>('FPDFText_IsGenerated'); -late final _FPDFText_IsGenerated = _FPDFText_IsGeneratedPtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_IsHyphen -/// Get if a character in a page is a hyphen. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character is a hyphen. -/// 0 if the character is not a hyphen. -/// -1 if there was an error. -int FPDFText_IsHyphen(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_IsHyphen(text_page, -index, -); -} - -late final _FPDFText_IsHyphenPtr = _lookup< - ffi.NativeFunction>('FPDFText_IsHyphen'); -late final _FPDFText_IsHyphen = _FPDFText_IsHyphenPtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_HasUnicodeMapError -/// Get if a character in a page has an invalid unicode mapping. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// 1 if the character has an invalid unicode mapping. -/// 0 if the character has no known unicode mapping issues. -/// -1 if there was an error. -int FPDFText_HasUnicodeMapError(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_HasUnicodeMapError(text_page, -index, -); -} - -late final _FPDFText_HasUnicodeMapErrorPtr = _lookup< - ffi.NativeFunction>('FPDFText_HasUnicodeMapError'); -late final _FPDFText_HasUnicodeMapError = _FPDFText_HasUnicodeMapErrorPtr.asFunction(); - -/// Function: FPDFText_GetFontSize -/// Get the font size of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// The font size of the particular character, measured in points (about -/// 1/72 inch). This is the typographic size of the font (so called -/// "em size"). -double FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_GetFontSize(text_page, -index, -); -} - -late final _FPDFText_GetFontSizePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetFontSize'); -late final _FPDFText_GetFontSize = _FPDFText_GetFontSizePtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_GetFontInfo -/// Get the font name and flags of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// buffer - A buffer receiving the font name. -/// buflen - The length of |buffer| in bytes. -/// flags - Optional pointer to an int receiving the font flags. -/// These flags should be interpreted per PDF spec 1.7 -/// Section 5.7.1 Font Descriptor Flags. -/// Return value: -/// On success, return the length of the font name, including the -/// trailing NUL character, in bytes. If this length is less than or -/// equal to |length|, |buffer| is set to the font name, |flags| is -/// set to the font flags. |buffer| is in UTF-8 encoding. Return 0 on -/// failure. -int FPDFText_GetFontInfo(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer buffer, -int buflen, -ffi.Pointer flags, -) { - return _FPDFText_GetFontInfo(text_page, -index, -buffer, -buflen, -flags, -); -} - -late final _FPDFText_GetFontInfoPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFText_GetFontInfo'); -late final _FPDFText_GetFontInfo = _FPDFText_GetFontInfoPtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDFText_GetFontWeight -/// Get the font weight of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return value: -/// On success, return the font weight of the particular character. If -/// |text_page| is invalid, if |index| is out of bounds, or if the -/// character's text object is undefined, return -1. -int FPDFText_GetFontWeight(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_GetFontWeight(text_page, -index, -); -} - -late final _FPDFText_GetFontWeightPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetFontWeight'); -late final _FPDFText_GetFontWeight = _FPDFText_GetFontWeightPtr.asFunction(); - -/// Experimental API. -/// Function: FPDFText_GetFillColor -/// Get the fill color of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// R - Pointer to an unsigned int number receiving the -/// red value of the fill color. -/// G - Pointer to an unsigned int number receiving the -/// green value of the fill color. -/// B - Pointer to an unsigned int number receiving the -/// blue value of the fill color. -/// A - Pointer to an unsigned int number receiving the -/// alpha value of the fill color. -/// Return value: -/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are -/// unchanged. -int FPDFText_GetFillColor(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, -) { - return _FPDFText_GetFillColor(text_page, -index, -R, -G, -B, -A, -); -} - -late final _FPDFText_GetFillColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetFillColor'); -late final _FPDFText_GetFillColor = _FPDFText_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDFText_GetStrokeColor -/// Get the stroke color of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// R - Pointer to an unsigned int number receiving the -/// red value of the stroke color. -/// G - Pointer to an unsigned int number receiving the -/// green value of the stroke color. -/// B - Pointer to an unsigned int number receiving the -/// blue value of the stroke color. -/// A - Pointer to an unsigned int number receiving the -/// alpha value of the stroke color. -/// Return value: -/// Whether the call succeeded. If false, |R|, |G|, |B| and |A| are -/// unchanged. -int FPDFText_GetStrokeColor(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, -) { - return _FPDFText_GetStrokeColor(text_page, -index, -R, -G, -B, -A, -); -} - -late final _FPDFText_GetStrokeColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetStrokeColor'); -late final _FPDFText_GetStrokeColor = _FPDFText_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDFText_GetCharAngle -/// Get character rotation angle. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// Return Value: -/// On success, return the angle value in radian. Value will always be -/// greater or equal to 0. If |text_page| is invalid, or if |index| is -/// out of bounds, then return -1. -double FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page, -int index, -) { - return _FPDFText_GetCharAngle(text_page, -index, -); -} - -late final _FPDFText_GetCharAnglePtr = _lookup< - ffi.NativeFunction>('FPDFText_GetCharAngle'); -late final _FPDFText_GetCharAngle = _FPDFText_GetCharAnglePtr.asFunction(); - -/// Function: FPDFText_GetCharBox -/// Get bounding box of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// left - Pointer to a double number receiving left position -/// of the character box. -/// right - Pointer to a double number receiving right position -/// of the character box. -/// bottom - Pointer to a double number receiving bottom position -/// of the character box. -/// top - Pointer to a double number receiving top position of -/// the character box. -/// Return Value: -/// On success, return TRUE and fill in |left|, |right|, |bottom|, and -/// |top|. If |text_page| is invalid, or if |index| is out of bounds, -/// then return FALSE, and the out parameters remain unmodified. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer left, -ffi.Pointer right, -ffi.Pointer bottom, -ffi.Pointer top, -) { - return _FPDFText_GetCharBox(text_page, -index, -left, -right, -bottom, -top, -); -} - -late final _FPDFText_GetCharBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetCharBox'); -late final _FPDFText_GetCharBox = _FPDFText_GetCharBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDFText_GetLooseCharBox -/// Get a "loose" bounding box of a particular character, i.e., covering -/// the entire glyph bounds, without taking the actual glyph shape into -/// account. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// rect - Pointer to a FS_RECTF receiving the character box. -/// Return Value: -/// On success, return TRUE and fill in |rect|. If |text_page| is -/// invalid, or if |index| is out of bounds, then return FALSE, and the -/// |rect| out parameter remains unmodified. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetLooseCharBox(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer rect, -) { - return _FPDFText_GetLooseCharBox(text_page, -index, -rect, -); -} - -late final _FPDFText_GetLooseCharBoxPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetLooseCharBox'); -late final _FPDFText_GetLooseCharBox = _FPDFText_GetLooseCharBoxPtr.asFunction )>(); - -/// Experimental API. -/// Function: FPDFText_GetMatrix -/// Get the effective transformation matrix for a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage(). -/// index - Zero-based index of the character. -/// matrix - Pointer to a FS_MATRIX receiving the transformation -/// matrix. -/// Return Value: -/// On success, return TRUE and fill in |matrix|. If |text_page| is -/// invalid, or if |index| is out of bounds, or if |matrix| is NULL, -/// then return FALSE, and |matrix| remains unmodified. -int FPDFText_GetMatrix(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer matrix, -) { - return _FPDFText_GetMatrix(text_page, -index, -matrix, -); -} - -late final _FPDFText_GetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetMatrix'); -late final _FPDFText_GetMatrix = _FPDFText_GetMatrixPtr.asFunction )>(); - -/// Function: FPDFText_GetCharOrigin -/// Get origin of a particular character. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// index - Zero-based index of the character. -/// x - Pointer to a double number receiving x coordinate of -/// the character origin. -/// y - Pointer to a double number receiving y coordinate of -/// the character origin. -/// Return Value: -/// Whether the call succeeded. If false, x and y are unchanged. -/// Comments: -/// All positions are measured in PDF "user space". -int FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, -int index, -ffi.Pointer x, -ffi.Pointer y, -) { - return _FPDFText_GetCharOrigin(text_page, -index, -x, -y, -); -} - -late final _FPDFText_GetCharOriginPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFText_GetCharOrigin'); -late final _FPDFText_GetCharOrigin = _FPDFText_GetCharOriginPtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDFText_GetCharIndexAtPos -/// Get the index of a character at or nearby a certain position on the -/// page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// x - X position in PDF "user space". -/// y - Y position in PDF "user space". -/// xTolerance - An x-axis tolerance value for character hit -/// detection, in point units. -/// yTolerance - A y-axis tolerance value for character hit -/// detection, in point units. -/// Return Value: -/// The zero-based index of the character at, or nearby the point (x,y). -/// If there is no character at or nearby the point, return value will -/// be -1. If an error occurs, -3 will be returned. -int FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, -double x, -double y, -double xTolerance, -double yTolerance, -) { - return _FPDFText_GetCharIndexAtPos(text_page, -x, -y, -xTolerance, -yTolerance, -); -} - -late final _FPDFText_GetCharIndexAtPosPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetCharIndexAtPos'); -late final _FPDFText_GetCharIndexAtPos = _FPDFText_GetCharIndexAtPosPtr.asFunction(); - -/// Function: FPDFText_GetText -/// Extract unicode text string from the page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// start_index - Index for the start characters. -/// count - Number of UCS-2 values to be extracted. -/// result - A buffer (allocated by application) receiving the -/// extracted UCS-2 values. The buffer must be able to -/// hold `count` UCS-2 values plus a terminator. -/// Return Value: -/// Number of characters written into the result buffer, including the -/// trailing terminator. -/// Comments: -/// This function ignores characters without UCS-2 representations. -/// It considers all characters on the page, even those that are not -/// visible when the page has a cropbox. To filter out the characters -/// outside of the cropbox, use FPDF_GetPageBoundingBox() and -/// FPDFText_GetCharBox(). -int FPDFText_GetText(FPDF_TEXTPAGE text_page, -int start_index, -int count, -ffi.Pointer result, -) { - return _FPDFText_GetText(text_page, -start_index, -count, -result, -); -} - -late final _FPDFText_GetTextPtr = _lookup< - ffi.NativeFunction )>>('FPDFText_GetText'); -late final _FPDFText_GetText = _FPDFText_GetTextPtr.asFunction )>(); - -/// Function: FPDFText_CountRects -/// Counts number of rectangular areas occupied by a segment of text, -/// and caches the result for subsequent FPDFText_GetRect() calls. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// start_index - Index for the start character. -/// count - Number of characters, or -1 for all remaining. -/// Return value: -/// Number of rectangles, 0 if text_page is null, or -1 on bad -/// start_index. -/// Comments: -/// This function, along with FPDFText_GetRect can be used by -/// applications to detect the position on the page for a text segment, -/// so proper areas can be highlighted. The FPDFText_* functions will -/// automatically merge small character boxes into bigger one if those -/// characters are on the same line and use same font settings. -int FPDFText_CountRects(FPDF_TEXTPAGE text_page, -int start_index, -int count, -) { - return _FPDFText_CountRects(text_page, -start_index, -count, -); -} - -late final _FPDFText_CountRectsPtr = _lookup< - ffi.NativeFunction>('FPDFText_CountRects'); -late final _FPDFText_CountRects = _FPDFText_CountRectsPtr.asFunction(); - -/// Function: FPDFText_GetRect -/// Get a rectangular area from the result generated by -/// FPDFText_CountRects. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// rect_index - Zero-based index for the rectangle. -/// left - Pointer to a double value receiving the rectangle -/// left boundary. -/// top - Pointer to a double value receiving the rectangle -/// top boundary. -/// right - Pointer to a double value receiving the rectangle -/// right boundary. -/// bottom - Pointer to a double value receiving the rectangle -/// bottom boundary. -/// Return Value: -/// On success, return TRUE and fill in |left|, |top|, |right|, and -/// |bottom|. If |text_page| is invalid then return FALSE, and the out -/// parameters remain unmodified. If |text_page| is valid but -/// |rect_index| is out of bounds, then return FALSE and set the out -/// parameters to 0. -int FPDFText_GetRect(FPDF_TEXTPAGE text_page, -int rect_index, -ffi.Pointer left, -ffi.Pointer top, -ffi.Pointer right, -ffi.Pointer bottom, -) { - return _FPDFText_GetRect(text_page, -rect_index, -left, -top, -right, -bottom, -); -} - -late final _FPDFText_GetRectPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFText_GetRect'); -late final _FPDFText_GetRect = _FPDFText_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Function: FPDFText_GetBoundedText -/// Extract unicode text within a rectangular boundary on the page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// left - Left boundary. -/// top - Top boundary. -/// right - Right boundary. -/// bottom - Bottom boundary. -/// buffer - Caller-allocated buffer to receive UTF-16 values. -/// buflen - Number of UTF-16 values (not bytes) that `buffer` -/// is capable of holding. -/// Return Value: -/// If buffer is NULL or buflen is zero, return number of UTF-16 -/// values (not bytes) of text present within the rectangle, excluding -/// a terminating NUL. Generally you should pass a buffer at least one -/// larger than this if you want a terminating NUL, which will be -/// provided if space is available. Otherwise, return number of UTF-16 -/// values copied into the buffer, including the terminating NUL when -/// space for it is available. -/// Comment: -/// If the buffer is too small, as much text as will fit is copied into -/// it. May return a split surrogate in that case. -int FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, -double left, -double top, -double right, -double bottom, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFText_GetBoundedText(text_page, -left, -top, -right, -bottom, -buffer, -buflen, -); -} - -late final _FPDFText_GetBoundedTextPtr = _lookup< - ffi.NativeFunction , ffi.Int )>>('FPDFText_GetBoundedText'); -late final _FPDFText_GetBoundedText = _FPDFText_GetBoundedTextPtr.asFunction , int )>(); - -/// Function: FPDFText_FindStart -/// Start a search. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// findwhat - A unicode match pattern. -/// flags - Option flags. -/// start_index - Start from this character. -1 for end of the page. -/// Return Value: -/// A handle for the search context. FPDFText_FindClose must be called -/// to release this handle. -FPDF_SCHHANDLE FPDFText_FindStart(FPDF_TEXTPAGE text_page, -FPDF_WIDESTRING findwhat, -int flags, -int start_index, -) { - return _FPDFText_FindStart(text_page, -findwhat, -flags, -start_index, -); -} - -late final _FPDFText_FindStartPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindStart'); -late final _FPDFText_FindStart = _FPDFText_FindStartPtr.asFunction(); - -/// Function: FPDFText_FindNext -/// Search in the direction from page start to end. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Whether a match is found. -int FPDFText_FindNext(FPDF_SCHHANDLE handle, -) { - return _FPDFText_FindNext(handle, -); -} - -late final _FPDFText_FindNextPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindNext'); -late final _FPDFText_FindNext = _FPDFText_FindNextPtr.asFunction(); - -/// Function: FPDFText_FindPrev -/// Search in the direction from page end to start. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Whether a match is found. -int FPDFText_FindPrev(FPDF_SCHHANDLE handle, -) { - return _FPDFText_FindPrev(handle, -); -} - -late final _FPDFText_FindPrevPtr = _lookup< - ffi.NativeFunction>('FPDFText_FindPrev'); -late final _FPDFText_FindPrev = _FPDFText_FindPrevPtr.asFunction(); - -/// Function: FPDFText_GetSchResultIndex -/// Get the starting character index of the search result. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Index for the starting character. -int FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle, -) { - return _FPDFText_GetSchResultIndex(handle, -); -} - -late final _FPDFText_GetSchResultIndexPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetSchResultIndex'); -late final _FPDFText_GetSchResultIndex = _FPDFText_GetSchResultIndexPtr.asFunction(); - -/// Function: FPDFText_GetSchCount -/// Get the number of matched characters in the search result. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// Number of matched characters. -int FPDFText_GetSchCount(FPDF_SCHHANDLE handle, -) { - return _FPDFText_GetSchCount(handle, -); -} - -late final _FPDFText_GetSchCountPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetSchCount'); -late final _FPDFText_GetSchCount = _FPDFText_GetSchCountPtr.asFunction(); - -/// Function: FPDFText_FindClose -/// Release a search context. -/// Parameters: -/// handle - A search context handle returned by -/// FPDFText_FindStart. -/// Return Value: -/// None. -void FPDFText_FindClose(FPDF_SCHHANDLE handle, -) { - return _FPDFText_FindClose(handle, -); -} - -late final _FPDFText_FindClosePtr = _lookup< - ffi.NativeFunction>('FPDFText_FindClose'); -late final _FPDFText_FindClose = _FPDFText_FindClosePtr.asFunction(); - -/// Function: FPDFLink_LoadWebLinks -/// Prepare information about weblinks in a page. -/// Parameters: -/// text_page - Handle to a text page information structure. -/// Returned by FPDFText_LoadPage function. -/// Return Value: -/// A handle to the page's links information structure, or -/// NULL if something goes wrong. -/// Comments: -/// Weblinks are those links implicitly embedded in PDF pages. PDF also -/// has a type of annotation called "link" (FPDFTEXT doesn't deal with -/// that kind of link). FPDFTEXT weblink feature is useful for -/// automatically detecting links in the page contents. For example, -/// things like "https://www.example.com" will be detected, so -/// applications can allow user to click on those characters to activate -/// the link, even the PDF doesn't come with link annotations. -/// -/// FPDFLink_CloseWebLinks must be called to release resources. -FPDF_PAGELINK FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page, -) { - return _FPDFLink_LoadWebLinks(text_page, -); -} - -late final _FPDFLink_LoadWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_LoadWebLinks'); -late final _FPDFLink_LoadWebLinks = _FPDFLink_LoadWebLinksPtr.asFunction(); - -/// Function: FPDFLink_CountWebLinks -/// Count number of detected web links. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// Return Value: -/// Number of detected web links. -int FPDFLink_CountWebLinks(FPDF_PAGELINK link_page, -) { - return _FPDFLink_CountWebLinks(link_page, -); -} - -late final _FPDFLink_CountWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountWebLinks'); -late final _FPDFLink_CountWebLinks = _FPDFLink_CountWebLinksPtr.asFunction(); - -/// Function: FPDFLink_GetURL -/// Fetch the URL information for a detected web link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// buffer - A unicode buffer for the result. -/// buflen - Number of 16-bit code units (not bytes) for the -/// buffer, including an additional terminator. -/// Return Value: -/// If |buffer| is NULL or |buflen| is zero, return the number of 16-bit -/// code units (not bytes) needed to buffer the result (an additional -/// terminator is included in this count). -/// Otherwise, copy the result into |buffer|, truncating at |buflen| if -/// the result is too large to fit, and return the number of 16-bit code -/// units actually copied into the buffer (the additional terminator is -/// also included in this count). -/// If |link_index| does not correspond to a valid link, then the result -/// is an empty string. -int FPDFLink_GetURL(FPDF_PAGELINK link_page, -int link_index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFLink_GetURL(link_page, -link_index, -buffer, -buflen, -); -} - -late final _FPDFLink_GetURLPtr = _lookup< - ffi.NativeFunction , ffi.Int )>>('FPDFLink_GetURL'); -late final _FPDFLink_GetURL = _FPDFLink_GetURLPtr.asFunction , int )>(); - -/// Function: FPDFLink_CountRects -/// Count number of rectangular areas for the link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// Return Value: -/// Number of rectangular areas for the link. If |link_index| does -/// not correspond to a valid link, then 0 is returned. -int FPDFLink_CountRects(FPDF_PAGELINK link_page, -int link_index, -) { - return _FPDFLink_CountRects(link_page, -link_index, -); -} - -late final _FPDFLink_CountRectsPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountRects'); -late final _FPDFLink_CountRects = _FPDFLink_CountRectsPtr.asFunction(); - -/// Function: FPDFLink_GetRect -/// Fetch the boundaries of a rectangle for a link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// rect_index - Zero-based index for a rectangle. -/// left - Pointer to a double value receiving the rectangle -/// left boundary. -/// top - Pointer to a double value receiving the rectangle -/// top boundary. -/// right - Pointer to a double value receiving the rectangle -/// right boundary. -/// bottom - Pointer to a double value receiving the rectangle -/// bottom boundary. -/// Return Value: -/// On success, return TRUE and fill in |left|, |top|, |right|, and -/// |bottom|. If |link_page| is invalid or if |link_index| does not -/// correspond to a valid link, then return FALSE, and the out -/// parameters remain unmodified. -int FPDFLink_GetRect(FPDF_PAGELINK link_page, -int link_index, -int rect_index, -ffi.Pointer left, -ffi.Pointer top, -ffi.Pointer right, -ffi.Pointer bottom, -) { - return _FPDFLink_GetRect(link_page, -link_index, -rect_index, -left, -top, -right, -bottom, -); -} - -late final _FPDFLink_GetRectPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFLink_GetRect'); -late final _FPDFLink_GetRect = _FPDFLink_GetRectPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDFLink_GetTextRange -/// Fetch the start char index and char count for a link. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// link_index - Zero-based index for the link. -/// start_char_index - pointer to int receiving the start char index -/// char_count - pointer to int receiving the char count -/// Return Value: -/// On success, return TRUE and fill in |start_char_index| and -/// |char_count|. if |link_page| is invalid or if |link_index| does -/// not correspond to a valid link, then return FALSE and the out -/// parameters remain unmodified. -int FPDFLink_GetTextRange(FPDF_PAGELINK link_page, -int link_index, -ffi.Pointer start_char_index, -ffi.Pointer char_count, -) { - return _FPDFLink_GetTextRange(link_page, -link_index, -start_char_index, -char_count, -); -} - -late final _FPDFLink_GetTextRangePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_GetTextRange'); -late final _FPDFLink_GetTextRange = _FPDFLink_GetTextRangePtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDFLink_CloseWebLinks -/// Release resources used by weblink feature. -/// Parameters: -/// link_page - Handle returned by FPDFLink_LoadWebLinks. -/// Return Value: -/// None. -void FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page, -) { - return _FPDFLink_CloseWebLinks(link_page, -); -} - -late final _FPDFLink_CloseWebLinksPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CloseWebLinks'); -late final _FPDFLink_CloseWebLinks = _FPDFLink_CloseWebLinksPtr.asFunction(); - -/// Get the character index in |text_page| internal character list. -/// -/// text_page - a text page information structure. -/// nTextIndex - index of the text returned from FPDFText_GetText(). -/// -/// Returns the index of the character in internal character list. -1 for error. -int FPDFText_GetCharIndexFromTextIndex(FPDF_TEXTPAGE text_page, -int nTextIndex, -) { - return _FPDFText_GetCharIndexFromTextIndex(text_page, -nTextIndex, -); -} - -late final _FPDFText_GetCharIndexFromTextIndexPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetCharIndexFromTextIndex'); -late final _FPDFText_GetCharIndexFromTextIndex = _FPDFText_GetCharIndexFromTextIndexPtr.asFunction(); - -/// Get the text index in |text_page| internal character list. -/// -/// text_page - a text page information structure. -/// nCharIndex - index of the character in internal character list. -/// -/// Returns the index of the text returned from FPDFText_GetText(). -1 for error. -int FPDFText_GetTextIndexFromCharIndex(FPDF_TEXTPAGE text_page, -int nCharIndex, -) { - return _FPDFText_GetTextIndexFromCharIndex(text_page, -nCharIndex, -); -} - -late final _FPDFText_GetTextIndexFromCharIndexPtr = _lookup< - ffi.NativeFunction>('FPDFText_GetTextIndexFromCharIndex'); -late final _FPDFText_GetTextIndexFromCharIndex = _FPDFText_GetTextIndexFromCharIndexPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_RenderPageBitmapWithColorScheme_Start -/// Start to render page contents to a device independent bitmap -/// progressively with a specified color scheme for the content. -/// Parameters: -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). Bitmap handle can be created by -/// FPDFBitmap_Create function. -/// page - Handle to the page as returned by FPDF_LoadPage -/// function. -/// start_x - Left pixel position of the display area in the -/// bitmap coordinate. -/// start_y - Top pixel position of the display area in the -/// bitmap coordinate. -/// size_x - Horizontal size (in pixels) for displaying the -/// page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: 0 (normal), 1 (rotated 90 -/// degrees clockwise), 2 (rotated 180 degrees), -/// 3 (rotated 90 degrees counter-clockwise). -/// flags - 0 for normal display, or combination of flags -/// defined in fpdfview.h. With FPDF_ANNOT flag, it -/// renders all annotations that does not require -/// user-interaction, which are all annotations except -/// widget and popup annotations. -/// color_scheme - Color scheme to be used in rendering the |page|. -/// If null, this function will work similar to -/// FPDF_RenderPageBitmap_Start(). -/// pause - The IFSDK_PAUSE interface. A callback mechanism -/// allowing the page rendering process. -/// Return value: -/// Rendering Status. See flags for progressive process status for the -/// details. -int FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, -FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int flags, -ffi.Pointer color_scheme, -ffi.Pointer pause, -) { - return _FPDF_RenderPageBitmapWithColorScheme_Start(bitmap, -page, -start_x, -start_y, -size_x, -size_y, -rotate, -flags, -color_scheme, -pause, -); -} - -late final _FPDF_RenderPageBitmapWithColorScheme_StartPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDF_RenderPageBitmapWithColorScheme_Start'); -late final _FPDF_RenderPageBitmapWithColorScheme_Start = _FPDF_RenderPageBitmapWithColorScheme_StartPtr.asFunction , ffi.Pointer )>(); - -/// Function: FPDF_RenderPageBitmap_Start -/// Start to render page contents to a device independent bitmap -/// progressively. -/// Parameters: -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). Bitmap handle can be created by -/// FPDFBitmap_Create(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// start_x - Left pixel position of the display area in the -/// bitmap coordinates. -/// start_y - Top pixel position of the display area in the bitmap -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees -/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 -/// degrees counter-clockwise). -/// flags - 0 for normal display, or combination of flags -/// defined in fpdfview.h. With FPDF_ANNOT flag, it -/// renders all annotations that does not require -/// user-interaction, which are all annotations except -/// widget and popup annotations. -/// pause - The IFSDK_PAUSE interface.A callback mechanism -/// allowing the page rendering process -/// Return value: -/// Rendering Status. See flags for progressive process status for the -/// details. -int FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap, -FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int flags, -ffi.Pointer pause, -) { - return _FPDF_RenderPageBitmap_Start(bitmap, -page, -start_x, -start_y, -size_x, -size_y, -rotate, -flags, -pause, -); -} - -late final _FPDF_RenderPageBitmap_StartPtr = _lookup< - ffi.NativeFunction )>>('FPDF_RenderPageBitmap_Start'); -late final _FPDF_RenderPageBitmap_Start = _FPDF_RenderPageBitmap_StartPtr.asFunction )>(); - -/// Function: FPDF_RenderPage_Continue -/// Continue rendering a PDF page. -/// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// pause - The IFSDK_PAUSE interface (a callback mechanism -/// allowing the page rendering process to be paused -/// before it's finished). This can be NULL if you -/// don't want to pause. -/// Return value: -/// The rendering status. See flags for progressive process status for -/// the details. -int FPDF_RenderPage_Continue(FPDF_PAGE page, -ffi.Pointer pause, -) { - return _FPDF_RenderPage_Continue(page, -pause, -); -} - -late final _FPDF_RenderPage_ContinuePtr = _lookup< - ffi.NativeFunction )>>('FPDF_RenderPage_Continue'); -late final _FPDF_RenderPage_Continue = _FPDF_RenderPage_ContinuePtr.asFunction )>(); - -/// Function: FPDF_RenderPage_Close -/// Release the resource allocate during page rendering. Need to be -/// called after finishing rendering or -/// cancel the rendering. -/// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return value: -/// None. -void FPDF_RenderPage_Close(FPDF_PAGE page, -) { - return _FPDF_RenderPage_Close(page, -); -} - -late final _FPDF_RenderPage_ClosePtr = _lookup< - ffi.NativeFunction>('FPDF_RenderPage_Close'); -late final _FPDF_RenderPage_Close = _FPDF_RenderPage_ClosePtr.asFunction(); - -/// Create a new PDF document. -/// -/// Returns a handle to a new document, or NULL on failure. -FPDF_DOCUMENT FPDF_CreateNewDocument() { - return _FPDF_CreateNewDocument(); -} - -late final _FPDF_CreateNewDocumentPtr = _lookup< - ffi.NativeFunction>('FPDF_CreateNewDocument'); -late final _FPDF_CreateNewDocument = _FPDF_CreateNewDocumentPtr.asFunction(); - -/// Create a new PDF page. -/// -/// document - handle to document. -/// page_index - suggested 0-based index of the page to create. If it is larger -/// than document's current last index(L), the created page index -/// is the next available index -- L+1. -/// width - the page width in points. -/// height - the page height in points. -/// -/// Returns the handle to the new page or NULL on failure. -/// -/// The page should be closed with FPDF_ClosePage() when finished as -/// with any other page in the document. -FPDF_PAGE FPDFPage_New(FPDF_DOCUMENT document, -int page_index, -double width, -double height, -) { - return _FPDFPage_New(document, -page_index, -width, -height, -); -} - -late final _FPDFPage_NewPtr = _lookup< - ffi.NativeFunction>('FPDFPage_New'); -late final _FPDFPage_New = _FPDFPage_NewPtr.asFunction(); - -/// Delete the page at |page_index|. -/// -/// document - handle to document. -/// page_index - the index of the page to delete. -void FPDFPage_Delete(FPDF_DOCUMENT document, -int page_index, -) { - return _FPDFPage_Delete(document, -page_index, -); -} - -late final _FPDFPage_DeletePtr = _lookup< - ffi.NativeFunction>('FPDFPage_Delete'); -late final _FPDFPage_Delete = _FPDFPage_DeletePtr.asFunction(); - -/// Experimental API. -/// Move the given pages to a new index position. -/// -/// page_indices - the ordered list of pages to move. No duplicates allowed. -/// page_indices_len - the number of elements in |page_indices| -/// dest_page_index - the new index position to which the pages in -/// |page_indices| are moved. -/// -/// Returns TRUE on success. If it returns FALSE, the document may be left in an -/// indeterminate state. -/// -/// Example: The PDF document starts out with pages [A, B, C, D], with indices -/// [0, 1, 2, 3]. -/// -/// > Move(doc, [3, 2], 2, 1); // returns true -/// > // The document has pages [A, D, C, B]. -/// > -/// > Move(doc, [0, 4, 3], 3, 1); // returns false -/// > // Returned false because index 4 is out of range. -/// > -/// > Move(doc, [0, 3, 1], 3, 2); // returns false -/// > // Returned false because index 2 is out of range for 3 page indices. -/// > -/// > Move(doc, [2, 2], 2, 0); // returns false -/// > // Returned false because [2, 2] contains duplicates. -int FPDF_MovePages(FPDF_DOCUMENT document, -ffi.Pointer page_indices, -int page_indices_len, -int dest_page_index, -) { - return _FPDF_MovePages(document, -page_indices, -page_indices_len, -dest_page_index, -); -} - -late final _FPDF_MovePagesPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_MovePages'); -late final _FPDF_MovePages = _FPDF_MovePagesPtr.asFunction , int , int )>(); - -/// Get the rotation of |page|. -/// -/// page - handle to a page -/// -/// Returns one of the following indicating the page rotation: -/// 0 - No rotation. -/// 1 - Rotated 90 degrees clockwise. -/// 2 - Rotated 180 degrees clockwise. -/// 3 - Rotated 270 degrees clockwise. -int FPDFPage_GetRotation(FPDF_PAGE page, -) { - return _FPDFPage_GetRotation(page, -); -} - -late final _FPDFPage_GetRotationPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetRotation'); -late final _FPDFPage_GetRotation = _FPDFPage_GetRotationPtr.asFunction(); - -/// Set rotation for |page|. -/// -/// page - handle to a page. -/// rotate - the rotation value, one of: -/// 0 - No rotation. -/// 1 - Rotated 90 degrees clockwise. -/// 2 - Rotated 180 degrees clockwise. -/// 3 - Rotated 270 degrees clockwise. -void FPDFPage_SetRotation(FPDF_PAGE page, -int rotate, -) { - return _FPDFPage_SetRotation(page, -rotate, -); -} - -late final _FPDFPage_SetRotationPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetRotation'); -late final _FPDFPage_SetRotation = _FPDFPage_SetRotationPtr.asFunction(); - -/// Insert |page_object| into |page|. -/// -/// page - handle to a page -/// page_object - handle to a page object. The |page_object| will be -/// automatically freed. -void FPDFPage_InsertObject(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, -) { - return _FPDFPage_InsertObject(page, -page_object, -); -} - -late final _FPDFPage_InsertObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_InsertObject'); -late final _FPDFPage_InsertObject = _FPDFPage_InsertObjectPtr.asFunction(); - -/// Insert |page_object| into |page| at the specified |index|. -/// -/// page - handle to a page -/// page_object - handle to a page object as previously obtained by -/// FPDFPageObj_CreateNew{Path|Rect}() or -/// FPDFPageObj_New{Text|Image}Obj(). Ownership of the object -/// is transferred back to PDFium. -/// index - the index position to insert the object at. If index equals -/// the current object count, the object will be appended to the -/// end. If index is greater than the object count, the function -/// will fail and return false. -/// -/// Returns true if successful. -int FPDFPage_InsertObjectAtIndex(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, -int index, -) { - return _FPDFPage_InsertObjectAtIndex(page, -page_object, -index, -); -} - -late final _FPDFPage_InsertObjectAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDFPage_InsertObjectAtIndex'); -late final _FPDFPage_InsertObjectAtIndex = _FPDFPage_InsertObjectAtIndexPtr.asFunction(); - -/// Experimental API. -/// Remove |page_object| from |page|. -/// -/// page - handle to a page -/// page_object - handle to a page object to be removed. -/// -/// Returns TRUE on success. -/// -/// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free -/// it. -/// Note that when removing a |page_object| of type FPDF_PAGEOBJ_TEXT, all -/// FPDF_TEXTPAGE handles for |page| are no longer valid. -int FPDFPage_RemoveObject(FPDF_PAGE page, -FPDF_PAGEOBJECT page_object, -) { - return _FPDFPage_RemoveObject(page, -page_object, -); -} - -late final _FPDFPage_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_RemoveObject'); -late final _FPDFPage_RemoveObject = _FPDFPage_RemoveObjectPtr.asFunction(); - -/// Get number of page objects inside |page|. -/// -/// page - handle to a page. -/// -/// Returns the number of objects in |page|. -int FPDFPage_CountObjects(FPDF_PAGE page, -) { - return _FPDFPage_CountObjects(page, -); -} - -late final _FPDFPage_CountObjectsPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CountObjects'); -late final _FPDFPage_CountObjects = _FPDFPage_CountObjectsPtr.asFunction(); - -/// Get object in |page| at |index|. -/// -/// page - handle to a page. -/// index - the index of a page object. -/// -/// Returns the handle to the page object, or NULL on failed. -FPDF_PAGEOBJECT FPDFPage_GetObject(FPDF_PAGE page, -int index, -) { - return _FPDFPage_GetObject(page, -index, -); -} - -late final _FPDFPage_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetObject'); -late final _FPDFPage_GetObject = _FPDFPage_GetObjectPtr.asFunction(); - -/// Checks if |page| contains transparency. -/// -/// page - handle to a page. -/// -/// Returns TRUE if |page| contains transparency. -int FPDFPage_HasTransparency(FPDF_PAGE page, -) { - return _FPDFPage_HasTransparency(page, -); -} - -late final _FPDFPage_HasTransparencyPtr = _lookup< - ffi.NativeFunction>('FPDFPage_HasTransparency'); -late final _FPDFPage_HasTransparency = _FPDFPage_HasTransparencyPtr.asFunction(); - -/// Generate the content of |page|. -/// -/// page - handle to a page. -/// -/// Returns TRUE on success. -/// -/// Before you save the page to a file, or reload the page, you must call -/// |FPDFPage_GenerateContent| or any changes to |page| will be lost. -int FPDFPage_GenerateContent(FPDF_PAGE page, -) { - return _FPDFPage_GenerateContent(page, -); -} - -late final _FPDFPage_GenerateContentPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GenerateContent'); -late final _FPDFPage_GenerateContent = _FPDFPage_GenerateContentPtr.asFunction(); - -/// Destroy |page_object| by releasing its resources. |page_object| must have -/// been created by FPDFPageObj_CreateNew{Path|Rect}() or -/// FPDFPageObj_New{Text|Image}Obj(). This function must be called on -/// newly-created objects if they are not added to a page through -/// FPDFPage_InsertObject() or to an annotation through FPDFAnnot_AppendObject(). -/// -/// page_object - handle to a page object. -void FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_Destroy(page_object, -); -} - -late final _FPDFPageObj_DestroyPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_Destroy'); -late final _FPDFPageObj_Destroy = _FPDFPageObj_DestroyPtr.asFunction(); - -/// Checks if |page_object| contains transparency. -/// -/// page_object - handle to a page object. -/// -/// Returns TRUE if |page_object| contains transparency. -int FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_HasTransparency(page_object, -); -} - -late final _FPDFPageObj_HasTransparencyPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_HasTransparency'); -late final _FPDFPageObj_HasTransparency = _FPDFPageObj_HasTransparencyPtr.asFunction(); - -/// Get type of |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns one of the FPDF_PAGEOBJ_* values on success, FPDF_PAGEOBJ_UNKNOWN on -/// error. -int FPDFPageObj_GetType(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetType(page_object, -); -} - -late final _FPDFPageObj_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetType'); -late final _FPDFPageObj_GetType = _FPDFPageObj_GetTypePtr.asFunction(); - -/// Experimental API. -/// Gets active state for |page_object| within page. -/// -/// page_object - handle to a page object. -/// active - pointer to variable that will receive if the page object is -/// active. This is a required parameter. Not filled if FALSE -/// is returned. -/// -/// For page objects where |active| is filled with FALSE, the |page_object| is -/// treated as if it wasn't in the document even though it is still held -/// internally. -/// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObj_GetIsActive(FPDF_PAGEOBJECT page_object, -ffi.Pointer active, -) { - return _FPDFPageObj_GetIsActive(page_object, -active, -); -} - -late final _FPDFPageObj_GetIsActivePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetIsActive'); -late final _FPDFPageObj_GetIsActive = _FPDFPageObj_GetIsActivePtr.asFunction )>(); - -/// Experimental API. -/// Sets if |page_object| is active within page. -/// -/// page_object - handle to a page object. -/// active - a boolean specifying if the object is active. -/// -/// Returns TRUE on success. -/// -/// Page objects all start in the active state by default, and remain in that -/// state unless this function is called. -/// -/// When |active| is false, this makes the |page_object| be treated as if it -/// wasn't in the document even though it is still held internally. -int FPDFPageObj_SetIsActive(FPDF_PAGEOBJECT page_object, -int active, -) { - return _FPDFPageObj_SetIsActive(page_object, -active, -); -} - -late final _FPDFPageObj_SetIsActivePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetIsActive'); -late final _FPDFPageObj_SetIsActive = _FPDFPageObj_SetIsActivePtr.asFunction(); - -/// Transform |page_object| by the given matrix. -/// -/// page_object - handle to a page object. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |page_object|. -void FPDFPageObj_Transform(FPDF_PAGEOBJECT page_object, -double a, -double b, -double c, -double d, -double e, -double f, -) { - return _FPDFPageObj_Transform(page_object, -a, -b, -c, -d, -e, -f, -); -} - -late final _FPDFPageObj_TransformPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_Transform'); -late final _FPDFPageObj_Transform = _FPDFPageObj_TransformPtr.asFunction(); - -/// Experimental API. -/// Transform |page_object| by the given matrix. -/// -/// page_object - handle to a page object. -/// matrix - the transform matrix. -/// -/// Returns TRUE on success. -/// -/// This can be used to scale, rotate, shear and translate the |page_object|. -/// It is an improved version of FPDFPageObj_Transform() that does not do -/// unnecessary double to float conversions, and only uses 1 parameter for the -/// matrix. It also returns whether the operation succeeded or not. -int FPDFPageObj_TransformF(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, -) { - return _FPDFPageObj_TransformF(page_object, -matrix, -); -} - -late final _FPDFPageObj_TransformFPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_TransformF'); -late final _FPDFPageObj_TransformF = _FPDFPageObj_TransformFPtr.asFunction )>(); - -/// Experimental API. -/// Get the transform matrix of a page object. -/// -/// page_object - handle to a page object. -/// matrix - pointer to struct to receive the matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and used to scale, rotate, shear and translate the page object. -/// -/// For page objects outside form objects, the matrix values are relative to the -/// page that contains it. -/// For page objects inside form objects, the matrix values are relative to the -/// form that contains it. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetMatrix(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, -) { - return _FPDFPageObj_GetMatrix(page_object, -matrix, -); -} - -late final _FPDFPageObj_GetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetMatrix'); -late final _FPDFPageObj_GetMatrix = _FPDFPageObj_GetMatrixPtr.asFunction )>(); - -/// Experimental API. -/// Set the transform matrix of a page object. -/// -/// page_object - handle to a page object. -/// matrix - pointer to struct with the matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the page object. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, -ffi.Pointer matrix, -) { - return _FPDFPageObj_SetMatrix(page_object, -matrix, -); -} - -late final _FPDFPageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_SetMatrix'); -late final _FPDFPageObj_SetMatrix = _FPDFPageObj_SetMatrixPtr.asFunction )>(); - -/// Transform all annotations in |page|. -/// -/// page - handle to a page. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |page| annotations. -void FPDFPage_TransformAnnots(FPDF_PAGE page, -double a, -double b, -double c, -double d, -double e, -double f, -) { - return _FPDFPage_TransformAnnots(page, -a, -b, -c, -d, -e, -f, -); -} - -late final _FPDFPage_TransformAnnotsPtr = _lookup< - ffi.NativeFunction>('FPDFPage_TransformAnnots'); -late final _FPDFPage_TransformAnnots = _FPDFPage_TransformAnnotsPtr.asFunction(); - -/// Create a new image object. -/// -/// document - handle to a document. -/// -/// Returns a handle to a new image object. -FPDF_PAGEOBJECT FPDFPageObj_NewImageObj(FPDF_DOCUMENT document, -) { - return _FPDFPageObj_NewImageObj(document, -); -} - -late final _FPDFPageObj_NewImageObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_NewImageObj'); -late final _FPDFPageObj_NewImageObj = _FPDFPageObj_NewImageObjPtr.asFunction(); - -/// Experimental API. -/// Get the marked content ID for the object. -/// -/// page_object - handle to a page object. -/// -/// Returns the page object's marked content ID, or -1 on error. -int FPDFPageObj_GetMarkedContentID(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetMarkedContentID(page_object, -); -} - -late final _FPDFPageObj_GetMarkedContentIDPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetMarkedContentID'); -late final _FPDFPageObj_GetMarkedContentID = _FPDFPageObj_GetMarkedContentIDPtr.asFunction(); - -/// Experimental API. -/// Get number of content marks in |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns the number of content marks in |page_object|, or -1 in case of -/// failure. -int FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_CountMarks(page_object, -); -} - -late final _FPDFPageObj_CountMarksPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CountMarks'); -late final _FPDFPageObj_CountMarks = _FPDFPageObj_CountMarksPtr.asFunction(); - -/// Experimental API. -/// Get content mark in |page_object| at |index|. -/// -/// page_object - handle to a page object. -/// index - the index of a page object. -/// -/// Returns the handle to the content mark, or NULL on failure. The handle is -/// still owned by the library, and it should not be freed directly. It becomes -/// invalid if the page object is destroyed, either directly or indirectly by -/// unloading the page. -FPDF_PAGEOBJECTMARK FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, -int index, -) { - return _FPDFPageObj_GetMark(page_object, -index, -); -} - -late final _FPDFPageObj_GetMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetMark'); -late final _FPDFPageObj_GetMark = _FPDFPageObj_GetMarkPtr.asFunction(); - -/// Experimental API. -/// Add a new content mark to a |page_object|. -/// -/// page_object - handle to a page object. -/// name - the name (tag) of the mark. -/// -/// Returns the handle to the content mark, or NULL on failure. The handle is -/// still owned by the library, and it should not be freed directly. It becomes -/// invalid if the page object is destroyed, either directly or indirectly by -/// unloading the page. -FPDF_PAGEOBJECTMARK FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, -FPDF_BYTESTRING name, -) { - return _FPDFPageObj_AddMark(page_object, -name, -); -} - -late final _FPDFPageObj_AddMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_AddMark'); -late final _FPDFPageObj_AddMark = _FPDFPageObj_AddMarkPtr.asFunction(); - -/// Experimental API. -/// Removes a content |mark| from a |page_object|. -/// The mark handle will be invalid after the removal. -/// -/// page_object - handle to a page object. -/// mark - handle to a content mark in that object to remove. -/// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObj_RemoveMark(FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -) { - return _FPDFPageObj_RemoveMark(page_object, -mark, -); -} - -late final _FPDFPageObj_RemoveMarkPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_RemoveMark'); -late final _FPDFPageObj_RemoveMark = _FPDFPageObj_RemoveMarkPtr.asFunction(); - -/// Experimental API. -/// Get the name of a content mark. -/// -/// mark - handle to a content mark. -/// buffer - buffer for holding the returned name in UTF-16LE. This is only -/// modified if |buflen| is large enough to store the name. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the operation succeeded, FALSE if it failed. -int FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFPageObjMark_GetName(mark, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFPageObjMark_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetName'); -late final _FPDFPageObjMark_GetName = _FPDFPageObjMark_GetNamePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Get the number of key/value pair parameters in |mark|. -/// -/// mark - handle to a content mark. -/// -/// Returns the number of key/value pair parameters |mark|, or -1 in case of -/// failure. -int FPDFPageObjMark_CountParams(FPDF_PAGEOBJECTMARK mark, -) { - return _FPDFPageObjMark_CountParams(mark, -); -} - -late final _FPDFPageObjMark_CountParamsPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_CountParams'); -late final _FPDFPageObjMark_CountParams = _FPDFPageObjMark_CountParamsPtr.asFunction(); - -/// Experimental API. -/// Get the key of a property in a content mark. -/// -/// mark - handle to a content mark. -/// index - index of the property. -/// buffer - buffer for holding the returned key in UTF-16LE. This is only -/// modified if |buflen| is large enough to store the key. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the operation was successful, FALSE otherwise. -int FPDFPageObjMark_GetParamKey(FPDF_PAGEOBJECTMARK mark, -int index, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFPageObjMark_GetParamKey(mark, -index, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFPageObjMark_GetParamKeyPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamKey'); -late final _FPDFPageObjMark_GetParamKey = _FPDFPageObjMark_GetParamKeyPtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Get the type of the value of a property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// -/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of failure. -int FPDFPageObjMark_GetParamValueType(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -) { - return _FPDFPageObjMark_GetParamValueType(mark, -key, -); -} - -late final _FPDFPageObjMark_GetParamValueTypePtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_GetParamValueType'); -late final _FPDFPageObjMark_GetParamValueType = _FPDFPageObjMark_GetParamValueTypePtr.asFunction(); - -/// Experimental API. -/// Get the value of a number property in a content mark by key as int. -/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER -/// for this property. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// out_value - pointer to variable that will receive the value. Not filled if -/// false is returned. -/// -/// Returns TRUE if the key maps to a number value, FALSE otherwise. -int FPDFPageObjMark_GetParamIntValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer out_value, -) { - return _FPDFPageObjMark_GetParamIntValue(mark, -key, -out_value, -); -} - -late final _FPDFPageObjMark_GetParamIntValuePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObjMark_GetParamIntValue'); -late final _FPDFPageObjMark_GetParamIntValue = _FPDFPageObjMark_GetParamIntValuePtr.asFunction )>(); - -/// Experimental API. -/// Get the value of a number property in a content mark by key as float. -/// FPDFPageObjMark_GetParamValueType() should have returned FPDF_OBJECT_NUMBER -/// for this property. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// out_value - pointer to variable that will receive the value. Not filled if -/// false is returned. -/// -/// Returns TRUE if the key maps to a number value, FALSE otherwise. -int FPDFPageObjMark_GetParamFloatValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer out_value, -) { - return _FPDFPageObjMark_GetParamFloatValue(mark, -key, -out_value, -); -} - -late final _FPDFPageObjMark_GetParamFloatValuePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObjMark_GetParamFloatValue'); -late final _FPDFPageObjMark_GetParamFloatValue = _FPDFPageObjMark_GetParamFloatValuePtr.asFunction )>(); - -/// Experimental API. -/// Get the value of a string property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// buffer - buffer for holding the returned value in UTF-16LE. This is -/// only modified if |buflen| is large enough to store the value. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. -int FPDFPageObjMark_GetParamStringValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFPageObjMark_GetParamStringValue(mark, -key, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFPageObjMark_GetParamStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamStringValue'); -late final _FPDFPageObjMark_GetParamStringValue = _FPDFPageObjMark_GetParamStringValuePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Get the value of a blob property in a content mark by key. -/// -/// mark - handle to a content mark. -/// key - string key of the property. -/// buffer - buffer for holding the returned value. This is only modified -/// if |buflen| is large enough to store the value. -/// Optional, pass null to just retrieve the size of the buffer -/// needed. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to variable that will receive the minimum buffer size -/// in bytes to contain the name. This is a required parameter. -/// Not filled if FALSE is returned. -/// -/// Returns TRUE if the key maps to a string/blob value, FALSE otherwise. -int FPDFPageObjMark_GetParamBlobValue(FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFPageObjMark_GetParamBlobValue(mark, -key, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFPageObjMark_GetParamBlobValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFPageObjMark_GetParamBlobValue'); -late final _FPDFPageObjMark_GetParamBlobValue = _FPDFPageObjMark_GetParamBlobValuePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Set the value of an int property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - int value to set. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetIntParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -int value, -) { - return _FPDFPageObjMark_SetIntParam(document, -page_object, -mark, -key, -value, -); -} - -late final _FPDFPageObjMark_SetIntParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_SetIntParam'); -late final _FPDFPageObjMark_SetIntParam = _FPDFPageObjMark_SetIntParamPtr.asFunction(); - -/// Experimental API. -/// Set the value of a float property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - float value to set. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetFloatParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -double value, -) { - return _FPDFPageObjMark_SetFloatParam(document, -page_object, -mark, -key, -value, -); -} - -late final _FPDFPageObjMark_SetFloatParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_SetFloatParam'); -late final _FPDFPageObjMark_SetFloatParam = _FPDFPageObjMark_SetFloatParamPtr.asFunction(); - -/// Experimental API. -/// Set the value of a string property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - string value to set. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetStringParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -FPDF_BYTESTRING value, -) { - return _FPDFPageObjMark_SetStringParam(document, -page_object, -mark, -key, -value, -); -} - -late final _FPDFPageObjMark_SetStringParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_SetStringParam'); -late final _FPDFPageObjMark_SetStringParam = _FPDFPageObjMark_SetStringParamPtr.asFunction(); - -/// Experimental API. -/// Set the value of a blob property in a content mark by key. If a parameter -/// with key |key| exists, its value is set to |value|. Otherwise, it is added as -/// a new parameter. -/// -/// document - handle to the document. -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// value - pointer to blob value to set. -/// value_len - size in bytes of |value|. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_SetBlobParam(FPDF_DOCUMENT document, -FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -ffi.Pointer value, -int value_len, -) { - return _FPDFPageObjMark_SetBlobParam(document, -page_object, -mark, -key, -value, -value_len, -); -} - -late final _FPDFPageObjMark_SetBlobParamPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPageObjMark_SetBlobParam'); -late final _FPDFPageObjMark_SetBlobParam = _FPDFPageObjMark_SetBlobParamPtr.asFunction , int )>(); - -/// Experimental API. -/// Removes a property from a content mark by key. -/// -/// page_object - handle to the page object with the mark. -/// mark - handle to a content mark. -/// key - string key of the property. -/// -/// Returns TRUE if the operation succeeded, FALSE otherwise. -int FPDFPageObjMark_RemoveParam(FPDF_PAGEOBJECT page_object, -FPDF_PAGEOBJECTMARK mark, -FPDF_BYTESTRING key, -) { - return _FPDFPageObjMark_RemoveParam(page_object, -mark, -key, -); -} - -late final _FPDFPageObjMark_RemoveParamPtr = _lookup< - ffi.NativeFunction>('FPDFPageObjMark_RemoveParam'); -late final _FPDFPageObjMark_RemoveParam = _FPDFPageObjMark_RemoveParamPtr.asFunction(); - -/// Load an image from a JPEG image file and then set it into |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// file_access - file access handler which specifies the JPEG image file. -/// -/// Returns TRUE on success. -/// -/// The image object might already have an associated image, which is shared and -/// cached by the loaded pages. In that case, we need to clear the cached image -/// for all the loaded pages. Pass |pages| and page count (|count|) to this API -/// to clear the image cache. If the image is not previously shared, or NULL is a -/// valid |pages| value. -int FPDFImageObj_LoadJpegFile(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -ffi.Pointer file_access, -) { - return _FPDFImageObj_LoadJpegFile(pages, -count, -image_object, -file_access, -); -} - -late final _FPDFImageObj_LoadJpegFilePtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFile'); -late final _FPDFImageObj_LoadJpegFile = _FPDFImageObj_LoadJpegFilePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); - -/// Load an image from a JPEG image file and then set it into |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// file_access - file access handler which specifies the JPEG image file. -/// -/// Returns TRUE on success. -/// -/// The image object might already have an associated image, which is shared and -/// cached by the loaded pages. In that case, we need to clear the cached image -/// for all the loaded pages. Pass |pages| and page count (|count|) to this API -/// to clear the image cache. If the image is not previously shared, or NULL is a -/// valid |pages| value. This function loads the JPEG image inline, so the image -/// content is copied to the file. This allows |file_access| and its associated -/// data to be deleted after this function returns. -int FPDFImageObj_LoadJpegFileInline(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -ffi.Pointer file_access, -) { - return _FPDFImageObj_LoadJpegFileInline(pages, -count, -image_object, -file_access, -); -} - -late final _FPDFImageObj_LoadJpegFileInlinePtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , ffi.Pointer )>>('FPDFImageObj_LoadJpegFileInline'); -late final _FPDFImageObj_LoadJpegFileInline = _FPDFImageObj_LoadJpegFileInlinePtr.asFunction , int , FPDF_PAGEOBJECT , ffi.Pointer )>(); - -/// TODO(thestig): Start deprecating this once FPDFPageObj_SetMatrix() is stable. -/// -/// Set the transform matrix of |image_object|. -/// -/// image_object - handle to an image object. -/// a - matrix value. -/// b - matrix value. -/// c - matrix value. -/// d - matrix value. -/// e - matrix value. -/// f - matrix value. -/// -/// The matrix is composed as: -/// |a c e| -/// |b d f| -/// and can be used to scale, rotate, shear and translate the |image_object|. -/// -/// Returns TRUE on success. -int FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, -double a, -double b, -double c, -double d, -double e, -double f, -) { - return _FPDFImageObj_SetMatrix(image_object, -a, -b, -c, -d, -e, -f, -); -} - -late final _FPDFImageObj_SetMatrixPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_SetMatrix'); -late final _FPDFImageObj_SetMatrix = _FPDFImageObj_SetMatrixPtr.asFunction(); - -/// Set |bitmap| to |image_object|. -/// -/// pages - pointer to the start of all loaded pages, may be NULL. -/// count - number of |pages|, may be 0. -/// image_object - handle to an image object. -/// bitmap - handle of the bitmap. -/// -/// Returns TRUE on success. -int FPDFImageObj_SetBitmap(ffi.Pointer pages, -int count, -FPDF_PAGEOBJECT image_object, -FPDF_BITMAP bitmap, -) { - return _FPDFImageObj_SetBitmap(pages, -count, -image_object, -bitmap, -); -} - -late final _FPDFImageObj_SetBitmapPtr = _lookup< - ffi.NativeFunction , ffi.Int , FPDF_PAGEOBJECT , FPDF_BITMAP )>>('FPDFImageObj_SetBitmap'); -late final _FPDFImageObj_SetBitmap = _FPDFImageObj_SetBitmapPtr.asFunction , int , FPDF_PAGEOBJECT , FPDF_BITMAP )>(); - -/// Get a bitmap rasterization of |image_object|. FPDFImageObj_GetBitmap() only -/// operates on |image_object| and does not take the associated image mask into -/// account. It also ignores the matrix for |image_object|. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. -/// -/// image_object - handle to an image object. -/// -/// Returns the bitmap. -FPDF_BITMAP FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object, -) { - return _FPDFImageObj_GetBitmap(image_object, -); -} - -late final _FPDFImageObj_GetBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetBitmap'); -late final _FPDFImageObj_GetBitmap = _FPDFImageObj_GetBitmapPtr.asFunction(); - -/// Experimental API. -/// Get a bitmap rasterization of |image_object| that takes the image mask and -/// image matrix into account. To render correctly, the caller must provide the -/// |document| associated with |image_object|. If there is a |page| associated -/// with |image_object|, the caller should provide that as well. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. -/// -/// document - handle to a document associated with |image_object|. -/// page - handle to an optional page associated with |image_object|. -/// image_object - handle to an image object. -/// -/// Returns the bitmap or NULL on failure. -FPDF_BITMAP FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, -FPDF_PAGE page, -FPDF_PAGEOBJECT image_object, -) { - return _FPDFImageObj_GetRenderedBitmap(document, -page, -image_object, -); -} - -late final _FPDFImageObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetRenderedBitmap'); -late final _FPDFImageObj_GetRenderedBitmap = _FPDFImageObj_GetRenderedBitmapPtr.asFunction(); - -/// Get the decoded image data of |image_object|. The decoded data is the -/// uncompressed image data, i.e. the raw image data after having all filters -/// applied. |buffer| is only modified if |buflen| is longer than the length of -/// the decoded image data. -/// -/// image_object - handle to an image object. -/// buffer - buffer for holding the decoded image data. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the decoded image data. -int FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFImageObj_GetImageDataDecoded(image_object, -buffer, -buflen, -); -} - -late final _FPDFImageObj_GetImageDataDecodedPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataDecoded'); -late final _FPDFImageObj_GetImageDataDecoded = _FPDFImageObj_GetImageDataDecodedPtr.asFunction , int )>(); - -/// Get the raw image data of |image_object|. The raw data is the image data as -/// stored in the PDF without applying any filters. |buffer| is only modified if -/// |buflen| is longer than the length of the raw image data. -/// -/// image_object - handle to an image object. -/// buffer - buffer for holding the raw image data. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the raw image data. -int FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFImageObj_GetImageDataRaw(image_object, -buffer, -buflen, -); -} - -late final _FPDFImageObj_GetImageDataRawPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageDataRaw'); -late final _FPDFImageObj_GetImageDataRaw = _FPDFImageObj_GetImageDataRawPtr.asFunction , int )>(); - -/// Get the number of filters (i.e. decoders) of the image in |image_object|. -/// -/// image_object - handle to an image object. -/// -/// Returns the number of |image_object|'s filters. -int FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object, -) { - return _FPDFImageObj_GetImageFilterCount(image_object, -); -} - -late final _FPDFImageObj_GetImageFilterCountPtr = _lookup< - ffi.NativeFunction>('FPDFImageObj_GetImageFilterCount'); -late final _FPDFImageObj_GetImageFilterCount = _FPDFImageObj_GetImageFilterCountPtr.asFunction(); - -/// Get the filter at |index| of |image_object|'s list of filters. Note that the -/// filters need to be applied in order, i.e. the first filter should be applied -/// first, then the second, etc. |buffer| is only modified if |buflen| is longer -/// than the length of the filter string. -/// -/// image_object - handle to an image object. -/// index - the index of the filter requested. -/// buffer - buffer for holding filter string, encoded in UTF-8. -/// buflen - length of the buffer. -/// -/// Returns the length of the filter string. -int FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, -int index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFImageObj_GetImageFilter(image_object, -index, -buffer, -buflen, -); -} - -late final _FPDFImageObj_GetImageFilterPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFImageObj_GetImageFilter'); -late final _FPDFImageObj_GetImageFilter = _FPDFImageObj_GetImageFilterPtr.asFunction , int )>(); - -/// Get the image metadata of |image_object|, including dimension, DPI, bits per -/// pixel, and colorspace. If the |image_object| is not an image object or if it -/// does not have an image, then the return value will be false. Otherwise, -/// failure to retrieve any specific parameter would result in its value being 0. -/// -/// image_object - handle to an image object. -/// page - handle to the page that |image_object| is on. Required for -/// retrieving the image's bits per pixel and colorspace. -/// metadata - receives the image metadata; must not be NULL. -/// -/// Returns true if successful. -int FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, -FPDF_PAGE page, -ffi.Pointer metadata, -) { - return _FPDFImageObj_GetImageMetadata(image_object, -page, -metadata, -); -} - -late final _FPDFImageObj_GetImageMetadataPtr = _lookup< - ffi.NativeFunction )>>('FPDFImageObj_GetImageMetadata'); -late final _FPDFImageObj_GetImageMetadata = _FPDFImageObj_GetImageMetadataPtr.asFunction )>(); - -/// Experimental API. -/// Get the image size in pixels. Faster method to get only image size. -/// -/// image_object - handle to an image object. -/// width - receives the image width in pixels; must not be NULL. -/// height - receives the image height in pixels; must not be NULL. -/// -/// Returns true if successful. -int FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, -ffi.Pointer width, -ffi.Pointer height, -) { - return _FPDFImageObj_GetImagePixelSize(image_object, -width, -height, -); -} - -late final _FPDFImageObj_GetImagePixelSizePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFImageObj_GetImagePixelSize'); -late final _FPDFImageObj_GetImagePixelSize = _FPDFImageObj_GetImagePixelSizePtr.asFunction , ffi.Pointer )>(); - -/// Experimental API. -/// Get ICC profile decoded data of |image_object|. If the |image_object| is not -/// an image object or if it does not have an image, then the return value will -/// be false. It also returns false if the |image_object| has no ICC profile. -/// |buffer| is only modified if ICC profile exists and |buflen| is longer than -/// the length of the ICC profile decoded data. -/// -/// image_object - handle to an image object; must not be NULL. -/// page - handle to the page containing |image_object|; must not be -/// NULL. Required for retrieving the image's colorspace. -/// buffer - Buffer to receive ICC profile data; may be NULL if querying -/// required size via |out_buflen|. -/// buflen - Length of the buffer in bytes. Ignored if |buffer| is NULL. -/// out_buflen - Pointer to receive the ICC profile data size in bytes; must -/// not be NULL. Will be set if this API returns true. -/// -/// Returns true if |out_buflen| is not null and an ICC profile exists for the -/// given |image_object|. -int FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, -FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFImageObj_GetIccProfileDataDecoded(image_object, -page, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFImageObj_GetIccProfileDataDecodedPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFImageObj_GetIccProfileDataDecoded'); -late final _FPDFImageObj_GetIccProfileDataDecoded = _FPDFImageObj_GetIccProfileDataDecodedPtr.asFunction , int , ffi.Pointer )>(); - -/// Create a new path object at an initial position. -/// -/// x - initial horizontal position. -/// y - initial vertical position. -/// -/// Returns a handle to a new path object. -FPDF_PAGEOBJECT FPDFPageObj_CreateNewPath(double x, -double y, -) { - return _FPDFPageObj_CreateNewPath(x, -y, -); -} - -late final _FPDFPageObj_CreateNewPathPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateNewPath'); -late final _FPDFPageObj_CreateNewPath = _FPDFPageObj_CreateNewPathPtr.asFunction(); - -/// Create a closed path consisting of a rectangle. -/// -/// x - horizontal position for the left boundary of the rectangle. -/// y - vertical position for the bottom boundary of the rectangle. -/// w - width of the rectangle. -/// h - height of the rectangle. -/// -/// Returns a handle to the new path object. -FPDF_PAGEOBJECT FPDFPageObj_CreateNewRect(double x, -double y, -double w, -double h, -) { - return _FPDFPageObj_CreateNewRect(x, -y, -w, -h, -); -} - -late final _FPDFPageObj_CreateNewRectPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateNewRect'); -late final _FPDFPageObj_CreateNewRect = _FPDFPageObj_CreateNewRectPtr.asFunction(); - -/// Get the bounding box of |page_object|. -/// -/// page_object - handle to a page object. -/// left - pointer where the left coordinate will be stored -/// bottom - pointer where the bottom coordinate will be stored -/// right - pointer where the right coordinate will be stored -/// top - pointer where the top coordinate will be stored -/// -/// On success, returns TRUE and fills in the 4 coordinates. -int FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPageObj_GetBounds(page_object, -left, -bottom, -right, -top, -); -} - -late final _FPDFPageObj_GetBoundsPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetBounds'); -late final _FPDFPageObj_GetBounds = _FPDFPageObj_GetBoundsPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Get the quad points that bounds |page_object|. -/// -/// page_object - handle to a page object. -/// quad_points - pointer where the quadrilateral points will be stored. -/// -/// On success, returns TRUE and fills in |quad_points|. -/// -/// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page -/// object. When the object is rotated by a non-multiple of 90 degrees, this API -/// returns a tighter bound that cannot be represented with just the 4 sides of -/// a rectangle. -/// -/// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and -/// FPDF_PAGEOBJ_IMAGE. -int FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, -ffi.Pointer quad_points, -) { - return _FPDFPageObj_GetRotatedBounds(page_object, -quad_points, -); -} - -late final _FPDFPageObj_GetRotatedBoundsPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetRotatedBounds'); -late final _FPDFPageObj_GetRotatedBounds = _FPDFPageObj_GetRotatedBoundsPtr.asFunction )>(); - -/// Set the blend mode of |page_object|. -/// -/// page_object - handle to a page object. -/// blend_mode - string containing the blend mode. -/// -/// Blend mode can be one of following: Color, ColorBurn, ColorDodge, Darken, -/// Difference, Exclusion, HardLight, Hue, Lighten, Luminosity, Multiply, Normal, -/// Overlay, Saturation, Screen, SoftLight -void FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, -FPDF_BYTESTRING blend_mode, -) { - return _FPDFPageObj_SetBlendMode(page_object, -blend_mode, -); -} - -late final _FPDFPageObj_SetBlendModePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetBlendMode'); -late final _FPDFPageObj_SetBlendMode = _FPDFPageObj_SetBlendModePtr.asFunction(); - -/// Set the stroke RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component for the object's stroke color. -/// G - the green component for the object's stroke color. -/// B - the blue component for the object's stroke color. -/// A - the stroke alpha for the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, -int R, -int G, -int B, -int A, -) { - return _FPDFPageObj_SetStrokeColor(page_object, -R, -G, -B, -A, -); -} - -late final _FPDFPageObj_SetStrokeColorPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetStrokeColor'); -late final _FPDFPageObj_SetStrokeColor = _FPDFPageObj_SetStrokeColorPtr.asFunction(); - -/// Get the stroke RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component of the path stroke color. -/// G - the green component of the object's stroke color. -/// B - the blue component of the object's stroke color. -/// A - the stroke alpha of the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetStrokeColor(FPDF_PAGEOBJECT page_object, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, -) { - return _FPDFPageObj_GetStrokeColor(page_object, -R, -G, -B, -A, -); -} - -late final _FPDFPageObj_GetStrokeColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetStrokeColor'); -late final _FPDFPageObj_GetStrokeColor = _FPDFPageObj_GetStrokeColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Set the stroke width of a page object. -/// -/// path - the handle to the page object. -/// width - the width of the stroke. -/// -/// Returns TRUE on success -int FPDFPageObj_SetStrokeWidth(FPDF_PAGEOBJECT page_object, -double width, -) { - return _FPDFPageObj_SetStrokeWidth(page_object, -width, -); -} - -late final _FPDFPageObj_SetStrokeWidthPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetStrokeWidth'); -late final _FPDFPageObj_SetStrokeWidth = _FPDFPageObj_SetStrokeWidthPtr.asFunction(); - -/// Get the stroke width of a page object. -/// -/// path - the handle to the page object. -/// width - the width of the stroke. -/// -/// Returns TRUE on success -int FPDFPageObj_GetStrokeWidth(FPDF_PAGEOBJECT page_object, -ffi.Pointer width, -) { - return _FPDFPageObj_GetStrokeWidth(page_object, -width, -); -} - -late final _FPDFPageObj_GetStrokeWidthPtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetStrokeWidth'); -late final _FPDFPageObj_GetStrokeWidth = _FPDFPageObj_GetStrokeWidthPtr.asFunction )>(); - -/// Get the line join of |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns the line join, or -1 on failure. -/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, -/// FPDF_LINEJOIN_BEVEL -int FPDFPageObj_GetLineJoin(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetLineJoin(page_object, -); -} - -late final _FPDFPageObj_GetLineJoinPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetLineJoin'); -late final _FPDFPageObj_GetLineJoin = _FPDFPageObj_GetLineJoinPtr.asFunction(); - -/// Set the line join of |page_object|. -/// -/// page_object - handle to a page object. -/// line_join - line join -/// -/// Line join can be one of following: FPDF_LINEJOIN_MITER, FPDF_LINEJOIN_ROUND, -/// FPDF_LINEJOIN_BEVEL -int FPDFPageObj_SetLineJoin(FPDF_PAGEOBJECT page_object, -int line_join, -) { - return _FPDFPageObj_SetLineJoin(page_object, -line_join, -); -} - -late final _FPDFPageObj_SetLineJoinPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetLineJoin'); -late final _FPDFPageObj_SetLineJoin = _FPDFPageObj_SetLineJoinPtr.asFunction(); - -/// Get the line cap of |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns the line cap, or -1 on failure. -/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, -/// FPDF_LINECAP_PROJECTING_SQUARE -int FPDFPageObj_GetLineCap(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetLineCap(page_object, -); -} - -late final _FPDFPageObj_GetLineCapPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetLineCap'); -late final _FPDFPageObj_GetLineCap = _FPDFPageObj_GetLineCapPtr.asFunction(); - -/// Set the line cap of |page_object|. -/// -/// page_object - handle to a page object. -/// line_cap - line cap -/// -/// Line cap can be one of following: FPDF_LINECAP_BUTT, FPDF_LINECAP_ROUND, -/// FPDF_LINECAP_PROJECTING_SQUARE -int FPDFPageObj_SetLineCap(FPDF_PAGEOBJECT page_object, -int line_cap, -) { - return _FPDFPageObj_SetLineCap(page_object, -line_cap, -); -} - -late final _FPDFPageObj_SetLineCapPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetLineCap'); -late final _FPDFPageObj_SetLineCap = _FPDFPageObj_SetLineCapPtr.asFunction(); - -/// Set the fill RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component for the object's fill color. -/// G - the green component for the object's fill color. -/// B - the blue component for the object's fill color. -/// A - the fill alpha for the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, -int R, -int G, -int B, -int A, -) { - return _FPDFPageObj_SetFillColor(page_object, -R, -G, -B, -A, -); -} - -late final _FPDFPageObj_SetFillColorPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetFillColor'); -late final _FPDFPageObj_SetFillColor = _FPDFPageObj_SetFillColorPtr.asFunction(); - -/// Get the fill RGBA of a page object. Range of values: 0 - 255. -/// -/// page_object - the handle to the page object. -/// R - the red component of the object's fill color. -/// G - the green component of the object's fill color. -/// B - the blue component of the object's fill color. -/// A - the fill alpha of the object. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetFillColor(FPDF_PAGEOBJECT page_object, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, -) { - return _FPDFPageObj_GetFillColor(page_object, -R, -G, -B, -A, -); -} - -late final _FPDFPageObj_GetFillColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPageObj_GetFillColor'); -late final _FPDFPageObj_GetFillColor = _FPDFPageObj_GetFillColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Get the line dash |phase| of |page_object|. -/// -/// page_object - handle to a page object. -/// phase - pointer where the dashing phase will be stored. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, -ffi.Pointer phase, -) { - return _FPDFPageObj_GetDashPhase(page_object, -phase, -); -} - -late final _FPDFPageObj_GetDashPhasePtr = _lookup< - ffi.NativeFunction )>>('FPDFPageObj_GetDashPhase'); -late final _FPDFPageObj_GetDashPhase = _FPDFPageObj_GetDashPhasePtr.asFunction )>(); - -/// Experimental API. -/// Set the line dash phase of |page_object|. -/// -/// page_object - handle to a page object. -/// phase - line dash phase. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, -double phase, -) { - return _FPDFPageObj_SetDashPhase(page_object, -phase, -); -} - -late final _FPDFPageObj_SetDashPhasePtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_SetDashPhase'); -late final _FPDFPageObj_SetDashPhase = _FPDFPageObj_SetDashPhasePtr.asFunction(); - -/// Experimental API. -/// Get the line dash array of |page_object|. -/// -/// page_object - handle to a page object. -/// -/// Returns the line dash array size or -1 on failure. -int FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetDashCount(page_object, -); -} - -late final _FPDFPageObj_GetDashCountPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetDashCount'); -late final _FPDFPageObj_GetDashCount = _FPDFPageObj_GetDashCountPtr.asFunction(); - -/// Experimental API. -/// Get the line dash array of |page_object|. -/// -/// page_object - handle to a page object. -/// dash_array - pointer where the dashing array will be stored. -/// dash_count - number of elements in |dash_array|. -/// -/// Returns TRUE on success. -int FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object, -ffi.Pointer dash_array, -int dash_count, -) { - return _FPDFPageObj_GetDashArray(page_object, -dash_array, -dash_count, -); -} - -late final _FPDFPageObj_GetDashArrayPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFPageObj_GetDashArray'); -late final _FPDFPageObj_GetDashArray = _FPDFPageObj_GetDashArrayPtr.asFunction , int )>(); - -/// Experimental API. -/// Set the line dash array of |page_object|. -/// -/// page_object - handle to a page object. -/// dash_array - the dash array. -/// dash_count - number of elements in |dash_array|. -/// phase - the line dash phase. -/// -/// Returns TRUE on success. -int FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, -ffi.Pointer dash_array, -int dash_count, -double phase, -) { - return _FPDFPageObj_SetDashArray(page_object, -dash_array, -dash_count, -phase, -); -} - -late final _FPDFPageObj_SetDashArrayPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Float )>>('FPDFPageObj_SetDashArray'); -late final _FPDFPageObj_SetDashArray = _FPDFPageObj_SetDashArrayPtr.asFunction , int , double )>(); - -/// Get number of segments inside |path|. -/// -/// path - handle to a path. -/// -/// A segment is a command, created by e.g. FPDFPath_MoveTo(), -/// FPDFPath_LineTo() or FPDFPath_BezierTo(). -/// -/// Returns the number of objects in |path| or -1 on failure. -int FPDFPath_CountSegments(FPDF_PAGEOBJECT path, -) { - return _FPDFPath_CountSegments(path, -); -} - -late final _FPDFPath_CountSegmentsPtr = _lookup< - ffi.NativeFunction>('FPDFPath_CountSegments'); -late final _FPDFPath_CountSegments = _FPDFPath_CountSegmentsPtr.asFunction(); - -/// Get segment in |path| at |index|. -/// -/// path - handle to a path. -/// index - the index of a segment. -/// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_PATHSEGMENT FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, -int index, -) { - return _FPDFPath_GetPathSegment(path, -index, -); -} - -late final _FPDFPath_GetPathSegmentPtr = _lookup< - ffi.NativeFunction>('FPDFPath_GetPathSegment'); -late final _FPDFPath_GetPathSegment = _FPDFPath_GetPathSegmentPtr.asFunction(); - -/// Get coordinates of |segment|. -/// -/// segment - handle to a segment. -/// x - the horizontal position of the segment. -/// y - the vertical position of the segment. -/// -/// Returns TRUE on success, otherwise |x| and |y| is not set. -int FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, -ffi.Pointer x, -ffi.Pointer y, -) { - return _FPDFPathSegment_GetPoint(segment, -x, -y, -); -} - -late final _FPDFPathSegment_GetPointPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFPathSegment_GetPoint'); -late final _FPDFPathSegment_GetPoint = _FPDFPathSegment_GetPointPtr.asFunction , ffi.Pointer )>(); - -/// Get type of |segment|. -/// -/// segment - handle to a segment. -/// -/// Returns one of the FPDF_SEGMENT_* values on success, -/// FPDF_SEGMENT_UNKNOWN on error. -int FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment, -) { - return _FPDFPathSegment_GetType(segment, -); -} - -late final _FPDFPathSegment_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFPathSegment_GetType'); -late final _FPDFPathSegment_GetType = _FPDFPathSegment_GetTypePtr.asFunction(); - -/// Gets if the |segment| closes the current subpath of a given path. -/// -/// segment - handle to a segment. -/// -/// Returns close flag for non-NULL segment, FALSE otherwise. -int FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment, -) { - return _FPDFPathSegment_GetClose(segment, -); -} - -late final _FPDFPathSegment_GetClosePtr = _lookup< - ffi.NativeFunction>('FPDFPathSegment_GetClose'); -late final _FPDFPathSegment_GetClose = _FPDFPathSegment_GetClosePtr.asFunction(); - -/// Move a path's current point. -/// -/// path - the handle to the path object. -/// x - the horizontal position of the new current point. -/// y - the vertical position of the new current point. -/// -/// Note that no line will be created between the previous current point and the -/// new one. -/// -/// Returns TRUE on success -int FPDFPath_MoveTo(FPDF_PAGEOBJECT path, -double x, -double y, -) { - return _FPDFPath_MoveTo(path, -x, -y, -); -} - -late final _FPDFPath_MoveToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_MoveTo'); -late final _FPDFPath_MoveTo = _FPDFPath_MoveToPtr.asFunction(); - -/// Add a line between the current point and a new point in the path. -/// -/// path - the handle to the path object. -/// x - the horizontal position of the new point. -/// y - the vertical position of the new point. -/// -/// The path's current point is changed to (x, y). -/// -/// Returns TRUE on success -int FPDFPath_LineTo(FPDF_PAGEOBJECT path, -double x, -double y, -) { - return _FPDFPath_LineTo(path, -x, -y, -); -} - -late final _FPDFPath_LineToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_LineTo'); -late final _FPDFPath_LineTo = _FPDFPath_LineToPtr.asFunction(); - -/// Add a cubic Bezier curve to the given path, starting at the current point. -/// -/// path - the handle to the path object. -/// x1 - the horizontal position of the first Bezier control point. -/// y1 - the vertical position of the first Bezier control point. -/// x2 - the horizontal position of the second Bezier control point. -/// y2 - the vertical position of the second Bezier control point. -/// x3 - the horizontal position of the ending point of the Bezier curve. -/// y3 - the vertical position of the ending point of the Bezier curve. -/// -/// Returns TRUE on success -int FPDFPath_BezierTo(FPDF_PAGEOBJECT path, -double x1, -double y1, -double x2, -double y2, -double x3, -double y3, -) { - return _FPDFPath_BezierTo(path, -x1, -y1, -x2, -y2, -x3, -y3, -); -} - -late final _FPDFPath_BezierToPtr = _lookup< - ffi.NativeFunction>('FPDFPath_BezierTo'); -late final _FPDFPath_BezierTo = _FPDFPath_BezierToPtr.asFunction(); - -/// Close the current subpath of a given path. -/// -/// path - the handle to the path object. -/// -/// This will add a line between the current point and the initial point of the -/// subpath, thus terminating the current subpath. -/// -/// Returns TRUE on success -int FPDFPath_Close(FPDF_PAGEOBJECT path, -) { - return _FPDFPath_Close(path, -); -} - -late final _FPDFPath_ClosePtr = _lookup< - ffi.NativeFunction>('FPDFPath_Close'); -late final _FPDFPath_Close = _FPDFPath_ClosePtr.asFunction(); - -/// Set the drawing mode of a path. -/// -/// path - the handle to the path object. -/// fillmode - the filling mode to be set: one of the FPDF_FILLMODE_* flags. -/// stroke - a boolean specifying if the path should be stroked or not. -/// -/// Returns TRUE on success -int FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, -int fillmode, -int stroke, -) { - return _FPDFPath_SetDrawMode(path, -fillmode, -stroke, -); -} - -late final _FPDFPath_SetDrawModePtr = _lookup< - ffi.NativeFunction>('FPDFPath_SetDrawMode'); -late final _FPDFPath_SetDrawMode = _FPDFPath_SetDrawModePtr.asFunction(); - -/// Get the drawing mode of a path. -/// -/// path - the handle to the path object. -/// fillmode - the filling mode of the path: one of the FPDF_FILLMODE_* flags. -/// stroke - a boolean specifying if the path is stroked or not. -/// -/// Returns TRUE on success -int FPDFPath_GetDrawMode(FPDF_PAGEOBJECT path, -ffi.Pointer fillmode, -ffi.Pointer stroke, -) { - return _FPDFPath_GetDrawMode(path, -fillmode, -stroke, -); -} - -late final _FPDFPath_GetDrawModePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFPath_GetDrawMode'); -late final _FPDFPath_GetDrawMode = _FPDFPath_GetDrawModePtr.asFunction , ffi.Pointer )>(); - -/// Create a new text object using one of the standard PDF fonts. -/// -/// document - handle to the document. -/// font - string containing the font name, without spaces. -/// font_size - the font size for the new text object. -/// -/// Returns a handle to a new text object, or NULL on failure -FPDF_PAGEOBJECT FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, -FPDF_BYTESTRING font, -double font_size, -) { - return _FPDFPageObj_NewTextObj(document, -font, -font_size, -); -} - -late final _FPDFPageObj_NewTextObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_NewTextObj'); -late final _FPDFPageObj_NewTextObj = _FPDFPageObj_NewTextObjPtr.asFunction(); - -/// Set the text for a text object. If it had text, it will be replaced. -/// -/// text_object - handle to the text object. -/// text - the UTF-16LE encoded string containing the text to be added. -/// -/// Returns TRUE on success -int FPDFText_SetText(FPDF_PAGEOBJECT text_object, -FPDF_WIDESTRING text, -) { - return _FPDFText_SetText(text_object, -text, -); -} - -late final _FPDFText_SetTextPtr = _lookup< - ffi.NativeFunction>('FPDFText_SetText'); -late final _FPDFText_SetText = _FPDFText_SetTextPtr.asFunction(); - -/// Experimental API. -/// Set the text using charcodes for a text object. If it had text, it will be -/// replaced. -/// -/// text_object - handle to the text object. -/// charcodes - pointer to an array of charcodes to be added. -/// count - number of elements in |charcodes|. -/// -/// Returns TRUE on success -int FPDFText_SetCharcodes(FPDF_PAGEOBJECT text_object, -ffi.Pointer charcodes, -int count, -) { - return _FPDFText_SetCharcodes(text_object, -charcodes, -count, -); -} - -late final _FPDFText_SetCharcodesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFText_SetCharcodes'); -late final _FPDFText_SetCharcodes = _FPDFText_SetCharcodesPtr.asFunction , int )>(); - -/// Returns a font object loaded from a stream of data. The font is loaded -/// into the document. Various font data structures, such as the ToUnicode data, -/// are auto-generated based on the inputs. -/// -/// document - handle to the document. -/// data - the stream of font data, which will be copied by the font object. -/// size - the size of the font data, in bytes. -/// font_type - FPDF_FONT_TYPE1 or FPDF_FONT_TRUETYPE depending on the font type. -/// cid - a boolean specifying if the font is a CID font or not. -/// -/// The loaded font can be closed using FPDFFont_Close(). -/// -/// Returns NULL on failure -FPDF_FONT FPDFText_LoadFont(FPDF_DOCUMENT document, -ffi.Pointer data, -int size, -int font_type, -int cid, -) { - return _FPDFText_LoadFont(document, -data, -size, -font_type, -cid, -); -} - -late final _FPDFText_LoadFontPtr = _lookup< - ffi.NativeFunction , ffi.Uint32 , ffi.Int , FPDF_BOOL )>>('FPDFText_LoadFont'); -late final _FPDFText_LoadFont = _FPDFText_LoadFontPtr.asFunction , int , int , int )>(); - -/// Experimental API. -/// Loads one of the standard 14 fonts per PDF spec 1.7 page 416. The preferred -/// way of using font style is using a dash to separate the name from the style, -/// for example 'Helvetica-BoldItalic'. -/// -/// document - handle to the document. -/// font - string containing the font name, without spaces. -/// -/// The loaded font can be closed using FPDFFont_Close(). -/// -/// Returns NULL on failure. -FPDF_FONT FPDFText_LoadStandardFont(FPDF_DOCUMENT document, -FPDF_BYTESTRING font, -) { - return _FPDFText_LoadStandardFont(document, -font, -); -} - -late final _FPDFText_LoadStandardFontPtr = _lookup< - ffi.NativeFunction>('FPDFText_LoadStandardFont'); -late final _FPDFText_LoadStandardFont = _FPDFText_LoadStandardFontPtr.asFunction(); - -/// Experimental API. -/// Returns a font object loaded from a stream of data for a type 2 CID font. The -/// font is loaded into the document. Unlike FPDFText_LoadFont(), the ToUnicode -/// data and the CIDToGIDMap data are caller provided, instead of auto-generated. -/// -/// document - handle to the document. -/// font_data - the stream of font data, which will be copied by -/// the font object. -/// font_data_size - the size of the font data, in bytes. -/// to_unicode_cmap - the ToUnicode data. -/// cid_to_gid_map_data - the stream of CIDToGIDMap data. -/// cid_to_gid_map_data_size - the size of the CIDToGIDMap data, in bytes. -/// -/// The loaded font can be closed using FPDFFont_Close(). -/// -/// Returns NULL on failure. -FPDF_FONT FPDFText_LoadCidType2Font(FPDF_DOCUMENT document, -ffi.Pointer font_data, -int font_data_size, -FPDF_BYTESTRING to_unicode_cmap, -ffi.Pointer cid_to_gid_map_data, -int cid_to_gid_map_data_size, -) { - return _FPDFText_LoadCidType2Font(document, -font_data, -font_data_size, -to_unicode_cmap, -cid_to_gid_map_data, -cid_to_gid_map_data_size, -); -} - -late final _FPDFText_LoadCidType2FontPtr = _lookup< - ffi.NativeFunction , ffi.Uint32 , FPDF_BYTESTRING , ffi.Pointer , ffi.Uint32 )>>('FPDFText_LoadCidType2Font'); -late final _FPDFText_LoadCidType2Font = _FPDFText_LoadCidType2FontPtr.asFunction , int , FPDF_BYTESTRING , ffi.Pointer , int )>(); - -/// Get the font size of a text object. -/// -/// text - handle to a text. -/// size - pointer to the font size of the text object, measured in points -/// (about 1/72 inch) -/// -/// Returns TRUE on success. -int FPDFTextObj_GetFontSize(FPDF_PAGEOBJECT text, -ffi.Pointer size, -) { - return _FPDFTextObj_GetFontSize(text, -size, -); -} - -late final _FPDFTextObj_GetFontSizePtr = _lookup< - ffi.NativeFunction )>>('FPDFTextObj_GetFontSize'); -late final _FPDFTextObj_GetFontSize = _FPDFTextObj_GetFontSizePtr.asFunction )>(); - -/// Close a loaded PDF font. -/// -/// font - Handle to the loaded font. -void FPDFFont_Close(FPDF_FONT font, -) { - return _FPDFFont_Close(font, -); -} - -late final _FPDFFont_ClosePtr = _lookup< - ffi.NativeFunction>('FPDFFont_Close'); -late final _FPDFFont_Close = _FPDFFont_ClosePtr.asFunction(); - -/// Create a new text object using a loaded font. -/// -/// document - handle to the document. -/// font - handle to the font object. -/// font_size - the font size for the new text object. -/// -/// Returns a handle to a new text object, or NULL on failure -FPDF_PAGEOBJECT FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, -FPDF_FONT font, -double font_size, -) { - return _FPDFPageObj_CreateTextObj(document, -font, -font_size, -); -} - -late final _FPDFPageObj_CreateTextObjPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_CreateTextObj'); -late final _FPDFPageObj_CreateTextObj = _FPDFPageObj_CreateTextObjPtr.asFunction(); - -/// Get the text rendering mode of a text object. -/// -/// text - the handle to the text object. -/// -/// Returns one of the known FPDF_TEXT_RENDERMODE enum values on success, -/// FPDF_TEXTRENDERMODE_UNKNOWN on error. -FPDF_TEXT_RENDERMODE FPDFTextObj_GetTextRenderMode(FPDF_PAGEOBJECT text, -) { - return FPDF_TEXT_RENDERMODE.fromValue(_FPDFTextObj_GetTextRenderMode(text, -)); -} - -late final _FPDFTextObj_GetTextRenderModePtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetTextRenderMode'); -late final _FPDFTextObj_GetTextRenderMode = _FPDFTextObj_GetTextRenderModePtr.asFunction(); - -/// Experimental API. -/// Set the text rendering mode of a text object. -/// -/// text - the handle to the text object. -/// render_mode - the FPDF_TEXT_RENDERMODE enum value to be set (cannot set to -/// FPDF_TEXTRENDERMODE_UNKNOWN). -/// -/// Returns TRUE on success. -DartFPDF_BOOL FPDFTextObj_SetTextRenderMode(FPDF_PAGEOBJECT text, -FPDF_TEXT_RENDERMODE render_mode, -) { - return _FPDFTextObj_SetTextRenderMode(text, -render_mode.value, -); -} - -late final _FPDFTextObj_SetTextRenderModePtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_SetTextRenderMode'); -late final _FPDFTextObj_SetTextRenderMode = _FPDFTextObj_SetTextRenderModePtr.asFunction(); - -/// Get the text of a text object. -/// -/// text_object - the handle to the text object. -/// text_page - the handle to the text page. -/// buffer - the address of a buffer that receives the text. -/// length - the size, in bytes, of |buffer|. -/// -/// Returns the number of bytes in the text (including the trailing NUL -/// character) on success, 0 on error. -/// -/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFTextObj_GetText(FPDF_PAGEOBJECT text_object, -FPDF_TEXTPAGE text_page, -ffi.Pointer buffer, -int length, -) { - return _FPDFTextObj_GetText(text_object, -text_page, -buffer, -length, -); -} - -late final _FPDFTextObj_GetTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFTextObj_GetText'); -late final _FPDFTextObj_GetText = _FPDFTextObj_GetTextPtr.asFunction , int )>(); - -/// Experimental API. -/// Get a bitmap rasterization of |text_object|. To render correctly, the caller -/// must provide the |document| associated with |text_object|. If there is a -/// |page| associated with |text_object|, the caller should provide that as well. -/// The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy() -/// must be called on the returned bitmap when it is no longer needed. -/// -/// document - handle to a document associated with |text_object|. -/// page - handle to an optional page associated with |text_object|. -/// text_object - handle to a text object. -/// scale - the scaling factor, which must be greater than 0. -/// -/// Returns the bitmap or NULL on failure. -FPDF_BITMAP FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, -FPDF_PAGE page, -FPDF_PAGEOBJECT text_object, -double scale, -) { - return _FPDFTextObj_GetRenderedBitmap(document, -page, -text_object, -scale, -); -} - -late final _FPDFTextObj_GetRenderedBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetRenderedBitmap'); -late final _FPDFTextObj_GetRenderedBitmap = _FPDFTextObj_GetRenderedBitmapPtr.asFunction(); - -/// Experimental API. -/// Get the font of a text object. -/// -/// text - the handle to the text object. -/// -/// Returns a handle to the font object held by |text| which retains ownership. -FPDF_FONT FPDFTextObj_GetFont(FPDF_PAGEOBJECT text, -) { - return _FPDFTextObj_GetFont(text, -); -} - -late final _FPDFTextObj_GetFontPtr = _lookup< - ffi.NativeFunction>('FPDFTextObj_GetFont'); -late final _FPDFTextObj_GetFont = _FPDFTextObj_GetFontPtr.asFunction(); - -/// Experimental API. -/// Get the base name of a font. -/// -/// font - the handle to the font object. -/// buffer - the address of a buffer that receives the base font name. -/// length - the size, in bytes, of |buffer|. -/// -/// Returns the number of bytes in the base name (including the trailing NUL -/// character) on success, 0 on error. The base name is typically the font's -/// PostScript name. See descriptions of "BaseFont" in ISO 32000-1:2008 spec. -/// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFFont_GetBaseFontName(FPDF_FONT font, -ffi.Pointer buffer, -int length, -) { - return _FPDFFont_GetBaseFontName(font, -buffer, -length, -); -} - -late final _FPDFFont_GetBaseFontNamePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetBaseFontName'); -late final _FPDFFont_GetBaseFontName = _FPDFFont_GetBaseFontNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the family name of a font. -/// -/// font - the handle to the font object. -/// buffer - the address of a buffer that receives the font name. -/// length - the size, in bytes, of |buffer|. -/// -/// Returns the number of bytes in the family name (including the trailing NUL -/// character) on success, 0 on error. -/// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |length| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFFont_GetFamilyName(FPDF_FONT font, -ffi.Pointer buffer, -int length, -) { - return _FPDFFont_GetFamilyName(font, -buffer, -length, -); -} - -late final _FPDFFont_GetFamilyNamePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFFont_GetFamilyName'); -late final _FPDFFont_GetFamilyName = _FPDFFont_GetFamilyNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the decoded data from the |font| object. -/// -/// font - The handle to the font object. (Required) -/// buffer - The address of a buffer that receives the font data. -/// buflen - Length of the buffer. -/// out_buflen - Pointer to variable that will receive the minimum buffer size -/// to contain the font data. Not filled if the return value is -/// FALSE. (Required) -/// -/// Returns TRUE on success. In which case, |out_buflen| will be filled, and -/// |buffer| will be filled if it is large enough. Returns FALSE if any of the -/// required parameters are null. -/// -/// The decoded data is the uncompressed font data. i.e. the raw font data after -/// having all stream filters applied, when the data is embedded. -/// -/// If the font is not embedded, then this API will instead return the data for -/// the substitution font it is using. -int FPDFFont_GetFontData(FPDF_FONT font, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFFont_GetFontData(font, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFFont_GetFontDataPtr = _lookup< - ffi.NativeFunction , ffi.Size , ffi.Pointer )>>('FPDFFont_GetFontData'); -late final _FPDFFont_GetFontData = _FPDFFont_GetFontDataPtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Get whether |font| is embedded or not. -/// -/// font - the handle to the font object. -/// -/// Returns 1 if the font is embedded, 0 if it not, and -1 on failure. -int FPDFFont_GetIsEmbedded(FPDF_FONT font, -) { - return _FPDFFont_GetIsEmbedded(font, -); -} - -late final _FPDFFont_GetIsEmbeddedPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetIsEmbedded'); -late final _FPDFFont_GetIsEmbedded = _FPDFFont_GetIsEmbeddedPtr.asFunction(); - -/// Experimental API. -/// Get the descriptor flags of a font. -/// -/// font - the handle to the font object. -/// -/// Returns the bit flags specifying various characteristics of the font as -/// defined in ISO 32000-1:2008, table 123, -1 on failure. -int FPDFFont_GetFlags(FPDF_FONT font, -) { - return _FPDFFont_GetFlags(font, -); -} - -late final _FPDFFont_GetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetFlags'); -late final _FPDFFont_GetFlags = _FPDFFont_GetFlagsPtr.asFunction(); - -/// Experimental API. -/// Get the font weight of a font. -/// -/// font - the handle to the font object. -/// -/// Returns the font weight, -1 on failure. -/// Typical values are 400 (normal) and 700 (bold). -int FPDFFont_GetWeight(FPDF_FONT font, -) { - return _FPDFFont_GetWeight(font, -); -} - -late final _FPDFFont_GetWeightPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetWeight'); -late final _FPDFFont_GetWeight = _FPDFFont_GetWeightPtr.asFunction(); - -/// Experimental API. -/// Get the italic angle of a font. -/// -/// font - the handle to the font object. -/// angle - pointer where the italic angle will be stored -/// -/// The italic angle of a |font| is defined as degrees counterclockwise -/// from vertical. For a font that slopes to the right, this will be negative. -/// -/// Returns TRUE on success; |angle| unmodified on failure. -int FPDFFont_GetItalicAngle(FPDF_FONT font, -ffi.Pointer angle, -) { - return _FPDFFont_GetItalicAngle(font, -angle, -); -} - -late final _FPDFFont_GetItalicAnglePtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetItalicAngle'); -late final _FPDFFont_GetItalicAngle = _FPDFFont_GetItalicAnglePtr.asFunction )>(); - -/// Experimental API. -/// Get ascent distance of a font. -/// -/// font - the handle to the font object. -/// font_size - the size of the |font|. -/// ascent - pointer where the font ascent will be stored -/// -/// Ascent is the maximum distance in points above the baseline reached by the -/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). -/// -/// Returns TRUE on success; |ascent| unmodified on failure. -int FPDFFont_GetAscent(FPDF_FONT font, -double font_size, -ffi.Pointer ascent, -) { - return _FPDFFont_GetAscent(font, -font_size, -ascent, -); -} - -late final _FPDFFont_GetAscentPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetAscent'); -late final _FPDFFont_GetAscent = _FPDFFont_GetAscentPtr.asFunction )>(); - -/// Experimental API. -/// Get descent distance of a font. -/// -/// font - the handle to the font object. -/// font_size - the size of the |font|. -/// descent - pointer where the font descent will be stored -/// -/// Descent is the maximum distance in points below the baseline reached by the -/// glyphs of the |font|. One point is 1/72 inch (around 0.3528 mm). -/// -/// Returns TRUE on success; |descent| unmodified on failure. -int FPDFFont_GetDescent(FPDF_FONT font, -double font_size, -ffi.Pointer descent, -) { - return _FPDFFont_GetDescent(font, -font_size, -descent, -); -} - -late final _FPDFFont_GetDescentPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetDescent'); -late final _FPDFFont_GetDescent = _FPDFFont_GetDescentPtr.asFunction )>(); - -/// Experimental API. -/// Get the width of a glyph in a font. -/// -/// font - the handle to the font object. -/// glyph - the glyph. -/// font_size - the size of the font. -/// width - pointer where the glyph width will be stored -/// -/// Glyph width is the distance from the end of the prior glyph to the next -/// glyph. This will be the vertical distance for vertical writing. -/// -/// Returns TRUE on success; |width| unmodified on failure. -int FPDFFont_GetGlyphWidth(FPDF_FONT font, -int glyph, -double font_size, -ffi.Pointer width, -) { - return _FPDFFont_GetGlyphWidth(font, -glyph, -font_size, -width, -); -} - -late final _FPDFFont_GetGlyphWidthPtr = _lookup< - ffi.NativeFunction )>>('FPDFFont_GetGlyphWidth'); -late final _FPDFFont_GetGlyphWidth = _FPDFFont_GetGlyphWidthPtr.asFunction )>(); - -/// Experimental API. -/// Get the glyphpath describing how to draw a font glyph. -/// -/// font - the handle to the font object. -/// glyph - the glyph being drawn. -/// font_size - the size of the font. -/// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_GLYPHPATH FPDFFont_GetGlyphPath(FPDF_FONT font, -int glyph, -double font_size, -) { - return _FPDFFont_GetGlyphPath(font, -glyph, -font_size, -); -} - -late final _FPDFFont_GetGlyphPathPtr = _lookup< - ffi.NativeFunction>('FPDFFont_GetGlyphPath'); -late final _FPDFFont_GetGlyphPath = _FPDFFont_GetGlyphPathPtr.asFunction(); - -/// Experimental API. -/// Get number of segments inside glyphpath. -/// -/// glyphpath - handle to a glyph path. -/// -/// Returns the number of objects in |glyphpath| or -1 on failure. -int FPDFGlyphPath_CountGlyphSegments(FPDF_GLYPHPATH glyphpath, -) { - return _FPDFGlyphPath_CountGlyphSegments(glyphpath, -); -} - -late final _FPDFGlyphPath_CountGlyphSegmentsPtr = _lookup< - ffi.NativeFunction>('FPDFGlyphPath_CountGlyphSegments'); -late final _FPDFGlyphPath_CountGlyphSegments = _FPDFGlyphPath_CountGlyphSegmentsPtr.asFunction(); - -/// Experimental API. -/// Get segment in glyphpath at index. -/// -/// glyphpath - handle to a glyph path. -/// index - the index of a segment. -/// -/// Returns the handle to the segment, or NULL on faiure. -FPDF_PATHSEGMENT FPDFGlyphPath_GetGlyphPathSegment(FPDF_GLYPHPATH glyphpath, -int index, -) { - return _FPDFGlyphPath_GetGlyphPathSegment(glyphpath, -index, -); -} - -late final _FPDFGlyphPath_GetGlyphPathSegmentPtr = _lookup< - ffi.NativeFunction>('FPDFGlyphPath_GetGlyphPathSegment'); -late final _FPDFGlyphPath_GetGlyphPathSegment = _FPDFGlyphPath_GetGlyphPathSegmentPtr.asFunction(); - -/// Get number of page objects inside |form_object|. -/// -/// form_object - handle to a form object. -/// -/// Returns the number of objects in |form_object| on success, -1 on error. -int FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object, -) { - return _FPDFFormObj_CountObjects(form_object, -); -} - -late final _FPDFFormObj_CountObjectsPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_CountObjects'); -late final _FPDFFormObj_CountObjects = _FPDFFormObj_CountObjectsPtr.asFunction(); - -/// Get page object in |form_object| at |index|. -/// -/// form_object - handle to a form object. -/// index - the 0-based index of a page object. -/// -/// Returns the handle to the page object, or NULL on error. -FPDF_PAGEOBJECT FPDFFormObj_GetObject(FPDF_PAGEOBJECT form_object, -int index, -) { - return _FPDFFormObj_GetObject(form_object, -index, -); -} - -late final _FPDFFormObj_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_GetObject'); -late final _FPDFFormObj_GetObject = _FPDFFormObj_GetObjectPtr.asFunction(); - -/// Experimental API. -/// -/// Remove |page_object| from |form_object|. -/// -/// form_object - handle to a form object. -/// page_object - handle to a page object to be removed from the form. -/// -/// Returns TRUE on success. -/// -/// Ownership of the removed |page_object| is transferred to the caller. -/// Call FPDFPageObj_Destroy() on the removed page_object to free it. -int FPDFFormObj_RemoveObject(FPDF_PAGEOBJECT form_object, -FPDF_PAGEOBJECT page_object, -) { - return _FPDFFormObj_RemoveObject(form_object, -page_object, -); -} - -late final _FPDFFormObj_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFFormObj_RemoveObject'); -late final _FPDFFormObj_RemoveObject = _FPDFFormObj_RemoveObjectPtr.asFunction(); - -/// Experimental API. -/// Get the number of embedded files in |document|. -/// -/// document - handle to a document. -/// -/// Returns the number of embedded files in |document|. -int FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document, -) { - return _FPDFDoc_GetAttachmentCount(document, -); -} - -late final _FPDFDoc_GetAttachmentCountPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_GetAttachmentCount'); -late final _FPDFDoc_GetAttachmentCount = _FPDFDoc_GetAttachmentCountPtr.asFunction(); - -/// Experimental API. -/// Add an embedded file with |name| in |document|. If |name| is empty, or if -/// |name| is the name of a existing embedded file in |document|, or if -/// |document|'s embedded file name tree is too deep (i.e. |document| has too -/// many embedded files already), then a new attachment will not be added. -/// -/// document - handle to a document. -/// name - name of the new attachment. -/// -/// Returns a handle to the new attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFDoc_AddAttachment(FPDF_DOCUMENT document, -FPDF_WIDESTRING name, -) { - return _FPDFDoc_AddAttachment(document, -name, -); -} - -late final _FPDFDoc_AddAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_AddAttachment'); -late final _FPDFDoc_AddAttachment = _FPDFDoc_AddAttachmentPtr.asFunction(); - -/// Experimental API. -/// Get the embedded attachment at |index| in |document|. Note that the returned -/// attachment handle is only valid while |document| is open. -/// -/// document - handle to a document. -/// index - the index of the requested embedded file. -/// -/// Returns the handle to the attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFDoc_GetAttachment(FPDF_DOCUMENT document, -int index, -) { - return _FPDFDoc_GetAttachment(document, -index, -); -} - -late final _FPDFDoc_GetAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_GetAttachment'); -late final _FPDFDoc_GetAttachment = _FPDFDoc_GetAttachmentPtr.asFunction(); - -/// Experimental API. -/// Delete the embedded attachment at |index| in |document|. Note that this does -/// not remove the attachment data from the PDF file; it simply removes the -/// file's entry in the embedded files name tree so that it does not appear in -/// the attachment list. This behavior may change in the future. -/// -/// document - handle to a document. -/// index - the index of the embedded file to be deleted. -/// -/// Returns true if successful. -int FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, -int index, -) { - return _FPDFDoc_DeleteAttachment(document, -index, -); -} - -late final _FPDFDoc_DeleteAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFDoc_DeleteAttachment'); -late final _FPDFDoc_DeleteAttachment = _FPDFDoc_DeleteAttachmentPtr.asFunction(); - -/// Experimental API. -/// Get the name of the |attachment| file. |buffer| is only modified if |buflen| -/// is longer than the length of the file name. On errors, |buffer| is unmodified -/// and the returned length is 0. -/// -/// attachment - handle to an attachment. -/// buffer - buffer for holding the file name, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the file name in bytes. -int FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAttachment_GetName(attachment, -buffer, -buflen, -); -} - -late final _FPDFAttachment_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetName'); -late final _FPDFAttachment_GetName = _FPDFAttachment_GetNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Check if the params dictionary of |attachment| has |key| as a key. -/// -/// attachment - handle to an attachment. -/// key - the key to look for, encoded in UTF-8. -/// -/// Returns true if |key| exists. -int FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, -FPDF_BYTESTRING key, -) { - return _FPDFAttachment_HasKey(attachment, -key, -); -} - -late final _FPDFAttachment_HasKeyPtr = _lookup< - ffi.NativeFunction>('FPDFAttachment_HasKey'); -late final _FPDFAttachment_HasKey = _FPDFAttachment_HasKeyPtr.asFunction(); - -/// Experimental API. -/// Get the type of the value corresponding to |key| in the params dictionary of -/// the embedded |attachment|. -/// -/// attachment - handle to an attachment. -/// key - the key to look for, encoded in UTF-8. -/// -/// Returns the type of the dictionary value. -int FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, -FPDF_BYTESTRING key, -) { - return _FPDFAttachment_GetValueType(attachment, -key, -); -} - -late final _FPDFAttachment_GetValueTypePtr = _lookup< - ffi.NativeFunction>('FPDFAttachment_GetValueType'); -late final _FPDFAttachment_GetValueType = _FPDFAttachment_GetValueTypePtr.asFunction(); - -/// Experimental API. -/// Set the string value corresponding to |key| in the params dictionary of the -/// embedded file |attachment|, overwriting the existing value if any. The value -/// type should be FPDF_OBJECT_STRING after this function call succeeds. -/// -/// attachment - handle to an attachment. -/// key - the key to the dictionary entry, encoded in UTF-8. -/// value - the string value to be set, encoded in UTF-16LE. -/// -/// Returns true if successful. -int FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, -FPDF_BYTESTRING key, -FPDF_WIDESTRING value, -) { - return _FPDFAttachment_SetStringValue(attachment, -key, -value, -); -} - -late final _FPDFAttachment_SetStringValuePtr = _lookup< - ffi.NativeFunction>('FPDFAttachment_SetStringValue'); -late final _FPDFAttachment_SetStringValue = _FPDFAttachment_SetStringValuePtr.asFunction(); - -/// Experimental API. -/// Get the string value corresponding to |key| in the params dictionary of the -/// embedded file |attachment|. |buffer| is only modified if |buflen| is longer -/// than the length of the string value. Note that if |key| does not exist in the -/// dictionary or if |key|'s corresponding value in the dictionary is not a -/// string (i.e. the value is not of type FPDF_OBJECT_STRING or -/// FPDF_OBJECT_NAME), then an empty string would be copied to |buffer| and the -/// return value would be 2. On other errors, nothing would be added to |buffer| -/// and the return value would be 0. -/// -/// attachment - handle to an attachment. -/// key - the key to the requested string value, encoded in UTF-8. -/// buffer - buffer for holding the string value encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the dictionary value string in bytes. -int FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAttachment_GetStringValue(attachment, -key, -buffer, -buflen, -); -} - -late final _FPDFAttachment_GetStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetStringValue'); -late final _FPDFAttachment_GetStringValue = _FPDFAttachment_GetStringValuePtr.asFunction , int )>(); - -/// Experimental API. -/// Set the file data of |attachment|, overwriting the existing file data if any. -/// The creation date and checksum will be updated, while all other dictionary -/// entries will be deleted. Note that only contents with |len| smaller than -/// INT_MAX is supported. -/// -/// attachment - handle to an attachment. -/// contents - buffer holding the file data to write to |attachment|. -/// len - length of file data in bytes. -/// -/// Returns true if successful. -int FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, -FPDF_DOCUMENT document, -ffi.Pointer contents, -int len, -) { - return _FPDFAttachment_SetFile(attachment, -document, -contents, -len, -); -} - -late final _FPDFAttachment_SetFilePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_SetFile'); -late final _FPDFAttachment_SetFile = _FPDFAttachment_SetFilePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the file data of |attachment|. -/// When the attachment file data is readable, true is returned, and |out_buflen| -/// is updated to indicate the file data size. |buffer| is only modified if -/// |buflen| is non-null and long enough to contain the entire file data. Callers -/// must check both the return value and the input |buflen| is no less than the -/// returned |out_buflen| before using the data. -/// -/// Otherwise, when the attachment file data is unreadable or when |out_buflen| -/// is null, false is returned and |buffer| and |out_buflen| remain unmodified. -/// -/// attachment - handle to an attachment. -/// buffer - buffer for holding the file data from |attachment|. -/// buflen - length of the buffer in bytes. -/// out_buflen - pointer to the variable that will receive the minimum buffer -/// size to contain the file data of |attachment|. -/// -/// Returns true on success, false otherwise. -int FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDFAttachment_GetFile(attachment, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDFAttachment_GetFilePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDFAttachment_GetFile'); -late final _FPDFAttachment_GetFile = _FPDFAttachment_GetFilePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Get the MIME type (Subtype) of the embedded file |attachment|. |buffer| is -/// only modified if |buflen| is longer than the length of the MIME type string. -/// If the Subtype is not found or if there is no file stream, an empty string -/// would be copied to |buffer| and the return value would be 2. On other errors, -/// nothing would be added to |buffer| and the return value would be 0. -/// -/// attachment - handle to an attachment. -/// buffer - buffer for holding the MIME type string encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the MIME type string in bytes. -int FPDFAttachment_GetSubtype(FPDF_ATTACHMENT attachment, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAttachment_GetSubtype(attachment, -buffer, -buflen, -); -} - -late final _FPDFAttachment_GetSubtypePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAttachment_GetSubtype'); -late final _FPDFAttachment_GetSubtype = _FPDFAttachment_GetSubtypePtr.asFunction , int )>(); - -/// Function: FPDFDOC_InitFormFillEnvironment -/// Initialize form fill environment. -/// Parameters: -/// document - Handle to document from FPDF_LoadDocument(). -/// formInfo - Pointer to a FPDF_FORMFILLINFO structure. -/// Return Value: -/// Handle to the form fill module, or NULL on failure. -/// Comments: -/// This function should be called before any form fill operation. -/// The FPDF_FORMFILLINFO passed in via |formInfo| must remain valid until -/// the returned FPDF_FORMHANDLE is closed. -FPDF_FORMHANDLE FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, -ffi.Pointer formInfo, -) { - return _FPDFDOC_InitFormFillEnvironment(document, -formInfo, -); -} - -late final _FPDFDOC_InitFormFillEnvironmentPtr = _lookup< - ffi.NativeFunction )>>('FPDFDOC_InitFormFillEnvironment'); -late final _FPDFDOC_InitFormFillEnvironment = _FPDFDOC_InitFormFillEnvironmentPtr.asFunction )>(); - -/// Function: FPDFDOC_ExitFormFillEnvironment -/// Take ownership of |hHandle| and exit form fill environment. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// This function is a no-op when |hHandle| is null. -void FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle, -) { - return _FPDFDOC_ExitFormFillEnvironment(hHandle, -); -} - -late final _FPDFDOC_ExitFormFillEnvironmentPtr = _lookup< - ffi.NativeFunction>('FPDFDOC_ExitFormFillEnvironment'); -late final _FPDFDOC_ExitFormFillEnvironment = _FPDFDOC_ExitFormFillEnvironmentPtr.asFunction(); - -/// Function: FORM_OnAfterLoadPage -/// This method is required for implementing all the form related -/// functions. Should be invoked after user successfully loaded a -/// PDF page, and FPDFDOC_InitFormFillEnvironment() has been invoked. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -void FORM_OnAfterLoadPage(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, -) { - return _FORM_OnAfterLoadPage(page, -hHandle, -); -} - -late final _FORM_OnAfterLoadPagePtr = _lookup< - ffi.NativeFunction>('FORM_OnAfterLoadPage'); -late final _FORM_OnAfterLoadPage = _FORM_OnAfterLoadPagePtr.asFunction(); - -/// Function: FORM_OnBeforeClosePage -/// This method is required for implementing all the form related -/// functions. Should be invoked before user closes the PDF page. -/// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -void FORM_OnBeforeClosePage(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, -) { - return _FORM_OnBeforeClosePage(page, -hHandle, -); -} - -late final _FORM_OnBeforeClosePagePtr = _lookup< - ffi.NativeFunction>('FORM_OnBeforeClosePage'); -late final _FORM_OnBeforeClosePage = _FORM_OnBeforeClosePagePtr.asFunction(); - -/// Function: FORM_DoDocumentJSAction -/// This method is required for performing document-level JavaScript -/// actions. It should be invoked after the PDF document has been loaded. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// If there is document-level JavaScript action embedded in the -/// document, this method will execute the JavaScript action. Otherwise, -/// the method will do nothing. -void FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle, -) { - return _FORM_DoDocumentJSAction(hHandle, -); -} - -late final _FORM_DoDocumentJSActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentJSAction'); -late final _FORM_DoDocumentJSAction = _FORM_DoDocumentJSActionPtr.asFunction(); - -/// Function: FORM_DoDocumentOpenAction -/// This method is required for performing open-action when the document -/// is opened. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if there are no open-actions embedded -/// in the document. -void FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle, -) { - return _FORM_DoDocumentOpenAction(hHandle, -); -} - -late final _FORM_DoDocumentOpenActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentOpenAction'); -late final _FORM_DoDocumentOpenAction = _FORM_DoDocumentOpenActionPtr.asFunction(); - -/// Function: FORM_DoDocumentAAction -/// This method is required for performing the document's -/// additional-action. -/// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// aaType - The type of the additional-actions which defined -/// above. -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if there is no document -/// additional-action corresponding to the specified |aaType|. -void FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, -int aaType, -) { - return _FORM_DoDocumentAAction(hHandle, -aaType, -); -} - -late final _FORM_DoDocumentAActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoDocumentAAction'); -late final _FORM_DoDocumentAAction = _FORM_DoDocumentAActionPtr.asFunction(); - -/// Function: FORM_DoPageAAction -/// This method is required for performing the page object's -/// additional-action when opened or closed. -/// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// aaType - The type of the page object's additional-actions -/// which defined above. -/// Return Value: -/// None. -/// Comments: -/// This method will do nothing if no additional-action corresponding -/// to the specified |aaType| exists. -void FORM_DoPageAAction(FPDF_PAGE page, -FPDF_FORMHANDLE hHandle, -int aaType, -) { - return _FORM_DoPageAAction(page, -hHandle, -aaType, -); -} - -late final _FORM_DoPageAActionPtr = _lookup< - ffi.NativeFunction>('FORM_DoPageAAction'); -late final _FORM_DoPageAAction = _FORM_DoPageAActionPtr.asFunction(); - -/// Function: FORM_OnMouseMove -/// Call this member function when the mouse cursor moves. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnMouseMove(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnMouseMovePtr = _lookup< - ffi.NativeFunction>('FORM_OnMouseMove'); -late final _FORM_OnMouseMove = _FORM_OnMouseMovePtr.asFunction(); - -/// Experimental API -/// Function: FORM_OnMouseWheel -/// Call this member function when the user scrolls the mouse wheel. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_coord - Specifies the coordinates of the cursor in PDF user -/// space. -/// delta_x - Specifies the amount of wheel movement on the x-axis, -/// in units of platform-agnostic wheel deltas. Negative -/// values mean left. -/// delta_y - Specifies the amount of wheel movement on the y-axis, -/// in units of platform-agnostic wheel deltas. Negative -/// values mean down. -/// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// For |delta_x| and |delta_y|, the caller must normalize -/// platform-specific wheel deltas. e.g. On Windows, a delta value of 240 -/// for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines -/// WHEEL_DELTA as 120. -int FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -ffi.Pointer page_coord, -int delta_x, -int delta_y, -) { - return _FORM_OnMouseWheel(hHandle, -page, -modifier, -page_coord, -delta_x, -delta_y, -); -} - -late final _FORM_OnMouseWheelPtr = _lookup< - ffi.NativeFunction , ffi.Int , ffi.Int )>>('FORM_OnMouseWheel'); -late final _FORM_OnMouseWheel = _FORM_OnMouseWheelPtr.asFunction , int , int )>(); - -/// Function: FORM_OnFocus -/// This function focuses the form annotation at a given point. If the -/// annotation at the point already has focus, nothing happens. If there -/// is no annotation at the point, removes form focus. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. -/// Return Value: -/// True if there is an annotation at the given point and it has focus. -int FORM_OnFocus(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnFocus(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnFocusPtr = _lookup< - ffi.NativeFunction>('FORM_OnFocus'); -late final _FORM_OnFocus = _FORM_OnFocusPtr.asFunction(); - -/// Function: FORM_OnLButtonDown -/// Call this member function when the user presses the left -/// mouse button. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnLButtonDown(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnLButtonDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonDown'); -late final _FORM_OnLButtonDown = _FORM_OnLButtonDownPtr.asFunction(); - -/// Function: FORM_OnRButtonDown -/// Same as above, execpt for the right mouse button. -/// Comments: -/// At the present time, has no effect except in XFA builds, but is -/// included for the sake of symmetry. -int FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnRButtonDown(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnRButtonDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnRButtonDown'); -late final _FORM_OnRButtonDown = _FORM_OnRButtonDownPtr.asFunction(); - -/// Function: FORM_OnLButtonUp -/// Call this member function when the user releases the left -/// mouse button. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in device. -/// page_y - Specifies the y-coordinate of the cursor in device. -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnLButtonUp(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnLButtonUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonUp'); -late final _FORM_OnLButtonUp = _FORM_OnLButtonUpPtr.asFunction(); - -/// Function: FORM_OnRButtonUp -/// Same as above, execpt for the right mouse button. -/// Comments: -/// At the present time, has no effect except in XFA builds, but is -/// included for the sake of symmetry. -int FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnRButtonUp(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnRButtonUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnRButtonUp'); -late final _FORM_OnRButtonUp = _FORM_OnRButtonUpPtr.asFunction(); - -/// Function: FORM_OnLButtonDoubleClick -/// Call this member function when the user double clicks the -/// left mouse button. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// modifier - Indicates whether various virtual keys are down. -/// page_x - Specifies the x-coordinate of the cursor in PDF user -/// space. -/// page_y - Specifies the y-coordinate of the cursor in PDF user -/// space. -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnLButtonDoubleClick(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int modifier, -double page_x, -double page_y, -) { - return _FORM_OnLButtonDoubleClick(hHandle, -page, -modifier, -page_x, -page_y, -); -} - -late final _FORM_OnLButtonDoubleClickPtr = _lookup< - ffi.NativeFunction>('FORM_OnLButtonDoubleClick'); -late final _FORM_OnLButtonDoubleClick = _FORM_OnLButtonDoubleClickPtr.asFunction(); - -/// Function: FORM_OnKeyDown -/// Call this member function when a nonsystem key is pressed. -/// Parameters: -/// hHandle - Handle to the form fill module, aseturned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nKeyCode - The virtual-key code of the given key (see -/// fpdf_fwlevent.h for virtual key codes). -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nKeyCode, -int modifier, -) { - return _FORM_OnKeyDown(hHandle, -page, -nKeyCode, -modifier, -); -} - -late final _FORM_OnKeyDownPtr = _lookup< - ffi.NativeFunction>('FORM_OnKeyDown'); -late final _FORM_OnKeyDown = _FORM_OnKeyDownPtr.asFunction(); - -/// Function: FORM_OnKeyUp -/// Call this member function when a nonsystem key is released. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nKeyCode - The virtual-key code of the given key (see -/// fpdf_fwlevent.h for virtual key codes). -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// Currently unimplemented and always returns false. PDFium reserves this -/// API and may implement it in the future on an as-needed basis. -int FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nKeyCode, -int modifier, -) { - return _FORM_OnKeyUp(hHandle, -page, -nKeyCode, -modifier, -); -} - -late final _FORM_OnKeyUpPtr = _lookup< - ffi.NativeFunction>('FORM_OnKeyUp'); -late final _FORM_OnKeyUp = _FORM_OnKeyUpPtr.asFunction(); - -/// Function: FORM_OnChar -/// Call this member function when a keystroke translates to a -/// nonsystem character. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// nChar - The character code value itself. -/// modifier - Mask of key flags (see fpdf_fwlevent.h for key -/// flag values). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_OnChar(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int nChar, -int modifier, -) { - return _FORM_OnChar(hHandle, -page, -nChar, -modifier, -); -} - -late final _FORM_OnCharPtr = _lookup< - ffi.NativeFunction>('FORM_OnChar'); -late final _FORM_OnChar = _FORM_OnCharPtr.asFunction(); - -/// Experimental API -/// Function: FORM_GetFocusedText -/// Call this function to obtain the text within the current focused -/// field, if any. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// buffer - Buffer for holding the form text, encoded in -/// UTF-16LE. If NULL, |buffer| is not modified. -/// buflen - Length of |buffer| in bytes. If |buflen| is less -/// than the length of the form text string, |buffer| is -/// not modified. -/// Return Value: -/// Length in bytes for the text in the focused field. -int FORM_GetFocusedText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -) { - return _FORM_GetFocusedText(hHandle, -page, -buffer, -buflen, -); -} - -late final _FORM_GetFocusedTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetFocusedText'); -late final _FORM_GetFocusedText = _FORM_GetFocusedTextPtr.asFunction , int )>(); - -/// Function: FORM_GetSelectedText -/// Call this function to obtain selected text within a form text -/// field or form combobox text field. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// buffer - Buffer for holding the selected text, encoded in -/// UTF-16LE. If NULL, |buffer| is not modified. -/// buflen - Length of |buffer| in bytes. If |buflen| is less -/// than the length of the selected text string, -/// |buffer| is not modified. -/// Return Value: -/// Length in bytes of selected text in form text field or form combobox -/// text field. -int FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -) { - return _FORM_GetSelectedText(hHandle, -page, -buffer, -buflen, -); -} - -late final _FORM_GetSelectedTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FORM_GetSelectedText'); -late final _FORM_GetSelectedText = _FORM_GetSelectedTextPtr.asFunction , int )>(); - -/// Experimental API -/// Function: FORM_ReplaceAndKeepSelection -/// Call this function to replace the selected text in a form -/// text field or user-editable form combobox text field with another -/// text string (which can be empty or non-empty). If there is no -/// selected text, this function will append the replacement text after -/// the current caret position. After the insertion, the inserted text -/// will be selected. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as Returned by FPDF_LoadPage(). -/// wsText - The text to be inserted, in UTF-16LE format. -/// Return Value: -/// None. -void FORM_ReplaceAndKeepSelection(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -FPDF_WIDESTRING wsText, -) { - return _FORM_ReplaceAndKeepSelection(hHandle, -page, -wsText, -); -} - -late final _FORM_ReplaceAndKeepSelectionPtr = _lookup< - ffi.NativeFunction>('FORM_ReplaceAndKeepSelection'); -late final _FORM_ReplaceAndKeepSelection = _FORM_ReplaceAndKeepSelectionPtr.asFunction(); - -/// Function: FORM_ReplaceSelection -/// Call this function to replace the selected text in a form -/// text field or user-editable form combobox text field with another -/// text string (which can be empty or non-empty). If there is no -/// selected text, this function will append the replacement text after -/// the current caret position. After the insertion, the selection range -/// will be set to empty. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as Returned by FPDF_LoadPage(). -/// wsText - The text to be inserted, in UTF-16LE format. -/// Return Value: -/// None. -void FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -FPDF_WIDESTRING wsText, -) { - return _FORM_ReplaceSelection(hHandle, -page, -wsText, -); -} - -late final _FORM_ReplaceSelectionPtr = _lookup< - ffi.NativeFunction>('FORM_ReplaceSelection'); -late final _FORM_ReplaceSelection = _FORM_ReplaceSelectionPtr.asFunction(); - -/// Experimental API -/// Function: FORM_SelectAllText -/// Call this function to select all the text within the currently focused -/// form text field or form combobox text field. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// Whether the operation succeeded or not. -int FORM_SelectAllText(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -) { - return _FORM_SelectAllText(hHandle, -page, -); -} - -late final _FORM_SelectAllTextPtr = _lookup< - ffi.NativeFunction>('FORM_SelectAllText'); -late final _FORM_SelectAllText = _FORM_SelectAllTextPtr.asFunction(); - -/// Function: FORM_CanUndo -/// Find out if it is possible for the current focused widget in a given -/// form to perform an undo operation. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if it is possible to undo. -int FORM_CanUndo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -) { - return _FORM_CanUndo(hHandle, -page, -); -} - -late final _FORM_CanUndoPtr = _lookup< - ffi.NativeFunction>('FORM_CanUndo'); -late final _FORM_CanUndo = _FORM_CanUndoPtr.asFunction(); - -/// Function: FORM_CanRedo -/// Find out if it is possible for the current focused widget in a given -/// form to perform a redo operation. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if it is possible to redo. -int FORM_CanRedo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -) { - return _FORM_CanRedo(hHandle, -page, -); -} - -late final _FORM_CanRedoPtr = _lookup< - ffi.NativeFunction>('FORM_CanRedo'); -late final _FORM_CanRedo = _FORM_CanRedoPtr.asFunction(); - -/// Function: FORM_Undo -/// Make the current focused widget perform an undo operation. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if the undo operation succeeded. -int FORM_Undo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -) { - return _FORM_Undo(hHandle, -page, -); -} - -late final _FORM_UndoPtr = _lookup< - ffi.NativeFunction>('FORM_Undo'); -late final _FORM_Undo = _FORM_UndoPtr.asFunction(); - -/// Function: FORM_Redo -/// Make the current focused widget perform a redo operation. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return Value: -/// True if the redo operation succeeded. -int FORM_Redo(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -) { - return _FORM_Redo(hHandle, -page, -); -} - -late final _FORM_RedoPtr = _lookup< - ffi.NativeFunction>('FORM_Redo'); -late final _FORM_Redo = _FORM_RedoPtr.asFunction(); - -/// Function: FORM_ForceToKillFocus. -/// Call this member function to force to kill the focus of the form -/// field which has focus. If it would kill the focus of a form field, -/// save the value of form field if was changed by theuser. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// True indicates success; otherwise false. -int FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle, -) { - return _FORM_ForceToKillFocus(hHandle, -); -} - -late final _FORM_ForceToKillFocusPtr = _lookup< - ffi.NativeFunction>('FORM_ForceToKillFocus'); -late final _FORM_ForceToKillFocus = _FORM_ForceToKillFocusPtr.asFunction(); - -/// Experimental API. -/// Function: FORM_GetFocusedAnnot. -/// Call this member function to get the currently focused annotation. -/// Parameters: -/// handle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page_index - Buffer to hold the index number of the page which -/// contains the focused annotation. 0 for the first page. -/// Can't be NULL. -/// annot - Buffer to hold the focused annotation. Can't be NULL. -/// Return Value: -/// On success, return true and write to the out parameters. Otherwise -/// return false and leave the out parameters unmodified. -/// Comments: -/// Not currently supported for XFA forms - will report no focused -/// annotation. -/// Must call FPDFPage_CloseAnnot() when the annotation returned in |annot| -/// by this function is no longer needed. -/// This will return true and set |page_index| to -1 and |annot| to NULL, -/// if there is no focused annotation. -int FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle, -ffi.Pointer page_index, -ffi.Pointer annot, -) { - return _FORM_GetFocusedAnnot(handle, -page_index, -annot, -); -} - -late final _FORM_GetFocusedAnnotPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FORM_GetFocusedAnnot'); -late final _FORM_GetFocusedAnnot = _FORM_GetFocusedAnnotPtr.asFunction , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FORM_SetFocusedAnnot. -/// Call this member function to set the currently focused annotation. -/// Parameters: -/// handle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - Handle to an annotation. -/// Return Value: -/// True indicates success; otherwise false. -/// Comments: -/// |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus() -/// instead. -int FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -) { - return _FORM_SetFocusedAnnot(handle, -annot, -); -} - -late final _FORM_SetFocusedAnnotPtr = _lookup< - ffi.NativeFunction>('FORM_SetFocusedAnnot'); -late final _FORM_SetFocusedAnnot = _FORM_SetFocusedAnnotPtr.asFunction(); - -/// Function: FPDFPage_HasFormFieldAtPoint -/// Get the form field type by point. -/// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// page_x - X position in PDF "user space". -/// page_y - Y position in PDF "user space". -/// Return Value: -/// Return the type of the form field; -1 indicates no field. -/// See field types above. -int FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -double page_x, -double page_y, -) { - return _FPDFPage_HasFormFieldAtPoint(hHandle, -page, -page_x, -page_y, -); -} - -late final _FPDFPage_HasFormFieldAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFPage_HasFormFieldAtPoint'); -late final _FPDFPage_HasFormFieldAtPoint = _FPDFPage_HasFormFieldAtPointPtr.asFunction(); - -/// Function: FPDFPage_FormFieldZOrderAtPoint -/// Get the form field z-order by point. -/// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - Handle to the page. Returned by FPDF_LoadPage(). -/// page_x - X position in PDF "user space". -/// page_y - Y position in PDF "user space". -/// Return Value: -/// Return the z-order of the form field; -1 indicates no field. -/// Higher numbers are closer to the front. -int FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -double page_x, -double page_y, -) { - return _FPDFPage_FormFieldZOrderAtPoint(hHandle, -page, -page_x, -page_y, -); -} - -late final _FPDFPage_FormFieldZOrderAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFPage_FormFieldZOrderAtPoint'); -late final _FPDFPage_FormFieldZOrderAtPoint = _FPDFPage_FormFieldZOrderAtPointPtr.asFunction(); - -/// Function: FPDF_SetFormFieldHighlightColor -/// Set the highlight color of the specified (or all) form fields -/// in the document. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// doc - Handle to the document, as returned by -/// FPDF_LoadDocument(). -/// fieldType - A 32-bit integer indicating the type of a form -/// field (defined above). -/// color - The highlight color of the form field. Constructed by -/// 0xxxrrggbb. -/// Return Value: -/// None. -/// Comments: -/// When the parameter fieldType is set to FPDF_FORMFIELD_UNKNOWN, the -/// highlight color will be applied to all the form fields in the -/// document. -/// Please refresh the client window to show the highlight immediately -/// if necessary. -void FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, -int fieldType, -int color, -) { - return _FPDF_SetFormFieldHighlightColor(hHandle, -fieldType, -color, -); -} - -late final _FPDF_SetFormFieldHighlightColorPtr = _lookup< - ffi.NativeFunction>('FPDF_SetFormFieldHighlightColor'); -late final _FPDF_SetFormFieldHighlightColor = _FPDF_SetFormFieldHighlightColorPtr.asFunction(); - -/// Function: FPDF_SetFormFieldHighlightAlpha -/// Set the transparency of the form field highlight color in the -/// document. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// doc - Handle to the document, as returaned by -/// FPDF_LoadDocument(). -/// alpha - The transparency of the form field highlight color, -/// between 0-255. -/// Return Value: -/// None. -void FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, -int alpha, -) { - return _FPDF_SetFormFieldHighlightAlpha(hHandle, -alpha, -); -} - -late final _FPDF_SetFormFieldHighlightAlphaPtr = _lookup< - ffi.NativeFunction>('FPDF_SetFormFieldHighlightAlpha'); -late final _FPDF_SetFormFieldHighlightAlpha = _FPDF_SetFormFieldHighlightAlphaPtr.asFunction(); - -/// Function: FPDF_RemoveFormFieldHighlight -/// Remove the form field highlight color in the document. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// Return Value: -/// None. -/// Comments: -/// Please refresh the client window to remove the highlight immediately -/// if necessary. -void FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle, -) { - return _FPDF_RemoveFormFieldHighlight(hHandle, -); -} - -late final _FPDF_RemoveFormFieldHighlightPtr = _lookup< - ffi.NativeFunction>('FPDF_RemoveFormFieldHighlight'); -late final _FPDF_RemoveFormFieldHighlight = _FPDF_RemoveFormFieldHighlightPtr.asFunction(); - -/// Function: FPDF_FFLDraw -/// Render FormFields and popup window on a page to a device independent -/// bitmap. -/// Parameters: -/// hHandle - Handle to the form fill module, as returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// bitmap - Handle to the device independent bitmap (as the -/// output buffer). Bitmap handles can be created by -/// FPDFBitmap_Create(). -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// start_x - Left pixel position of the display area in the -/// device coordinates. -/// start_y - Top pixel position of the display area in the device -/// coordinates. -/// size_x - Horizontal size (in pixels) for displaying the page. -/// size_y - Vertical size (in pixels) for displaying the page. -/// rotate - Page orientation: 0 (normal), 1 (rotated 90 degrees -/// clockwise), 2 (rotated 180 degrees), 3 (rotated 90 -/// degrees counter-clockwise). -/// flags - 0 for normal display, or combination of flags -/// defined above. -/// Return Value: -/// None. -/// Comments: -/// This function is designed to render annotations that are -/// user-interactive, which are widget annotations (for FormFields) and -/// popup annotations. -/// With the FPDF_ANNOT flag, this function will render a popup annotation -/// when users mouse-hover on a non-widget annotation. Regardless of -/// FPDF_ANNOT flag, this function will always render widget annotations -/// for FormFields. -/// In order to implement the FormFill functions, implementation should -/// call this function after rendering functions, such as -/// FPDF_RenderPageBitmap() or FPDF_RenderPageBitmap_Start(), have -/// finished rendering the page contents. -void FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, -FPDF_BITMAP bitmap, -FPDF_PAGE page, -int start_x, -int start_y, -int size_x, -int size_y, -int rotate, -int flags, -) { - return _FPDF_FFLDraw(hHandle, -bitmap, -page, -start_x, -start_y, -size_x, -size_y, -rotate, -flags, -); -} - -late final _FPDF_FFLDrawPtr = _lookup< - ffi.NativeFunction>('FPDF_FFLDraw'); -late final _FPDF_FFLDraw = _FPDF_FFLDrawPtr.asFunction(); - -/// Experimental API -/// Function: FPDF_GetFormType -/// Returns the type of form contained in the PDF document. -/// Parameters: -/// document - Handle to document. -/// Return Value: -/// Integer value representing one of the FORMTYPE_ values. -/// Comments: -/// If |document| is NULL, then the return value is FORMTYPE_NONE. -int FPDF_GetFormType(FPDF_DOCUMENT document, -) { - return _FPDF_GetFormType(document, -); -} - -late final _FPDF_GetFormTypePtr = _lookup< - ffi.NativeFunction>('FPDF_GetFormType'); -late final _FPDF_GetFormType = _FPDF_GetFormTypePtr.asFunction(); - -/// Experimental API -/// Function: FORM_SetIndexSelected -/// Selects/deselects the value at the given |index| of the focused -/// annotation. -/// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// page - Handle to the page. Returned by FPDF_LoadPage -/// index - 0-based index of value to be set as -/// selected/unselected -/// selected - true to select, false to deselect -/// Return Value: -/// TRUE if the operation succeeded. -/// FALSE if the operation failed or widget is not a supported type. -/// Comments: -/// Intended for use with listbox/combobox widget types. Comboboxes -/// have at most a single value selected at a time which cannot be -/// deselected. Deselect on a combobox is a no-op that returns false. -/// Default implementation is a no-op that will return false for -/// other types. -/// Not currently supported for XFA forms - will return false. -int FORM_SetIndexSelected(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int index, -int selected, -) { - return _FORM_SetIndexSelected(hHandle, -page, -index, -selected, -); -} - -late final _FORM_SetIndexSelectedPtr = _lookup< - ffi.NativeFunction>('FORM_SetIndexSelected'); -late final _FORM_SetIndexSelected = _FORM_SetIndexSelectedPtr.asFunction(); - -/// Experimental API -/// Function: FORM_IsIndexSelected -/// Returns whether or not the value at |index| of the focused -/// annotation is currently selected. -/// Parameters: -/// hHandle - Handle to the form fill module. Returned by -/// FPDFDOC_InitFormFillEnvironment. -/// page - Handle to the page. Returned by FPDF_LoadPage -/// index - 0-based Index of value to check -/// Return Value: -/// TRUE if value at |index| is currently selected. -/// FALSE if value at |index| is not selected or widget is not a -/// supported type. -/// Comments: -/// Intended for use with listbox/combobox widget types. Default -/// implementation is a no-op that will return false for other types. -/// Not currently supported for XFA forms - will return false. -int FORM_IsIndexSelected(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -int index, -) { - return _FORM_IsIndexSelected(hHandle, -page, -index, -); -} - -late final _FORM_IsIndexSelectedPtr = _lookup< - ffi.NativeFunction>('FORM_IsIndexSelected'); -late final _FORM_IsIndexSelected = _FORM_IsIndexSelectedPtr.asFunction(); - -/// Function: FPDF_LoadXFA -/// If the document consists of XFA fields, call this method to -/// attempt to load XFA fields. -/// Parameters: -/// document - Handle to document from FPDF_LoadDocument(). -/// Return Value: -/// TRUE upon success, otherwise FALSE. If XFA support is not built -/// into PDFium, performs no action and always returns FALSE. -int FPDF_LoadXFA(FPDF_DOCUMENT document, -) { - return _FPDF_LoadXFA(document, -); -} - -late final _FPDF_LoadXFAPtr = _lookup< - ffi.NativeFunction>('FPDF_LoadXFA'); -late final _FPDF_LoadXFA = _FPDF_LoadXFAPtr.asFunction(); - -/// Experimental API. -/// Check if an annotation subtype is currently supported for creation. -/// Currently supported subtypes: -/// - circle -/// - fileattachment -/// - freetext -/// - highlight -/// - ink -/// - link -/// - popup -/// - square, -/// - squiggly -/// - stamp -/// - strikeout -/// - text -/// - underline -/// -/// subtype - the subtype to be checked. -/// -/// Returns true if this subtype supported. -int FPDFAnnot_IsSupportedSubtype(int subtype, -) { - return _FPDFAnnot_IsSupportedSubtype(subtype, -); -} - -late final _FPDFAnnot_IsSupportedSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsSupportedSubtype'); -late final _FPDFAnnot_IsSupportedSubtype = _FPDFAnnot_IsSupportedSubtypePtr.asFunction(); - -/// Experimental API. -/// Create an annotation in |page| of the subtype |subtype|. If the specified -/// subtype is illegal or unsupported, then a new annotation will not be created. -/// Must call FPDFPage_CloseAnnot() when the annotation returned by this -/// function is no longer needed. -/// -/// page - handle to a page. -/// subtype - the subtype of the new annotation. -/// -/// Returns a handle to the new annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFPage_CreateAnnot(FPDF_PAGE page, -int subtype, -) { - return _FPDFPage_CreateAnnot(page, -subtype, -); -} - -late final _FPDFPage_CreateAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CreateAnnot'); -late final _FPDFPage_CreateAnnot = _FPDFPage_CreateAnnotPtr.asFunction(); - -/// Experimental API. -/// Get the number of annotations in |page|. -/// -/// page - handle to a page. -/// -/// Returns the number of annotations in |page|. -int FPDFPage_GetAnnotCount(FPDF_PAGE page, -) { - return _FPDFPage_GetAnnotCount(page, -); -} - -late final _FPDFPage_GetAnnotCountPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnotCount'); -late final _FPDFPage_GetAnnotCount = _FPDFPage_GetAnnotCountPtr.asFunction(); - -/// Experimental API. -/// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the -/// annotation returned by this function is no longer needed. -/// -/// page - handle to a page. -/// index - the index of the annotation. -/// -/// Returns a handle to the annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFPage_GetAnnot(FPDF_PAGE page, -int index, -) { - return _FPDFPage_GetAnnot(page, -index, -); -} - -late final _FPDFPage_GetAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnot'); -late final _FPDFPage_GetAnnot = _FPDFPage_GetAnnotPtr.asFunction(); - -/// Experimental API. -/// Get the index of |annot| in |page|. This is the opposite of -/// FPDFPage_GetAnnot(). -/// -/// page - handle to the page that the annotation is on. -/// annot - handle to an annotation. -/// -/// Returns the index of |annot|, or -1 on failure. -int FPDFPage_GetAnnotIndex(FPDF_PAGE page, -FPDF_ANNOTATION annot, -) { - return _FPDFPage_GetAnnotIndex(page, -annot, -); -} - -late final _FPDFPage_GetAnnotIndexPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetAnnotIndex'); -late final _FPDFPage_GetAnnotIndex = _FPDFPage_GetAnnotIndexPtr.asFunction(); - -/// Experimental API. -/// Close an annotation. Must be called when the annotation returned by -/// FPDFPage_CreateAnnot() or FPDFPage_GetAnnot() is no longer needed. This -/// function does not remove the annotation from the document. -/// -/// annot - handle to an annotation. -void FPDFPage_CloseAnnot(FPDF_ANNOTATION annot, -) { - return _FPDFPage_CloseAnnot(annot, -); -} - -late final _FPDFPage_CloseAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_CloseAnnot'); -late final _FPDFPage_CloseAnnot = _FPDFPage_CloseAnnotPtr.asFunction(); - -/// Experimental API. -/// Remove the annotation in |page| at |index|. -/// -/// page - handle to a page. -/// index - the index of the annotation. -/// -/// Returns true if successful. -int FPDFPage_RemoveAnnot(FPDF_PAGE page, -int index, -) { - return _FPDFPage_RemoveAnnot(page, -index, -); -} - -late final _FPDFPage_RemoveAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFPage_RemoveAnnot'); -late final _FPDFPage_RemoveAnnot = _FPDFPage_RemoveAnnotPtr.asFunction(); - -/// Experimental API. -/// Get the subtype of an annotation. -/// -/// annot - handle to an annotation. -/// -/// Returns the annotation subtype. -int FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetSubtype(annot, -); -} - -late final _FPDFAnnot_GetSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetSubtype'); -late final _FPDFAnnot_GetSubtype = _FPDFAnnot_GetSubtypePtr.asFunction(); - -/// Experimental API. -/// Check if an annotation subtype is currently supported for object extraction, -/// update, and removal. -/// Currently supported subtypes: ink and stamp. -/// -/// subtype - the subtype to be checked. -/// -/// Returns true if this subtype supported. -int FPDFAnnot_IsObjectSupportedSubtype(int subtype, -) { - return _FPDFAnnot_IsObjectSupportedSubtype(subtype, -); -} - -late final _FPDFAnnot_IsObjectSupportedSubtypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsObjectSupportedSubtype'); -late final _FPDFAnnot_IsObjectSupportedSubtype = _FPDFAnnot_IsObjectSupportedSubtypePtr.asFunction(); - -/// Experimental API. -/// Update |obj| in |annot|. |obj| must be in |annot| already and must have -/// been retrieved by FPDFAnnot_GetObject(). Currently, only ink and stamp -/// annotations are supported by this API. Also note that only path, image, and -/// text objects have APIs for modification; see FPDFPath_*(), FPDFText_*(), and -/// FPDFImageObj_*(). -/// -/// annot - handle to an annotation. -/// obj - handle to the object that |annot| needs to update. -/// -/// Return true if successful. -int FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, -FPDF_PAGEOBJECT obj, -) { - return _FPDFAnnot_UpdateObject(annot, -obj, -); -} - -late final _FPDFAnnot_UpdateObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_UpdateObject'); -late final _FPDFAnnot_UpdateObject = _FPDFAnnot_UpdateObjectPtr.asFunction(); - -/// Experimental API. -/// Add a new InkStroke, represented by an array of points, to the InkList of -/// |annot|. The API creates an InkList if one doesn't already exist in |annot|. -/// This API works only for ink annotations. Please refer to ISO 32000-1:2008 -/// spec, section 12.5.6.13. -/// -/// annot - handle to an annotation. -/// points - pointer to a FS_POINTF array representing input points. -/// point_count - number of elements in |points| array. This should not exceed -/// the maximum value that can be represented by an int32_t). -/// -/// Returns the 0-based index at which the new InkStroke is added in the InkList -/// of the |annot|. Returns -1 on failure. -int FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot, -ffi.Pointer points, -int point_count, -) { - return _FPDFAnnot_AddInkStroke(annot, -points, -point_count, -); -} - -late final _FPDFAnnot_AddInkStrokePtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_AddInkStroke'); -late final _FPDFAnnot_AddInkStroke = _FPDFAnnot_AddInkStrokePtr.asFunction , int )>(); - -/// Experimental API. -/// Removes an InkList in |annot|. -/// This API works only for ink annotations. -/// -/// annot - handle to an annotation. -/// -/// Return true on successful removal of /InkList entry from context of the -/// non-null ink |annot|. Returns false on failure. -int FPDFAnnot_RemoveInkList(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_RemoveInkList(annot, -); -} - -late final _FPDFAnnot_RemoveInkListPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_RemoveInkList'); -late final _FPDFAnnot_RemoveInkList = _FPDFAnnot_RemoveInkListPtr.asFunction(); - -/// Experimental API. -/// Add |obj| to |annot|. |obj| must have been created by -/// FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and -/// will be owned by |annot|. Note that an |obj| cannot belong to more than one -/// |annot|. Currently, only ink and stamp annotations are supported by this API. -/// Also note that only path, image, and text objects have APIs for creation. -/// -/// annot - handle to an annotation. -/// obj - handle to the object that is to be added to |annot|. -/// -/// Return true if successful. -int FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, -FPDF_PAGEOBJECT obj, -) { - return _FPDFAnnot_AppendObject(annot, -obj, -); -} - -late final _FPDFAnnot_AppendObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_AppendObject'); -late final _FPDFAnnot_AppendObject = _FPDFAnnot_AppendObjectPtr.asFunction(); - -/// Experimental API. -/// Get the total number of objects in |annot|, including path objects, text -/// objects, external objects, image objects, and shading objects. -/// -/// annot - handle to an annotation. -/// -/// Returns the number of objects in |annot|. -int FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetObjectCount(annot, -); -} - -late final _FPDFAnnot_GetObjectCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetObjectCount'); -late final _FPDFAnnot_GetObjectCount = _FPDFAnnot_GetObjectCountPtr.asFunction(); - -/// Experimental API. -/// Get the object in |annot| at |index|. -/// -/// annot - handle to an annotation. -/// index - the index of the object. -/// -/// Return a handle to the object, or NULL on failure. -FPDF_PAGEOBJECT FPDFAnnot_GetObject(FPDF_ANNOTATION annot, -int index, -) { - return _FPDFAnnot_GetObject(annot, -index, -); -} - -late final _FPDFAnnot_GetObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetObject'); -late final _FPDFAnnot_GetObject = _FPDFAnnot_GetObjectPtr.asFunction(); - -/// Experimental API. -/// Remove the object in |annot| at |index|. -/// -/// annot - handle to an annotation. -/// index - the index of the object to be removed. -/// -/// Return true if successful. -int FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, -int index, -) { - return _FPDFAnnot_RemoveObject(annot, -index, -); -} - -late final _FPDFAnnot_RemoveObjectPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_RemoveObject'); -late final _FPDFAnnot_RemoveObject = _FPDFAnnot_RemoveObjectPtr.asFunction(); - -/// Experimental API. -/// Set the color of an annotation. Fails when called on annotations with -/// appearance streams already defined; instead use -/// FPDFPageObj_Set{Stroke|Fill}Color(). -/// -/// annot - handle to an annotation. -/// type - type of the color to be set. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. -/// A - buffer to hold the opacity. Ranges from 0 to 255. -/// -/// Returns true if successful. -DartFPDF_BOOL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, -FPDFANNOT_COLORTYPE type, -int R, -int G, -int B, -int A, -) { - return _FPDFAnnot_SetColor(annot, -type.value, -R, -G, -B, -A, -); -} - -late final _FPDFAnnot_SetColorPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetColor'); -late final _FPDFAnnot_SetColor = _FPDFAnnot_SetColorPtr.asFunction(); - -/// Experimental API. -/// Get the color of an annotation. If no color is specified, default to yellow -/// for highlight annotation, black for all else. Fails when called on -/// annotations with appearance streams already defined; instead use -/// FPDFPageObj_Get{Stroke|Fill}Color(). -/// -/// annot - handle to an annotation. -/// type - type of the color requested. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. -/// A - buffer to hold the opacity. Ranges from 0 to 255. -/// -/// Returns true if successful. -DartFPDF_BOOL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, -FPDFANNOT_COLORTYPE type, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -ffi.Pointer A, -) { - return _FPDFAnnot_GetColor(annot, -type.value, -R, -G, -B, -A, -); -} - -late final _FPDFAnnot_GetColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetColor'); -late final _FPDFAnnot_GetColor = _FPDFAnnot_GetColorPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Check if the annotation is of a type that has attachment points -/// (i.e. quadpoints). Quadpoints are the vertices of the rectangle that -/// encompasses the texts affected by the annotation. They provide the -/// coordinates in the page where the annotation is attached. Only text markup -/// annotations (i.e. highlight, strikeout, squiggly, and underline) and link -/// annotations have quadpoints. -/// -/// annot - handle to an annotation. -/// -/// Returns true if the annotation is of a type that has quadpoints, false -/// otherwise. -int FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_HasAttachmentPoints(annot, -); -} - -late final _FPDFAnnot_HasAttachmentPointsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_HasAttachmentPoints'); -late final _FPDFAnnot_HasAttachmentPoints = _FPDFAnnot_HasAttachmentPointsPtr.asFunction(); - -/// Experimental API. -/// Replace the attachment points (i.e. quadpoints) set of an annotation at -/// |quad_index|. This index needs to be within the result of -/// FPDFAnnot_CountAttachmentPoints(). -/// If the annotation's appearance stream is defined and this annotation is of a -/// type with quadpoints, then update the bounding box too if the new quadpoints -/// define a bigger one. -/// -/// annot - handle to an annotation. -/// quad_index - index of the set of quadpoints. -/// quad_points - the quadpoints to be set. -/// -/// Returns true if successful. -int FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, -int quad_index, -ffi.Pointer quad_points, -) { - return _FPDFAnnot_SetAttachmentPoints(annot, -quad_index, -quad_points, -); -} - -late final _FPDFAnnot_SetAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetAttachmentPoints'); -late final _FPDFAnnot_SetAttachmentPoints = _FPDFAnnot_SetAttachmentPointsPtr.asFunction )>(); - -/// Experimental API. -/// Append to the list of attachment points (i.e. quadpoints) of an annotation. -/// If the annotation's appearance stream is defined and this annotation is of a -/// type with quadpoints, then update the bounding box too if the new quadpoints -/// define a bigger one. -/// -/// annot - handle to an annotation. -/// quad_points - the quadpoints to be set. -/// -/// Returns true if successful. -int FPDFAnnot_AppendAttachmentPoints(FPDF_ANNOTATION annot, -ffi.Pointer quad_points, -) { - return _FPDFAnnot_AppendAttachmentPoints(annot, -quad_points, -); -} - -late final _FPDFAnnot_AppendAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_AppendAttachmentPoints'); -late final _FPDFAnnot_AppendAttachmentPoints = _FPDFAnnot_AppendAttachmentPointsPtr.asFunction )>(); - -/// Experimental API. -/// Get the number of sets of quadpoints of an annotation. -/// -/// annot - handle to an annotation. -/// -/// Returns the number of sets of quadpoints, or 0 on failure. -int FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_CountAttachmentPoints(annot, -); -} - -late final _FPDFAnnot_CountAttachmentPointsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_CountAttachmentPoints'); -late final _FPDFAnnot_CountAttachmentPoints = _FPDFAnnot_CountAttachmentPointsPtr.asFunction(); - -/// Experimental API. -/// Get the attachment points (i.e. quadpoints) of an annotation. -/// -/// annot - handle to an annotation. -/// quad_index - index of the set of quadpoints. -/// quad_points - receives the quadpoints; must not be NULL. -/// -/// Returns true if successful. -int FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, -int quad_index, -ffi.Pointer quad_points, -) { - return _FPDFAnnot_GetAttachmentPoints(annot, -quad_index, -quad_points, -); -} - -late final _FPDFAnnot_GetAttachmentPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetAttachmentPoints'); -late final _FPDFAnnot_GetAttachmentPoints = _FPDFAnnot_GetAttachmentPointsPtr.asFunction )>(); - -/// Experimental API. -/// Set the annotation rectangle defining the location of the annotation. If the -/// annotation's appearance stream is defined and this annotation is of a type -/// without quadpoints, then update the bounding box too if the new rectangle -/// defines a bigger one. -/// -/// annot - handle to an annotation. -/// rect - the annotation rectangle to be set. -/// -/// Returns true if successful. -int FPDFAnnot_SetRect(FPDF_ANNOTATION annot, -ffi.Pointer rect, -) { - return _FPDFAnnot_SetRect(annot, -rect, -); -} - -late final _FPDFAnnot_SetRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetRect'); -late final _FPDFAnnot_SetRect = _FPDFAnnot_SetRectPtr.asFunction )>(); - -/// Experimental API. -/// Get the annotation rectangle defining the location of the annotation. -/// -/// annot - handle to an annotation. -/// rect - receives the rectangle; must not be NULL. -/// -/// Returns true if successful. -int FPDFAnnot_GetRect(FPDF_ANNOTATION annot, -ffi.Pointer rect, -) { - return _FPDFAnnot_GetRect(annot, -rect, -); -} - -late final _FPDFAnnot_GetRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetRect'); -late final _FPDFAnnot_GetRect = _FPDFAnnot_GetRectPtr.asFunction )>(); - -/// Experimental API. -/// Get the vertices of a polygon or polyline annotation. |buffer| is an array of -/// points of the annotation. If |length| is less than the returned length, or -/// |annot| or |buffer| is NULL, |buffer| will not be modified. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// buffer - buffer for holding the points. -/// length - length of the buffer in points. -/// -/// Returns the number of points if the annotation is of type polygon or -/// polyline, 0 otherwise. -int FPDFAnnot_GetVertices(FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int length, -) { - return _FPDFAnnot_GetVertices(annot, -buffer, -length, -); -} - -late final _FPDFAnnot_GetVerticesPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetVertices'); -late final _FPDFAnnot_GetVertices = _FPDFAnnot_GetVerticesPtr.asFunction , int )>(); - -/// Experimental API. -/// Get the number of paths in the ink list of an ink annotation. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// -/// Returns the number of paths in the ink list if the annotation is of type ink, -/// 0 otherwise. -int FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetInkListCount(annot, -); -} - -late final _FPDFAnnot_GetInkListCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetInkListCount'); -late final _FPDFAnnot_GetInkListCount = _FPDFAnnot_GetInkListCountPtr.asFunction(); - -/// Experimental API. -/// Get a path in the ink list of an ink annotation. |buffer| is an array of -/// points of the path. If |length| is less than the returned length, or |annot| -/// or |buffer| is NULL, |buffer| will not be modified. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// path_index - index of the path -/// buffer - buffer for holding the points. -/// length - length of the buffer in points. -/// -/// Returns the number of points of the path if the annotation is of type ink, 0 -/// otherwise. -int FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot, -int path_index, -ffi.Pointer buffer, -int length, -) { - return _FPDFAnnot_GetInkListPath(annot, -path_index, -buffer, -length, -); -} - -late final _FPDFAnnot_GetInkListPathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetInkListPath'); -late final _FPDFAnnot_GetInkListPath = _FPDFAnnot_GetInkListPathPtr.asFunction , int )>(); - -/// Experimental API. -/// Get the starting and ending coordinates of a line annotation. -/// -/// annot - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot() -/// start - starting point -/// end - ending point -/// -/// Returns true if the annotation is of type line, |start| and |end| are not -/// NULL, false otherwise. -int FPDFAnnot_GetLine(FPDF_ANNOTATION annot, -ffi.Pointer start, -ffi.Pointer end, -) { - return _FPDFAnnot_GetLine(annot, -start, -end, -); -} - -late final _FPDFAnnot_GetLinePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFAnnot_GetLine'); -late final _FPDFAnnot_GetLine = _FPDFAnnot_GetLinePtr.asFunction , ffi.Pointer )>(); - -/// Experimental API. -/// Set the characteristics of the annotation's border (rounded rectangle). -/// -/// annot - handle to an annotation -/// horizontal_radius - horizontal corner radius, in default user space units -/// vertical_radius - vertical corner radius, in default user space units -/// border_width - border width, in default user space units -/// -/// Returns true if setting the border for |annot| succeeds, false otherwise. -/// -/// If |annot| contains an appearance stream that overrides the border values, -/// then the appearance stream will be removed on success. -int FPDFAnnot_SetBorder(FPDF_ANNOTATION annot, -double horizontal_radius, -double vertical_radius, -double border_width, -) { - return _FPDFAnnot_SetBorder(annot, -horizontal_radius, -vertical_radius, -border_width, -); -} - -late final _FPDFAnnot_SetBorderPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetBorder'); -late final _FPDFAnnot_SetBorder = _FPDFAnnot_SetBorderPtr.asFunction(); - -/// Experimental API. -/// Get the characteristics of the annotation's border (rounded rectangle). -/// -/// annot - handle to an annotation -/// horizontal_radius - horizontal corner radius, in default user space units -/// vertical_radius - vertical corner radius, in default user space units -/// border_width - border width, in default user space units -/// -/// Returns true if |horizontal_radius|, |vertical_radius| and |border_width| are -/// not NULL, false otherwise. -int FPDFAnnot_GetBorder(FPDF_ANNOTATION annot, -ffi.Pointer horizontal_radius, -ffi.Pointer vertical_radius, -ffi.Pointer border_width, -) { - return _FPDFAnnot_GetBorder(annot, -horizontal_radius, -vertical_radius, -border_width, -); -} - -late final _FPDFAnnot_GetBorderPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetBorder'); -late final _FPDFAnnot_GetBorder = _FPDFAnnot_GetBorderPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Get the JavaScript of an event of the annotation's additional actions. -/// |buffer| is only modified if |buflen| is large enough to hold the whole -/// JavaScript string. If |buflen| is smaller, the total size of the JavaScript -/// is still returned, but nothing is copied. If there is no JavaScript for -/// |event| in |annot|, an empty string is written to |buf| and 2 is returned, -/// denoting the size of the null terminator in the buffer. On other errors, -/// nothing is written to |buffer| and 0 is returned. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// event - event type, one of the FPDF_ANNOT_AACTION_* values. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes, including the 2-byte -/// null terminator. -int FPDFAnnot_GetFormAdditionalActionJavaScript(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -int event, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetFormAdditionalActionJavaScript(hHandle, -annot, -event, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormAdditionalActionJavaScript'); -late final _FPDFAnnot_GetFormAdditionalActionJavaScript = _FPDFAnnot_GetFormAdditionalActionJavaScriptPtr.asFunction , int )>(); - -/// Experimental API. -/// Check if |annot|'s dictionary has |key| as a key. -/// -/// annot - handle to an annotation. -/// key - the key to look for, encoded in UTF-8. -/// -/// Returns true if |key| exists. -int FPDFAnnot_HasKey(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -) { - return _FPDFAnnot_HasKey(annot, -key, -); -} - -late final _FPDFAnnot_HasKeyPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_HasKey'); -late final _FPDFAnnot_HasKey = _FPDFAnnot_HasKeyPtr.asFunction(); - -/// Experimental API. -/// Get the type of the value corresponding to |key| in |annot|'s dictionary. -/// -/// annot - handle to an annotation. -/// key - the key to look for, encoded in UTF-8. -/// -/// Returns the type of the dictionary value. -int FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -) { - return _FPDFAnnot_GetValueType(annot, -key, -); -} - -late final _FPDFAnnot_GetValueTypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetValueType'); -late final _FPDFAnnot_GetValueType = _FPDFAnnot_GetValueTypePtr.asFunction(); - -/// Experimental API. -/// Set the string value corresponding to |key| in |annot|'s dictionary, -/// overwriting the existing value if any. The value type would be -/// FPDF_OBJECT_STRING after this function call succeeds. -/// -/// annot - handle to an annotation. -/// key - the key to the dictionary entry to be set, encoded in UTF-8. -/// value - the string value to be set, encoded in UTF-16LE. -/// -/// Returns true if successful. -int FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -FPDF_WIDESTRING value, -) { - return _FPDFAnnot_SetStringValue(annot, -key, -value, -); -} - -late final _FPDFAnnot_SetStringValuePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetStringValue'); -late final _FPDFAnnot_SetStringValue = _FPDFAnnot_SetStringValuePtr.asFunction(); - -/// Experimental API. -/// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| -/// is only modified if |buflen| is longer than the length of contents. Note that -/// if |key| does not exist in the dictionary or if |key|'s corresponding value -/// in the dictionary is not a string (i.e. the value is not of type -/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied -/// to |buffer| and the return value would be 2. On other errors, nothing would -/// be added to |buffer| and the return value would be 0. -/// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetStringValue(annot, -key, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetStringValue'); -late final _FPDFAnnot_GetStringValue = _FPDFAnnot_GetStringValuePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the float value corresponding to |key| in |annot|'s dictionary. Writes -/// value to |value| and returns True if |key| exists in the dictionary and -/// |key|'s corresponding value is a number (FPDF_OBJECT_NUMBER), False -/// otherwise. -/// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. -/// value - receives the value, must not be NULL. -/// -/// Returns True if value found, False otherwise. -int FPDFAnnot_GetNumberValue(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -ffi.Pointer value, -) { - return _FPDFAnnot_GetNumberValue(annot, -key, -value, -); -} - -late final _FPDFAnnot_GetNumberValuePtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetNumberValue'); -late final _FPDFAnnot_GetNumberValue = _FPDFAnnot_GetNumberValuePtr.asFunction )>(); - -/// Experimental API. -/// Set the AP (appearance string) in |annot|'s dictionary for a given -/// |appearanceMode|. -/// -/// annot - handle to an annotation. -/// appearanceMode - the appearance mode (normal, rollover or down) for which -/// to get the AP. -/// value - the string value to be set, encoded in UTF-16LE. If -/// nullptr is passed, the AP is cleared for that mode. If the -/// mode is Normal, APs for all modes are cleared. -/// -/// Returns true if successful. -int FPDFAnnot_SetAP(FPDF_ANNOTATION annot, -int appearanceMode, -FPDF_WIDESTRING value, -) { - return _FPDFAnnot_SetAP(annot, -appearanceMode, -value, -); -} - -late final _FPDFAnnot_SetAPPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetAP'); -late final _FPDFAnnot_SetAP = _FPDFAnnot_SetAPPtr.asFunction(); - -/// Experimental API. -/// Get the AP (appearance string) from |annot|'s dictionary for a given -/// |appearanceMode|. -/// |buffer| is only modified if |buflen| is large enough to hold the whole AP -/// string. If |buflen| is smaller, the total size of the AP is still returned, -/// but nothing is copied. -/// If there is no appearance stream for |annot| in |appearanceMode|, an empty -/// string is written to |buf| and 2 is returned. -/// On other errors, nothing is written to |buffer| and 0 is returned. -/// -/// annot - handle to an annotation. -/// appearanceMode - the appearance mode (normal, rollover or down) for which -/// to get the AP. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetAP(FPDF_ANNOTATION annot, -int appearanceMode, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetAP(annot, -appearanceMode, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetAPPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetAP'); -late final _FPDFAnnot_GetAP = _FPDFAnnot_GetAPPtr.asFunction , int )>(); - -/// Experimental API. -/// Get the annotation corresponding to |key| in |annot|'s dictionary. Common -/// keys for linking annotations include "IRT" and "Popup". Must call -/// FPDFPage_CloseAnnot() when the annotation returned by this function is no -/// longer needed. -/// -/// annot - handle to an annotation. -/// key - the key to the requested dictionary entry, encoded in UTF-8. -/// -/// Returns a handle to the linked annotation object, or NULL on failure. -FPDF_ANNOTATION FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, -FPDF_BYTESTRING key, -) { - return _FPDFAnnot_GetLinkedAnnot(annot, -key, -); -} - -late final _FPDFAnnot_GetLinkedAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetLinkedAnnot'); -late final _FPDFAnnot_GetLinkedAnnot = _FPDFAnnot_GetLinkedAnnotPtr.asFunction(); - -/// Experimental API. -/// Get the annotation flags of |annot|. -/// -/// annot - handle to an annotation. -/// -/// Returns the annotation flags. -int FPDFAnnot_GetFlags(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFlags(annot, -); -} - -late final _FPDFAnnot_GetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFlags'); -late final _FPDFAnnot_GetFlags = _FPDFAnnot_GetFlagsPtr.asFunction(); - -/// Experimental API. -/// Set the |annot|'s flags to be of the value |flags|. -/// -/// annot - handle to an annotation. -/// flags - the flag values to be set. -/// -/// Returns true if successful. -int FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, -int flags, -) { - return _FPDFAnnot_SetFlags(annot, -flags, -); -} - -late final _FPDFAnnot_SetFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFlags'); -late final _FPDFAnnot_SetFlags = _FPDFAnnot_SetFlagsPtr.asFunction(); - -/// Experimental API. -/// Get the annotation flags of |annot|. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// -/// Returns the annotation flags specific to interactive forms. -int FPDFAnnot_GetFormFieldFlags(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFormFieldFlags(handle, -annot, -); -} - -late final _FPDFAnnot_GetFormFieldFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormFieldFlags'); -late final _FPDFAnnot_GetFormFieldFlags = _FPDFAnnot_GetFormFieldFlagsPtr.asFunction(); - -/// Experimental API. -/// Sets the form field flags for an interactive form annotation. -/// -/// handle - the handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// flags - the form field flags to be set. -/// -/// Returns true if successful. -int FPDFAnnot_SetFormFieldFlags(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int flags, -) { - return _FPDFAnnot_SetFormFieldFlags(handle, -annot, -flags, -); -} - -late final _FPDFAnnot_SetFormFieldFlagsPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFormFieldFlags'); -late final _FPDFAnnot_SetFormFieldFlags = _FPDFAnnot_SetFormFieldFlagsPtr.asFunction(); - -/// Experimental API. -/// Retrieves an interactive form annotation whose rectangle contains a given -/// point on a page. Must call FPDFPage_CloseAnnot() when the annotation returned -/// is no longer needed. -/// -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// page - handle to the page, returned by FPDF_LoadPage function. -/// point - position in PDF "user space". -/// -/// Returns the interactive form annotation whose rectangle contains the given -/// coordinates on the page. If there is no such annotation, return NULL. -FPDF_ANNOTATION FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, -FPDF_PAGE page, -ffi.Pointer point, -) { - return _FPDFAnnot_GetFormFieldAtPoint(hHandle, -page, -point, -); -} - -late final _FPDFAnnot_GetFormFieldAtPointPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetFormFieldAtPoint'); -late final _FPDFAnnot_GetFormFieldAtPoint = _FPDFAnnot_GetFormFieldAtPointPtr.asFunction )>(); - -/// Experimental API. -/// Gets the name of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the name string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldName(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetFormFieldName(hHandle, -annot, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetFormFieldNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldName'); -late final _FPDFAnnot_GetFormFieldName = _FPDFAnnot_GetFormFieldNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Gets the alternate name of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the alternate name string, encoded in -/// UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldAlternateName(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetFormFieldAlternateName(hHandle, -annot, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetFormFieldAlternateNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldAlternateName'); -late final _FPDFAnnot_GetFormFieldAlternateName = _FPDFAnnot_GetFormFieldAlternateNamePtr.asFunction , int )>(); - -/// Experimental API. -/// Gets the form field type of |annot|, which is an interactive form annotation. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// -/// Returns the type of the form field (one of the FPDF_FORMFIELD_* values) on -/// success. Returns -1 on error. -/// See field types in fpdf_formfill.h. -int FPDFAnnot_GetFormFieldType(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFormFieldType(hHandle, -annot, -); -} - -late final _FPDFAnnot_GetFormFieldTypePtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormFieldType'); -late final _FPDFAnnot_GetFormFieldType = _FPDFAnnot_GetFormFieldTypePtr.asFunction(); - -/// Experimental API. -/// Gets the value of |annot|, which is an interactive form annotation. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value will -/// be 0. Note that return value of empty string is 2 for "\0\0". -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldValue(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetFormFieldValue(hHandle, -annot, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetFormFieldValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldValue'); -late final _FPDFAnnot_GetFormFieldValue = _FPDFAnnot_GetFormFieldValuePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the number of options in the |annot|'s "Opt" dictionary. Intended for -/// use with listbox and combobox widget annotations. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// -/// Returns the number of options in "Opt" dictionary on success. Return value -/// will be -1 if annotation does not have an "Opt" dictionary or other error. -int FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetOptionCount(hHandle, -annot, -); -} - -late final _FPDFAnnot_GetOptionCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetOptionCount'); -late final _FPDFAnnot_GetOptionCount = _FPDFAnnot_GetOptionCountPtr.asFunction(); - -/// Experimental API. -/// Get the string value for the label of the option at |index| in |annot|'s -/// "Opt" dictionary. Intended for use with listbox and combobox widget -/// annotations. |buffer| is only modified if |buflen| is longer than the length -/// of contents. If index is out of range or in case of other error, nothing -/// will be added to |buffer| and the return value will be 0. Note that -/// return value of empty string is 2 for "\0\0". -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// index - numeric index of the option in the "Opt" array -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -/// If |annot| does not have an "Opt" array, |index| is out of range or if any -/// other error occurs, returns 0. -int FPDFAnnot_GetOptionLabel(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -int index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetOptionLabel(hHandle, -annot, -index, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetOptionLabelPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetOptionLabel'); -late final _FPDFAnnot_GetOptionLabel = _FPDFAnnot_GetOptionLabelPtr.asFunction , int )>(); - -/// Experimental API. -/// Determine whether or not the option at |index| in |annot|'s "Opt" dictionary -/// is selected. Intended for use with listbox and combobox widget annotations. -/// -/// handle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// index - numeric index of the option in the "Opt" array. -/// -/// Returns true if the option at |index| in |annot|'s "Opt" dictionary is -/// selected, false otherwise. -int FPDFAnnot_IsOptionSelected(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int index, -) { - return _FPDFAnnot_IsOptionSelected(handle, -annot, -index, -); -} - -late final _FPDFAnnot_IsOptionSelectedPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsOptionSelected'); -late final _FPDFAnnot_IsOptionSelected = _FPDFAnnot_IsOptionSelectedPtr.asFunction(); - -/// Experimental API. -/// Get the float value of the font size for an |annot| with variable text. -/// If 0, the font is to be auto-sized: its size is computed as a function of -/// the height of the annotation rectangle. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// value - Required. Float which will be set to font size on success. -/// -/// Returns true if the font size was set in |value|, false on error or if -/// |value| not provided. -int FPDFAnnot_GetFontSize(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer value, -) { - return _FPDFAnnot_GetFontSize(hHandle, -annot, -value, -); -} - -late final _FPDFAnnot_GetFontSizePtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_GetFontSize'); -late final _FPDFAnnot_GetFontSize = _FPDFAnnot_GetFontSizePtr.asFunction )>(); - -/// Experimental API. -/// Set the text color of an annotation. -/// -/// handle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// R - the red component for the text color. -/// G - the green component for the text color. -/// B - the blue component for the text color. -/// -/// Returns true if successful. -/// -/// Currently supported subtypes: freetext. -/// The range for the color components is 0 to 255. -int FPDFAnnot_SetFontColor(FPDF_FORMHANDLE handle, -FPDF_ANNOTATION annot, -int R, -int G, -int B, -) { - return _FPDFAnnot_SetFontColor(handle, -annot, -R, -G, -B, -); -} - -late final _FPDFAnnot_SetFontColorPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_SetFontColor'); -late final _FPDFAnnot_SetFontColor = _FPDFAnnot_SetFontColorPtr.asFunction(); - -/// Experimental API. -/// Get the RGB value of the font color for an |annot| with variable text. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// R, G, B - buffer to hold the RGB value of the color. Ranges from 0 to 255. -/// -/// Returns true if the font color was set, false on error or if the font -/// color was not provided. -int FPDFAnnot_GetFontColor(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer R, -ffi.Pointer G, -ffi.Pointer B, -) { - return _FPDFAnnot_GetFontColor(hHandle, -annot, -R, -G, -B, -); -} - -late final _FPDFAnnot_GetFontColorPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer )>>('FPDFAnnot_GetFontColor'); -late final _FPDFAnnot_GetFontColor = _FPDFAnnot_GetFontColorPtr.asFunction , ffi.Pointer , ffi.Pointer )>(); - -/// Experimental API. -/// Determine if |annot| is a form widget that is checked. Intended for use with -/// checkbox and radio button widgets. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// -/// Returns true if |annot| is a form widget and is checked, false otherwise. -int FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_IsChecked(hHandle, -annot, -); -} - -late final _FPDFAnnot_IsCheckedPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_IsChecked'); -late final _FPDFAnnot_IsChecked = _FPDFAnnot_IsCheckedPtr.asFunction(); - -/// Experimental API. -/// Set the list of focusable annotation subtypes. Annotations of subtype -/// FPDF_ANNOT_WIDGET are by default focusable. New subtypes set using this API -/// will override the existing subtypes. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// subtypes - list of annotation subtype which can be tabbed over. -/// count - total number of annotation subtype in list. -/// Returns true if list of annotation subtype is set successfully, false -/// otherwise. -int FPDFAnnot_SetFocusableSubtypes(FPDF_FORMHANDLE hHandle, -ffi.Pointer subtypes, -int count, -) { - return _FPDFAnnot_SetFocusableSubtypes(hHandle, -subtypes, -count, -); -} - -late final _FPDFAnnot_SetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_SetFocusableSubtypes'); -late final _FPDFAnnot_SetFocusableSubtypes = _FPDFAnnot_SetFocusableSubtypesPtr.asFunction , int )>(); - -/// Experimental API. -/// Get the count of focusable annotation subtypes as set by host -/// for a |hHandle|. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// Returns the count of focusable annotation subtypes or -1 on error. -/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. -int FPDFAnnot_GetFocusableSubtypesCount(FPDF_FORMHANDLE hHandle, -) { - return _FPDFAnnot_GetFocusableSubtypesCount(hHandle, -); -} - -late final _FPDFAnnot_GetFocusableSubtypesCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFocusableSubtypesCount'); -late final _FPDFAnnot_GetFocusableSubtypesCount = _FPDFAnnot_GetFocusableSubtypesCountPtr.asFunction(); - -/// Experimental API. -/// Get the list of focusable annotation subtype as set by host. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// subtypes - receives the list of annotation subtype which can be tabbed -/// over. Caller must have allocated |subtypes| more than or -/// equal to the count obtained from -/// FPDFAnnot_GetFocusableSubtypesCount() API. -/// count - size of |subtypes|. -/// Returns true on success and set list of annotation subtype to |subtypes|, -/// false otherwise. -/// Note : Annotations of type FPDF_ANNOT_WIDGET are by default focusable. -int FPDFAnnot_GetFocusableSubtypes(FPDF_FORMHANDLE hHandle, -ffi.Pointer subtypes, -int count, -) { - return _FPDFAnnot_GetFocusableSubtypes(hHandle, -subtypes, -count, -); -} - -late final _FPDFAnnot_GetFocusableSubtypesPtr = _lookup< - ffi.NativeFunction , ffi.Size )>>('FPDFAnnot_GetFocusableSubtypes'); -late final _FPDFAnnot_GetFocusableSubtypes = _FPDFAnnot_GetFocusableSubtypesPtr.asFunction , int )>(); - -/// Experimental API. -/// Gets FPDF_LINK object for |annot|. Intended to use for link annotations. -/// -/// annot - handle to an annotation. -/// -/// Returns FPDF_LINK from the FPDF_ANNOTATION and NULL on failure, -/// if the input annot is NULL or input annot's subtype is not link. -FPDF_LINK FPDFAnnot_GetLink(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetLink(annot, -); -} - -late final _FPDFAnnot_GetLinkPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetLink'); -late final _FPDFAnnot_GetLink = _FPDFAnnot_GetLinkPtr.asFunction(); - -/// Experimental API. -/// Gets the count of annotations in the |annot|'s control group. -/// A group of interactive form annotations is collectively called a form -/// control group. Here, |annot|, an interactive form annotation, should be -/// either a radio button or a checkbox. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// -/// Returns number of controls in its control group or -1 on error. -int FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFormControlCount(hHandle, -annot, -); -} - -late final _FPDFAnnot_GetFormControlCountPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormControlCount'); -late final _FPDFAnnot_GetFormControlCount = _FPDFAnnot_GetFormControlCountPtr.asFunction(); - -/// Experimental API. -/// Gets the index of |annot| in |annot|'s control group. -/// A group of interactive form annotations is collectively called a form -/// control group. Here, |annot|, an interactive form annotation, should be -/// either a radio button or a checkbox. -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment. -/// annot - handle to an annotation. -/// -/// Returns index of a given |annot| in its control group or -1 on error. -int FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFormControlIndex(hHandle, -annot, -); -} - -late final _FPDFAnnot_GetFormControlIndexPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFormControlIndex'); -late final _FPDFAnnot_GetFormControlIndex = _FPDFAnnot_GetFormControlIndexPtr.asFunction(); - -/// Experimental API. -/// Gets the export value of |annot| which is an interactive form annotation. -/// Intended for use with radio button and checkbox widget annotations. -/// |buffer| is only modified if |buflen| is longer than the length of contents. -/// In case of error, nothing will be added to |buffer| and the return value -/// will be 0. Note that return value of empty string is 2 for "\0\0". -/// -/// hHandle - handle to the form fill module, returned by -/// FPDFDOC_InitFormFillEnvironment(). -/// annot - handle to an interactive form annotation. -/// buffer - buffer for holding the value string, encoded in UTF-16LE. -/// buflen - length of the buffer in bytes. -/// -/// Returns the length of the string value in bytes. -int FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle, -FPDF_ANNOTATION annot, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAnnot_GetFormFieldExportValue(hHandle, -annot, -buffer, -buflen, -); -} - -late final _FPDFAnnot_GetFormFieldExportValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAnnot_GetFormFieldExportValue'); -late final _FPDFAnnot_GetFormFieldExportValue = _FPDFAnnot_GetFormFieldExportValuePtr.asFunction , int )>(); - -/// Experimental API. -/// Add a URI action to |annot|, overwriting the existing action, if any. -/// -/// annot - handle to a link annotation. -/// uri - the URI to be set, encoded in 7-bit ASCII. -/// -/// Returns true if successful. -int FPDFAnnot_SetURI(FPDF_ANNOTATION annot, -ffi.Pointer uri, -) { - return _FPDFAnnot_SetURI(annot, -uri, -); -} - -late final _FPDFAnnot_SetURIPtr = _lookup< - ffi.NativeFunction )>>('FPDFAnnot_SetURI'); -late final _FPDFAnnot_SetURI = _FPDFAnnot_SetURIPtr.asFunction )>(); - -/// Experimental API. -/// Get the attachment from |annot|. -/// -/// annot - handle to a file annotation. -/// -/// Returns the handle to the attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFAnnot_GetFileAttachment(FPDF_ANNOTATION annot, -) { - return _FPDFAnnot_GetFileAttachment(annot, -); -} - -late final _FPDFAnnot_GetFileAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_GetFileAttachment'); -late final _FPDFAnnot_GetFileAttachment = _FPDFAnnot_GetFileAttachmentPtr.asFunction(); - -/// Experimental API. -/// Add an embedded file with |name| to |annot|. -/// -/// annot - handle to a file annotation. -/// name - name of the new attachment. -/// -/// Returns a handle to the new attachment object, or NULL on failure. -FPDF_ATTACHMENT FPDFAnnot_AddFileAttachment(FPDF_ANNOTATION annot, -FPDF_WIDESTRING name, -) { - return _FPDFAnnot_AddFileAttachment(annot, -name, -); -} - -late final _FPDFAnnot_AddFileAttachmentPtr = _lookup< - ffi.NativeFunction>('FPDFAnnot_AddFileAttachment'); -late final _FPDFAnnot_AddFileAttachment = _FPDFAnnot_AddFileAttachmentPtr.asFunction(); - -/// Experimental API. -/// -/// Determine if |document| represents a tagged PDF. -/// -/// For the definition of tagged PDF, See (see 10.7 "Tagged PDF" in PDF -/// Reference 1.7). -/// -/// document - handle to a document. -/// -/// Returns |true| iff |document| is a tagged PDF. -int FPDFCatalog_IsTagged(FPDF_DOCUMENT document, -) { - return _FPDFCatalog_IsTagged(document, -); -} - -late final _FPDFCatalog_IsTaggedPtr = _lookup< - ffi.NativeFunction>('FPDFCatalog_IsTagged'); -late final _FPDFCatalog_IsTagged = _FPDFCatalog_IsTaggedPtr.asFunction(); - -/// Experimental API. -/// Sets the language of |document| to |language|. -/// -/// document - handle to a document. -/// language - the language to set to. -/// -/// Returns TRUE on success. -int FPDFCatalog_SetLanguage(FPDF_DOCUMENT document, -FPDF_BYTESTRING language, -) { - return _FPDFCatalog_SetLanguage(document, -language, -); -} - -late final _FPDFCatalog_SetLanguagePtr = _lookup< - ffi.NativeFunction>('FPDFCatalog_SetLanguage'); -late final _FPDFCatalog_SetLanguage = _FPDFCatalog_SetLanguagePtr.asFunction(); - -/// Experimental API. -/// Import pages to a FPDF_DOCUMENT. -/// -/// dest_doc - The destination document for the pages. -/// src_doc - The document to be imported. -/// page_indices - An array of page indices to be imported. The first page is -/// zero. If |page_indices| is NULL, all pages from |src_doc| -/// are imported. -/// length - The length of the |page_indices| array. -/// index - The page index at which to insert the first imported page -/// into |dest_doc|. The first page is zero. -/// -/// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is -/// invalid. -int FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -ffi.Pointer page_indices, -int length, -int index, -) { - return _FPDF_ImportPagesByIndex(dest_doc, -src_doc, -page_indices, -length, -index, -); -} - -late final _FPDF_ImportPagesByIndexPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Int )>>('FPDF_ImportPagesByIndex'); -late final _FPDF_ImportPagesByIndex = _FPDF_ImportPagesByIndexPtr.asFunction , int , int )>(); - -/// Import pages to a FPDF_DOCUMENT. -/// -/// dest_doc - The destination document for the pages. -/// src_doc - The document to be imported. -/// pagerange - A page range string, Such as "1,3,5-7". The first page is one. -/// If |pagerange| is NULL, all pages from |src_doc| are imported. -/// index - The page index at which to insert the first imported page into -/// |dest_doc|. The first page is zero. -/// -/// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is -/// invalid or if |pagerange| cannot be read. -int FPDF_ImportPages(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -FPDF_BYTESTRING pagerange, -int index, -) { - return _FPDF_ImportPages(dest_doc, -src_doc, -pagerange, -index, -); -} - -late final _FPDF_ImportPagesPtr = _lookup< - ffi.NativeFunction>('FPDF_ImportPages'); -late final _FPDF_ImportPages = _FPDF_ImportPagesPtr.asFunction(); - -/// Experimental API. -/// Create a new document from |src_doc|. The pages of |src_doc| will be -/// combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per -/// |output_doc| page. -/// -/// src_doc - The document to be imported. -/// output_width - The output page width in PDF "user space" units. -/// output_height - The output page height in PDF "user space" units. -/// num_pages_on_x_axis - The number of pages on X Axis. -/// num_pages_on_y_axis - The number of pages on Y Axis. -/// -/// Return value: -/// A handle to the created document, or NULL on failure. -/// -/// Comments: -/// number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis -FPDF_DOCUMENT FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, -double output_width, -double output_height, -int num_pages_on_x_axis, -int num_pages_on_y_axis, -) { - return _FPDF_ImportNPagesToOne(src_doc, -output_width, -output_height, -num_pages_on_x_axis, -num_pages_on_y_axis, -); -} - -late final _FPDF_ImportNPagesToOnePtr = _lookup< - ffi.NativeFunction>('FPDF_ImportNPagesToOne'); -late final _FPDF_ImportNPagesToOne = _FPDF_ImportNPagesToOnePtr.asFunction(); - -/// Experimental API. -/// Create a template to generate form xobjects from |src_doc|'s page at -/// |src_page_index|, for use in |dest_doc|. -/// -/// Returns a handle on success, or NULL on failure. Caller owns the newly -/// created object. -FPDF_XOBJECT FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -int src_page_index, -) { - return _FPDF_NewXObjectFromPage(dest_doc, -src_doc, -src_page_index, -); -} - -late final _FPDF_NewXObjectFromPagePtr = _lookup< - ffi.NativeFunction>('FPDF_NewXObjectFromPage'); -late final _FPDF_NewXObjectFromPage = _FPDF_NewXObjectFromPagePtr.asFunction(); - -/// Experimental API. -/// Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage(). -/// FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected. -void FPDF_CloseXObject(FPDF_XOBJECT xobject, -) { - return _FPDF_CloseXObject(xobject, -); -} - -late final _FPDF_CloseXObjectPtr = _lookup< - ffi.NativeFunction>('FPDF_CloseXObject'); -late final _FPDF_CloseXObject = _FPDF_CloseXObjectPtr.asFunction(); - -/// Experimental API. -/// Create a new form object from an FPDF_XOBJECT object. -/// -/// Returns a new form object on success, or NULL on failure. Caller owns the -/// newly created object. -FPDF_PAGEOBJECT FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject, -) { - return _FPDF_NewFormObjectFromXObject(xobject, -); -} - -late final _FPDF_NewFormObjectFromXObjectPtr = _lookup< - ffi.NativeFunction>('FPDF_NewFormObjectFromXObject'); -late final _FPDF_NewFormObjectFromXObject = _FPDF_NewFormObjectFromXObjectPtr.asFunction(); - -/// Copy the viewer preferences from |src_doc| into |dest_doc|. -/// -/// dest_doc - Document to write the viewer preferences into. -/// src_doc - Document to read the viewer preferences from. -/// -/// Returns TRUE on success. -int FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, -FPDF_DOCUMENT src_doc, -) { - return _FPDF_CopyViewerPreferences(dest_doc, -src_doc, -); -} - -late final _FPDF_CopyViewerPreferencesPtr = _lookup< - ffi.NativeFunction>('FPDF_CopyViewerPreferences'); -late final _FPDF_CopyViewerPreferences = _FPDF_CopyViewerPreferencesPtr.asFunction(); - -/// Function: FPDF_SaveAsCopy -/// Saves the copy of specified document in custom way. -/// Parameters: -/// document - Handle to document, as returned by -/// FPDF_LoadDocument() or FPDF_CreateNewDocument(). -/// pFileWrite - A pointer to a custom file write structure. -/// flags - Flags above that affect how the PDF gets saved. -/// Pass in 0 when there are no flags. -/// Return value: -/// TRUE for succeed, FALSE for failed. -int FPDF_SaveAsCopy(FPDF_DOCUMENT document, -ffi.Pointer pFileWrite, -int flags, -) { - return _FPDF_SaveAsCopy(document, -pFileWrite, -flags, -); -} - -late final _FPDF_SaveAsCopyPtr = _lookup< - ffi.NativeFunction , FPDF_DWORD )>>('FPDF_SaveAsCopy'); -late final _FPDF_SaveAsCopy = _FPDF_SaveAsCopyPtr.asFunction , int )>(); - -/// Function: FPDF_SaveWithVersion -/// Same as FPDF_SaveAsCopy(), except the file version of the -/// saved document can be specified by the caller. -/// Parameters: -/// document - Handle to document. -/// pFileWrite - A pointer to a custom file write structure. -/// flags - The creating flags. -/// fileVersion - The PDF file version. File version: 14 for 1.4, -/// 15 for 1.5, ... -/// Return value: -/// TRUE if succeed, FALSE if failed. -int FPDF_SaveWithVersion(FPDF_DOCUMENT document, -ffi.Pointer pFileWrite, -int flags, -int fileVersion, -) { - return _FPDF_SaveWithVersion(document, -pFileWrite, -flags, -fileVersion, -); -} - -late final _FPDF_SaveWithVersionPtr = _lookup< - ffi.NativeFunction , FPDF_DWORD , ffi.Int )>>('FPDF_SaveWithVersion'); -late final _FPDF_SaveWithVersion = _FPDF_SaveWithVersionPtr.asFunction , int , int )>(); - -/// Get the first child of |bookmark|, or the first top-level bookmark item. -/// -/// document - handle to the document. -/// bookmark - handle to the current bookmark. Pass NULL for the first top -/// level item. -/// -/// Returns a handle to the first child of |bookmark| or the first top-level -/// bookmark item. NULL if no child or top-level bookmark found. -/// Note that another name for the bookmarks is the document outline, as -/// described in ISO 32000-1:2008, section 12.3.3. -FPDF_BOOKMARK FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, -) { - return _FPDFBookmark_GetFirstChild(document, -bookmark, -); -} - -late final _FPDFBookmark_GetFirstChildPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetFirstChild'); -late final _FPDFBookmark_GetFirstChild = _FPDFBookmark_GetFirstChildPtr.asFunction(); - -/// Get the next sibling of |bookmark|. -/// -/// document - handle to the document. -/// bookmark - handle to the current bookmark. -/// -/// Returns a handle to the next sibling of |bookmark|, or NULL if this is the -/// last bookmark at this level. -/// -/// Note that the caller is responsible for handling circular bookmark -/// references, as may arise from malformed documents. -FPDF_BOOKMARK FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, -) { - return _FPDFBookmark_GetNextSibling(document, -bookmark, -); -} - -late final _FPDFBookmark_GetNextSiblingPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetNextSibling'); -late final _FPDFBookmark_GetNextSibling = _FPDFBookmark_GetNextSiblingPtr.asFunction(); - -/// Get the title of |bookmark|. -/// -/// bookmark - handle to the bookmark. -/// buffer - buffer for the title. May be NULL. -/// buflen - the length of the buffer in bytes. May be 0. -/// -/// Returns the number of bytes in the title, including the terminating NUL -/// character. The number of bytes is returned regardless of the |buffer| and -/// |buflen| parameters. -/// -/// Regardless of the platform, the |buffer| is always in UTF-16LE encoding. The -/// string is terminated by a UTF16 NUL character. If |buflen| is less than the -/// required length, or |buffer| is NULL, |buffer| will not be modified. -int FPDFBookmark_GetTitle(FPDF_BOOKMARK bookmark, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFBookmark_GetTitle(bookmark, -buffer, -buflen, -); -} - -late final _FPDFBookmark_GetTitlePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFBookmark_GetTitle'); -late final _FPDFBookmark_GetTitle = _FPDFBookmark_GetTitlePtr.asFunction , int )>(); - -/// Experimental API. -/// Get the number of chlidren of |bookmark|. -/// -/// bookmark - handle to the bookmark. -/// -/// Returns a signed integer that represents the number of sub-items the given -/// bookmark has. If the value is positive, child items shall be shown by default -/// (open state). If the value is negative, child items shall be hidden by -/// default (closed state). Please refer to PDF 32000-1:2008, Table 153. -/// Returns 0 if the bookmark has no children or is invalid. -int FPDFBookmark_GetCount(FPDF_BOOKMARK bookmark, -) { - return _FPDFBookmark_GetCount(bookmark, -); -} - -late final _FPDFBookmark_GetCountPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetCount'); -late final _FPDFBookmark_GetCount = _FPDFBookmark_GetCountPtr.asFunction(); - -/// Find the bookmark with |title| in |document|. -/// -/// document - handle to the document. -/// title - the UTF-16LE encoded Unicode title for which to search. -/// -/// Returns the handle to the bookmark, or NULL if |title| can't be found. -/// -/// FPDFBookmark_Find() will always return the first bookmark found even if -/// multiple bookmarks have the same |title|. -FPDF_BOOKMARK FPDFBookmark_Find(FPDF_DOCUMENT document, -FPDF_WIDESTRING title, -) { - return _FPDFBookmark_Find(document, -title, -); -} - -late final _FPDFBookmark_FindPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_Find'); -late final _FPDFBookmark_Find = _FPDFBookmark_FindPtr.asFunction(); - -/// Get the destination associated with |bookmark|. -/// -/// document - handle to the document. -/// bookmark - handle to the bookmark. -/// -/// Returns the handle to the destination data, or NULL if no destination is -/// associated with |bookmark|. -FPDF_DEST FPDFBookmark_GetDest(FPDF_DOCUMENT document, -FPDF_BOOKMARK bookmark, -) { - return _FPDFBookmark_GetDest(document, -bookmark, -); -} - -late final _FPDFBookmark_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetDest'); -late final _FPDFBookmark_GetDest = _FPDFBookmark_GetDestPtr.asFunction(); - -/// Get the action associated with |bookmark|. -/// -/// bookmark - handle to the bookmark. -/// -/// Returns the handle to the action data, or NULL if no action is associated -/// with |bookmark|. -/// If this function returns a valid handle, it is valid as long as |bookmark| is -/// valid. -/// If this function returns NULL, FPDFBookmark_GetDest() should be called to get -/// the |bookmark| destination data. -FPDF_ACTION FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark, -) { - return _FPDFBookmark_GetAction(bookmark, -); -} - -late final _FPDFBookmark_GetActionPtr = _lookup< - ffi.NativeFunction>('FPDFBookmark_GetAction'); -late final _FPDFBookmark_GetAction = _FPDFBookmark_GetActionPtr.asFunction(); - -/// Get the type of |action|. -/// -/// action - handle to the action. -/// -/// Returns one of: -/// PDFACTION_UNSUPPORTED -/// PDFACTION_GOTO -/// PDFACTION_REMOTEGOTO -/// PDFACTION_URI -/// PDFACTION_LAUNCH -int FPDFAction_GetType(FPDF_ACTION action, -) { - return _FPDFAction_GetType(action, -); -} - -late final _FPDFAction_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDFAction_GetType'); -late final _FPDFAction_GetType = _FPDFAction_GetTypePtr.asFunction(); - -/// Get the destination of |action|. -/// -/// document - handle to the document. -/// action - handle to the action. |action| must be a |PDFACTION_GOTO| or -/// |PDFACTION_REMOTEGOTO|. -/// -/// Returns a handle to the destination data, or NULL on error, typically -/// because the arguments were bad or the action was of the wrong type. -/// -/// In the case of |PDFACTION_REMOTEGOTO|, you must first call -/// FPDFAction_GetFilePath(), then load the document at that path, then pass -/// the document handle from that document as |document| to FPDFAction_GetDest(). -FPDF_DEST FPDFAction_GetDest(FPDF_DOCUMENT document, -FPDF_ACTION action, -) { - return _FPDFAction_GetDest(document, -action, -); -} - -late final _FPDFAction_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFAction_GetDest'); -late final _FPDFAction_GetDest = _FPDFAction_GetDestPtr.asFunction(); - -/// Get the file path of |action|. -/// -/// action - handle to the action. |action| must be a |PDFACTION_LAUNCH| or -/// |PDFACTION_REMOTEGOTO|. -/// buffer - a buffer for output the path string. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the file path, including the trailing NUL -/// character, or 0 on error, typically because the arguments were bad or the -/// action was of the wrong type. -/// -/// Regardless of the platform, the |buffer| is always in UTF-8 encoding. -/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -int FPDFAction_GetFilePath(FPDF_ACTION action, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAction_GetFilePath(action, -buffer, -buflen, -); -} - -late final _FPDFAction_GetFilePathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetFilePath'); -late final _FPDFAction_GetFilePath = _FPDFAction_GetFilePathPtr.asFunction , int )>(); - -/// Get the URI path of |action|. -/// -/// document - handle to the document. -/// action - handle to the action. Must be a |PDFACTION_URI|. -/// buffer - a buffer for the path string. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the URI path, including the trailing NUL -/// character, or 0 on error, typically because the arguments were bad or the -/// action was of the wrong type. -/// -/// The |buffer| may contain badly encoded data. The caller should validate the -/// output. e.g. Check to see if it is UTF-8. -/// -/// If |buflen| is less than the returned length, or |buffer| is NULL, |buffer| -/// will not be modified. -/// -/// Historically, the documentation for this API claimed |buffer| is always -/// encoded in 7-bit ASCII, but did not actually enforce it. -/// https://pdfium.googlesource.com/pdfium.git/+/d609e84cee2e14a18333247485af91df48a40592 -/// added that enforcement, but that did not work well for real world PDFs that -/// used UTF-8. As of this writing, this API reverted back to its original -/// behavior prior to commit d609e84cee. -int FPDFAction_GetURIPath(FPDF_DOCUMENT document, -FPDF_ACTION action, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFAction_GetURIPath(document, -action, -buffer, -buflen, -); -} - -late final _FPDFAction_GetURIPathPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFAction_GetURIPath'); -late final _FPDFAction_GetURIPath = _FPDFAction_GetURIPathPtr.asFunction , int )>(); - -/// Get the page index of |dest|. -/// -/// document - handle to the document. -/// dest - handle to the destination. -/// -/// Returns the 0-based page index containing |dest|. Returns -1 on error. -int FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, -FPDF_DEST dest, -) { - return _FPDFDest_GetDestPageIndex(document, -dest, -); -} - -late final _FPDFDest_GetDestPageIndexPtr = _lookup< - ffi.NativeFunction>('FPDFDest_GetDestPageIndex'); -late final _FPDFDest_GetDestPageIndex = _FPDFDest_GetDestPageIndexPtr.asFunction(); - -/// Experimental API. -/// Get the view (fit type) specified by |dest|. -/// -/// dest - handle to the destination. -/// pNumParams - receives the number of view parameters, which is at most 4. -/// pParams - buffer to write the view parameters. Must be at least 4 -/// FS_FLOATs long. -/// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if -/// |dest| does not specify a view. -int FPDFDest_GetView(FPDF_DEST dest, -ffi.Pointer pNumParams, -ffi.Pointer pParams, -) { - return _FPDFDest_GetView(dest, -pNumParams, -pParams, -); -} - -late final _FPDFDest_GetViewPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFDest_GetView'); -late final _FPDFDest_GetView = _FPDFDest_GetViewPtr.asFunction , ffi.Pointer )>(); - -/// Get the (x, y, zoom) location of |dest| in the destination page, if the -/// destination is in [page /XYZ x y zoom] syntax. -/// -/// dest - handle to the destination. -/// hasXVal - out parameter; true if the x value is not null -/// hasYVal - out parameter; true if the y value is not null -/// hasZoomVal - out parameter; true if the zoom value is not null -/// x - out parameter; the x coordinate, in page coordinates. -/// y - out parameter; the y coordinate, in page coordinates. -/// zoom - out parameter; the zoom value. -/// Returns TRUE on successfully reading the /XYZ value. -/// -/// Note the [x, y, zoom] values are only set if the corresponding hasXVal, -/// hasYVal or hasZoomVal flags are true. -int FPDFDest_GetLocationInPage(FPDF_DEST dest, -ffi.Pointer hasXVal, -ffi.Pointer hasYVal, -ffi.Pointer hasZoomVal, -ffi.Pointer x, -ffi.Pointer y, -ffi.Pointer zoom, -) { - return _FPDFDest_GetLocationInPage(dest, -hasXVal, -hasYVal, -hasZoomVal, -x, -y, -zoom, -); -} - -late final _FPDFDest_GetLocationInPagePtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFDest_GetLocationInPage'); -late final _FPDFDest_GetLocationInPage = _FPDFDest_GetLocationInPagePtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Find a link at point (|x|,|y|) on |page|. -/// -/// page - handle to the document page. -/// x - the x coordinate, in the page coordinate system. -/// y - the y coordinate, in the page coordinate system. -/// -/// Returns a handle to the link, or NULL if no link found at the given point. -/// -/// You can convert coordinates from screen coordinates to page coordinates using -/// FPDF_DeviceToPage(). -FPDF_LINK FPDFLink_GetLinkAtPoint(FPDF_PAGE page, -double x, -double y, -) { - return _FPDFLink_GetLinkAtPoint(page, -x, -y, -); -} - -late final _FPDFLink_GetLinkAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetLinkAtPoint'); -late final _FPDFLink_GetLinkAtPoint = _FPDFLink_GetLinkAtPointPtr.asFunction(); - -/// Find the Z-order of link at point (|x|,|y|) on |page|. -/// -/// page - handle to the document page. -/// x - the x coordinate, in the page coordinate system. -/// y - the y coordinate, in the page coordinate system. -/// -/// Returns the Z-order of the link, or -1 if no link found at the given point. -/// Larger Z-order numbers are closer to the front. -/// -/// You can convert coordinates from screen coordinates to page coordinates using -/// FPDF_DeviceToPage(). -int FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, -double x, -double y, -) { - return _FPDFLink_GetLinkZOrderAtPoint(page, -x, -y, -); -} - -late final _FPDFLink_GetLinkZOrderAtPointPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetLinkZOrderAtPoint'); -late final _FPDFLink_GetLinkZOrderAtPoint = _FPDFLink_GetLinkZOrderAtPointPtr.asFunction(); - -/// Get destination info for |link|. -/// -/// document - handle to the document. -/// link - handle to the link. -/// -/// Returns a handle to the destination, or NULL if there is no destination -/// associated with the link. In this case, you should call FPDFLink_GetAction() -/// to retrieve the action associated with |link|. -FPDF_DEST FPDFLink_GetDest(FPDF_DOCUMENT document, -FPDF_LINK link, -) { - return _FPDFLink_GetDest(document, -link, -); -} - -late final _FPDFLink_GetDestPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetDest'); -late final _FPDFLink_GetDest = _FPDFLink_GetDestPtr.asFunction(); - -/// Get action info for |link|. -/// -/// link - handle to the link. -/// -/// Returns a handle to the action associated to |link|, or NULL if no action. -/// If this function returns a valid handle, it is valid as long as |link| is -/// valid. -FPDF_ACTION FPDFLink_GetAction(FPDF_LINK link, -) { - return _FPDFLink_GetAction(link, -); -} - -late final _FPDFLink_GetActionPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetAction'); -late final _FPDFLink_GetAction = _FPDFLink_GetActionPtr.asFunction(); - -/// Enumerates all the link annotations in |page|. -/// -/// page - handle to the page. -/// start_pos - the start position, should initially be 0 and is updated with -/// the next start position on return. -/// link_annot - the link handle for |startPos|. -/// -/// Returns TRUE on success. -int FPDFLink_Enumerate(FPDF_PAGE page, -ffi.Pointer start_pos, -ffi.Pointer link_annot, -) { - return _FPDFLink_Enumerate(page, -start_pos, -link_annot, -); -} - -late final _FPDFLink_EnumeratePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFLink_Enumerate'); -late final _FPDFLink_Enumerate = _FPDFLink_EnumeratePtr.asFunction , ffi.Pointer )>(); - -/// Experimental API. -/// Gets FPDF_ANNOTATION object for |link_annot|. -/// -/// page - handle to the page in which FPDF_LINK object is present. -/// link_annot - handle to link annotation. -/// -/// Returns FPDF_ANNOTATION from the FPDF_LINK and NULL on failure, -/// if the input link annot or page is NULL. -FPDF_ANNOTATION FPDFLink_GetAnnot(FPDF_PAGE page, -FPDF_LINK link_annot, -) { - return _FPDFLink_GetAnnot(page, -link_annot, -); -} - -late final _FPDFLink_GetAnnotPtr = _lookup< - ffi.NativeFunction>('FPDFLink_GetAnnot'); -late final _FPDFLink_GetAnnot = _FPDFLink_GetAnnotPtr.asFunction(); - -/// Get the rectangle for |link_annot|. -/// -/// link_annot - handle to the link annotation. -/// rect - the annotation rectangle. -/// -/// Returns true on success. -int FPDFLink_GetAnnotRect(FPDF_LINK link_annot, -ffi.Pointer rect, -) { - return _FPDFLink_GetAnnotRect(link_annot, -rect, -); -} - -late final _FPDFLink_GetAnnotRectPtr = _lookup< - ffi.NativeFunction )>>('FPDFLink_GetAnnotRect'); -late final _FPDFLink_GetAnnotRect = _FPDFLink_GetAnnotRectPtr.asFunction )>(); - -/// Get the count of quadrilateral points to the |link_annot|. -/// -/// link_annot - handle to the link annotation. -/// -/// Returns the count of quadrilateral points. -int FPDFLink_CountQuadPoints(FPDF_LINK link_annot, -) { - return _FPDFLink_CountQuadPoints(link_annot, -); -} - -late final _FPDFLink_CountQuadPointsPtr = _lookup< - ffi.NativeFunction>('FPDFLink_CountQuadPoints'); -late final _FPDFLink_CountQuadPoints = _FPDFLink_CountQuadPointsPtr.asFunction(); - -/// Get the quadrilateral points for the specified |quad_index| in |link_annot|. -/// -/// link_annot - handle to the link annotation. -/// quad_index - the specified quad point index. -/// quad_points - receives the quadrilateral points. -/// -/// Returns true on success. -int FPDFLink_GetQuadPoints(FPDF_LINK link_annot, -int quad_index, -ffi.Pointer quad_points, -) { - return _FPDFLink_GetQuadPoints(link_annot, -quad_index, -quad_points, -); -} - -late final _FPDFLink_GetQuadPointsPtr = _lookup< - ffi.NativeFunction )>>('FPDFLink_GetQuadPoints'); -late final _FPDFLink_GetQuadPoints = _FPDFLink_GetQuadPointsPtr.asFunction )>(); - -/// Experimental API -/// Gets an additional-action from |page|. -/// -/// page - handle to the page, as returned by FPDF_LoadPage(). -/// aa_type - the type of the page object's addtional-action, defined -/// in public/fpdf_formfill.h -/// -/// Returns the handle to the action data, or NULL if there is no -/// additional-action of type |aa_type|. -/// If this function returns a valid handle, it is valid as long as |page| is -/// valid. -FPDF_ACTION FPDF_GetPageAAction(FPDF_PAGE page, -int aa_type, -) { - return _FPDF_GetPageAAction(page, -aa_type, -); -} - -late final _FPDF_GetPageAActionPtr = _lookup< - ffi.NativeFunction>('FPDF_GetPageAAction'); -late final _FPDF_GetPageAAction = _FPDF_GetPageAActionPtr.asFunction(); - -/// Experimental API. -/// Get the file identifer defined in the trailer of |document|. -/// -/// document - handle to the document. -/// id_type - the file identifier type to retrieve. -/// buffer - a buffer for the file identifier. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the file identifier, including the NUL -/// terminator. -/// -/// The |buffer| is always a byte string. The |buffer| is followed by a NUL -/// terminator. If |buflen| is less than the returned length, or |buffer| is -/// NULL, |buffer| will not be modified. -int FPDF_GetFileIdentifier(FPDF_DOCUMENT document, -FPDF_FILEIDTYPE id_type, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_GetFileIdentifier(document, -id_type.value, -buffer, -buflen, -); -} - -late final _FPDF_GetFileIdentifierPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetFileIdentifier'); -late final _FPDF_GetFileIdentifier = _FPDF_GetFileIdentifierPtr.asFunction , int )>(); - -/// Get meta-data |tag| content from |document|. -/// -/// document - handle to the document. -/// tag - the tag to retrieve. The tag can be one of: -/// Title, Author, Subject, Keywords, Creator, Producer, -/// CreationDate, or ModDate. -/// For detailed explanations of these tags and their respective -/// values, please refer to PDF Reference 1.6, section 10.2.1, -/// 'Document Information Dictionary'. -/// buffer - a buffer for the tag. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the tag, including trailing zeros. -/// -/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two -/// bytes of zeros indicating the end of the string. If |buflen| is less than -/// the returned length, or |buffer| is NULL, |buffer| will not be modified. -/// -/// For linearized files, FPDFAvail_IsFormAvail must be called before this, and -/// it must have returned PDF_FORM_AVAIL or PDF_FORM_NOTEXIST. Before that, there -/// is no guarantee the metadata has been loaded. -int FPDF_GetMetaText(FPDF_DOCUMENT document, -FPDF_BYTESTRING tag, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_GetMetaText(document, -tag, -buffer, -buflen, -); -} - -late final _FPDF_GetMetaTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetMetaText'); -late final _FPDF_GetMetaText = _FPDF_GetMetaTextPtr.asFunction , int )>(); - -/// Get the page label for |page_index| from |document|. -/// -/// document - handle to the document. -/// page_index - the 0-based index of the page. -/// buffer - a buffer for the page label. May be NULL. -/// buflen - the length of the buffer, in bytes. May be 0. -/// -/// Returns the number of bytes in the page label, including trailing zeros. -/// -/// The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two -/// bytes of zeros indicating the end of the string. If |buflen| is less than -/// the returned length, or |buffer| is NULL, |buffer| will not be modified. -int FPDF_GetPageLabel(FPDF_DOCUMENT document, -int page_index, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_GetPageLabel(document, -page_index, -buffer, -buflen, -); -} - -late final _FPDF_GetPageLabelPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_GetPageLabel'); -late final _FPDF_GetPageLabel = _FPDF_GetPageLabelPtr.asFunction , int )>(); - -/// Function: FPDF_StructTree_GetForPage -/// Get the structure tree for a page. -/// Parameters: -/// page - Handle to the page, as returned by FPDF_LoadPage(). -/// Return value: -/// A handle to the structure tree or NULL on error. The caller owns the -/// returned handle and must use FPDF_StructTree_Close() to release it. -/// The handle should be released before |page| gets released. -FPDF_STRUCTTREE FPDF_StructTree_GetForPage(FPDF_PAGE page, -) { - return _FPDF_StructTree_GetForPage(page, -); -} - -late final _FPDF_StructTree_GetForPagePtr = _lookup< - ffi.NativeFunction>('FPDF_StructTree_GetForPage'); -late final _FPDF_StructTree_GetForPage = _FPDF_StructTree_GetForPagePtr.asFunction(); - -/// Function: FPDF_StructTree_Close -/// Release a resource allocated by FPDF_StructTree_GetForPage(). -/// Parameters: -/// struct_tree - Handle to the structure tree, as returned by -/// FPDF_StructTree_LoadPage(). -/// Return value: -/// None. -void FPDF_StructTree_Close(FPDF_STRUCTTREE struct_tree, -) { - return _FPDF_StructTree_Close(struct_tree, -); -} - -late final _FPDF_StructTree_ClosePtr = _lookup< - ffi.NativeFunction>('FPDF_StructTree_Close'); -late final _FPDF_StructTree_Close = _FPDF_StructTree_ClosePtr.asFunction(); - -/// Function: FPDF_StructTree_CountChildren -/// Count the number of children for the structure tree. -/// Parameters: -/// struct_tree - Handle to the structure tree, as returned by -/// FPDF_StructTree_LoadPage(). -/// Return value: -/// The number of children, or -1 on error. -int FPDF_StructTree_CountChildren(FPDF_STRUCTTREE struct_tree, -) { - return _FPDF_StructTree_CountChildren(struct_tree, -); -} - -late final _FPDF_StructTree_CountChildrenPtr = _lookup< - ffi.NativeFunction>('FPDF_StructTree_CountChildren'); -late final _FPDF_StructTree_CountChildren = _FPDF_StructTree_CountChildrenPtr.asFunction(); - -/// Function: FPDF_StructTree_GetChildAtIndex -/// Get a child in the structure tree. -/// Parameters: -/// struct_tree - Handle to the structure tree, as returned by -/// FPDF_StructTree_LoadPage(). -/// index - The index for the child, 0-based. -/// Return value: -/// The child at the n-th index or NULL on error. The caller does not -/// own the handle. The handle remains valid as long as |struct_tree| -/// remains valid. -/// Comments: -/// The |index| must be less than the FPDF_StructTree_CountChildren() -/// return value. -FPDF_STRUCTELEMENT FPDF_StructTree_GetChildAtIndex(FPDF_STRUCTTREE struct_tree, -int index, -) { - return _FPDF_StructTree_GetChildAtIndex(struct_tree, -index, -); -} - -late final _FPDF_StructTree_GetChildAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDF_StructTree_GetChildAtIndex'); -late final _FPDF_StructTree_GetChildAtIndex = _FPDF_StructTree_GetChildAtIndexPtr.asFunction(); - -/// Function: FPDF_StructElement_GetAltText -/// Get the alt text for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output the alt text. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the alt text, including the terminating NUL -/// character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetAltText(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetAltText(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetAltTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetAltText'); -late final _FPDF_StructElement_GetAltText = _FPDF_StructElement_GetAltTextPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetActualText -/// Get the actual text for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output the actual text. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the actual text, including the terminating -/// NUL character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetActualText(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetActualText(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetActualTextPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetActualText'); -late final _FPDF_StructElement_GetActualText = _FPDF_StructElement_GetActualTextPtr.asFunction , int )>(); - -/// Function: FPDF_StructElement_GetID -/// Get the ID for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output the ID string. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the ID string, including the terminating NUL -/// character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetID(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetID(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetIDPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetID'); -late final _FPDF_StructElement_GetID = _FPDF_StructElement_GetIDPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetLang -/// Get the case-insensitive IETF BCP 47 language code for an element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output the lang string. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the ID string, including the terminating NUL -/// character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetLang(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetLang(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetLangPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetLang'); -late final _FPDF_StructElement_GetLang = _FPDF_StructElement_GetLangPtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetStringAttribute -/// Get a struct element attribute of type "name" or "string". -/// Parameters: -/// struct_element - Handle to the struct element. -/// attr_name - The name of the attribute to retrieve. -/// buffer - A buffer for output. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the attribute value, including the -/// terminating NUL character. The number of bytes is returned -/// regardless of the |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetStringAttribute(FPDF_STRUCTELEMENT struct_element, -FPDF_BYTESTRING attr_name, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetStringAttribute(struct_element, -attr_name, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetStringAttributePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetStringAttribute'); -late final _FPDF_StructElement_GetStringAttribute = _FPDF_StructElement_GetStringAttributePtr.asFunction , int )>(); - -/// Function: FPDF_StructElement_GetMarkedContentID -/// Get the marked content ID for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// Return value: -/// The marked content ID of the element. If no ID exists, returns -/// -1. -/// Comments: -/// FPDF_StructElement_GetMarkedContentIdAtIndex() may be able to -/// extract more marked content IDs out of |struct_element|. This API -/// may be deprecated in the future. -int FPDF_StructElement_GetMarkedContentID(FPDF_STRUCTELEMENT struct_element, -) { - return _FPDF_StructElement_GetMarkedContentID(struct_element, -); -} - -late final _FPDF_StructElement_GetMarkedContentIDPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentID'); -late final _FPDF_StructElement_GetMarkedContentID = _FPDF_StructElement_GetMarkedContentIDPtr.asFunction(); - -/// Function: FPDF_StructElement_GetType -/// Get the type (/S) for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the type, including the terminating NUL -/// character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetType(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetType(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetTypePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetType'); -late final _FPDF_StructElement_GetType = _FPDF_StructElement_GetTypePtr.asFunction , int )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetObjType -/// Get the object type (/Type) for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the object type, including the terminating -/// NUL character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetObjType(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetObjType(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetObjTypePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetObjType'); -late final _FPDF_StructElement_GetObjType = _FPDF_StructElement_GetObjTypePtr.asFunction , int )>(); - -/// Function: FPDF_StructElement_GetTitle -/// Get the title (/T) for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// buffer - A buffer for output. May be NULL. -/// buflen - The length of the buffer, in bytes. May be 0. -/// Return value: -/// The number of bytes in the title, including the terminating NUL -/// character. The number of bytes is returned regardless of the -/// |buffer| and |buflen| parameters. -/// Comments: -/// Regardless of the platform, the |buffer| is always in UTF-16LE -/// encoding. The string is terminated by a UTF16 NUL character. If -/// |buflen| is less than the required length, or |buffer| is NULL, -/// |buffer| will not be modified. -int FPDF_StructElement_GetTitle(FPDF_STRUCTELEMENT struct_element, -ffi.Pointer buffer, -int buflen, -) { - return _FPDF_StructElement_GetTitle(struct_element, -buffer, -buflen, -); -} - -late final _FPDF_StructElement_GetTitlePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDF_StructElement_GetTitle'); -late final _FPDF_StructElement_GetTitle = _FPDF_StructElement_GetTitlePtr.asFunction , int )>(); - -/// Function: FPDF_StructElement_CountChildren -/// Count the number of children for the structure element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// Return value: -/// The number of children, or -1 on error. -int FPDF_StructElement_CountChildren(FPDF_STRUCTELEMENT struct_element, -) { - return _FPDF_StructElement_CountChildren(struct_element, -); -} - -late final _FPDF_StructElement_CountChildrenPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_CountChildren'); -late final _FPDF_StructElement_CountChildren = _FPDF_StructElement_CountChildrenPtr.asFunction(); - -/// Function: FPDF_StructElement_GetChildAtIndex -/// Get a child in the structure element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// index - The index for the child, 0-based. -/// Return value: -/// The child at the n-th index or NULL on error. -/// Comments: -/// If the child exists but is not an element, then this function will -/// return NULL. This will also return NULL for out of bounds indices. -/// The |index| must be less than the FPDF_StructElement_CountChildren() -/// return value. -FPDF_STRUCTELEMENT FPDF_StructElement_GetChildAtIndex(FPDF_STRUCTELEMENT struct_element, -int index, -) { - return _FPDF_StructElement_GetChildAtIndex(struct_element, -index, -); -} - -late final _FPDF_StructElement_GetChildAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetChildAtIndex'); -late final _FPDF_StructElement_GetChildAtIndex = _FPDF_StructElement_GetChildAtIndexPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetChildMarkedContentID -/// Get the child's content id -/// Parameters: -/// struct_element - Handle to the struct element. -/// index - The index for the child, 0-based. -/// Return value: -/// The marked content ID of the child. If no ID exists, returns -1. -/// Comments: -/// If the child exists but is not a stream or object, then this -/// function will return -1. This will also return -1 for out of bounds -/// indices. Compared to FPDF_StructElement_GetMarkedContentIdAtIndex, -/// it is scoped to the current page. -/// The |index| must be less than the FPDF_StructElement_CountChildren() -/// return value. -int FPDF_StructElement_GetChildMarkedContentID(FPDF_STRUCTELEMENT struct_element, -int index, -) { - return _FPDF_StructElement_GetChildMarkedContentID(struct_element, -index, -); -} - -late final _FPDF_StructElement_GetChildMarkedContentIDPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetChildMarkedContentID'); -late final _FPDF_StructElement_GetChildMarkedContentID = _FPDF_StructElement_GetChildMarkedContentIDPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetParent -/// Get the parent of the structure element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// Return value: -/// The parent structure element or NULL on error. -/// Comments: -/// If structure element is StructTreeRoot, then this function will -/// return NULL. -FPDF_STRUCTELEMENT FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element, -) { - return _FPDF_StructElement_GetParent(struct_element, -); -} - -late final _FPDF_StructElement_GetParentPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetParent'); -late final _FPDF_StructElement_GetParent = _FPDF_StructElement_GetParentPtr.asFunction(); - -/// Function: FPDF_StructElement_GetAttributeCount -/// Count the number of attributes for the structure element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// Return value: -/// The number of attributes, or -1 on error. -int FPDF_StructElement_GetAttributeCount(FPDF_STRUCTELEMENT struct_element, -) { - return _FPDF_StructElement_GetAttributeCount(struct_element, -); -} - -late final _FPDF_StructElement_GetAttributeCountPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetAttributeCount'); -late final _FPDF_StructElement_GetAttributeCount = _FPDF_StructElement_GetAttributeCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetAttributeAtIndex -/// Get an attribute object in the structure element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// index - The index for the attribute object, 0-based. -/// Return value: -/// The attribute object at the n-th index or NULL on error. -/// Comments: -/// If the attribute object exists but is not a dict, then this -/// function will return NULL. This will also return NULL for out of -/// bounds indices. The caller does not own the handle. The handle -/// remains valid as long as |struct_element| remains valid. -/// The |index| must be less than the -/// FPDF_StructElement_GetAttributeCount() return value. -FPDF_STRUCTELEMENT_ATTR FPDF_StructElement_GetAttributeAtIndex(FPDF_STRUCTELEMENT struct_element, -int index, -) { - return _FPDF_StructElement_GetAttributeAtIndex(struct_element, -index, -); -} - -late final _FPDF_StructElement_GetAttributeAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetAttributeAtIndex'); -late final _FPDF_StructElement_GetAttributeAtIndex = _FPDF_StructElement_GetAttributeAtIndexPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetCount -/// Count the number of attributes in a structure element attribute map. -/// Parameters: -/// struct_attribute - Handle to the struct element attribute. -/// Return value: -/// The number of attributes, or -1 on error. -int FPDF_StructElement_Attr_GetCount(FPDF_STRUCTELEMENT_ATTR struct_attribute, -) { - return _FPDF_StructElement_Attr_GetCount(struct_attribute, -); -} - -late final _FPDF_StructElement_Attr_GetCountPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_Attr_GetCount'); -late final _FPDF_StructElement_Attr_GetCount = _FPDF_StructElement_Attr_GetCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetName -/// Get the name of an attribute in a structure element attribute map. -/// Parameters: -/// struct_attribute - Handle to the struct element attribute. -/// index - The index of attribute in the map. -/// buffer - A buffer for output. May be NULL. This is only -/// modified if |buflen| is longer than the length -/// of the key. Optional, pass null to just -/// retrieve the size of the buffer needed. -/// buflen - The length of the buffer. -/// out_buflen - A pointer to variable that will receive the -/// minimum buffer size to contain the key. Not -/// filled if FALSE is returned. -/// Return value: -/// TRUE if the operation was successful, FALSE otherwise. -int FPDF_StructElement_Attr_GetName(FPDF_STRUCTELEMENT_ATTR struct_attribute, -int index, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDF_StructElement_Attr_GetName(struct_attribute, -index, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDF_StructElement_Attr_GetNamePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetName'); -late final _FPDF_StructElement_Attr_GetName = _FPDF_StructElement_Attr_GetNamePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetValue -/// Get a handle to a value for an attribute in a structure element -/// attribute map. -/// Parameters: -/// struct_attribute - Handle to the struct element attribute. -/// name - The attribute name. -/// Return value: -/// Returns a handle to the value associated with the input, if any. -/// Returns NULL on failure. The caller does not own the handle. -/// The handle remains valid as long as |struct_attribute| remains -/// valid. -FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetValue(FPDF_STRUCTELEMENT_ATTR struct_attribute, -FPDF_BYTESTRING name, -) { - return _FPDF_StructElement_Attr_GetValue(struct_attribute, -name, -); -} - -late final _FPDF_StructElement_Attr_GetValuePtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_Attr_GetValue'); -late final _FPDF_StructElement_Attr_GetValue = _FPDF_StructElement_Attr_GetValuePtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetType -/// Get the type of an attribute in a structure element attribute map. -/// Parameters: -/// value - Handle to the value. -/// Return value: -/// Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of -/// failure. Note that this will never return FPDF_OBJECT_REFERENCE, as -/// references are always dereferenced. -int FPDF_StructElement_Attr_GetType(FPDF_STRUCTELEMENT_ATTR_VALUE value, -) { - return _FPDF_StructElement_Attr_GetType(value, -); -} - -late final _FPDF_StructElement_Attr_GetTypePtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_Attr_GetType'); -late final _FPDF_StructElement_Attr_GetType = _FPDF_StructElement_Attr_GetTypePtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetBooleanValue -/// Get the value of a boolean attribute in an attribute map as -/// FPDF_BOOL. FPDF_StructElement_Attr_GetType() should have returned -/// FPDF_OBJECT_BOOLEAN for this property. -/// Parameters: -/// value - Handle to the value. -/// out_value - A pointer to variable that will receive the value. Not -/// filled if false is returned. -/// Return value: -/// Returns TRUE if the attribute maps to a boolean value, FALSE -/// otherwise. -int FPDF_StructElement_Attr_GetBooleanValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, -ffi.Pointer out_value, -) { - return _FPDF_StructElement_Attr_GetBooleanValue(value, -out_value, -); -} - -late final _FPDF_StructElement_Attr_GetBooleanValuePtr = _lookup< - ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetBooleanValue'); -late final _FPDF_StructElement_Attr_GetBooleanValue = _FPDF_StructElement_Attr_GetBooleanValuePtr.asFunction )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetNumberValue -/// Get the value of a number attribute in an attribute map as float. -/// FPDF_StructElement_Attr_GetType() should have returned -/// FPDF_OBJECT_NUMBER for this property. -/// Parameters: -/// value - Handle to the value. -/// out_value - A pointer to variable that will receive the value. Not -/// filled if false is returned. -/// Return value: -/// Returns TRUE if the attribute maps to a number value, FALSE -/// otherwise. -int FPDF_StructElement_Attr_GetNumberValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, -ffi.Pointer out_value, -) { - return _FPDF_StructElement_Attr_GetNumberValue(value, -out_value, -); -} - -late final _FPDF_StructElement_Attr_GetNumberValuePtr = _lookup< - ffi.NativeFunction )>>('FPDF_StructElement_Attr_GetNumberValue'); -late final _FPDF_StructElement_Attr_GetNumberValue = _FPDF_StructElement_Attr_GetNumberValuePtr.asFunction )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetStringValue -/// Get the value of a string attribute in an attribute map as string. -/// FPDF_StructElement_Attr_GetType() should have returned -/// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME for this property. -/// Parameters: -/// value - Handle to the value. -/// buffer - A buffer for holding the returned key in UTF-16LE. -/// This is only modified if |buflen| is longer than the -/// length of the key. Optional, pass null to just -/// retrieve the size of the buffer needed. -/// buflen - The length of the buffer. -/// out_buflen - A pointer to variable that will receive the minimum -/// buffer size to contain the key. Not filled if FALSE is -/// returned. -/// Return value: -/// Returns TRUE if the attribute maps to a string value, FALSE -/// otherwise. -int FPDF_StructElement_Attr_GetStringValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDF_StructElement_Attr_GetStringValue(value, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDF_StructElement_Attr_GetStringValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetStringValue'); -late final _FPDF_StructElement_Attr_GetStringValue = _FPDF_StructElement_Attr_GetStringValuePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetBlobValue -/// Get the value of a blob attribute in an attribute map as string. -/// Parameters: -/// value - Handle to the value. -/// buffer - A buffer for holding the returned value. This is only -/// modified if |buflen| is at least as long as the length -/// of the value. Optional, pass null to just retrieve the -/// size of the buffer needed. -/// buflen - The length of the buffer. -/// out_buflen - A pointer to variable that will receive the minimum -/// buffer size to contain the key. Not filled if FALSE is -/// returned. -/// Return value: -/// Returns TRUE if the attribute maps to a string value, FALSE -/// otherwise. -int FPDF_StructElement_Attr_GetBlobValue(FPDF_STRUCTELEMENT_ATTR_VALUE value, -ffi.Pointer buffer, -int buflen, -ffi.Pointer out_buflen, -) { - return _FPDF_StructElement_Attr_GetBlobValue(value, -buffer, -buflen, -out_buflen, -); -} - -late final _FPDF_StructElement_Attr_GetBlobValuePtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong , ffi.Pointer )>>('FPDF_StructElement_Attr_GetBlobValue'); -late final _FPDF_StructElement_Attr_GetBlobValue = _FPDF_StructElement_Attr_GetBlobValuePtr.asFunction , int , ffi.Pointer )>(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_CountChildren -/// Count the number of children values in an attribute. -/// Parameters: -/// value - Handle to the value. -/// Return value: -/// The number of children, or -1 on error. -int FPDF_StructElement_Attr_CountChildren(FPDF_STRUCTELEMENT_ATTR_VALUE value, -) { - return _FPDF_StructElement_Attr_CountChildren(value, -); -} - -late final _FPDF_StructElement_Attr_CountChildrenPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_Attr_CountChildren'); -late final _FPDF_StructElement_Attr_CountChildren = _FPDF_StructElement_Attr_CountChildrenPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_Attr_GetChildAtIndex -/// Get a child from an attribute. -/// Parameters: -/// value - Handle to the value. -/// index - The index for the child, 0-based. -/// Return value: -/// The child at the n-th index or NULL on error. -/// Comments: -/// The |index| must be less than the -/// FPDF_StructElement_Attr_CountChildren() return value. -FPDF_STRUCTELEMENT_ATTR_VALUE FPDF_StructElement_Attr_GetChildAtIndex(FPDF_STRUCTELEMENT_ATTR_VALUE value, -int index, -) { - return _FPDF_StructElement_Attr_GetChildAtIndex(value, -index, -); -} - -late final _FPDF_StructElement_Attr_GetChildAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_Attr_GetChildAtIndex'); -late final _FPDF_StructElement_Attr_GetChildAtIndex = _FPDF_StructElement_Attr_GetChildAtIndexPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetMarkedContentIdCount -/// Get the count of marked content ids for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// Return value: -/// The count of marked content ids or -1 if none exists. -int FPDF_StructElement_GetMarkedContentIdCount(FPDF_STRUCTELEMENT struct_element, -) { - return _FPDF_StructElement_GetMarkedContentIdCount(struct_element, -); -} - -late final _FPDF_StructElement_GetMarkedContentIdCountPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdCount'); -late final _FPDF_StructElement_GetMarkedContentIdCount = _FPDF_StructElement_GetMarkedContentIdCountPtr.asFunction(); - -/// Experimental API. -/// Function: FPDF_StructElement_GetMarkedContentIdAtIndex -/// Get the marked content id at a given index for a given element. -/// Parameters: -/// struct_element - Handle to the struct element. -/// index - The index of the marked content id, 0-based. -/// Return value: -/// The marked content ID of the element. If no ID exists, returns -/// -1. -/// Comments: -/// The |index| must be less than the -/// FPDF_StructElement_GetMarkedContentIdCount() return value. -/// This will likely supersede FPDF_StructElement_GetMarkedContentID(). -int FPDF_StructElement_GetMarkedContentIdAtIndex(FPDF_STRUCTELEMENT struct_element, -int index, -) { - return _FPDF_StructElement_GetMarkedContentIdAtIndex(struct_element, -index, -); -} - -late final _FPDF_StructElement_GetMarkedContentIdAtIndexPtr = _lookup< - ffi.NativeFunction>('FPDF_StructElement_GetMarkedContentIdAtIndex'); -late final _FPDF_StructElement_GetMarkedContentIdAtIndex = _FPDF_StructElement_GetMarkedContentIdAtIndexPtr.asFunction(); - -/// Create a document availability provider. -/// -/// file_avail - pointer to file availability interface. -/// file - pointer to a file access interface. -/// -/// Returns a handle to the document availability provider, or NULL on error. -/// -/// FPDFAvail_Destroy() must be called when done with the availability provider. -FPDF_AVAIL FPDFAvail_Create(ffi.Pointer file_avail, -ffi.Pointer file, -) { - return _FPDFAvail_Create(file_avail, -file, -); -} - -late final _FPDFAvail_CreatePtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFAvail_Create'); -late final _FPDFAvail_Create = _FPDFAvail_CreatePtr.asFunction , ffi.Pointer )>(); - -/// Destroy the |avail| document availability provider. -/// -/// avail - handle to document availability provider to be destroyed. -void FPDFAvail_Destroy(FPDF_AVAIL avail, -) { - return _FPDFAvail_Destroy(avail, -); -} - -late final _FPDFAvail_DestroyPtr = _lookup< - ffi.NativeFunction>('FPDFAvail_Destroy'); -late final _FPDFAvail_Destroy = _FPDFAvail_DestroyPtr.asFunction(); - -/// Checks if the document is ready for loading, if not, gets download hints. -/// -/// avail - handle to document availability provider. -/// hints - pointer to a download hints interface. -/// -/// Returns one of: -/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. -/// PDF_DATA_NOTAVAIL: Data not yet available. -/// PDF_DATA_AVAIL: Data available. -/// -/// Applications should call this function whenever new data arrives, and process -/// all the generated download hints, if any, until the function returns -/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. -/// if hints is nullptr, the function just check current document availability. -/// -/// Once all data is available, call FPDFAvail_GetDocument() to get a document -/// handle. -int FPDFAvail_IsDocAvail(FPDF_AVAIL avail, -ffi.Pointer hints, -) { - return _FPDFAvail_IsDocAvail(avail, -hints, -); -} - -late final _FPDFAvail_IsDocAvailPtr = _lookup< - ffi.NativeFunction )>>('FPDFAvail_IsDocAvail'); -late final _FPDFAvail_IsDocAvail = _FPDFAvail_IsDocAvailPtr.asFunction )>(); - -/// Get document from the availability provider. -/// -/// avail - handle to document availability provider. -/// password - password for decrypting the PDF file. Optional. -/// -/// Returns a handle to the document. -/// -/// When FPDFAvail_IsDocAvail() returns TRUE, call FPDFAvail_GetDocument() to -/// retrieve the document handle. -/// See the comments for FPDF_LoadDocument() regarding the encoding for -/// |password|. -FPDF_DOCUMENT FPDFAvail_GetDocument(FPDF_AVAIL avail, -FPDF_BYTESTRING password, -) { - return _FPDFAvail_GetDocument(avail, -password, -); -} - -late final _FPDFAvail_GetDocumentPtr = _lookup< - ffi.NativeFunction>('FPDFAvail_GetDocument'); -late final _FPDFAvail_GetDocument = _FPDFAvail_GetDocumentPtr.asFunction(); - -/// Get the page number for the first available page in a linearized PDF. -/// -/// doc - document handle. -/// -/// Returns the zero-based index for the first available page. -/// -/// For most linearized PDFs, the first available page will be the first page, -/// however, some PDFs might make another page the first available page. -/// For non-linearized PDFs, this function will always return zero. -int FPDFAvail_GetFirstPageNum(FPDF_DOCUMENT doc, -) { - return _FPDFAvail_GetFirstPageNum(doc, -); -} - -late final _FPDFAvail_GetFirstPageNumPtr = _lookup< - ffi.NativeFunction>('FPDFAvail_GetFirstPageNum'); -late final _FPDFAvail_GetFirstPageNum = _FPDFAvail_GetFirstPageNumPtr.asFunction(); - -/// Check if |page_index| is ready for loading, if not, get the -/// |FX_DOWNLOADHINTS|. -/// -/// avail - handle to document availability provider. -/// page_index - index number of the page. Zero for the first page. -/// hints - pointer to a download hints interface. Populated if -/// |page_index| is not available. -/// -/// Returns one of: -/// PDF_DATA_ERROR: A common error is returned. Data availability unknown. -/// PDF_DATA_NOTAVAIL: Data not yet available. -/// PDF_DATA_AVAIL: Data available. -/// -/// This function can be called only after FPDFAvail_GetDocument() is called. -/// Applications should call this function whenever new data arrives and process -/// all the generated download |hints|, if any, until this function returns -/// |PDF_DATA_ERROR| or |PDF_DATA_AVAIL|. Applications can then perform page -/// loading. -/// if hints is nullptr, the function just check current availability of -/// specified page. -int FPDFAvail_IsPageAvail(FPDF_AVAIL avail, -int page_index, -ffi.Pointer hints, -) { - return _FPDFAvail_IsPageAvail(avail, -page_index, -hints, -); -} - -late final _FPDFAvail_IsPageAvailPtr = _lookup< - ffi.NativeFunction )>>('FPDFAvail_IsPageAvail'); -late final _FPDFAvail_IsPageAvail = _FPDFAvail_IsPageAvailPtr.asFunction )>(); - -/// Check if form data is ready for initialization, if not, get the -/// |FX_DOWNLOADHINTS|. -/// -/// avail - handle to document availability provider. -/// hints - pointer to a download hints interface. Populated if form is not -/// ready for initialization. -/// -/// Returns one of: -/// PDF_FORM_ERROR: A common eror, in general incorrect parameters. -/// PDF_FORM_NOTAVAIL: Data not available. -/// PDF_FORM_AVAIL: Data available. -/// PDF_FORM_NOTEXIST: No form data. -/// -/// This function can be called only after FPDFAvail_GetDocument() is called. -/// The application should call this function whenever new data arrives and -/// process all the generated download |hints|, if any, until the function -/// |PDF_FORM_ERROR|, |PDF_FORM_AVAIL| or |PDF_FORM_NOTEXIST|. -/// if hints is nullptr, the function just check current form availability. -/// -/// Applications can then perform page loading. It is recommend to call -/// FPDFDOC_InitFormFillEnvironment() when |PDF_FORM_AVAIL| is returned. -int FPDFAvail_IsFormAvail(FPDF_AVAIL avail, -ffi.Pointer hints, -) { - return _FPDFAvail_IsFormAvail(avail, -hints, -); -} - -late final _FPDFAvail_IsFormAvailPtr = _lookup< - ffi.NativeFunction )>>('FPDFAvail_IsFormAvail'); -late final _FPDFAvail_IsFormAvail = _FPDFAvail_IsFormAvailPtr.asFunction )>(); - -/// Check whether a document is a linearized PDF. -/// -/// avail - handle to document availability provider. -/// -/// Returns one of: -/// PDF_LINEARIZED -/// PDF_NOT_LINEARIZED -/// PDF_LINEARIZATION_UNKNOWN -/// -/// FPDFAvail_IsLinearized() will return |PDF_LINEARIZED| or |PDF_NOT_LINEARIZED| -/// when we have 1k of data. If the files size less than 1k, it returns -/// |PDF_LINEARIZATION_UNKNOWN| as there is insufficient information to determine -/// if the PDF is linearlized. -int FPDFAvail_IsLinearized(FPDF_AVAIL avail, -) { - return _FPDFAvail_IsLinearized(avail, -); -} - -late final _FPDFAvail_IsLinearizedPtr = _lookup< - ffi.NativeFunction>('FPDFAvail_IsLinearized'); -late final _FPDFAvail_IsLinearized = _FPDFAvail_IsLinearizedPtr.asFunction(); - -/// Setup an unsupported object handler. -/// -/// unsp_info - Pointer to an UNSUPPORT_INFO structure. -/// -/// Returns TRUE on success. -int FSDK_SetUnSpObjProcessHandler(ffi.Pointer unsp_info, -) { - return _FSDK_SetUnSpObjProcessHandler(unsp_info, -); -} - -late final _FSDK_SetUnSpObjProcessHandlerPtr = _lookup< - ffi.NativeFunction )>>('FSDK_SetUnSpObjProcessHandler'); -late final _FSDK_SetUnSpObjProcessHandler = _FSDK_SetUnSpObjProcessHandlerPtr.asFunction )>(); - -/// Set replacement function for calls to time(). -/// -/// This API is intended to be used only for testing, thus may cause PDFium to -/// behave poorly in production environments. -/// -/// func - Function pointer to alternate implementation of time(), or -/// NULL to restore to actual time() call itself. -void FSDK_SetTimeFunction(ffi.Pointer> func, -) { - return _FSDK_SetTimeFunction(func, -); -} - -late final _FSDK_SetTimeFunctionPtr = _lookup< - ffi.NativeFunction> )>>('FSDK_SetTimeFunction'); -late final _FSDK_SetTimeFunction = _FSDK_SetTimeFunctionPtr.asFunction> )>(); - -/// Set replacement function for calls to localtime(). -/// -/// This API is intended to be used only for testing, thus may cause PDFium to -/// behave poorly in production environments. -/// -/// func - Function pointer to alternate implementation of localtime(), or -/// NULL to restore to actual localtime() call itself. -void FSDK_SetLocaltimeFunction(ffi.Pointer Function(ffi.Pointer )>> func, -) { - return _FSDK_SetLocaltimeFunction(func, -); -} - -late final _FSDK_SetLocaltimeFunctionPtr = _lookup< - ffi.NativeFunction Function(ffi.Pointer )>> )>>('FSDK_SetLocaltimeFunction'); -late final _FSDK_SetLocaltimeFunction = _FSDK_SetLocaltimeFunctionPtr.asFunction Function(ffi.Pointer )>> )>(); - -/// Get the document's PageMode. -/// -/// doc - Handle to document. -/// -/// Returns one of the |PAGEMODE_*| flags defined above. -/// -/// The page mode defines how the document should be initially displayed. -int FPDFDoc_GetPageMode(FPDF_DOCUMENT document, -) { - return _FPDFDoc_GetPageMode(document, -); -} - -late final _FPDFDoc_GetPageModePtr = _lookup< - ffi.NativeFunction>('FPDFDoc_GetPageMode'); -late final _FPDFDoc_GetPageMode = _FPDFDoc_GetPageModePtr.asFunction(); - -/// Set "MediaBox" entry to the page dictionary. -/// -/// page - Handle to a page. -/// left - The left of the rectangle. -/// bottom - The bottom of the rectangle. -/// right - The right of the rectangle. -/// top - The top of the rectangle. -void FPDFPage_SetMediaBox(FPDF_PAGE page, -double left, -double bottom, -double right, -double top, -) { - return _FPDFPage_SetMediaBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_SetMediaBoxPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetMediaBox'); -late final _FPDFPage_SetMediaBox = _FPDFPage_SetMediaBoxPtr.asFunction(); - -/// Set "CropBox" entry to the page dictionary. -/// -/// page - Handle to a page. -/// left - The left of the rectangle. -/// bottom - The bottom of the rectangle. -/// right - The right of the rectangle. -/// top - The top of the rectangle. -void FPDFPage_SetCropBox(FPDF_PAGE page, -double left, -double bottom, -double right, -double top, -) { - return _FPDFPage_SetCropBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_SetCropBoxPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetCropBox'); -late final _FPDFPage_SetCropBox = _FPDFPage_SetCropBoxPtr.asFunction(); - -/// Set "BleedBox" entry to the page dictionary. -/// -/// page - Handle to a page. -/// left - The left of the rectangle. -/// bottom - The bottom of the rectangle. -/// right - The right of the rectangle. -/// top - The top of the rectangle. -void FPDFPage_SetBleedBox(FPDF_PAGE page, -double left, -double bottom, -double right, -double top, -) { - return _FPDFPage_SetBleedBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_SetBleedBoxPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetBleedBox'); -late final _FPDFPage_SetBleedBox = _FPDFPage_SetBleedBoxPtr.asFunction(); - -/// Set "TrimBox" entry to the page dictionary. -/// -/// page - Handle to a page. -/// left - The left of the rectangle. -/// bottom - The bottom of the rectangle. -/// right - The right of the rectangle. -/// top - The top of the rectangle. -void FPDFPage_SetTrimBox(FPDF_PAGE page, -double left, -double bottom, -double right, -double top, -) { - return _FPDFPage_SetTrimBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_SetTrimBoxPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetTrimBox'); -late final _FPDFPage_SetTrimBox = _FPDFPage_SetTrimBoxPtr.asFunction(); - -/// Set "ArtBox" entry to the page dictionary. -/// -/// page - Handle to a page. -/// left - The left of the rectangle. -/// bottom - The bottom of the rectangle. -/// right - The right of the rectangle. -/// top - The top of the rectangle. -void FPDFPage_SetArtBox(FPDF_PAGE page, -double left, -double bottom, -double right, -double top, -) { - return _FPDFPage_SetArtBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_SetArtBoxPtr = _lookup< - ffi.NativeFunction>('FPDFPage_SetArtBox'); -late final _FPDFPage_SetArtBox = _FPDFPage_SetArtBoxPtr.asFunction(); - -/// Get "MediaBox" entry from the page dictionary. -/// -/// page - Handle to a page. -/// left - Pointer to a float value receiving the left of the rectangle. -/// bottom - Pointer to a float value receiving the bottom of the rectangle. -/// right - Pointer to a float value receiving the right of the rectangle. -/// top - Pointer to a float value receiving the top of the rectangle. -/// -/// On success, return true and write to the out parameters. Otherwise return -/// false and leave the out parameters unmodified. -int FPDFPage_GetMediaBox(FPDF_PAGE page, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPage_GetMediaBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_GetMediaBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetMediaBox'); -late final _FPDFPage_GetMediaBox = _FPDFPage_GetMediaBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Get "CropBox" entry from the page dictionary. -/// -/// page - Handle to a page. -/// left - Pointer to a float value receiving the left of the rectangle. -/// bottom - Pointer to a float value receiving the bottom of the rectangle. -/// right - Pointer to a float value receiving the right of the rectangle. -/// top - Pointer to a float value receiving the top of the rectangle. -/// -/// On success, return true and write to the out parameters. Otherwise return -/// false and leave the out parameters unmodified. -int FPDFPage_GetCropBox(FPDF_PAGE page, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPage_GetCropBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_GetCropBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetCropBox'); -late final _FPDFPage_GetCropBox = _FPDFPage_GetCropBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Get "BleedBox" entry from the page dictionary. -/// -/// page - Handle to a page. -/// left - Pointer to a float value receiving the left of the rectangle. -/// bottom - Pointer to a float value receiving the bottom of the rectangle. -/// right - Pointer to a float value receiving the right of the rectangle. -/// top - Pointer to a float value receiving the top of the rectangle. -/// -/// On success, return true and write to the out parameters. Otherwise return -/// false and leave the out parameters unmodified. -int FPDFPage_GetBleedBox(FPDF_PAGE page, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPage_GetBleedBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_GetBleedBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetBleedBox'); -late final _FPDFPage_GetBleedBox = _FPDFPage_GetBleedBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Get "TrimBox" entry from the page dictionary. -/// -/// page - Handle to a page. -/// left - Pointer to a float value receiving the left of the rectangle. -/// bottom - Pointer to a float value receiving the bottom of the rectangle. -/// right - Pointer to a float value receiving the right of the rectangle. -/// top - Pointer to a float value receiving the top of the rectangle. -/// -/// On success, return true and write to the out parameters. Otherwise return -/// false and leave the out parameters unmodified. -int FPDFPage_GetTrimBox(FPDF_PAGE page, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPage_GetTrimBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_GetTrimBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetTrimBox'); -late final _FPDFPage_GetTrimBox = _FPDFPage_GetTrimBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Get "ArtBox" entry from the page dictionary. -/// -/// page - Handle to a page. -/// left - Pointer to a float value receiving the left of the rectangle. -/// bottom - Pointer to a float value receiving the bottom of the rectangle. -/// right - Pointer to a float value receiving the right of the rectangle. -/// top - Pointer to a float value receiving the top of the rectangle. -/// -/// On success, return true and write to the out parameters. Otherwise return -/// false and leave the out parameters unmodified. -int FPDFPage_GetArtBox(FPDF_PAGE page, -ffi.Pointer left, -ffi.Pointer bottom, -ffi.Pointer right, -ffi.Pointer top, -) { - return _FPDFPage_GetArtBox(page, -left, -bottom, -right, -top, -); -} - -late final _FPDFPage_GetArtBoxPtr = _lookup< - ffi.NativeFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>>('FPDFPage_GetArtBox'); -late final _FPDFPage_GetArtBox = _FPDFPage_GetArtBoxPtr.asFunction , ffi.Pointer , ffi.Pointer , ffi.Pointer )>(); - -/// Apply transforms to |page|. -/// -/// If |matrix| is provided it will be applied to transform the page. -/// If |clipRect| is provided it will be used to clip the resulting page. -/// If neither |matrix| or |clipRect| are provided this method returns |false|. -/// Returns |true| if transforms are applied. -/// -/// This function will transform the whole page, and would take effect to all the -/// objects in the page. -/// -/// page - Page handle. -/// matrix - Transform matrix. -/// clipRect - Clipping rectangle. -int FPDFPage_TransFormWithClip(FPDF_PAGE page, -ffi.Pointer matrix, -ffi.Pointer clipRect, -) { - return _FPDFPage_TransFormWithClip(page, -matrix, -clipRect, -); -} - -late final _FPDFPage_TransFormWithClipPtr = _lookup< - ffi.NativeFunction , ffi.Pointer )>>('FPDFPage_TransFormWithClip'); -late final _FPDFPage_TransFormWithClip = _FPDFPage_TransFormWithClipPtr.asFunction , ffi.Pointer )>(); - -/// Transform (scale, rotate, shear, move) the clip path of page object. -/// page_object - Handle to a page object. Returned by -/// FPDFPageObj_NewImageObj(). -/// -/// a - The coefficient "a" of the matrix. -/// b - The coefficient "b" of the matrix. -/// c - The coefficient "c" of the matrix. -/// d - The coefficient "d" of the matrix. -/// e - The coefficient "e" of the matrix. -/// f - The coefficient "f" of the matrix. -void FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object, -double a, -double b, -double c, -double d, -double e, -double f, -) { - return _FPDFPageObj_TransformClipPath(page_object, -a, -b, -c, -d, -e, -f, -); -} - -late final _FPDFPageObj_TransformClipPathPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_TransformClipPath'); -late final _FPDFPageObj_TransformClipPath = _FPDFPageObj_TransformClipPathPtr.asFunction(); - -/// Experimental API. -/// Get the clip path of the page object. -/// -/// page object - Handle to a page object. Returned by e.g. -/// FPDFPage_GetObject(). -/// -/// Returns the handle to the clip path, or NULL on failure. The caller does not -/// take ownership of the returned FPDF_CLIPPATH. Instead, it remains valid until -/// FPDF_ClosePage() is called for the page containing |page_object|. -FPDF_CLIPPATH FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object, -) { - return _FPDFPageObj_GetClipPath(page_object, -); -} - -late final _FPDFPageObj_GetClipPathPtr = _lookup< - ffi.NativeFunction>('FPDFPageObj_GetClipPath'); -late final _FPDFPageObj_GetClipPath = _FPDFPageObj_GetClipPathPtr.asFunction(); - -/// Experimental API. -/// Get number of paths inside |clip_path|. -/// -/// clip_path - handle to a clip_path. -/// -/// Returns the number of objects in |clip_path| or -1 on failure. -int FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path, -) { - return _FPDFClipPath_CountPaths(clip_path, -); -} - -late final _FPDFClipPath_CountPathsPtr = _lookup< - ffi.NativeFunction>('FPDFClipPath_CountPaths'); -late final _FPDFClipPath_CountPaths = _FPDFClipPath_CountPathsPtr.asFunction(); - -/// Experimental API. -/// Get number of segments inside one path of |clip_path|. -/// -/// clip_path - handle to a clip_path. -/// path_index - index into the array of paths of the clip path. -/// -/// Returns the number of segments or -1 on failure. -int FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, -int path_index, -) { - return _FPDFClipPath_CountPathSegments(clip_path, -path_index, -); -} - -late final _FPDFClipPath_CountPathSegmentsPtr = _lookup< - ffi.NativeFunction>('FPDFClipPath_CountPathSegments'); -late final _FPDFClipPath_CountPathSegments = _FPDFClipPath_CountPathSegmentsPtr.asFunction(); - -/// Experimental API. -/// Get segment in one specific path of |clip_path| at index. -/// -/// clip_path - handle to a clip_path. -/// path_index - the index of a path. -/// segment_index - the index of a segment. -/// -/// Returns the handle to the segment, or NULL on failure. The caller does not -/// take ownership of the returned FPDF_PATHSEGMENT. Instead, it remains valid -/// until FPDF_ClosePage() is called for the page containing |clip_path|. -FPDF_PATHSEGMENT FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path, -int path_index, -int segment_index, -) { - return _FPDFClipPath_GetPathSegment(clip_path, -path_index, -segment_index, -); -} - -late final _FPDFClipPath_GetPathSegmentPtr = _lookup< - ffi.NativeFunction>('FPDFClipPath_GetPathSegment'); -late final _FPDFClipPath_GetPathSegment = _FPDFClipPath_GetPathSegmentPtr.asFunction(); - -/// Create a new clip path, with a rectangle inserted. -/// -/// Caller takes ownership of the returned FPDF_CLIPPATH. It should be freed with -/// FPDF_DestroyClipPath(). -/// -/// left - The left of the clip box. -/// bottom - The bottom of the clip box. -/// right - The right of the clip box. -/// top - The top of the clip box. -FPDF_CLIPPATH FPDF_CreateClipPath(double left, -double bottom, -double right, -double top, -) { - return _FPDF_CreateClipPath(left, -bottom, -right, -top, -); -} - -late final _FPDF_CreateClipPathPtr = _lookup< - ffi.NativeFunction>('FPDF_CreateClipPath'); -late final _FPDF_CreateClipPath = _FPDF_CreateClipPathPtr.asFunction(); - -/// Destroy the clip path. -/// -/// clipPath - A handle to the clip path. It will be invalid after this call. -void FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath, -) { - return _FPDF_DestroyClipPath(clipPath, -); -} - -late final _FPDF_DestroyClipPathPtr = _lookup< - ffi.NativeFunction>('FPDF_DestroyClipPath'); -late final _FPDF_DestroyClipPath = _FPDF_DestroyClipPathPtr.asFunction(); - -/// Clip the page content, the page content that outside the clipping region -/// become invisible. -/// -/// A clip path will be inserted before the page content stream or content array. -/// In this way, the page content will be clipped by this clip path. -/// -/// page - A page handle. -/// clipPath - A handle to the clip path. (Does not take ownership.) -void FPDFPage_InsertClipPath(FPDF_PAGE page, -FPDF_CLIPPATH clipPath, -) { - return _FPDFPage_InsertClipPath(page, -clipPath, -); -} - -late final _FPDFPage_InsertClipPathPtr = _lookup< - ffi.NativeFunction>('FPDFPage_InsertClipPath'); -late final _FPDFPage_InsertClipPath = _FPDFPage_InsertClipPathPtr.asFunction(); - -/// Flatten annotations and form fields into the page contents. -/// -/// page - handle to the page. -/// nFlag - One of the |FLAT_*| values denoting the page usage. -/// -/// Returns one of the |FLATTEN_*| values. -/// -/// Currently, all failures return |FLATTEN_FAIL| with no indication of the -/// cause. -int FPDFPage_Flatten(FPDF_PAGE page, -int nFlag, -) { - return _FPDFPage_Flatten(page, -nFlag, -); -} - -late final _FPDFPage_FlattenPtr = _lookup< - ffi.NativeFunction>('FPDFPage_Flatten'); -late final _FPDFPage_Flatten = _FPDFPage_FlattenPtr.asFunction(); - -/// Experimental API. -/// Gets the decoded data from the thumbnail of |page| if it exists. -/// This only modifies |buffer| if |buflen| less than or equal to the -/// size of the decoded data. Returns the size of the decoded -/// data or 0 if thumbnail DNE. Optional, pass null to just retrieve -/// the size of the buffer needed. -/// -/// page - handle to a page. -/// buffer - buffer for holding the decoded image data. -/// buflen - length of the buffer in bytes. -int FPDFPage_GetDecodedThumbnailData(FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFPage_GetDecodedThumbnailData(page, -buffer, -buflen, -); -} - -late final _FPDFPage_GetDecodedThumbnailDataPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetDecodedThumbnailData'); -late final _FPDFPage_GetDecodedThumbnailData = _FPDFPage_GetDecodedThumbnailDataPtr.asFunction , int )>(); - -/// Experimental API. -/// Gets the raw data from the thumbnail of |page| if it exists. -/// This only modifies |buffer| if |buflen| is less than or equal to -/// the size of the raw data. Returns the size of the raw data or 0 -/// if thumbnail DNE. Optional, pass null to just retrieve the size -/// of the buffer needed. -/// -/// page - handle to a page. -/// buffer - buffer for holding the raw image data. -/// buflen - length of the buffer in bytes. -int FPDFPage_GetRawThumbnailData(FPDF_PAGE page, -ffi.Pointer buffer, -int buflen, -) { - return _FPDFPage_GetRawThumbnailData(page, -buffer, -buflen, -); -} - -late final _FPDFPage_GetRawThumbnailDataPtr = _lookup< - ffi.NativeFunction , ffi.UnsignedLong )>>('FPDFPage_GetRawThumbnailData'); -late final _FPDFPage_GetRawThumbnailData = _FPDFPage_GetRawThumbnailDataPtr.asFunction , int )>(); - -/// Experimental API. -/// Returns the thumbnail of |page| as a FPDF_BITMAP. Returns a nullptr -/// if unable to access the thumbnail's stream. -/// -/// page - handle to a page. -FPDF_BITMAP FPDFPage_GetThumbnailAsBitmap(FPDF_PAGE page, -) { - return _FPDFPage_GetThumbnailAsBitmap(page, -); -} - -late final _FPDFPage_GetThumbnailAsBitmapPtr = _lookup< - ffi.NativeFunction>('FPDFPage_GetThumbnailAsBitmap'); -late final _FPDFPage_GetThumbnailAsBitmap = _FPDFPage_GetThumbnailAsBitmapPtr.asFunction(); - -} - -/// PDF text rendering modes -enum FPDF_TEXT_RENDERMODE { - FPDF_TEXTRENDERMODE_UNKNOWN(-1), - FPDF_TEXTRENDERMODE_FILL(0), - FPDF_TEXTRENDERMODE_STROKE(1), - FPDF_TEXTRENDERMODE_FILL_STROKE(2), - FPDF_TEXTRENDERMODE_INVISIBLE(3), - FPDF_TEXTRENDERMODE_FILL_CLIP(4), - FPDF_TEXTRENDERMODE_STROKE_CLIP(5), - FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP(6), - FPDF_TEXTRENDERMODE_CLIP(7); - - static const FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP; - - final int value; - const FPDF_TEXT_RENDERMODE(this.value); - - static FPDF_TEXT_RENDERMODE fromValue(int value) => switch (value) { - -1 => FPDF_TEXTRENDERMODE_UNKNOWN, - 0 => FPDF_TEXTRENDERMODE_FILL, - 1 => FPDF_TEXTRENDERMODE_STROKE, - 2 => FPDF_TEXTRENDERMODE_FILL_STROKE, - 3 => FPDF_TEXTRENDERMODE_INVISIBLE, - 4 => FPDF_TEXTRENDERMODE_FILL_CLIP, - 5 => FPDF_TEXTRENDERMODE_STROKE_CLIP, - 6 => FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP, - 7 => FPDF_TEXTRENDERMODE_CLIP, - _ => throw ArgumentError('Unknown value for FPDF_TEXT_RENDERMODE: $value'), - }; - - @override - String toString() { - if (this == FPDF_TEXTRENDERMODE_CLIP) return "FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE.FPDF_TEXTRENDERMODE_LAST"; - return super.toString(); - }} - -final class fpdf_action_t__ extends ffi.Opaque{ -} - -/// PDF types - use incomplete types (never completed) to force API type safety. -typedef FPDF_ACTION = ffi.Pointer; -final class fpdf_annotation_t__ extends ffi.Opaque{ -} - -typedef FPDF_ANNOTATION = ffi.Pointer; -final class fpdf_attachment_t__ extends ffi.Opaque{ -} - -typedef FPDF_ATTACHMENT = ffi.Pointer; -final class fpdf_avail_t__ extends ffi.Opaque{ -} - -typedef FPDF_AVAIL = ffi.Pointer; -final class fpdf_bitmap_t__ extends ffi.Opaque{ -} - -typedef FPDF_BITMAP = ffi.Pointer; -final class fpdf_bookmark_t__ extends ffi.Opaque{ -} - -typedef FPDF_BOOKMARK = ffi.Pointer; -final class fpdf_clippath_t__ extends ffi.Opaque{ -} - -typedef FPDF_CLIPPATH = ffi.Pointer; -final class fpdf_dest_t__ extends ffi.Opaque{ -} - -typedef FPDF_DEST = ffi.Pointer; -final class fpdf_document_t__ extends ffi.Opaque{ -} - -typedef FPDF_DOCUMENT = ffi.Pointer; -final class fpdf_font_t__ extends ffi.Opaque{ -} - -typedef FPDF_FONT = ffi.Pointer; -final class fpdf_form_handle_t__ extends ffi.Opaque{ -} - -typedef FPDF_FORMHANDLE = ffi.Pointer; -final class fpdf_glyphpath_t__ extends ffi.Opaque{ -} - -typedef FPDF_GLYPHPATH = ffi.Pointer; -final class fpdf_javascript_action_t extends ffi.Opaque{ -} - -typedef FPDF_JAVASCRIPT_ACTION = ffi.Pointer; -final class fpdf_link_t__ extends ffi.Opaque{ -} - -typedef FPDF_LINK = ffi.Pointer; -final class fpdf_page_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGE = ffi.Pointer; -final class fpdf_pagelink_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGELINK = ffi.Pointer; -final class fpdf_pageobject_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGEOBJECT = ffi.Pointer; -final class fpdf_pageobjectmark_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGEOBJECTMARK = ffi.Pointer; -final class fpdf_pagerange_t__ extends ffi.Opaque{ -} - -typedef FPDF_PAGERANGE = ffi.Pointer; -final class fpdf_pathsegment_t extends ffi.Opaque{ -} - -typedef FPDF_PATHSEGMENT = ffi.Pointer; -final class fpdf_schhandle_t__ extends ffi.Opaque{ -} - -typedef FPDF_SCHHANDLE = ffi.Pointer; -final class fpdf_signature_t__ extends ffi.Opaque{ -} - -typedef FPDF_SIGNATURE = ffi.Pointer; -typedef FPDF_SKIA_CANVAS = ffi.Pointer; -final class fpdf_structelement_t__ extends ffi.Opaque{ -} - -typedef FPDF_STRUCTELEMENT = ffi.Pointer; -final class fpdf_structelement_attr_t__ extends ffi.Opaque{ -} - -typedef FPDF_STRUCTELEMENT_ATTR = ffi.Pointer; -final class fpdf_structelement_attr_value_t__ extends ffi.Opaque{ -} - -typedef FPDF_STRUCTELEMENT_ATTR_VALUE = ffi.Pointer; -final class fpdf_structtree_t__ extends ffi.Opaque{ -} - -typedef FPDF_STRUCTTREE = ffi.Pointer; -final class fpdf_textpage_t__ extends ffi.Opaque{ -} - -typedef FPDF_TEXTPAGE = ffi.Pointer; -final class fpdf_widget_t__ extends ffi.Opaque{ -} - -typedef FPDF_WIDGET = ffi.Pointer; -final class fpdf_xobject_t__ extends ffi.Opaque{ -} - -typedef FPDF_XOBJECT = ffi.Pointer; -/// Basic data types -typedef FPDF_BOOL = ffi.Int; -typedef DartFPDF_BOOL = int; -typedef FPDF_RESULT = ffi.Int; -typedef DartFPDF_RESULT = int; -typedef FPDF_DWORD = ffi.UnsignedLong; -typedef DartFPDF_DWORD = int; -typedef FS_FLOAT = ffi.Float; -typedef DartFS_FLOAT = double; -/// Duplex types -enum _FPDF_DUPLEXTYPE_ { - DuplexUndefined(0), - Simplex(1), - DuplexFlipShortEdge(2), - DuplexFlipLongEdge(3); - - - final int value; - const _FPDF_DUPLEXTYPE_(this.value); - - static _FPDF_DUPLEXTYPE_ fromValue(int value) => switch (value) { - 0 => DuplexUndefined, - 1 => Simplex, - 2 => DuplexFlipShortEdge, - 3 => DuplexFlipLongEdge, - _ => throw ArgumentError('Unknown value for _FPDF_DUPLEXTYPE_: $value'), - }; - -} - -/// String types -typedef FPDF_WCHAR = ffi.UnsignedShort; -typedef DartFPDF_WCHAR = int; -/// Public PDFium API type for byte strings. -typedef FPDF_BYTESTRING = ffi.Pointer; -/// The public PDFium API always uses UTF-16LE encoded wide strings, each -/// character uses 2 bytes (except surrogation), with the low byte first. -typedef FPDF_WIDESTRING = ffi.Pointer; -/// Structure for persisting a string beyond the duration of a callback. -/// Note: although represented as a char*, string may be interpreted as -/// a UTF-16LE formated string. Used only by XFA callbacks. -final class FPDF_BSTR_ extends ffi.Struct{ - /// String buffer, manipulate only with FPDF_BStr_* methods. - external ffi.Pointer str; - - /// Length of the string, in bytes. - @ffi.Int() - external int len; - -} - -/// Structure for persisting a string beyond the duration of a callback. -/// Note: although represented as a char*, string may be interpreted as -/// a UTF-16LE formated string. Used only by XFA callbacks. -typedef FPDF_BSTR = FPDF_BSTR_; -/// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a -/// Windows unicode string, however, special care needs to be taken if you -/// expect to process Unicode larger than 0xffff. -/// -/// For Linux/Unix programmers: most compiler/library environments use 4 bytes -/// for a Unicode character, and you have to convert between FPDF_WIDESTRING and -/// system wide string by yourself. -typedef FPDF_STRING = ffi.Pointer; -/// Matrix for transformation, in the form [a b c d e f], equivalent to: -/// | a b 0 | -/// | c d 0 | -/// | e f 1 | -/// -/// Translation is performed with [1 0 0 1 tx ty]. -/// Scaling is performed with [sx 0 0 sy 0 0]. -/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. -final class _FS_MATRIX_ extends ffi.Struct{ - @ffi.Float() - external double a; - - @ffi.Float() - external double b; - - @ffi.Float() - external double c; - - @ffi.Float() - external double d; - - @ffi.Float() - external double e; - - @ffi.Float() - external double f; - -} - -/// Matrix for transformation, in the form [a b c d e f], equivalent to: -/// | a b 0 | -/// | c d 0 | -/// | e f 1 | -/// -/// Translation is performed with [1 0 0 1 tx ty]. -/// Scaling is performed with [sx 0 0 sy 0 0]. -/// See PDF Reference 1.7, 4.2.2 Common Transformations for more. -typedef FS_MATRIX = _FS_MATRIX_; -/// Rectangle area(float) in device or page coordinate system. -final class _FS_RECTF_ extends ffi.Struct{ - /// The x-coordinate of the left-top corner. - @ffi.Float() - external double left; - - /// The y-coordinate of the left-top corner. - @ffi.Float() - external double top; - - /// The x-coordinate of the right-bottom corner. - @ffi.Float() - external double right; - - /// The y-coordinate of the right-bottom corner. - @ffi.Float() - external double bottom; - -} - -/// Rectangle area(float) in device or page coordinate system. -typedef FS_LPRECTF = ffi.Pointer<_FS_RECTF_>; -typedef FS_RECTF = _FS_RECTF_; -/// Const Pointer to FS_RECTF structure. -typedef FS_LPCRECTF = ffi.Pointer; -/// Rectangle size. Coordinate system agnostic. -final class FS_SIZEF_ extends ffi.Struct{ - @ffi.Float() - external double width; - - @ffi.Float() - external double height; - -} - -/// Rectangle size. Coordinate system agnostic. -typedef FS_LPSIZEF = ffi.Pointer; -typedef FS_SIZEF = FS_SIZEF_; -/// Const Pointer to FS_SIZEF structure. -typedef FS_LPCSIZEF = ffi.Pointer; -/// 2D Point. Coordinate system agnostic. -final class FS_POINTF_ extends ffi.Struct{ - @ffi.Float() - external double x; - - @ffi.Float() - external double y; - -} - -/// 2D Point. Coordinate system agnostic. -typedef FS_LPPOINTF = ffi.Pointer; -typedef FS_POINTF = FS_POINTF_; -/// Const Pointer to FS_POINTF structure. -typedef FS_LPCPOINTF = ffi.Pointer; -final class _FS_QUADPOINTSF extends ffi.Struct{ - @FS_FLOAT() - external double x1; - - @FS_FLOAT() - external double y1; - - @FS_FLOAT() - external double x2; - - @FS_FLOAT() - external double y2; - - @FS_FLOAT() - external double x3; - - @FS_FLOAT() - external double y3; - - @FS_FLOAT() - external double x4; - - @FS_FLOAT() - external double y4; - -} - -typedef FS_QUADPOINTSF = _FS_QUADPOINTSF; -/// Annotation enums. -typedef FPDF_ANNOTATION_SUBTYPE = ffi.Int; -typedef DartFPDF_ANNOTATION_SUBTYPE = int; -typedef FPDF_ANNOT_APPEARANCEMODE = ffi.Int; -typedef DartFPDF_ANNOT_APPEARANCEMODE = int; -/// Dictionary value types. -typedef FPDF_OBJECT_TYPE = ffi.Int; -typedef DartFPDF_OBJECT_TYPE = int; -/// PDF renderer types - Experimental. -/// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. -enum FPDF_RENDERER_TYPE { - /// Anti-Grain Geometry - https://sourceforge.net/projects/agg/ - FPDF_RENDERERTYPE_AGG(0), - /// Skia - https://skia.org/ - FPDF_RENDERERTYPE_SKIA(1); - - - final int value; - const FPDF_RENDERER_TYPE(this.value); - - static FPDF_RENDERER_TYPE fromValue(int value) => switch (value) { - 0 => FPDF_RENDERERTYPE_AGG, - 1 => FPDF_RENDERERTYPE_SKIA, - _ => throw ArgumentError('Unknown value for FPDF_RENDERER_TYPE: $value'), - }; - -} - -/// Process-wide options for initializing the library. -final class FPDF_LIBRARY_CONFIG_ extends ffi.Struct{ - /// Version number of the interface. Currently must be 2. - /// Support for version 1 will be deprecated in the future. - @ffi.Int() - external int version; - - /// Array of paths to scan in place of the defaults when using built-in - /// FXGE font loading code. The array is terminated by a NULL pointer. - /// The Array may be NULL itself to use the default paths. May be ignored - /// entirely depending upon the platform. - external ffi.Pointer> m_pUserFontPaths; - - /// Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. - external ffi.Pointer m_pIsolate; - - /// The embedder data slot to use in the v8::Isolate to store PDFium's - /// per-isolate data. The value needs to be in the range - /// [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most - /// embedders. - @ffi.UnsignedInt() - external int m_v8EmbedderSlot; - - /// Pointer to the V8::Platform to use. - external ffi.Pointer m_pPlatform; - - /// Explicit specification of core renderer to use. |m_RendererType| must be - /// a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, - /// or else the initialization will fail with an immediate crash. - /// Note that use of a specified |FPDF_RENDERER_TYPE| value for which the - /// corresponding render library is not included in the build will similarly - /// fail with an immediate crash. - @ffi.UnsignedInt() - external int m_RendererTypeAsInt; - -FPDF_RENDERER_TYPE get m_RendererType => FPDF_RENDERER_TYPE.fromValue(m_RendererTypeAsInt); - -} - -/// Process-wide options for initializing the library. -typedef FPDF_LIBRARY_CONFIG = FPDF_LIBRARY_CONFIG_; -/// Structure for custom file access. -final class FPDF_FILEACCESS extends ffi.Struct{ - /// File length, in bytes. - @ffi.UnsignedLong() - external int m_FileLen; - - /// A function pointer for getting a block of data from a specific position. - /// Position is specified by byte offset from the beginning of the file. - /// The pointer to the buffer is never NULL and the size is never 0. - /// The position and size will never go out of range of the file length. - /// It may be possible for PDFium to call this function multiple times for - /// the same position. - /// Return value: should be non-zero if successful, zero for error. - external ffi.Pointer param, ffi.UnsignedLong position, ffi.Pointer pBuf, ffi.UnsignedLong size)>> m_GetBlock; - - /// A custom pointer for all implementation specific data. This pointer will - /// be used as the first parameter to the m_GetBlock callback. - external ffi.Pointer m_Param; - -} - -/// Structure for file reading or writing (I/O). -/// -/// Note: This is a handler and should be implemented by callers, -/// and is only used from XFA. -final class FPDF_FILEHANDLER_ extends ffi.Struct{ - /// User-defined data. - /// Note: Callers can use this field to track controls. - external ffi.Pointer clientData; - - /// Callback function to release the current file stream object. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// None. - external ffi.Pointer clientData)>> Release; - - /// Callback function to retrieve the current file stream size. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// Size of file stream. - external ffi.Pointer clientData)>> GetSize; - - /// Callback function to read data from the current file stream. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// offset - Offset position starts from the beginning of file - /// stream. This parameter indicates reading position. - /// buffer - Memory buffer to store data which are read from - /// file stream. This parameter should not be NULL. - /// size - Size of data which should be read from file stream, - /// in bytes. The buffer indicated by |buffer| must be - /// large enough to store specified data. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> ReadBlock; - - /// Callback function to write data into the current file stream. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// offset - Offset position starts from the beginning of file - /// stream. This parameter indicates writing position. - /// buffer - Memory buffer contains data which is written into - /// file stream. This parameter should not be NULL. - /// size - Size of data which should be written into file - /// stream, in bytes. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD offset, ffi.Pointer buffer, FPDF_DWORD size)>> WriteBlock; - - /// Callback function to flush all internal accessing buffers. - /// - /// Parameters: - /// clientData - Pointer to user-defined data. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData)>> Flush; - - /// Callback function to change file size. - /// - /// Description: - /// This function is called under writing mode usually. Implementer - /// can determine whether to realize it based on application requests. - /// Parameters: - /// clientData - Pointer to user-defined data. - /// size - New size of file stream, in bytes. - /// Returns: - /// 0 for success, other value for failure. - external ffi.Pointer clientData, FPDF_DWORD size)>> Truncate; - -} - -/// Structure for file reading or writing (I/O). -/// -/// Note: This is a handler and should be implemented by callers, -/// and is only used from XFA. -typedef FPDF_FILEHANDLER = FPDF_FILEHANDLER_; -/// Struct for color scheme. -/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. -final class FPDF_COLORSCHEME_ extends ffi.Struct{ - @FPDF_DWORD() - external int path_fill_color; - - @FPDF_DWORD() - external int path_stroke_color; - - @FPDF_DWORD() - external int text_fill_color; - - @FPDF_DWORD() - external int text_stroke_color; - -} - -/// Struct for color scheme. -/// Each should be a 32-bit value specifying the color, in 8888 ARGB format. -typedef FPDF_COLORSCHEME = FPDF_COLORSCHEME_; -/// Interface: FPDF_SYSFONTINFO -/// Interface for getting system font information and font mapping -final class _FPDF_SYSFONTINFO extends ffi.Struct{ - /// Version number of the interface. Currently must be 1. - @ffi.Int() - external int version; - - /// Method: Release - /// Give implementation a chance to release any data after the - /// interface is no longer used. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Return Value: - /// None - /// Comments: - /// Called by PDFium during the final cleanup process. - external ffi.Pointer pThis)>> Release; - - /// Method: EnumFonts - /// Enumerate all fonts installed on the system - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// pMapper - An opaque pointer to internal font mapper, used - /// when calling FPDF_AddInstalledFont(). - /// Return Value: - /// None - /// Comments: - /// Implementations should call FPDF_AddInstalledFont() function for - /// each font found. Only TrueType/OpenType and Type1 fonts are - /// accepted by PDFium. - external ffi.Pointer pThis, ffi.Pointer pMapper)>> EnumFonts; - - /// Method: MapFont - /// Use the system font mapper to get a font handle from requested - /// parameters. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Required if GetFont method is not implemented. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// weight - Weight of the requested font. 400 is normal and - /// 700 is bold. - /// bItalic - Italic option of the requested font, TRUE or - /// FALSE. - /// charset - Character set identifier for the requested font. - /// See above defined constants. - /// pitch_family - A combination of flags. See above defined - /// constants. - /// face - Typeface name. Currently use system local encoding - /// only. - /// bExact - Obsolete: this parameter is now ignored. - /// Return Value: - /// An opaque pointer for font handle, or NULL if system mapping is - /// not supported. - /// Comments: - /// If the system supports native font mapper (like Windows), - /// implementation can implement this method to get a font handle. - /// Otherwise, PDFium will do the mapping and then call GetFont - /// method. Only TrueType/OpenType and Type1 fonts are accepted - /// by PDFium. - external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Int weight, FPDF_BOOL bItalic, ffi.Int charset, ffi.Int pitch_family, ffi.Pointer face, ffi.Pointer bExact)>> MapFont; - - /// Method: GetFont - /// Get a handle to a particular font by its internal ID - /// Interface Version: - /// 1 - /// Implementation Required: - /// Required if MapFont method is not implemented. - /// Return Value: - /// An opaque pointer for font handle. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// face - Typeface name in system local encoding. - /// Comments: - /// If the system mapping not supported, PDFium will do the font - /// mapping and use this method to get a font handle. - external ffi.Pointer Function(ffi.Pointer<_FPDF_SYSFONTINFO> pThis, ffi.Pointer face)>> GetFont; - - /// Method: GetFontData - /// Get font data from a font - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// table - TrueType/OpenType table identifier (refer to - /// TrueType specification), or 0 for the whole file. - /// buffer - The buffer receiving the font data. Can be NULL if - /// not provided. - /// buf_size - Buffer size, can be zero if not provided. - /// Return Value: - /// Number of bytes needed, if buffer not provided or not large - /// enough, or number of bytes written into buffer otherwise. - /// Comments: - /// Can read either the full font file, or a particular - /// TrueType/OpenType table. - external ffi.Pointer pThis, ffi.Pointer hFont, ffi.UnsignedInt table, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFontData; - - /// Method: GetFaceName - /// Get face name from a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// buffer - The buffer receiving the face name. Can be NULL if - /// not provided - /// buf_size - Buffer size, can be zero if not provided - /// Return Value: - /// Number of bytes needed, if buffer not provided or not large - /// enough, or number of bytes written into buffer otherwise. - external ffi.Pointer pThis, ffi.Pointer hFont, ffi.Pointer buffer, ffi.UnsignedLong buf_size)>> GetFaceName; - - /// Method: GetFontCharset - /// Get character set information for a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// Return Value: - /// Character set identifier. See defined constants above. - external ffi.Pointer pThis, ffi.Pointer hFont)>> GetFontCharset; - - /// Method: DeleteFont - /// Delete a font handle - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// hFont - Font handle returned by MapFont or GetFont method - /// Return Value: - /// None - external ffi.Pointer pThis, ffi.Pointer hFont)>> DeleteFont; - -} - -/// Interface: FPDF_SYSFONTINFO -/// Interface for getting system font information and font mapping -typedef FPDF_SYSFONTINFO = _FPDF_SYSFONTINFO; -/// Struct: FPDF_CharsetFontMap -/// Provides the name of a font to use for a given charset value. -final class FPDF_CharsetFontMap_ extends ffi.Struct{ - /// Character Set Enum value, see FXFONT_*_CHARSET above. - @ffi.Int() - external int charset; - - /// Name of default font to use with that charset. - external ffi.Pointer fontname; - -} - -/// Struct: FPDF_CharsetFontMap -/// Provides the name of a font to use for a given charset value. -typedef FPDF_CharsetFontMap = FPDF_CharsetFontMap_; -/// IFPDF_RENDERINFO interface. -final class _IFSDK_PAUSE extends ffi.Struct{ - /// Version number of the interface. Currently must be 1. - @ffi.Int() - external int version; - - /// Method: NeedToPauseNow - /// Check if we need to pause a progressive process now. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Return Value: - /// Non-zero for pause now, 0 for continue. - external ffi.Pointer pThis)>> NeedToPauseNow; - - /// A user defined data pointer, used by user's application. Can be NULL. - external ffi.Pointer user; - -} - -/// IFPDF_RENDERINFO interface. -typedef IFSDK_PAUSE = _IFSDK_PAUSE; -final class FPDF_IMAGEOBJ_METADATA extends ffi.Struct{ - /// The image width in pixels. - @ffi.UnsignedInt() - external int width; - - /// The image height in pixels. - @ffi.UnsignedInt() - external int height; - - /// The image's horizontal pixel-per-inch. - @ffi.Float() - external double horizontal_dpi; - - /// The image's vertical pixel-per-inch. - @ffi.Float() - external double vertical_dpi; - - /// The number of bits used to represent each pixel. - @ffi.UnsignedInt() - external int bits_per_pixel; - - /// The image's colorspace. See above for the list of FPDF_COLORSPACE_*. - @ffi.Int() - external int colorspace; - - /// The image's marked content ID. Useful for pairing with associated alt-text. - /// A value of -1 indicates no ID. - @ffi.Int() - external int marked_content_id; - -} - -final class _IPDF_JsPlatform extends ffi.Struct{ - /// Version number of the interface. Currently must be 2. - @ffi.Int() - external int version; - - /// Method: app_alert - /// Pop up a dialog to show warning or hint. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Msg - A string containing the message to be displayed. - /// Title - The title of the dialog. - /// Type - The type of button group, one of the - /// JSPLATFORM_ALERT_BUTTON_* values above. - /// nIcon - The type of the icon, one of the - /// JSPLATFORM_ALERT_ICON_* above. - /// Return Value: - /// Option selected by user in dialogue, one of the - /// JSPLATFORM_ALERT_RETURN_* values above. - external ffi.Pointer pThis, FPDF_WIDESTRING Msg, FPDF_WIDESTRING Title, ffi.Int Type, ffi.Int Icon)>> app_alert; - - /// Method: app_beep - /// Causes the system to play a sound. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// nType - The sound type, see JSPLATFORM_BEEP_TYPE_* - /// above. - /// Return Value: - /// None - external ffi.Pointer pThis, ffi.Int nType)>> app_beep; - - /// Method: app_response - /// Displays a dialog box containing a question and an entry field for - /// the user to reply to the question. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Question - The question to be posed to the user. - /// Title - The title of the dialog box. - /// Default - A default value for the answer to the question. If - /// not specified, no default value is presented. - /// cLabel - A short string to appear in front of and on the - /// same line as the edit text field. - /// bPassword - If true, indicates that the user's response should - /// be shown as asterisks (*) or bullets (?) to mask - /// the response, which might be sensitive information. - /// response - A string buffer allocated by PDFium, to receive the - /// user's response. - /// length - The length of the buffer in bytes. Currently, it is - /// always 2048. - /// Return Value: - /// Number of bytes the complete user input would actually require, not - /// including trailing zeros, regardless of the value of the length - /// parameter or the presence of the response buffer. - /// Comments: - /// No matter on what platform, the response buffer should be always - /// written using UTF-16LE encoding. If a response buffer is - /// present and the size of the user input exceeds the capacity of the - /// buffer as specified by the length parameter, only the - /// first "length" bytes of the user input are to be written to the - /// buffer. - external ffi.Pointer pThis, FPDF_WIDESTRING Question, FPDF_WIDESTRING Title, FPDF_WIDESTRING Default, FPDF_WIDESTRING cLabel, FPDF_BOOL bPassword, ffi.Pointer response, ffi.Int length)>> app_response; - - /// Method: Doc_getFilePath - /// Get the file path of the current document. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// filePath - The string buffer to receive the file path. Can - /// be NULL. - /// length - The length of the buffer, number of bytes. Can - /// be 0. - /// Return Value: - /// Number of bytes the filePath consumes, including trailing zeros. - /// Comments: - /// The filePath should always be provided in the local encoding. - /// The return value always indicated number of bytes required for - /// the buffer, even when there is no buffer specified, or the buffer - /// size is less than required. In this case, the buffer will not - /// be modified. - external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Doc_getFilePath; - - /// Method: Doc_mail - /// Mails the data buffer as an attachment to all recipients, with or - /// without user interaction. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// mailData - Pointer to the data buffer to be sent. Can be NULL. - /// length - The size,in bytes, of the buffer pointed by - /// mailData parameter. Can be 0. - /// bUI - If true, the rest of the parameters are used in a - /// compose-new-message window that is displayed to the - /// user. If false, the cTo parameter is required and - /// all others are optional. - /// To - A semicolon-delimited list of recipients for the - /// message. - /// Subject - The subject of the message. The length limit is - /// 64 KB. - /// CC - A semicolon-delimited list of CC recipients for - /// the message. - /// BCC - A semicolon-delimited list of BCC recipients for - /// the message. - /// Msg - The content of the message. The length limit is - /// 64 KB. - /// Return Value: - /// None. - /// Comments: - /// If the parameter mailData is NULL or length is 0, the current - /// document will be mailed as an attachment to all recipients. - external ffi.Pointer pThis, ffi.Pointer mailData, ffi.Int length, FPDF_BOOL bUI, FPDF_WIDESTRING To, FPDF_WIDESTRING Subject, FPDF_WIDESTRING CC, FPDF_WIDESTRING BCC, FPDF_WIDESTRING Msg)>> Doc_mail; - - /// Method: Doc_print - /// Prints all or a specific number of pages of the document. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// bUI - If true, will cause a UI to be presented to the - /// user to obtain printing information and confirm - /// the action. - /// nStart - A 0-based index that defines the start of an - /// inclusive range of pages. - /// nEnd - A 0-based index that defines the end of an - /// inclusive page range. - /// bSilent - If true, suppresses the cancel dialog box while - /// the document is printing. The default is false. - /// bShrinkToFit - If true, the page is shrunk (if necessary) to - /// fit within the imageable area of the printed page. - /// bPrintAsImage - If true, print pages as an image. - /// bReverse - If true, print from nEnd to nStart. - /// bAnnotations - If true (the default), annotations are - /// printed. - /// Return Value: - /// None. - external ffi.Pointer pThis, FPDF_BOOL bUI, ffi.Int nStart, ffi.Int nEnd, FPDF_BOOL bSilent, FPDF_BOOL bShrinkToFit, FPDF_BOOL bPrintAsImage, FPDF_BOOL bReverse, FPDF_BOOL bAnnotations)>> Doc_print; - - /// Method: Doc_submitForm - /// Send the form data to a specified URL. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// formData - Pointer to the data buffer to be sent. - /// length - The size,in bytes, of the buffer pointed by - /// formData parameter. - /// URL - The URL to send to. - /// Return Value: - /// None. - external ffi.Pointer pThis, ffi.Pointer formData, ffi.Int length, FPDF_WIDESTRING URL)>> Doc_submitForm; - - /// Method: Doc_gotoPage - /// Jump to a specified page. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// nPageNum - The specified page number, zero for the first page. - /// Return Value: - /// None. - external ffi.Pointer pThis, ffi.Int nPageNum)>> Doc_gotoPage; - - /// Method: Field_browse - /// Show a file selection dialog, and return the selected file path. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// filePath - Pointer to the data buffer to receive the file - /// path. Can be NULL. - /// length - The length of the buffer, in bytes. Can be 0. - /// Return Value: - /// Number of bytes the filePath consumes, including trailing zeros. - /// Comments: - /// The filePath should always be provided in local encoding. - external ffi.Pointer pThis, ffi.Pointer filePath, ffi.Int length)>> Field_browse; - - /// Pointer for embedder-specific data. Unused by PDFium, and despite - /// its name, can be any data the embedder desires, though traditionally - /// a FPDF_FORMFILLINFO interface. - external ffi.Pointer m_pFormfillinfo; - - /// Unused in v3, retain for compatibility. - external ffi.Pointer m_isolate; - - /// Unused in v3, retain for compatibility. - @ffi.UnsignedInt() - external int m_v8EmbedderSlot; - -} - -typedef IPDF_JSPLATFORM = _IPDF_JsPlatform; -typedef TimerCallbackFunction = ffi.Void Function(ffi.Int idEvent); -typedef DartTimerCallbackFunction = void Function(int idEvent); -/// Function signature for the callback function passed to the FFI_SetTimer -/// method. -/// Parameters: -/// idEvent - Identifier of the timer. -/// Return value: -/// None. -typedef TimerCallback = ffi.Pointer>; -/// Declares of a struct type to the local system time. -final class _FPDF_SYSTEMTIME extends ffi.Struct{ - /// years since 1900 - @ffi.UnsignedShort() - external int wYear; - - /// months since January - [0,11] - @ffi.UnsignedShort() - external int wMonth; - - /// days since Sunday - [0,6] - @ffi.UnsignedShort() - external int wDayOfWeek; - - /// day of the month - [1,31] - @ffi.UnsignedShort() - external int wDay; - - /// hours since midnight - [0,23] - @ffi.UnsignedShort() - external int wHour; - - /// minutes after the hour - [0,59] - @ffi.UnsignedShort() - external int wMinute; - - /// seconds after the minute - [0,59] - @ffi.UnsignedShort() - external int wSecond; - - /// milliseconds after the second - [0,999] - @ffi.UnsignedShort() - external int wMilliseconds; - -} - -/// Declares of a struct type to the local system time. -typedef FPDF_SYSTEMTIME = _FPDF_SYSTEMTIME; -final class _FPDF_FORMFILLINFO extends ffi.Struct{ - /// Version number of the interface. - /// Version 1 contains stable interfaces. Version 2 has additional - /// experimental interfaces. - /// When PDFium is built without the XFA module, version can be 1 or 2. - /// With version 1, only stable interfaces are called. With version 2, - /// additional experimental interfaces are also called. - /// When PDFium is built with the XFA module, version must be 2. - /// All the XFA related interfaces are experimental. If PDFium is built with - /// the XFA module and version 1 then none of the XFA related interfaces - /// would be called. When PDFium is built with XFA module then the version - /// must be 2. - @ffi.Int() - external int version; - - /// Method: Release - /// Give the implementation a chance to release any resources after the - /// interface is no longer used. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Comments: - /// Called by PDFium during the final cleanup process. - /// Parameters: - /// pThis - Pointer to the interface structure itself - /// Return Value: - /// None - external ffi.Pointer pThis)>> Release; - - /// Method: FFI_Invalidate - /// Invalidate the client area within the specified rectangle. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to the page. Returned by FPDF_LoadPage(). - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return Value: - /// None. - /// Comments: - /// All positions are measured in PDF "user space". - /// Implementation should call FPDF_RenderPageBitmap() for repainting - /// the specified page area. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_Invalidate; - - /// Method: FFI_OutputSelectedRect - /// When the user selects text in form fields with the mouse, this - /// callback function will be invoked with the selected areas. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to the page. Returned by FPDF_LoadPage()/ - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return Value: - /// None. - /// Comments: - /// This callback function is useful for implementing special text - /// selection effects. An implementation should first record the - /// returned rectangles, then draw them one by one during the next - /// painting period. Lastly, it should remove all the recorded - /// rectangles when finished painting. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_OutputSelectedRect; - - /// Method: FFI_SetCursor - /// Set the Cursor shape. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nCursorType - Cursor type, see Flags for Cursor type for details. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Int nCursorType)>> FFI_SetCursor; - - /// Method: FFI_SetTimer - /// This method installs a system timer. An interval value is specified, - /// and every time that interval elapses, the system must call into the - /// callback function with the timer ID as returned by this function. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// uElapse - Specifies the time-out value, in milliseconds. - /// lpTimerFunc - A pointer to the callback function-TimerCallback. - /// Return value: - /// The timer identifier of the new timer if the function is successful. - /// An application passes this value to the FFI_KillTimer method to kill - /// the timer. Nonzero if it is successful; otherwise, it is zero. - external ffi.Pointer pThis, ffi.Int uElapse, TimerCallback lpTimerFunc)>> FFI_SetTimer; - - /// Method: FFI_KillTimer - /// This method uninstalls a system timer, as set by an earlier call to - /// FFI_SetTimer. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nTimerID - The timer ID returned by FFI_SetTimer function. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Int nTimerID)>> FFI_KillTimer; - - /// Method: FFI_GetLocalTime - /// This method receives the current local time on the system. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Return value: - /// The local time. See FPDF_SYSTEMTIME above for details. - /// Note: Unused. - external ffi.Pointer pThis)>> FFI_GetLocalTime; - - /// Method: FFI_OnChange - /// This method will be invoked to notify the implementation when the - /// value of any FormField on the document had been changed. - /// Interface Version: - /// 1 - /// Implementation Required: - /// no - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// Return value: - /// None. - external ffi.Pointer pThis)>> FFI_OnChange; - - /// Method: FFI_GetPage - /// This method receives the page handle associated with a specified - /// page index. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// nPageIndex - Index number of the page. 0 for the first page. - /// Return value: - /// Handle to the page, as previously returned to the implementation by - /// FPDF_LoadPage(). - /// Comments: - /// The implementation is expected to keep track of the page handles it - /// receives from PDFium, and their mappings to page numbers. In some - /// cases, the document-level JavaScript action may refer to a page - /// which hadn't been loaded yet. To successfully run the Javascript - /// action, the implementation needs to load the page. - external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int nPageIndex)>> FFI_GetPage; - - /// Method: FFI_GetCurrentPage - /// This method receives the handle to the current page. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes when V8 support is present, otherwise unused. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document. Returned by FPDF_LoadDocument(). - /// Return value: - /// Handle to the page. Returned by FPDF_LoadPage(). - /// Comments: - /// PDFium doesn't keep keep track of the "current page" (e.g. the one - /// that is most visible on screen), so it must ask the embedder for - /// this information. - external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPage; - - /// Method: FFI_GetRotation - /// This method receives currently rotation of the page view. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page, as returned by FPDF_LoadPage(). - /// Return value: - /// A number to indicate the page rotation in 90 degree increments - /// in a clockwise direction: - /// 0 - 0 degrees - /// 1 - 90 degrees - /// 2 - 180 degrees - /// 3 - 270 degrees - /// Note: Unused. - external ffi.Pointer pThis, FPDF_PAGE page)>> FFI_GetRotation; - - /// Method: FFI_ExecuteNamedAction - /// This method will execute a named action. - /// Interface Version: - /// 1 - /// Implementation Required: - /// yes - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// namedAction - A byte string which indicates the named action, - /// terminated by 0. - /// Return value: - /// None. - /// Comments: - /// See ISO 32000-1:2008, section 12.6.4.11 for descriptions of the - /// standard named actions, but note that a document may supply any - /// name of its choosing. - external ffi.Pointer pThis, FPDF_BYTESTRING namedAction)>> FFI_ExecuteNamedAction; - - /// Method: FFI_SetTextFieldFocus - /// Called when a text field is getting or losing focus. - /// Interface Version: - /// 1 - /// Implementation Required: - /// no - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// value - The string value of the form field, in UTF-16LE - /// format. - /// valueLen - The length of the string value. This is the - /// number of characters, not bytes. - /// is_focus - True if the form field is getting focus, false - /// if the form field is losing focus. - /// Return value: - /// None. - /// Comments: - /// Only supports text fields and combobox fields. - external ffi.Pointer pThis, FPDF_WIDESTRING value, FPDF_DWORD valueLen, FPDF_BOOL is_focus)>> FFI_SetTextFieldFocus; - - /// Method: FFI_DoURIAction - /// Ask the implementation to navigate to a uniform resource identifier. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// bsURI - A byte string which indicates the uniform - /// resource identifier, terminated by 0. - /// Return value: - /// None. - /// Comments: - /// If the embedder is version 2 or higher and have implementation for - /// FFI_DoURIActionWithKeyboardModifier, then - /// FFI_DoURIActionWithKeyboardModifier takes precedence over - /// FFI_DoURIAction. - /// See the URI actions description of <> - /// for more details. - external ffi.Pointer pThis, FPDF_BYTESTRING bsURI)>> FFI_DoURIAction; - - /// Method: FFI_DoGoToAction - /// This action changes the view to a specified destination. - /// Interface Version: - /// 1 - /// Implementation Required: - /// No - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// nPageIndex - The index of the PDF page. - /// zoomMode - The zoom mode for viewing page. See below. - /// fPosArray - The float array which carries the position info. - /// sizeofArray - The size of float array. - /// PDFZoom values: - /// - XYZ = 1 - /// - FITPAGE = 2 - /// - FITHORZ = 3 - /// - FITVERT = 4 - /// - FITRECT = 5 - /// - FITBBOX = 6 - /// - FITBHORZ = 7 - /// - FITBVERT = 8 - /// Return value: - /// None. - /// Comments: - /// See the Destinations description of <> - /// in 8.2.1 for more details. - external ffi.Pointer pThis, ffi.Int nPageIndex, ffi.Int zoomMode, ffi.Pointer fPosArray, ffi.Int sizeofArray)>> FFI_DoGoToAction; - - /// Pointer to IPDF_JSPLATFORM interface. - /// Unused if PDFium is built without V8 support. Otherwise, if NULL, then - /// JavaScript will be prevented from executing while rendering the document. - external ffi.Pointer m_pJsPlatform; - - /// Whether the XFA module is disabled when built with the XFA module. - /// Interface Version: - /// Ignored if |version| < 2. - @FPDF_BOOL() - external int xfa_disabled; - - /// Method: FFI_DisplayCaret - /// This method will show the caret at specified position. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// left - Left position of the client area in PDF page - /// coordinates. - /// top - Top position of the client area in PDF page - /// coordinates. - /// right - Right position of the client area in PDF page - /// coordinates. - /// bottom - Bottom position of the client area in PDF page - /// coordinates. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_PAGE page, FPDF_BOOL bVisible, ffi.Double left, ffi.Double top, ffi.Double right, ffi.Double bottom)>> FFI_DisplayCaret; - - /// Method: FFI_GetCurrentPageIndex - /// This method will get the current page index. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// Return value: - /// The index of current page. - external ffi.Pointer pThis, FPDF_DOCUMENT document)>> FFI_GetCurrentPageIndex; - - /// Method: FFI_SetCurrentPage - /// This method will set the current page. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// iCurPage - The index of the PDF page. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_DOCUMENT document, ffi.Int iCurPage)>> FFI_SetCurrentPage; - - /// Method: FFI_GotoURL - /// This method will navigate to the specified URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// document - Handle to document from FPDF_LoadDocument(). - /// wsURL - The string value of the URL, in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_DOCUMENT document, FPDF_WIDESTRING wsURL)>> FFI_GotoURL; - - /// Method: FFI_GetPageViewRect - /// This method will get the current page view rectangle. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// left - The pointer to receive left position of the page - /// view area in PDF page coordinates. - /// top - The pointer to receive top position of the page - /// view area in PDF page coordinates. - /// right - The pointer to receive right position of the - /// page view area in PDF page coordinates. - /// bottom - The pointer to receive bottom position of the - /// page view area in PDF page coordinates. - /// Return value: - /// None. - external ffi.Pointer pThis, FPDF_PAGE page, ffi.Pointer left, ffi.Pointer top, ffi.Pointer right, ffi.Pointer bottom)>> FFI_GetPageViewRect; - - /// Method: FFI_PageEvent - /// This method fires when pages have been added to or deleted from - /// the XFA document. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page_count - The number of pages to be added or deleted. - /// event_type - See FXFA_PAGEVIEWEVENT_* above. - /// Return value: - /// None. - /// Comments: - /// The pages to be added or deleted always start from the last page - /// of document. This means that if parameter page_count is 2 and - /// event type is FXFA_PAGEVIEWEVENT_POSTADDED, 2 new pages have been - /// appended to the tail of document; If page_count is 2 and - /// event type is FXFA_PAGEVIEWEVENT_POSTREMOVED, the last 2 pages - /// have been deleted. - external ffi.Pointer pThis, ffi.Int page_count, FPDF_DWORD event_type)>> FFI_PageEvent; - - /// Method: FFI_PopupMenu - /// This method will track the right context menu for XFA fields. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// page - Handle to page. Returned by FPDF_LoadPage(). - /// hWidget - Always null, exists for compatibility. - /// menuFlag - The menu flags. Please refer to macro definition - /// of FXFA_MENU_XXX and this can be one or a - /// combination of these macros. - /// x - X position of the client area in PDF page - /// coordinates. - /// y - Y position of the client area in PDF page - /// coordinates. - /// Return value: - /// TRUE indicates success; otherwise false. - external ffi.Pointer pThis, FPDF_PAGE page, FPDF_WIDGET hWidget, ffi.Int menuFlag, ffi.Float x, ffi.Float y)>> FFI_PopupMenu; - - /// Method: FFI_OpenFile - /// This method will open the specified file with the specified mode. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// fileFlag - The file flag. Please refer to macro definition - /// of FXFA_SAVEAS_XXX and use one of these macros. - /// wsURL - The string value of the file URL, in UTF-16LE - /// format. - /// mode - The mode for open file, e.g. "rb" or "wb". - /// Return value: - /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, ffi.Int fileFlag, FPDF_WIDESTRING wsURL, ffi.Pointer mode)>> FFI_OpenFile; - - /// Method: FFI_EmailTo - /// This method will email the specified file stream to the specified - /// contact. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// pFileHandler - Handle to the FPDF_FILEHANDLER. - /// pTo - A semicolon-delimited list of recipients for the - /// message,in UTF-16LE format. - /// pSubject - The subject of the message,in UTF-16LE format. - /// pCC - A semicolon-delimited list of CC recipients for - /// the message,in UTF-16LE format. - /// pBcc - A semicolon-delimited list of BCC recipients for - /// the message,in UTF-16LE format. - /// pMsg - Pointer to the data buffer to be sent.Can be - /// NULL,in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Pointer fileHandler, FPDF_WIDESTRING pTo, FPDF_WIDESTRING pSubject, FPDF_WIDESTRING pCC, FPDF_WIDESTRING pBcc, FPDF_WIDESTRING pMsg)>> FFI_EmailTo; - - /// Method: FFI_UploadTo - /// This method will upload the specified file stream to the - /// specified URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// pFileHandler - Handle to the FPDF_FILEHANDLER. - /// fileFlag - The file flag. Please refer to macro definition - /// of FXFA_SAVEAS_XXX and use one of these macros. - /// uploadTo - Pointer to the URL path, in UTF-16LE format. - /// Return value: - /// None. - external ffi.Pointer pThis, ffi.Pointer fileHandler, ffi.Int fileFlag, FPDF_WIDESTRING uploadTo)>> FFI_UploadTo; - - /// Method: FFI_GetPlatform - /// This method will get the current platform. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// platform - Pointer to the data buffer to receive the - /// platform,in UTF-16LE format. Can be NULL. - /// length - The length of the buffer in bytes. Can be - /// 0 to query the required size. - /// Return value: - /// The length of the buffer, number of bytes. - external ffi.Pointer pThis, ffi.Pointer platform, ffi.Int length)>> FFI_GetPlatform; - - /// Method: FFI_GetLanguage - /// This method will get the current language. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// language - Pointer to the data buffer to receive the - /// current language. Can be NULL. - /// length - The length of the buffer in bytes. Can be - /// 0 to query the required size. - /// Return value: - /// The length of the buffer, number of bytes. - external ffi.Pointer pThis, ffi.Pointer language, ffi.Int length)>> FFI_GetLanguage; - - /// Method: FFI_DownloadFromURL - /// This method will download the specified file from the URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// URL - The string value of the file URL, in UTF-16LE - /// format. - /// Return value: - /// The handle to FPDF_FILEHANDLER. - external ffi.Pointer Function(ffi.Pointer<_FPDF_FORMFILLINFO> pThis, FPDF_WIDESTRING URL)>> FFI_DownloadFromURL; - - /// Method: FFI_PostRequestURL - /// This method will post the request to the server URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// wsURL - The string value of the server URL, in UTF-16LE - /// format. - /// wsData - The post data,in UTF-16LE format. - /// wsContentType - The content type of the request data, in - /// UTF-16LE format. - /// wsEncode - The encode type, in UTF-16LE format. - /// wsHeader - The request header,in UTF-16LE format. - /// response - Pointer to the FPDF_BSTR to receive the response - /// data from the server, in UTF-16LE format. - /// Return value: - /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsContentType, FPDF_WIDESTRING wsEncode, FPDF_WIDESTRING wsHeader, ffi.Pointer response)>> FFI_PostRequestURL; - - /// Method: FFI_PutRequestURL - /// This method will put the request to the server URL. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// Required for XFA, otherwise set to NULL. - /// Parameters: - /// pThis - Pointer to the interface structure itself. - /// wsURL - The string value of the server URL, in UTF-16LE - /// format. - /// wsData - The put data, in UTF-16LE format. - /// wsEncode - The encode type, in UTR-16LE format. - /// Return value: - /// TRUE indicates success, otherwise FALSE. - external ffi.Pointer pThis, FPDF_WIDESTRING wsURL, FPDF_WIDESTRING wsData, FPDF_WIDESTRING wsEncode)>> FFI_PutRequestURL; - - /// Method: FFI_OnFocusChange - /// Called when the focused annotation is updated. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// No - /// Parameters: - /// param - Pointer to the interface structure itself. - /// annot - The focused annotation. - /// page_index - Index number of the page which contains the - /// focused annotation. 0 for the first page. - /// Return value: - /// None. - /// Comments: - /// This callback function is useful for implementing any view based - /// action such as scrolling the annotation rect into view. The - /// embedder should not copy and store the annot as its scope is - /// limited to this call only. - external ffi.Pointer param, FPDF_ANNOTATION annot, ffi.Int page_index)>> FFI_OnFocusChange; - - /// Method: FFI_DoURIActionWithKeyboardModifier - /// Ask the implementation to navigate to a uniform resource identifier - /// with the specified modifiers. - /// Interface Version: - /// Ignored if |version| < 2. - /// Implementation Required: - /// No - /// Parameters: - /// param - Pointer to the interface structure itself. - /// uri - A byte string which indicates the uniform - /// resource identifier, terminated by 0. - /// modifiers - Keyboard modifier that indicates which of - /// the virtual keys are down, if any. - /// Return value: - /// None. - /// Comments: - /// If the embedder who is version 2 and does not implement this API, - /// then a call will be redirected to FFI_DoURIAction. - /// See the URI actions description of <> - /// for more details. - external ffi.Pointer param, FPDF_BYTESTRING uri, ffi.Int modifiers)>> FFI_DoURIActionWithKeyboardModifier; - -} - -typedef FPDF_FORMFILLINFO = _FPDF_FORMFILLINFO; -enum FPDFANNOT_COLORTYPE { - FPDFANNOT_COLORTYPE_Color(0), - FPDFANNOT_COLORTYPE_InteriorColor(1); - - - final int value; - const FPDFANNOT_COLORTYPE(this.value); - - static FPDFANNOT_COLORTYPE fromValue(int value) => switch (value) { - 0 => FPDFANNOT_COLORTYPE_Color, - 1 => FPDFANNOT_COLORTYPE_InteriorColor, - _ => throw ArgumentError('Unknown value for FPDFANNOT_COLORTYPE: $value'), - }; - -} - -/// Structure for custom file write -final class FPDF_FILEWRITE_ extends ffi.Struct{ - /// Version number of the interface. Currently must be 1. - @ffi.Int() - external int version; - - /// Method: WriteBlock - /// Output a block of data in your custom way. - /// Interface Version: - /// 1 - /// Implementation Required: - /// Yes - /// Comments: - /// Called by function FPDF_SaveDocument - /// Parameters: - /// pThis - Pointer to the structure itself - /// pData - Pointer to a buffer to output - /// size - The size of the buffer. - /// Return value: - /// Should be non-zero if successful, zero for error. - external ffi.Pointer pThis, ffi.Pointer pData, ffi.UnsignedLong size)>> WriteBlock; - -} - -/// Structure for custom file write -typedef FPDF_FILEWRITE = FPDF_FILEWRITE_; -/// The file identifier entry type. See section 14.4 "File Identifiers" of the -/// ISO 32000-1:2008 spec. -enum FPDF_FILEIDTYPE { - FILEIDTYPE_PERMANENT(0), - FILEIDTYPE_CHANGING(1); - - - final int value; - const FPDF_FILEIDTYPE(this.value); - - static FPDF_FILEIDTYPE fromValue(int value) => switch (value) { - 0 => FILEIDTYPE_PERMANENT, - 1 => FILEIDTYPE_CHANGING, - _ => throw ArgumentError('Unknown value for FPDF_FILEIDTYPE: $value'), - }; - -} - -/// Interface for checking whether sections of the file are available. -final class _FX_FILEAVAIL extends ffi.Struct{ - /// Version number of the interface. Must be 1. - @ffi.Int() - external int version; - - /// Reports if the specified data section is currently available. A section is - /// available if all bytes in the section are available. - /// - /// Interface Version: 1 - /// Implementation Required: Yes - /// - /// pThis - pointer to the interface structure. - /// offset - the offset of the data section in the file. - /// size - the size of the data section. - /// - /// Returns true if the specified data section at |offset| of |size| - /// is available. - external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> IsDataAvail; - -} - -/// Interface for checking whether sections of the file are available. -typedef FX_FILEAVAIL = _FX_FILEAVAIL; -/// Download hints interface. Used to receive hints for further downloading. -final class _FX_DOWNLOADHINTS extends ffi.Struct{ - /// Version number of the interface. Must be 1. - @ffi.Int() - external int version; - - /// Add a section to be downloaded. - /// - /// Interface Version: 1 - /// Implementation Required: Yes - /// - /// pThis - pointer to the interface structure. - /// offset - the offset of the hint reported to be downloaded. - /// size - the size of the hint reported to be downloaded. - /// - /// The |offset| and |size| of the section may not be unique. Part of the - /// section might be already available. The download manager must deal with - /// overlapping sections. - external ffi.Pointer pThis, ffi.Size offset, ffi.Size size)>> AddSegment; - -} - -/// Download hints interface. Used to receive hints for further downloading. -typedef FX_DOWNLOADHINTS = _FX_DOWNLOADHINTS; -/// Key flags. -enum FWL_EVENTFLAG { - FWL_EVENTFLAG_ShiftKey(1), - FWL_EVENTFLAG_ControlKey(2), - FWL_EVENTFLAG_AltKey(4), - FWL_EVENTFLAG_MetaKey(8), - FWL_EVENTFLAG_KeyPad(16), - FWL_EVENTFLAG_AutoRepeat(32), - FWL_EVENTFLAG_LeftButtonDown(64), - FWL_EVENTFLAG_MiddleButtonDown(128), - FWL_EVENTFLAG_RightButtonDown(256); - - - final int value; - const FWL_EVENTFLAG(this.value); - - static FWL_EVENTFLAG fromValue(int value) => switch (value) { - 1 => FWL_EVENTFLAG_ShiftKey, - 2 => FWL_EVENTFLAG_ControlKey, - 4 => FWL_EVENTFLAG_AltKey, - 8 => FWL_EVENTFLAG_MetaKey, - 16 => FWL_EVENTFLAG_KeyPad, - 32 => FWL_EVENTFLAG_AutoRepeat, - 64 => FWL_EVENTFLAG_LeftButtonDown, - 128 => FWL_EVENTFLAG_MiddleButtonDown, - 256 => FWL_EVENTFLAG_RightButtonDown, - _ => throw ArgumentError('Unknown value for FWL_EVENTFLAG: $value'), - }; - -} - -/// Virtual keycodes. -enum FWL_VKEYCODE { - FWL_VKEY_Back(8), - FWL_VKEY_Tab(9), - FWL_VKEY_NewLine(10), - FWL_VKEY_Clear(12), - FWL_VKEY_Return(13), - FWL_VKEY_Shift(16), - FWL_VKEY_Control(17), - FWL_VKEY_Menu(18), - FWL_VKEY_Pause(19), - FWL_VKEY_Capital(20), - FWL_VKEY_Kana(21), - FWL_VKEY_Junja(23), - FWL_VKEY_Final(24), - FWL_VKEY_Hanja(25), - FWL_VKEY_Escape(27), - FWL_VKEY_Convert(28), - FWL_VKEY_NonConvert(29), - FWL_VKEY_Accept(30), - FWL_VKEY_ModeChange(31), - FWL_VKEY_Space(32), - FWL_VKEY_Prior(33), - FWL_VKEY_Next(34), - FWL_VKEY_End(35), - FWL_VKEY_Home(36), - FWL_VKEY_Left(37), - FWL_VKEY_Up(38), - FWL_VKEY_Right(39), - FWL_VKEY_Down(40), - FWL_VKEY_Select(41), - FWL_VKEY_Print(42), - FWL_VKEY_Execute(43), - FWL_VKEY_Snapshot(44), - FWL_VKEY_Insert(45), - FWL_VKEY_Delete(46), - FWL_VKEY_Help(47), - FWL_VKEY_0(48), - FWL_VKEY_1(49), - FWL_VKEY_2(50), - FWL_VKEY_3(51), - FWL_VKEY_4(52), - FWL_VKEY_5(53), - FWL_VKEY_6(54), - FWL_VKEY_7(55), - FWL_VKEY_8(56), - FWL_VKEY_9(57), - FWL_VKEY_A(65), - FWL_VKEY_B(66), - FWL_VKEY_C(67), - FWL_VKEY_D(68), - FWL_VKEY_E(69), - FWL_VKEY_F(70), - FWL_VKEY_G(71), - FWL_VKEY_H(72), - FWL_VKEY_I(73), - FWL_VKEY_J(74), - FWL_VKEY_K(75), - FWL_VKEY_L(76), - FWL_VKEY_M(77), - FWL_VKEY_N(78), - FWL_VKEY_O(79), - FWL_VKEY_P(80), - FWL_VKEY_Q(81), - FWL_VKEY_R(82), - FWL_VKEY_S(83), - FWL_VKEY_T(84), - FWL_VKEY_U(85), - FWL_VKEY_V(86), - FWL_VKEY_W(87), - FWL_VKEY_X(88), - FWL_VKEY_Y(89), - FWL_VKEY_Z(90), - FWL_VKEY_LWin(91), - FWL_VKEY_RWin(92), - FWL_VKEY_Apps(93), - FWL_VKEY_Sleep(95), - FWL_VKEY_NumPad0(96), - FWL_VKEY_NumPad1(97), - FWL_VKEY_NumPad2(98), - FWL_VKEY_NumPad3(99), - FWL_VKEY_NumPad4(100), - FWL_VKEY_NumPad5(101), - FWL_VKEY_NumPad6(102), - FWL_VKEY_NumPad7(103), - FWL_VKEY_NumPad8(104), - FWL_VKEY_NumPad9(105), - FWL_VKEY_Multiply(106), - FWL_VKEY_Add(107), - FWL_VKEY_Separator(108), - FWL_VKEY_Subtract(109), - FWL_VKEY_Decimal(110), - FWL_VKEY_Divide(111), - FWL_VKEY_F1(112), - FWL_VKEY_F2(113), - FWL_VKEY_F3(114), - FWL_VKEY_F4(115), - FWL_VKEY_F5(116), - FWL_VKEY_F6(117), - FWL_VKEY_F7(118), - FWL_VKEY_F8(119), - FWL_VKEY_F9(120), - FWL_VKEY_F10(121), - FWL_VKEY_F11(122), - FWL_VKEY_F12(123), - FWL_VKEY_F13(124), - FWL_VKEY_F14(125), - FWL_VKEY_F15(126), - FWL_VKEY_F16(127), - FWL_VKEY_F17(128), - FWL_VKEY_F18(129), - FWL_VKEY_F19(130), - FWL_VKEY_F20(131), - FWL_VKEY_F21(132), - FWL_VKEY_F22(133), - FWL_VKEY_F23(134), - FWL_VKEY_F24(135), - FWL_VKEY_NunLock(144), - FWL_VKEY_Scroll(145), - FWL_VKEY_LShift(160), - FWL_VKEY_RShift(161), - FWL_VKEY_LControl(162), - FWL_VKEY_RControl(163), - FWL_VKEY_LMenu(164), - FWL_VKEY_RMenu(165), - FWL_VKEY_BROWSER_Back(166), - FWL_VKEY_BROWSER_Forward(167), - FWL_VKEY_BROWSER_Refresh(168), - FWL_VKEY_BROWSER_Stop(169), - FWL_VKEY_BROWSER_Search(170), - FWL_VKEY_BROWSER_Favorites(171), - FWL_VKEY_BROWSER_Home(172), - FWL_VKEY_VOLUME_Mute(173), - FWL_VKEY_VOLUME_Down(174), - FWL_VKEY_VOLUME_Up(175), - FWL_VKEY_MEDIA_NEXT_Track(176), - FWL_VKEY_MEDIA_PREV_Track(177), - FWL_VKEY_MEDIA_Stop(178), - FWL_VKEY_MEDIA_PLAY_Pause(179), - FWL_VKEY_MEDIA_LAUNCH_Mail(180), - FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select(181), - FWL_VKEY_MEDIA_LAUNCH_APP1(182), - FWL_VKEY_MEDIA_LAUNCH_APP2(183), - FWL_VKEY_OEM_1(186), - FWL_VKEY_OEM_Plus(187), - FWL_VKEY_OEM_Comma(188), - FWL_VKEY_OEM_Minus(189), - FWL_VKEY_OEM_Period(190), - FWL_VKEY_OEM_2(191), - FWL_VKEY_OEM_3(192), - FWL_VKEY_OEM_4(219), - FWL_VKEY_OEM_5(220), - FWL_VKEY_OEM_6(221), - FWL_VKEY_OEM_7(222), - FWL_VKEY_OEM_8(223), - FWL_VKEY_OEM_102(226), - FWL_VKEY_ProcessKey(229), - FWL_VKEY_Packet(231), - FWL_VKEY_Attn(246), - FWL_VKEY_Crsel(247), - FWL_VKEY_Exsel(248), - FWL_VKEY_Ereof(249), - FWL_VKEY_Play(250), - FWL_VKEY_Zoom(251), - FWL_VKEY_NoName(252), - FWL_VKEY_PA1(253), - FWL_VKEY_OEM_Clear(254), - FWL_VKEY_Unknown(0); - - static const FWL_VKEY_Hangul = FWL_VKEY_Kana; - static const FWL_VKEY_Kanji = FWL_VKEY_Hanja; - static const FWL_VKEY_Command = FWL_VKEY_LWin; - - final int value; - const FWL_VKEYCODE(this.value); - - static FWL_VKEYCODE fromValue(int value) => switch (value) { - 8 => FWL_VKEY_Back, - 9 => FWL_VKEY_Tab, - 10 => FWL_VKEY_NewLine, - 12 => FWL_VKEY_Clear, - 13 => FWL_VKEY_Return, - 16 => FWL_VKEY_Shift, - 17 => FWL_VKEY_Control, - 18 => FWL_VKEY_Menu, - 19 => FWL_VKEY_Pause, - 20 => FWL_VKEY_Capital, - 21 => FWL_VKEY_Kana, - 23 => FWL_VKEY_Junja, - 24 => FWL_VKEY_Final, - 25 => FWL_VKEY_Hanja, - 27 => FWL_VKEY_Escape, - 28 => FWL_VKEY_Convert, - 29 => FWL_VKEY_NonConvert, - 30 => FWL_VKEY_Accept, - 31 => FWL_VKEY_ModeChange, - 32 => FWL_VKEY_Space, - 33 => FWL_VKEY_Prior, - 34 => FWL_VKEY_Next, - 35 => FWL_VKEY_End, - 36 => FWL_VKEY_Home, - 37 => FWL_VKEY_Left, - 38 => FWL_VKEY_Up, - 39 => FWL_VKEY_Right, - 40 => FWL_VKEY_Down, - 41 => FWL_VKEY_Select, - 42 => FWL_VKEY_Print, - 43 => FWL_VKEY_Execute, - 44 => FWL_VKEY_Snapshot, - 45 => FWL_VKEY_Insert, - 46 => FWL_VKEY_Delete, - 47 => FWL_VKEY_Help, - 48 => FWL_VKEY_0, - 49 => FWL_VKEY_1, - 50 => FWL_VKEY_2, - 51 => FWL_VKEY_3, - 52 => FWL_VKEY_4, - 53 => FWL_VKEY_5, - 54 => FWL_VKEY_6, - 55 => FWL_VKEY_7, - 56 => FWL_VKEY_8, - 57 => FWL_VKEY_9, - 65 => FWL_VKEY_A, - 66 => FWL_VKEY_B, - 67 => FWL_VKEY_C, - 68 => FWL_VKEY_D, - 69 => FWL_VKEY_E, - 70 => FWL_VKEY_F, - 71 => FWL_VKEY_G, - 72 => FWL_VKEY_H, - 73 => FWL_VKEY_I, - 74 => FWL_VKEY_J, - 75 => FWL_VKEY_K, - 76 => FWL_VKEY_L, - 77 => FWL_VKEY_M, - 78 => FWL_VKEY_N, - 79 => FWL_VKEY_O, - 80 => FWL_VKEY_P, - 81 => FWL_VKEY_Q, - 82 => FWL_VKEY_R, - 83 => FWL_VKEY_S, - 84 => FWL_VKEY_T, - 85 => FWL_VKEY_U, - 86 => FWL_VKEY_V, - 87 => FWL_VKEY_W, - 88 => FWL_VKEY_X, - 89 => FWL_VKEY_Y, - 90 => FWL_VKEY_Z, - 91 => FWL_VKEY_LWin, - 92 => FWL_VKEY_RWin, - 93 => FWL_VKEY_Apps, - 95 => FWL_VKEY_Sleep, - 96 => FWL_VKEY_NumPad0, - 97 => FWL_VKEY_NumPad1, - 98 => FWL_VKEY_NumPad2, - 99 => FWL_VKEY_NumPad3, - 100 => FWL_VKEY_NumPad4, - 101 => FWL_VKEY_NumPad5, - 102 => FWL_VKEY_NumPad6, - 103 => FWL_VKEY_NumPad7, - 104 => FWL_VKEY_NumPad8, - 105 => FWL_VKEY_NumPad9, - 106 => FWL_VKEY_Multiply, - 107 => FWL_VKEY_Add, - 108 => FWL_VKEY_Separator, - 109 => FWL_VKEY_Subtract, - 110 => FWL_VKEY_Decimal, - 111 => FWL_VKEY_Divide, - 112 => FWL_VKEY_F1, - 113 => FWL_VKEY_F2, - 114 => FWL_VKEY_F3, - 115 => FWL_VKEY_F4, - 116 => FWL_VKEY_F5, - 117 => FWL_VKEY_F6, - 118 => FWL_VKEY_F7, - 119 => FWL_VKEY_F8, - 120 => FWL_VKEY_F9, - 121 => FWL_VKEY_F10, - 122 => FWL_VKEY_F11, - 123 => FWL_VKEY_F12, - 124 => FWL_VKEY_F13, - 125 => FWL_VKEY_F14, - 126 => FWL_VKEY_F15, - 127 => FWL_VKEY_F16, - 128 => FWL_VKEY_F17, - 129 => FWL_VKEY_F18, - 130 => FWL_VKEY_F19, - 131 => FWL_VKEY_F20, - 132 => FWL_VKEY_F21, - 133 => FWL_VKEY_F22, - 134 => FWL_VKEY_F23, - 135 => FWL_VKEY_F24, - 144 => FWL_VKEY_NunLock, - 145 => FWL_VKEY_Scroll, - 160 => FWL_VKEY_LShift, - 161 => FWL_VKEY_RShift, - 162 => FWL_VKEY_LControl, - 163 => FWL_VKEY_RControl, - 164 => FWL_VKEY_LMenu, - 165 => FWL_VKEY_RMenu, - 166 => FWL_VKEY_BROWSER_Back, - 167 => FWL_VKEY_BROWSER_Forward, - 168 => FWL_VKEY_BROWSER_Refresh, - 169 => FWL_VKEY_BROWSER_Stop, - 170 => FWL_VKEY_BROWSER_Search, - 171 => FWL_VKEY_BROWSER_Favorites, - 172 => FWL_VKEY_BROWSER_Home, - 173 => FWL_VKEY_VOLUME_Mute, - 174 => FWL_VKEY_VOLUME_Down, - 175 => FWL_VKEY_VOLUME_Up, - 176 => FWL_VKEY_MEDIA_NEXT_Track, - 177 => FWL_VKEY_MEDIA_PREV_Track, - 178 => FWL_VKEY_MEDIA_Stop, - 179 => FWL_VKEY_MEDIA_PLAY_Pause, - 180 => FWL_VKEY_MEDIA_LAUNCH_Mail, - 181 => FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select, - 182 => FWL_VKEY_MEDIA_LAUNCH_APP1, - 183 => FWL_VKEY_MEDIA_LAUNCH_APP2, - 186 => FWL_VKEY_OEM_1, - 187 => FWL_VKEY_OEM_Plus, - 188 => FWL_VKEY_OEM_Comma, - 189 => FWL_VKEY_OEM_Minus, - 190 => FWL_VKEY_OEM_Period, - 191 => FWL_VKEY_OEM_2, - 192 => FWL_VKEY_OEM_3, - 219 => FWL_VKEY_OEM_4, - 220 => FWL_VKEY_OEM_5, - 221 => FWL_VKEY_OEM_6, - 222 => FWL_VKEY_OEM_7, - 223 => FWL_VKEY_OEM_8, - 226 => FWL_VKEY_OEM_102, - 229 => FWL_VKEY_ProcessKey, - 231 => FWL_VKEY_Packet, - 246 => FWL_VKEY_Attn, - 247 => FWL_VKEY_Crsel, - 248 => FWL_VKEY_Exsel, - 249 => FWL_VKEY_Ereof, - 250 => FWL_VKEY_Play, - 251 => FWL_VKEY_Zoom, - 252 => FWL_VKEY_NoName, - 253 => FWL_VKEY_PA1, - 254 => FWL_VKEY_OEM_Clear, - 0 => FWL_VKEY_Unknown, - _ => throw ArgumentError('Unknown value for FWL_VKEYCODE: $value'), - }; - - @override - String toString() { - if (this == FWL_VKEY_Kana) return "FWL_VKEYCODE.FWL_VKEY_Kana, FWL_VKEYCODE.FWL_VKEY_Hangul"; - if (this == FWL_VKEY_Hanja) return "FWL_VKEYCODE.FWL_VKEY_Hanja, FWL_VKEYCODE.FWL_VKEY_Kanji"; - if (this == FWL_VKEY_LWin) return "FWL_VKEYCODE.FWL_VKEY_LWin, FWL_VKEYCODE.FWL_VKEY_Command"; - return super.toString(); - }} - -/// Interface for unsupported feature notifications. -final class _UNSUPPORT_INFO extends ffi.Struct{ - /// Version number of the interface. Must be 1. - @ffi.Int() - external int version; - - /// Unsupported object notification function. - /// Interface Version: 1 - /// Implementation Required: Yes - /// - /// pThis - pointer to the interface structure. - /// nType - the type of unsupported object. One of the |FPDF_UNSP_*| entries. - external ffi.Pointer pThis, ffi.Int nType)>> FSDK_UnSupport_Handler; - -} - -/// Interface for unsupported feature notifications. -typedef UNSUPPORT_INFO = _UNSUPPORT_INFO; -typedef __time_t = ffi.Long; -typedef Dart__time_t = int; -typedef time_t = __time_t; -final class tm extends ffi.Struct{ - @ffi.Int() - external int tm_sec; - - @ffi.Int() - external int tm_min; - - @ffi.Int() - external int tm_hour; - - @ffi.Int() - external int tm_mday; - - @ffi.Int() - external int tm_mon; - - @ffi.Int() - external int tm_year; - - @ffi.Int() - external int tm_wday; - - @ffi.Int() - external int tm_yday; - - @ffi.Int() - external int tm_isdst; - - @ffi.Long() - external int tm_gmtoff; - - external ffi.Pointer tm_zone; - -} - - -const int FPDF_OBJECT_UNKNOWN = 0; - - -const int FPDF_OBJECT_BOOLEAN = 1; - - -const int FPDF_OBJECT_NUMBER = 2; - - -const int FPDF_OBJECT_STRING = 3; - - -const int FPDF_OBJECT_NAME = 4; - - -const int FPDF_OBJECT_ARRAY = 5; - - -const int FPDF_OBJECT_DICTIONARY = 6; - - -const int FPDF_OBJECT_STREAM = 7; - - -const int FPDF_OBJECT_NULLOBJ = 8; - - -const int FPDF_OBJECT_REFERENCE = 9; - - -const int FPDF_POLICY_MACHINETIME_ACCESS = 0; - - -const int FPDF_ERR_SUCCESS = 0; - - -const int FPDF_ERR_UNKNOWN = 1; - - -const int FPDF_ERR_FILE = 2; - - -const int FPDF_ERR_FORMAT = 3; - - -const int FPDF_ERR_PASSWORD = 4; - - -const int FPDF_ERR_SECURITY = 5; - - -const int FPDF_ERR_PAGE = 6; - - -const int FPDF_ANNOT = 1; - - -const int FPDF_LCD_TEXT = 2; - - -const int FPDF_NO_NATIVETEXT = 4; - - -const int FPDF_GRAYSCALE = 8; - - -const int FPDF_DEBUG_INFO = 128; - - -const int FPDF_NO_CATCH = 256; - - -const int FPDF_RENDER_LIMITEDIMAGECACHE = 512; - - -const int FPDF_RENDER_FORCEHALFTONE = 1024; - - -const int FPDF_PRINTING = 2048; - - -const int FPDF_RENDER_NO_SMOOTHTEXT = 4096; - - -const int FPDF_RENDER_NO_SMOOTHIMAGE = 8192; - - -const int FPDF_RENDER_NO_SMOOTHPATH = 16384; - - -const int FPDF_REVERSE_BYTE_ORDER = 16; - - -const int FPDF_CONVERT_FILL_TO_STROKE = 32; - - -const int FPDFBitmap_Unknown = 0; - - -const int FPDFBitmap_Gray = 1; - - -const int FPDFBitmap_BGR = 2; - - -const int FPDFBitmap_BGRx = 3; - - -const int FPDFBitmap_BGRA = 4; - - -const int FPDFBitmap_BGRA_Premul = 5; - - -const int FXFONT_ANSI_CHARSET = 0; - - -const int FXFONT_DEFAULT_CHARSET = 1; - - -const int FXFONT_SYMBOL_CHARSET = 2; - - -const int FXFONT_SHIFTJIS_CHARSET = 128; - - -const int FXFONT_HANGEUL_CHARSET = 129; - - -const int FXFONT_GB2312_CHARSET = 134; - - -const int FXFONT_CHINESEBIG5_CHARSET = 136; - - -const int FXFONT_GREEK_CHARSET = 161; - - -const int FXFONT_VIETNAMESE_CHARSET = 163; - - -const int FXFONT_HEBREW_CHARSET = 177; - - -const int FXFONT_ARABIC_CHARSET = 178; - - -const int FXFONT_CYRILLIC_CHARSET = 204; - - -const int FXFONT_THAI_CHARSET = 222; - - -const int FXFONT_EASTERNEUROPEAN_CHARSET = 238; - - -const int FXFONT_FF_FIXEDPITCH = 1; - - -const int FXFONT_FF_ROMAN = 16; - - -const int FXFONT_FF_SCRIPT = 64; - - -const int FXFONT_FW_NORMAL = 400; - - -const int FXFONT_FW_BOLD = 700; - - -const int FPDF_MATCHCASE = 1; - - -const int FPDF_MATCHWHOLEWORD = 2; - - -const int FPDF_CONSECUTIVE = 4; - - -const int FPDF_RENDER_READY = 0; - - -const int FPDF_RENDER_TOBECONTINUED = 1; - - -const int FPDF_RENDER_DONE = 2; - - -const int FPDF_RENDER_FAILED = 3; - - -const int FPDF_COLORSPACE_UNKNOWN = 0; - - -const int FPDF_COLORSPACE_DEVICEGRAY = 1; - - -const int FPDF_COLORSPACE_DEVICERGB = 2; - - -const int FPDF_COLORSPACE_DEVICECMYK = 3; - - -const int FPDF_COLORSPACE_CALGRAY = 4; - - -const int FPDF_COLORSPACE_CALRGB = 5; - - -const int FPDF_COLORSPACE_LAB = 6; - - -const int FPDF_COLORSPACE_ICCBASED = 7; - - -const int FPDF_COLORSPACE_SEPARATION = 8; - - -const int FPDF_COLORSPACE_DEVICEN = 9; - - -const int FPDF_COLORSPACE_INDEXED = 10; - - -const int FPDF_COLORSPACE_PATTERN = 11; - - -const int FPDF_PAGEOBJ_UNKNOWN = 0; - - -const int FPDF_PAGEOBJ_TEXT = 1; - - -const int FPDF_PAGEOBJ_PATH = 2; - - -const int FPDF_PAGEOBJ_IMAGE = 3; - - -const int FPDF_PAGEOBJ_SHADING = 4; - - -const int FPDF_PAGEOBJ_FORM = 5; - - -const int FPDF_SEGMENT_UNKNOWN = -1; - - -const int FPDF_SEGMENT_LINETO = 0; - - -const int FPDF_SEGMENT_BEZIERTO = 1; - - -const int FPDF_SEGMENT_MOVETO = 2; - - -const int FPDF_FILLMODE_NONE = 0; - - -const int FPDF_FILLMODE_ALTERNATE = 1; - - -const int FPDF_FILLMODE_WINDING = 2; - - -const int FPDF_FONT_TYPE1 = 1; - - -const int FPDF_FONT_TRUETYPE = 2; - - -const int FPDF_LINECAP_BUTT = 0; - - -const int FPDF_LINECAP_ROUND = 1; - - -const int FPDF_LINECAP_PROJECTING_SQUARE = 2; - - -const int FPDF_LINEJOIN_MITER = 0; - - -const int FPDF_LINEJOIN_ROUND = 1; - - -const int FPDF_LINEJOIN_BEVEL = 2; - - -const int FPDF_PRINTMODE_EMF = 0; - - -const int FPDF_PRINTMODE_TEXTONLY = 1; - - -const int FPDF_PRINTMODE_POSTSCRIPT2 = 2; - - -const int FPDF_PRINTMODE_POSTSCRIPT3 = 3; - - -const int FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4; - - -const int FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5; - - -const int FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6; - - -const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7; - - -const int FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8; - - -const int FORMTYPE_NONE = 0; - - -const int FORMTYPE_ACRO_FORM = 1; - - -const int FORMTYPE_XFA_FULL = 2; - - -const int FORMTYPE_XFA_FOREGROUND = 3; - - -const int FORMTYPE_COUNT = 4; - - -const int JSPLATFORM_ALERT_BUTTON_OK = 0; - - -const int JSPLATFORM_ALERT_BUTTON_OKCANCEL = 1; - - -const int JSPLATFORM_ALERT_BUTTON_YESNO = 2; - - -const int JSPLATFORM_ALERT_BUTTON_YESNOCANCEL = 3; - - -const int JSPLATFORM_ALERT_BUTTON_DEFAULT = 0; - - -const int JSPLATFORM_ALERT_ICON_ERROR = 0; - - -const int JSPLATFORM_ALERT_ICON_WARNING = 1; - - -const int JSPLATFORM_ALERT_ICON_QUESTION = 2; - - -const int JSPLATFORM_ALERT_ICON_STATUS = 3; - - -const int JSPLATFORM_ALERT_ICON_ASTERISK = 4; - - -const int JSPLATFORM_ALERT_ICON_DEFAULT = 0; - - -const int JSPLATFORM_ALERT_RETURN_OK = 1; - - -const int JSPLATFORM_ALERT_RETURN_CANCEL = 2; - - -const int JSPLATFORM_ALERT_RETURN_NO = 3; - - -const int JSPLATFORM_ALERT_RETURN_YES = 4; - - -const int JSPLATFORM_BEEP_ERROR = 0; - - -const int JSPLATFORM_BEEP_WARNING = 1; - - -const int JSPLATFORM_BEEP_QUESTION = 2; - - -const int JSPLATFORM_BEEP_STATUS = 3; - - -const int JSPLATFORM_BEEP_DEFAULT = 4; - - -const int FXCT_ARROW = 0; - - -const int FXCT_NESW = 1; - - -const int FXCT_NWSE = 2; - - -const int FXCT_VBEAM = 3; - - -const int FXCT_HBEAM = 4; - - -const int FXCT_HAND = 5; - - -const int FPDFDOC_AACTION_WC = 16; - - -const int FPDFDOC_AACTION_WS = 17; - - -const int FPDFDOC_AACTION_DS = 18; - - -const int FPDFDOC_AACTION_WP = 19; - - -const int FPDFDOC_AACTION_DP = 20; - - -const int FPDFPAGE_AACTION_OPEN = 0; - - -const int FPDFPAGE_AACTION_CLOSE = 1; - - -const int FPDF_FORMFIELD_UNKNOWN = 0; - - -const int FPDF_FORMFIELD_PUSHBUTTON = 1; - - -const int FPDF_FORMFIELD_CHECKBOX = 2; - - -const int FPDF_FORMFIELD_RADIOBUTTON = 3; - - -const int FPDF_FORMFIELD_COMBOBOX = 4; - - -const int FPDF_FORMFIELD_LISTBOX = 5; - - -const int FPDF_FORMFIELD_TEXTFIELD = 6; - - -const int FPDF_FORMFIELD_SIGNATURE = 7; - - -const int FPDF_FORMFIELD_COUNT = 8; - - -const int FPDF_ANNOT_UNKNOWN = 0; - - -const int FPDF_ANNOT_TEXT = 1; - - -const int FPDF_ANNOT_LINK = 2; - - -const int FPDF_ANNOT_FREETEXT = 3; - - -const int FPDF_ANNOT_LINE = 4; - - -const int FPDF_ANNOT_SQUARE = 5; - - -const int FPDF_ANNOT_CIRCLE = 6; - - -const int FPDF_ANNOT_POLYGON = 7; - - -const int FPDF_ANNOT_POLYLINE = 8; - - -const int FPDF_ANNOT_HIGHLIGHT = 9; - - -const int FPDF_ANNOT_UNDERLINE = 10; - - -const int FPDF_ANNOT_SQUIGGLY = 11; - - -const int FPDF_ANNOT_STRIKEOUT = 12; - - -const int FPDF_ANNOT_STAMP = 13; - - -const int FPDF_ANNOT_CARET = 14; - - -const int FPDF_ANNOT_INK = 15; - - -const int FPDF_ANNOT_POPUP = 16; - - -const int FPDF_ANNOT_FILEATTACHMENT = 17; - - -const int FPDF_ANNOT_SOUND = 18; - - -const int FPDF_ANNOT_MOVIE = 19; - - -const int FPDF_ANNOT_WIDGET = 20; - - -const int FPDF_ANNOT_SCREEN = 21; - - -const int FPDF_ANNOT_PRINTERMARK = 22; - - -const int FPDF_ANNOT_TRAPNET = 23; - - -const int FPDF_ANNOT_WATERMARK = 24; - - -const int FPDF_ANNOT_THREED = 25; - - -const int FPDF_ANNOT_RICHMEDIA = 26; - - -const int FPDF_ANNOT_XFAWIDGET = 27; - - -const int FPDF_ANNOT_REDACT = 28; - - -const int FPDF_ANNOT_FLAG_NONE = 0; - - -const int FPDF_ANNOT_FLAG_INVISIBLE = 1; - - -const int FPDF_ANNOT_FLAG_HIDDEN = 2; - - -const int FPDF_ANNOT_FLAG_PRINT = 4; - - -const int FPDF_ANNOT_FLAG_NOZOOM = 8; - - -const int FPDF_ANNOT_FLAG_NOROTATE = 16; - - -const int FPDF_ANNOT_FLAG_NOVIEW = 32; - - -const int FPDF_ANNOT_FLAG_READONLY = 64; - - -const int FPDF_ANNOT_FLAG_LOCKED = 128; - - -const int FPDF_ANNOT_FLAG_TOGGLENOVIEW = 256; - - -const int FPDF_ANNOT_APPEARANCEMODE_NORMAL = 0; - - -const int FPDF_ANNOT_APPEARANCEMODE_ROLLOVER = 1; - - -const int FPDF_ANNOT_APPEARANCEMODE_DOWN = 2; - - -const int FPDF_ANNOT_APPEARANCEMODE_COUNT = 3; - - -const int FPDF_FORMFLAG_NONE = 0; - - -const int FPDF_FORMFLAG_READONLY = 1; - - -const int FPDF_FORMFLAG_REQUIRED = 2; - - -const int FPDF_FORMFLAG_NOEXPORT = 4; - - -const int FPDF_FORMFLAG_TEXT_MULTILINE = 4096; - - -const int FPDF_FORMFLAG_TEXT_PASSWORD = 8192; - - -const int FPDF_FORMFLAG_CHOICE_COMBO = 131072; - - -const int FPDF_FORMFLAG_CHOICE_EDIT = 262144; - - -const int FPDF_FORMFLAG_CHOICE_MULTI_SELECT = 2097152; - - -const int FPDF_ANNOT_AACTION_KEY_STROKE = 12; - - -const int FPDF_ANNOT_AACTION_FORMAT = 13; - - -const int FPDF_ANNOT_AACTION_VALIDATE = 14; - - -const int FPDF_ANNOT_AACTION_CALCULATE = 15; - - -const int FPDF_INCREMENTAL = 1; - - -const int FPDF_NO_INCREMENTAL = 2; - - -const int FPDF_REMOVE_SECURITY = 3; - - -const int PDFACTION_UNSUPPORTED = 0; - - -const int PDFACTION_GOTO = 1; - - -const int PDFACTION_REMOTEGOTO = 2; - - -const int PDFACTION_URI = 3; - - -const int PDFACTION_LAUNCH = 4; - - -const int PDFACTION_EMBEDDEDGOTO = 5; - - -const int PDFDEST_VIEW_UNKNOWN_MODE = 0; - - -const int PDFDEST_VIEW_XYZ = 1; - - -const int PDFDEST_VIEW_FIT = 2; - - -const int PDFDEST_VIEW_FITH = 3; - - -const int PDFDEST_VIEW_FITV = 4; - - -const int PDFDEST_VIEW_FITR = 5; - - -const int PDFDEST_VIEW_FITB = 6; - - -const int PDFDEST_VIEW_FITBH = 7; - - -const int PDFDEST_VIEW_FITBV = 8; - - -const int PDF_LINEARIZATION_UNKNOWN = -1; - - -const int PDF_NOT_LINEARIZED = 0; - - -const int PDF_LINEARIZED = 1; - - -const int PDF_DATA_ERROR = -1; - - -const int PDF_DATA_NOTAVAIL = 0; - - -const int PDF_DATA_AVAIL = 1; - - -const int PDF_FORM_ERROR = -1; - - -const int PDF_FORM_NOTAVAIL = 0; - - -const int PDF_FORM_AVAIL = 1; - - -const int PDF_FORM_NOTEXIST = 2; - - -const int FPDF_UNSP_DOC_XFAFORM = 1; - - -const int FPDF_UNSP_DOC_PORTABLECOLLECTION = 2; - - -const int FPDF_UNSP_DOC_ATTACHMENT = 3; - - -const int FPDF_UNSP_DOC_SECURITY = 4; - - -const int FPDF_UNSP_DOC_SHAREDREVIEW = 5; - - -const int FPDF_UNSP_DOC_SHAREDFORM_ACROBAT = 6; - - -const int FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM = 7; - - -const int FPDF_UNSP_DOC_SHAREDFORM_EMAIL = 8; - - -const int FPDF_UNSP_ANNOT_3DANNOT = 11; - - -const int FPDF_UNSP_ANNOT_MOVIE = 12; - - -const int FPDF_UNSP_ANNOT_SOUND = 13; - - -const int FPDF_UNSP_ANNOT_SCREEN_MEDIA = 14; - - -const int FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA = 15; - - -const int FPDF_UNSP_ANNOT_ATTACHMENT = 16; - - -const int FPDF_UNSP_ANNOT_SIG = 17; - - -const int PAGEMODE_UNKNOWN = -1; - - -const int PAGEMODE_USENONE = 0; - - -const int PAGEMODE_USEOUTLINES = 1; - - -const int PAGEMODE_USETHUMBS = 2; - - -const int PAGEMODE_FULLSCREEN = 3; - - -const int PAGEMODE_USEOC = 4; - - -const int PAGEMODE_USEATTACHMENTS = 5; - - -const int FLATTEN_FAIL = 0; - - -const int FLATTEN_SUCCESS = 1; - - -const int FLATTEN_NOTHINGTODO = 2; - - -const int FLAT_NORMALDISPLAY = 0; - - -const int FLAT_PRINT = 1; - diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 6dc3f4df..f248e91e 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -31,7 +31,7 @@ import '../utils/shuffle_in_place.dart'; import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium.dart'; -import 'pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import 'pdfium_file_access.dart'; import 'worker.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart index 0f3625d6..f6200c04 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart @@ -4,7 +4,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import '../pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import '../pdfium_file_access.dart'; import '../worker.dart'; import 'pthread.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart index c7ba1dd5..2d91f73a 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart @@ -4,7 +4,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import '../pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import '../pdfium_file_write.dart'; import '../worker.dart'; import 'pthread.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart index 20887727..1e00315c 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart @@ -4,7 +4,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import '../pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import '../pdfium_file_access.dart'; import '../worker.dart'; import 'kernel32.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart index 96732947..41634190 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart @@ -4,7 +4,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import '../pdfium_bindings.dart' as pdfium_bindings; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import '../pdfium_file_write.dart'; import '../worker.dart'; import 'kernel32.dart'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 9d22df3c..8fe65353 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -1,7 +1,6 @@ import 'dart:io'; -import 'package:archive/archive_io.dart'; -import 'package:http/http.dart' as http; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_dart; import 'pdfrx.dart'; import 'pdfrx_entry_functions.dart'; @@ -20,10 +19,7 @@ bool _isInitialized = false; /// - Calls [PdfrxEntryFunctions.init] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. -Future pdfrxInitialize({ - String? tmpPath, - String? pdfiumRelease = _PdfiumDownloader.pdfrxCurrentPdfiumRelease, -}) async { +Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease = pdfium_dart.currentPdfiumRelease}) async { if (_isInitialized) return; Pdfrx.loadAsset ??= (name) async { @@ -37,72 +33,10 @@ Future pdfrxInitialize({ if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await _PdfiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); + Pdfrx.pdfiumModulePath = await pdfium_dart.PDFiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); } await PdfrxEntryFunctions.instance.init(); _isInitialized = true; } - -/// PdfiumDownloader is a utility class to download the Pdfium module for various platforms. -class _PdfiumDownloader { - _PdfiumDownloader._(); - - /// The release of pdfium to download. - static const pdfrxCurrentPdfiumRelease = 'chromium%2F7506'; - - /// Downloads the pdfium module for the current platform and architecture. - /// - /// Currently, the following platforms are supported: - /// - Windows x64 - /// - Linux x64, arm64 - /// - macOS x64, arm64 - /// - /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. - static Future downloadAndGetPdfiumModulePath( - String tmpPath, { - String? pdfiumRelease = pdfrxCurrentPdfiumRelease, - }) async { - final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; - final platform = pa[1]!; - final arch = pa[2]!; - if (platform == 'windows' && arch == 'x64') { - return await downloadPdfium(tmpPath, 'win', arch, 'bin/pdfium.dll', pdfiumRelease); - } - if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await downloadPdfium(tmpPath, platform, arch, 'lib/libpdfium.so', pdfiumRelease); - } - if (platform == 'macos') { - return await downloadPdfium(tmpPath, 'mac', arch, 'lib/libpdfium.dylib', pdfiumRelease); - } else { - throw Exception('Unsupported platform: $platform-$arch'); - } - } - - /// Downloads the pdfium module for the given platform and architecture. - static Future downloadPdfium( - String tmpRoot, - String platform, - String arch, - String modulePath, - String? pdfiumRelease, - ) async { - final tmpDir = Directory('$tmpRoot/$platform-$arch'); - final targetPath = '${tmpDir.path}/$modulePath'; - if (await File(targetPath).exists()) return targetPath; - - final uri = - 'https://github.com/bblanchon/pdfium-binaries/releases/download/$pdfiumRelease/pdfium-$platform-$arch.tgz'; - final tgz = await http.Client().get(Uri.parse(uri)); - if (tgz.statusCode != 200) { - throw Exception('Failed to download pdfium: $uri'); - } - final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(tgz.bodyBytes)); - try { - await tmpDir.delete(recursive: true); - } catch (_) {} - await extractArchiveToDisk(archive, tmpDir.path); - return targetPath; - } -} diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index f96aca83..4ebd214c 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -12,6 +12,7 @@ resolution: workspace # Some of the dependencies are intentionally not pinned to a specific version # to allow for more flexibility in resolving versions when used in larger projects. dependencies: + pdfium_dart: ^0.1.0 collection: crypto: ^3.0.6 ffi: @@ -24,49 +25,7 @@ dependencies: image: ^4.5.4 dev_dependencies: - ffigen: ^19.1.0 lints: ^6.0.0 test: ^1.26.2 -# To generate the bindings, firstly you must run the test once to download PDFium headers: -# dart run test -# then run the following command: -# dart run ffigen -ffigen: - output: - bindings: "lib/src/native/pdfium_bindings.dart" - headers: - entry-points: - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_signature.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_sysfontinfo.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_javascript.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_text.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_searchex.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_progressive.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdfview.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_edit.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_attachment.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_annot.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_catalog.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_ppo.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_formfill.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_save.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_doc.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_structtree.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_dataavail.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_fwlevent.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_ext.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_transformpage.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_flatten.h" - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/fpdf_thumbnail.h" - include-directives: - - "/tmp/pdfrx.cache/pdfium/linux-x64/include/**" - preamble: | - // ignore_for_file: unused_field - // dart format off - name: "pdfium" - comments: - style: any - length: full - From 5e7c85f4777c8d4854a4ce41585ac6154bbb5d95 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 15:55:28 +0900 Subject: [PATCH 542/663] Introduces pdfium_flutter --- AGENTS.md | 26 ++++- README.md | 22 ++++- packages/pdfium_dart/LICENSE | 11 +++ packages/pdfium_dart/README.md | 4 +- packages/pdfium_dart/test/pdfium_test.dart | 3 +- packages/pdfium_flutter/.gitignore | 4 + packages/pdfium_flutter/LICENSE | 11 +++ packages/pdfium_flutter/README.md | 50 ++++++++++ .../android/.gitignore | 0 .../android/CMakeLists.txt | 4 +- .../android/build.gradle | 4 +- .../pdfium_flutter/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 2 +- .../darwin/pdfium_flutter.podspec} | 10 +- .../darwin/pdfium_flutter}/Package.swift | 8 +- .../Sources/main/pdfium_flutter.swift} | 4 +- .../pdfium_flutter/lib/pdfium_flutter.dart | 8 ++ .../pdfium_flutter/lib/src/pdfium_loader.dart | 78 +++++++++++++++ .../linux/CMakeLists.txt | 4 +- packages/pdfium_flutter/pubspec.yaml | 39 ++++++++ .../windows/.gitignore | 0 .../windows/CMakeLists.txt | 6 +- packages/pdfrx/android/settings.gradle | 1 - .../pdfrx/example/viewer/ios/Podfile.lock | 26 +++++ .../ios/Runner.xcodeproj/project.pbxproj | 95 +++++++++++++++++++ .../pdfrx/example/viewer/macos/Podfile.lock | 26 +++++ .../macos/Runner.xcodeproj/project.pbxproj | 18 ++++ packages/pdfrx/pubspec.yaml | 13 +-- pubspec.yaml | 2 + 29 files changed, 438 insertions(+), 42 deletions(-) create mode 100644 packages/pdfium_dart/LICENSE create mode 100644 packages/pdfium_flutter/.gitignore create mode 100644 packages/pdfium_flutter/LICENSE create mode 100644 packages/pdfium_flutter/README.md rename packages/{pdfrx => pdfium_flutter}/android/.gitignore (100%) rename packages/{pdfrx => pdfium_flutter}/android/CMakeLists.txt (97%) rename packages/{pdfrx => pdfium_flutter}/android/build.gradle (91%) create mode 100644 packages/pdfium_flutter/android/settings.gradle rename packages/{pdfrx => pdfium_flutter}/android/src/main/AndroidManifest.xml (64%) rename packages/{pdfrx/darwin/pdfrx.podspec => pdfium_flutter/darwin/pdfium_flutter.podspec} (89%) rename packages/{pdfrx/darwin/pdfrx => pdfium_flutter/darwin/pdfium_flutter}/Package.swift (82%) rename packages/{pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift => pdfium_flutter/darwin/pdfium_flutter/Sources/main/pdfium_flutter.swift} (73%) create mode 100644 packages/pdfium_flutter/lib/pdfium_flutter.dart create mode 100644 packages/pdfium_flutter/lib/src/pdfium_loader.dart rename packages/{pdfrx => pdfium_flutter}/linux/CMakeLists.txt (97%) create mode 100644 packages/pdfium_flutter/pubspec.yaml rename packages/{pdfrx => pdfium_flutter}/windows/.gitignore (100%) rename packages/{pdfrx => pdfium_flutter}/windows/CMakeLists.txt (96%) delete mode 100644 packages/pdfrx/android/settings.gradle diff --git a/AGENTS.md b/AGENTS.md index a4ea022f..a67aec11 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,17 +66,37 @@ flutter build windows flutter build macos ``` -### FFI Bindings (pdfrx_engine) +### FFI Bindings -- Bindings are generated with `ffigen`. -- Pdfium headers download automatically when `dart test` runs on Linux. +FFI bindings for PDFium are maintained in the `pdfium_dart` package and generated using `ffigen`. + +#### Prerequisites + +The `ffigen` process requires the following prerequisites to be installed: + +- **LLVM/Clang**: Required for parsing C headers + - macOS: Install via Homebrew: `brew install llvm` + - Linux: Install via package manager: `apt-get install libclang-dev` (Ubuntu/Debian) or `dnf install clang-devel` (Fedora) + - Windows: Install LLVM from [llvm.org](https://releases.llvm.org/) + +#### Generating Bindings ```bash +# For pdfium_dart package +cd packages/pdfium_dart +dart test # Downloads PDFium headers automatically +dart run ffigen + +# For pdfrx_engine (if needed) cd packages/pdfrx_engine dart test dart run ffigen ``` +#### On-Demand PDFium Downloads + +The `pdfium_dart` package provides a `getPdfium()` function that downloads PDFium binaries on demand. This is useful for testing or CLI applications that don't want to bundle PDFium binaries. + ## Release Process See `RELEASING.md` for the full checklist. Agents should avoid editing release metadata unless the task explicitly covers publishing. diff --git a/README.md b/README.md index 7da797d6..19d73421 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdfrx -This repository contains three Dart/Flutter packages for PDF rendering, viewing, and manipulation: +This repository contains multiple Dart/Flutter packages for PDF rendering, viewing, and manipulation: ## Packages @@ -33,11 +33,31 @@ A cross-platform PDF viewer and manipulation plugin for Flutter. - Maintains full compatibility with pdfrx widget API - iOS and macOS only +### [pdfium_dart](packages/pdfium_dart/) + +Low-level Dart FFI bindings for the PDFium library. + +- Pure Dart package with auto-generated FFI bindings using `ffigen` +- Provides direct access to PDFium's C API from Dart +- Includes `getPdfium()` function that downloads PDFium binaries on demand +- Used as a foundation by higher-level packages + +### [pdfium_flutter](packages/pdfium_flutter/) + +Flutter FFI plugin for loading PDFium native libraries. + +- Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux) +- Provides utilities for loading PDFium at runtime +- Re-exports `pdfium_dart` FFI bindings +- Simplifies PDFium integration in Flutter applications + ## When to Use Which Package - **Use `pdfrx`** if you're building a Flutter application and need PDF viewing and manipulation capabilities with UI - **Use `pdfrx_engine`** if you need PDF rendering and manipulation without Flutter dependencies (e.g., server-side PDF processing, CLI tools, PDF combining utilities) - **Use `pdfrx_coregraphics`** (experimental) if you want to use CoreGraphics/PDFKit instead of PDFium on iOS/macOS +- **Use `pdfium_dart`** if you need low-level PDFium FFI bindings for Dart projects or want on-demand PDFium binary downloads +- **Use `pdfium_flutter`** if you're building a Flutter plugin that needs PDFium integration with bundled binaries ## Getting Started diff --git a/packages/pdfium_dart/LICENSE b/packages/pdfium_dart/LICENSE new file mode 100644 index 00000000..7de3ad69 --- /dev/null +++ b/packages/pdfium_dart/LICENSE @@ -0,0 +1,11 @@ + +The MIT License (MIT) +=============== + +Copyright (c) 2025 @espresso3389 (Takashi Kawasaki) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pdfium_dart/README.md b/packages/pdfium_dart/README.md index 29b0967c..46742bca 100644 --- a/packages/pdfium_dart/README.md +++ b/packages/pdfium_dart/README.md @@ -17,14 +17,14 @@ This package contains auto-generated FFI bindings for PDFium using `ffigen`. It ### Basic Usage -This package is primarily intended to be used as a dependency by higher-level packages like `pdfium_flutter` and `pdfrx_engine`. Direct usage is possible but not recommended unless you need low-level PDFium access. +This package is primarily intended to be used as a dependency by higher-level packages like [pdfium_flutter](https://pub.dev/packages/pdfium_flutter) and [pdfrx_engine](https://pub.dev/packages/pdfrx_engine). Direct usage is possible but not recommended unless you need low-level PDFium access. ```dart import 'package:pdfium_dart/pdfium_dart.dart'; import 'dart:ffi'; // If you already have PDFium loaded -final pdfium = pdfium(DynamicLibrary.open('/path/to/libpdfium.so')); +final pdfium = PDFium(DynamicLibrary.open('/path/to/libpdfium.so')); ``` ### On-Demand PDFium Downloads diff --git a/packages/pdfium_dart/test/pdfium_test.dart b/packages/pdfium_dart/test/pdfium_test.dart index ef2b8bf3..545c03db 100644 --- a/packages/pdfium_dart/test/pdfium_test.dart +++ b/packages/pdfium_dart/test/pdfium_test.dart @@ -1,9 +1,8 @@ import 'dart:io'; +import 'package:pdfium_dart/pdfium_dart.dart'; import 'package:test/test.dart'; -import '../lib/pdfium_dart.dart'; - final tmpRoot = Directory('${Directory.current.path}/test/.tmp'); final testPdfFile = File('../pdfrx/example/viewer/assets/hello.pdf'); diff --git a/packages/pdfium_flutter/.gitignore b/packages/pdfium_flutter/.gitignore new file mode 100644 index 00000000..463e519d --- /dev/null +++ b/packages/pdfium_flutter/.gitignore @@ -0,0 +1,4 @@ +.build/ +**.xcframework/ +*.zip +.pdfium_hash diff --git a/packages/pdfium_flutter/LICENSE b/packages/pdfium_flutter/LICENSE new file mode 100644 index 00000000..7de3ad69 --- /dev/null +++ b/packages/pdfium_flutter/LICENSE @@ -0,0 +1,11 @@ + +The MIT License (MIT) +=============== + +Copyright (c) 2025 @espresso3389 (Takashi Kawasaki) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pdfium_flutter/README.md b/packages/pdfium_flutter/README.md new file mode 100644 index 00000000..cf1ea1be --- /dev/null +++ b/packages/pdfium_flutter/README.md @@ -0,0 +1,50 @@ +# pdfium_flutter + +Flutter FFI plugin for loading PDFium native libraries. This package bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. + +## Overview + +This package provides: +- Pre-built PDFium native libraries for all supported platforms +- Utilities for loading PDFium at runtime +- Re-exports of `pdfium_dart` FFI bindings + +## Platform Support + +| Platform | Support | Notes | +|----------|---------|-------| +| Android | ✅ | ARM64, ARMv7, x86, x86_64 | +| iOS | ✅ | ARM64, Simulator | +| macOS | ✅ | ARM64, x86_64 | +| Windows | ✅ | x64, ARM64 | +| Linux | ✅ | x64, ARM64, ARM, x86 | +| Web | ❌ | FFI is not available for Web | + +## Usage + +This package is primarily intended to be used as a dependency by higher-level packages like [pdfrx](https://pub.dev/packages/pdfrx). Direct usage is possible but not recommended unless you need low-level PDFium access. + +```dart +import 'package:pdfium_flutter/pdfium_flutter.dart'; + +// Get PDFium bindings +final pdfium = pdfiumBindings; + +// Or load with custom path +final customPdfium = loadPdfium(modulePath: '/custom/path/to/pdfium.so'); +``` + +## Native Libraries + +### Android +PDFium binaries are downloaded during build from [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases). + +### iOS/macOS +PDFium XCFramework is downloaded using CocoaPods/SwiftPM install from [espresso3389/pdfium-xcframework](https://github.com/espresso3389/pdfium-xcframework/releases). + +### Windows/Linux +PDFium binaries are downloaded during build from [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases). + +## License + +This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. diff --git a/packages/pdfrx/android/.gitignore b/packages/pdfium_flutter/android/.gitignore similarity index 100% rename from packages/pdfrx/android/.gitignore rename to packages/pdfium_flutter/android/.gitignore diff --git a/packages/pdfrx/android/CMakeLists.txt b/packages/pdfium_flutter/android/CMakeLists.txt similarity index 97% rename from packages/pdfrx/android/CMakeLists.txt rename to packages/pdfium_flutter/android/CMakeLists.txt index de093356..a9d6333b 100644 --- a/packages/pdfrx/android/CMakeLists.txt +++ b/packages/pdfium_flutter/android/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.18.1) # Project-level configuration. -set(PROJECT_NAME "pdfrx") +set(PROJECT_NAME "pdfium_flutter") project(${PROJECT_NAME} LANGUAGES CXX) # Explicitly opt in to modern CMake behaviors to avoid warnings with recent @@ -63,7 +63,7 @@ endif() file(REMOVE ${PDFIUM_LATEST_DIR}) file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) -set(pdfrx_bundled_libraries +set(pdfium_flutter_bundled_libraries ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) diff --git a/packages/pdfrx/android/build.gradle b/packages/pdfium_flutter/android/build.gradle similarity index 91% rename from packages/pdfrx/android/build.gradle rename to packages/pdfium_flutter/android/build.gradle index 53b1f8b0..61d21411 100644 --- a/packages/pdfrx/android/build.gradle +++ b/packages/pdfium_flutter/android/build.gradle @@ -1,4 +1,4 @@ -group = "jp.espresso3389.pdfrx" +group = "jp.espresso3389.pdfium_flutter" version = "1.0-SNAPSHOT" buildscript { @@ -23,7 +23,7 @@ apply plugin: "com.android.library" android { if (project.android.hasProperty("namespace")) { - namespace = "jp.espresso3389.pdfrx" + namespace = "jp.espresso3389.pdfium_flutter" } compileSdk = 35 diff --git a/packages/pdfium_flutter/android/settings.gradle b/packages/pdfium_flutter/android/settings.gradle new file mode 100644 index 00000000..3681ab45 --- /dev/null +++ b/packages/pdfium_flutter/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pdfium_flutter' diff --git a/packages/pdfrx/android/src/main/AndroidManifest.xml b/packages/pdfium_flutter/android/src/main/AndroidManifest.xml similarity index 64% rename from packages/pdfrx/android/src/main/AndroidManifest.xml rename to packages/pdfium_flutter/android/src/main/AndroidManifest.xml index ec2aabfc..0469cbb3 100644 --- a/packages/pdfrx/android/src/main/AndroidManifest.xml +++ b/packages/pdfium_flutter/android/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ + package="jp.espresso3389.pdfium_flutter"> diff --git a/packages/pdfrx/darwin/pdfrx.podspec b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec similarity index 89% rename from packages/pdfrx/darwin/pdfrx.podspec rename to packages/pdfium_flutter/darwin/pdfium_flutter.podspec index a067da51..9bce2a2f 100644 --- a/packages/pdfrx/darwin/pdfrx.podspec +++ b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec @@ -4,17 +4,17 @@ PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/downlo PDFIUM_HASH = "0a900bb5b5d66c4caaaaef1cf291dd1ef34639069baa12c565eda296aee878ec" Pod::Spec.new do |s| - s.name = 'pdfrx' - s.version = '0.1.3' - s.summary = 'Yet another PDF renderer for Flutter using PDFium.' + s.name = 'pdfium_flutter' + s.version = '0.1.0' + s.summary = 'Flutter FFI plugin for loading PDFium native libraries.' s.description = <<-DESC - Yet another PDF renderer for Flutter using PDFium. + Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for iOS and macOS. DESC s.homepage = 'https://github.com/espresso3389/pdfrx' s.license = { :type => 'MIT', :file => '../LICENSE' } s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' } s.source = { :path => '.' } - s.source_files = 'pdfrx/Sources/**/*.swift' + s.source_files = 'pdfium_flutter/Sources/**/*.swift' s.preserve_paths = 'PDFium.xcframework/**/*' s.ios.deployment_target = '12.0' diff --git a/packages/pdfrx/darwin/pdfrx/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift similarity index 82% rename from packages/pdfrx/darwin/pdfrx/Package.swift rename to packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index 1bf89916..5c4073b1 100644 --- a/packages/pdfrx/darwin/pdfrx/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -2,20 +2,20 @@ import PackageDescription let package = Package( - name: "pdfrx", + name: "pdfium_flutter", platforms: [ .iOS(.v12), .macOS(.v10_13), ], products: [ .library( - name: "pdfrx", - targets: ["pdfrx"] + name: "pdfium_flutter", + targets: ["pdfium_flutter"] ), ], targets: [ .target( - name: "pdfrx", + name: "pdfium_flutter", dependencies: [ .target(name: "PDFium"), ], diff --git a/packages/pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Sources/main/pdfium_flutter.swift similarity index 73% rename from packages/pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift rename to packages/pdfium_flutter/darwin/pdfium_flutter/Sources/main/pdfium_flutter.swift index 1d43d541..25ec41ec 100644 --- a/packages/pdfrx/darwin/pdfrx/Sources/main/pdfrx.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Sources/main/pdfium_flutter.swift @@ -4,10 +4,10 @@ import FlutterMacOS #endif -class PdfrxPlugin: NSObject, FlutterPlugin { +class PDFiumFlutterPlugin: NSObject, FlutterPlugin { static func register(with _: FlutterPluginRegistrar) { // This is an FFI plugin - no platform channel needed - // The native code is accessed via FFI from pdfrx_engine + // The native code is accessed via FFI from pdfium_flutter } func dummyMethodToPreventStripping() { diff --git a/packages/pdfium_flutter/lib/pdfium_flutter.dart b/packages/pdfium_flutter/lib/pdfium_flutter.dart new file mode 100644 index 00000000..2556e381 --- /dev/null +++ b/packages/pdfium_flutter/lib/pdfium_flutter.dart @@ -0,0 +1,8 @@ +/// Flutter FFI plugin for loading PDFium native libraries. +/// +/// This package bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux +/// and provides utilities for loading them at runtime. +library pdfium_flutter; + +export 'package:pdfium_dart/pdfium_dart.dart'; +export 'src/pdfium_loader.dart'; diff --git a/packages/pdfium_flutter/lib/src/pdfium_loader.dart b/packages/pdfium_flutter/lib/src/pdfium_loader.dart new file mode 100644 index 00000000..ba7e26bf --- /dev/null +++ b/packages/pdfium_flutter/lib/src/pdfium_loader.dart @@ -0,0 +1,78 @@ +import 'dart:ffi' as ffi; +import 'dart:ffi'; +import 'dart:io'; + +import 'package:pdfium_dart/pdfium_dart.dart'; + +/// Get the module file name for pdfium. +String _getModuleFileName() { + if (Platform.isAndroid) return 'libpdfium.so'; + if (Platform.isWindows) return 'pdfium.dll'; + if (Platform.isLinux) { + return '${File(Platform.resolvedExecutable).parent.path}/lib/libpdfium.so'; + } + throw UnsupportedError('Unsupported platform'); +} + +DynamicLibrary _getModule({String? explicitPath}) { + // If the module path is explicitly specified, use it. + if (explicitPath != null) { + return DynamicLibrary.open(explicitPath); + } + // For iOS/macOS, we assume pdfium is already loaded (or statically linked) in the process. + if (Platform.isIOS || Platform.isMacOS) { + return DynamicLibrary.process(); + } + return DynamicLibrary.open(_getModuleFileName()); +} + +PDFium? _pdfium; + +/// Loaded PDFium module. +/// +/// This getter lazily loads the PDFium library and returns the bindings. +PDFium get pdfiumBindings { + _pdfium ??= PDFium(_getModule()); + return _pdfium!; +} + +/// Set custom PDFium bindings (for testing or custom library paths). +set pdfiumBindings(PDFium value) { + _pdfium = value; +} + +/// Load PDFium with an explicit module path. +/// +/// This is useful for custom deployment scenarios or testing. +PDFium loadPdfium({String? modulePath}) { + final bindings = PDFium(_getModule(explicitPath: modulePath)); + _pdfium = bindings; + return bindings; +} + +/// Reset the PDFium bindings (useful for testing). +void resetPdfiumBindings() { + _pdfium = null; +} + +typedef PdfiumNativeFunctionLookup = + ffi.Pointer Function(String symbolName); + +/// Create a native function lookup for PDFium symbols. +/// +/// This is used for custom native bindings or mock implementations. +PdfiumNativeFunctionLookup? createPdfiumNativeFunctionLookup< + T extends ffi.NativeType +>(Map? nativeBindings) { + if (nativeBindings != null) { + ffi.Pointer lookup(String symbolName) { + final ptr = nativeBindings[symbolName]; + if (ptr == null) + throw Exception('Failed to find binding for $symbolName'); + return ffi.Pointer.fromAddress(ptr); + } + + return lookup; + } + return null; +} diff --git a/packages/pdfrx/linux/CMakeLists.txt b/packages/pdfium_flutter/linux/CMakeLists.txt similarity index 97% rename from packages/pdfrx/linux/CMakeLists.txt rename to packages/pdfium_flutter/linux/CMakeLists.txt index 6c5c80a2..860d2b77 100644 --- a/packages/pdfrx/linux/CMakeLists.txt +++ b/packages/pdfium_flutter/linux/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) # Project-level configuration. -set(PROJECT_NAME "pdfrx") +set(PROJECT_NAME "pdfium_flutter") project(${PROJECT_NAME} LANGUAGES CXX) # Determine target processor name @@ -80,7 +80,7 @@ file(CREATE_LINK ${PDFIUM_LIBS_DIR} ${PDFIUM_LATEST_DIR} SYMBOLIC) # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. -set(pdfrx_bundled_libraries +set(pdfium_flutter_bundled_libraries ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml new file mode 100644 index 00000000..b81780da --- /dev/null +++ b/packages/pdfium_flutter/pubspec.yaml @@ -0,0 +1,39 @@ +name: pdfium_flutter +description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. +version: 0.1.0 +homepage: https://github.com/espresso3389/pdfrx +repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter +issue_tracker: https://github.com/espresso3389/pdfrx/issues + +environment: + sdk: '>=3.9.0 <4.0.0' + flutter: '>=3.29.0' +resolution: workspace + +dependencies: + pdfium_dart: ^0.1.0 + ffi: + flutter: + sdk: flutter + path: + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + sharedDarwinSource: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + sharedDarwinSource: true + windows: + ffiPlugin: true diff --git a/packages/pdfrx/windows/.gitignore b/packages/pdfium_flutter/windows/.gitignore similarity index 100% rename from packages/pdfrx/windows/.gitignore rename to packages/pdfium_flutter/windows/.gitignore diff --git a/packages/pdfrx/windows/CMakeLists.txt b/packages/pdfium_flutter/windows/CMakeLists.txt similarity index 96% rename from packages/pdfrx/windows/CMakeLists.txt rename to packages/pdfium_flutter/windows/CMakeLists.txt index f5961f99..6f0230bb 100644 --- a/packages/pdfrx/windows/CMakeLists.txt +++ b/packages/pdfium_flutter/windows/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.14) # Project-level configuration. -set(PROJECT_NAME "pdfrx") +set(PROJECT_NAME "pdfium_flutter") project(${PROJECT_NAME} LANGUAGES CXX) # Explicitly opt in to modern CMake behaviors to avoid warnings with recent @@ -93,7 +93,7 @@ else() "========================================================================\n" " ERROR: Windows Developer Mode is not enabled!\n" "------------------------------------------------------------------------\n" - " The pdfrx build process requires Developer Mode to create symbolic\n" + " The pdfium_flutter build process requires Developer Mode to create symbolic\n" " links. The build cannot continue without Developer Mode enabled.\n" "\n" " Please enable Developer Mode by following the instructions at:\n" @@ -108,7 +108,7 @@ endif() # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. -set(pdfrx_bundled_libraries +set(pdfium_flutter_bundled_libraries ${PDFIUM_LATEST_LIB_FILENAME} PARENT_SCOPE ) diff --git a/packages/pdfrx/android/settings.gradle b/packages/pdfrx/android/settings.gradle deleted file mode 100644 index bccb4d82..00000000 --- a/packages/pdfrx/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'pdfrx' diff --git a/packages/pdfrx/example/viewer/ios/Podfile.lock b/packages/pdfrx/example/viewer/ios/Podfile.lock index c3dcb254..d1bfe47e 100644 --- a/packages/pdfrx/example/viewer/ios/Podfile.lock +++ b/packages/pdfrx/example/viewer/ios/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_ios (0.0.1): + - Flutter - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfium_flutter (0.1.0): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: + - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - pdfium_flutter (from `.symlinks/plugins/pdfium_flutter/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: + file_selector_ios: + :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + pdfium_flutter: + :path: ".symlinks/plugins/pdfium_flutter/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + pdfium_flutter: 5dbf80fb3ce6d3a333a4b895a7b16844da6f9ac9 + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694 diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 8b7f4816..270e0634 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 06B83D6F6618DC7CCC4F8EC4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; @@ -15,6 +16,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + DB7AD93E01BB8E95A58E41D0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E52033D095D41B805F376 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -43,11 +45,15 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 312E52033D095D41B805F376 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 50E2E216D9E444E0C49E84D3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 5F4A74F28605FCB19004871A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -57,6 +63,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + C967F79C0E5199682A53E2C1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 06B83D6F6618DC7CCC4F8EC4 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +83,7 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + DB7AD93E01BB8E95A58E41D0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,6 +93,12 @@ 2C0A7AC090CFDFC89385173D /* Pods */ = { isa = PBXGroup; children = ( + 5F4A74F28605FCB19004871A /* Pods-Runner.debug.xcconfig */, + 50E2E216D9E444E0C49E84D3 /* Pods-Runner.release.xcconfig */, + C967F79C0E5199682A53E2C1 /* Pods-Runner.profile.xcconfig */, + 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */, + E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */, + E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -113,6 +131,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 2C0A7AC090CFDFC89385173D /* Pods */, + F1B23DD2AC5484B2739268AA /* Frameworks */, ); sourceTree = ""; }; @@ -140,6 +159,15 @@ path = Runner; sourceTree = ""; }; + F1B23DD2AC5484B2739268AA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 312E52033D095D41B805F376 /* Pods_Runner.framework */, + 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -147,6 +175,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 373523437F138D4BB02F2C05 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 8CA26D04F87237D4AA4DC2CF /* Frameworks */, @@ -165,12 +194,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + E003A8C4FC0CE09C63D52588 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + FAEC9DAC8178CC76D4BE498C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -248,6 +279,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 373523437F138D4BB02F2C05 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -279,6 +332,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + E003A8C4FC0CE09C63D52588 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FAEC9DAC8178CC76D4BE498C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -407,6 +499,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -424,6 +517,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -439,6 +533,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/pdfrx/example/viewer/macos/Podfile.lock b/packages/pdfrx/example/viewer/macos/Podfile.lock index 9d359e12..caf5cd15 100644 --- a/packages/pdfrx/example/viewer/macos/Podfile.lock +++ b/packages/pdfrx/example/viewer/macos/Podfile.lock @@ -1,15 +1,41 @@ PODS: + - file_selector_macos (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - pdfium_flutter (0.1.0): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - pdfium_flutter (from `Flutter/ephemeral/.symlinks/plugins/pdfium_flutter/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + pdfium_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/pdfium_flutter/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + pdfium_flutter: 5dbf80fb3ce6d3a333a4b895a7b16844da6f9ac9 + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index 8c12c8e6..59878f87 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -243,6 +243,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 03BECD5C4820C9F3BD651D30 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +330,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 03BECD5C4820C9F3BD651D30 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 425d7a83..3c091587 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -15,6 +15,7 @@ resolution: workspace dependencies: pdfrx_engine: ^0.2.4 + pdfium_flutter: ^0.1.0 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 @@ -44,18 +45,6 @@ executables: flutter: plugin: platforms: - android: - ffiPlugin: true - ios: - ffiPlugin: true - sharedDarwinSource: true - linux: - ffiPlugin: true - macos: - ffiPlugin: true - sharedDarwinSource: true - windows: - ffiPlugin: true web: assets: diff --git a/pubspec.yaml b/pubspec.yaml index 07aa75c1..86fdc087 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,6 +2,8 @@ name: pdfrx_workspace environment: sdk: ">=3.9.0 <4.0.0" workspace: + - packages/pdfium_dart + - packages/pdfium_flutter - packages/pdfrx - packages/pdfrx_engine - packages/pdfrx_coregraphics From 55966404b9ff1dc8d0261102e4ca9448674dc992 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 16:02:17 +0900 Subject: [PATCH 543/663] Document updates. --- AGENTS.md | 26 +++++++-- ...operability-with-other-PDFium-Libraries.md | 17 +++--- doc/Low-Level-PDFium-Bindings-Access.md | 56 +++++++++---------- packages/pdfrx_engine/README.md | 5 +- 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a67aec11..c5c14f54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,19 +11,37 @@ This file provides guidance to AI agents and developers when working with code i ## Project Overview -pdfrx is a monorepo containing two packages: +pdfrx is a monorepo containing five packages: -1. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium +1. **pdfium_dart** (`packages/pdfium_dart/`) - Low-level Dart FFI bindings for PDFium + - Pure Dart package with auto-generated FFI bindings using `ffigen` + - Provides direct access to PDFium's C API + - Includes `getPdfium()` function for on-demand PDFium binary downloads + - Used as a foundation by higher-level packages + +2. **pdfium_flutter** (`packages/pdfium_flutter/`) - Flutter FFI plugin for loading PDFium native libraries + - Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux) + - Provides utilities for loading PDFium at runtime + - Re-exports `pdfium_dart` FFI bindings + +3. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium - Pure Dart package with no Flutter dependencies - - Provides core PDF document API and PDFium bindings + - Depends on `pdfium_dart` for PDFium bindings + - Provides core PDF document API - Can be used independently for non-Flutter Dart applications -2. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter +4. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter - Depends on pdfrx_engine for PDF rendering functionality + - Depends on pdfium_flutter for bundled PDFium binaries - Provides Flutter widgets and UI components - Supports iOS, Android, Windows, macOS, Linux, and Web - Uses PDFium for native platforms and PDFium WASM for web platforms +5. **pdfrx_coregraphics** (`packages/pdfrx_coregraphics/`) - CoreGraphics-backed renderer for iOS/macOS + - Experimental package using PDFKit/CoreGraphics instead of PDFium + - Drop-in replacement for Apple platforms + - iOS and macOS only + ## Command and Tooling Expectations - Run commands directly in the repository environment with the correct `workdir`; coordinate with the user before escalating privileges or leaving the workspace. diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md index 0f7c0181..ecec5d9c 100644 --- a/doc/Interoperability-with-other-PDFium-Libraries.md +++ b/doc/Interoperability-with-other-PDFium-Libraries.md @@ -22,7 +22,7 @@ pdfrx provides mechanisms to coordinate PDFium access across different libraries ## Low-Level PDFium Bindings -For advanced use cases, pdfrx_engine exposes direct access to PDFium's C API through FFI bindings. This allows you to: +For advanced use cases, the [pdfium_dart](https://pub.dev/packages/pdfium_dart) package exposes direct access to PDFium's C API through FFI bindings. This allows you to: - Call any PDFium function directly - Implement custom PDF processing not covered by the high-level API @@ -119,22 +119,24 @@ class PdfProcessor { ### Example 2: Low-Level PDFium Bindings Access -pdfrx_engine provides direct access to PDFium bindings for advanced use cases. You can import the low-level bindings to make direct PDFium API calls: +The [pdfium_dart](https://pub.dev/packages/pdfium_dart) package provides direct access to PDFium bindings for advanced use cases. You can import the low-level bindings to make direct PDFium API calls: ```dart import 'dart:ffi'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:pdfrx/pdfrx.dart'; -// Access low-level PDFium bindings -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; -import 'package:pdfrx_engine/src/native/pdfium.dart'; +// Access low-level PDFium bindings from pdfium_dart +import 'package:pdfium_dart/pdfium_dart.dart'; class LowLevelPdfiumAccess { Future useDirectBindings() async { // Initialize PDFium through pdfrx await pdfrxFlutterInitialize(); + // Get PDFium bindings + final pdfium = await getPdfium(); + // Now you can use all PDFium C API functions through the bindings // Remember to wrap calls with suspendPdfiumWorkerDuringAction await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { @@ -146,6 +148,8 @@ class LowLevelPdfiumAccess { } Future> extractCustomMetadata(Uint8List pdfData) async { + final pdfium = await getPdfium(); + return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { return using((arena) { final dataPtr = arena.allocate(pdfData.length); @@ -199,8 +203,7 @@ class LowLevelPdfiumAccess { **Important Notes about Low-Level Bindings:** -- Import `package:pdfrx_engine/src/native/pdfium_bindings.dart` for the FFI binding definitions -- Import `package:pdfrx_engine/src/native/pdfium.dart` for the `loadPdfiumBindings()` function +- Import `package:pdfium_dart/pdfium_dart.dart` for the FFI binding definitions and `getPdfium()` function - Always wrap PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts - Remember to properly manage memory (use `Arena` for automatic memory management) - PDFium text APIs often return UTF-16 encoded strings diff --git a/doc/Low-Level-PDFium-Bindings-Access.md b/doc/Low-Level-PDFium-Bindings-Access.md index bd2a0d21..2a4930d9 100644 --- a/doc/Low-Level-PDFium-Bindings-Access.md +++ b/doc/Low-Level-PDFium-Bindings-Access.md @@ -4,33 +4,25 @@ This document explains how to access low-level PDFium bindings directly when wor ## Overview -While pdfrx_engine provides high-level Dart APIs for PDF operations, you may occasionally need direct access to PDFium's native functions. The package exposes these through two key imports: +While pdfrx_engine provides high-level Dart APIs for PDF operations, you may occasionally need direct access to PDFium's native functions. The low-level PDFium bindings are provided by the [pdfium_dart](https://pub.dev/packages/pdfium_dart) package: -- `package:pdfrx_engine/src/native/pdfium_bindings.dart` - Raw FFI bindings generated from PDFium headers -- `package:pdfrx_engine/src/native/pdfium.dart` - Helper utilities and wrapper functions +- `package:pdfium_dart/pdfium_dart.dart` - Raw FFI bindings generated from PDFium headers ## Importing Low-Level Bindings ### Raw FFI Bindings ```dart -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfium_dart/pdfium_dart.dart'; ``` This import provides access to the auto-generated FFI bindings that directly map to PDFium's C API. These bindings are generated using `ffigen` from PDFium headers and include all PDFium functions with their original names (e.g., `FPDF_InitLibrary`, `FPDF_LoadDocument`, etc.). -### Helper Utilities +The `pdfium_dart` package provides: -```dart -import 'package:pdfrx_engine/src/native/pdfium.dart'; -``` - -This import provides: - -- The `pdfium` global instance for accessing PDFium functions -- Helper utilities for memory management -- Platform-specific initialization functions -- Convenience wrappers around common PDFium operations +- The `PDFium` class for accessing PDFium functions +- Auto-generated FFI bindings for all PDFium C API functions +- `getPdfium()` function for on-demand PDFium binary downloads ### Initialization @@ -41,11 +33,11 @@ There are basically three ways to initialize PDFium: #### Manual Initialization ```dart -import 'package:pdfrx_engine/pdfrx_engine.dart'; // or import 'package:pdfrx/pdfrx.dart'; -import 'package:pdfrx_engine/src/native/pdfium.dart'; -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfium_dart/pdfium_dart.dart'; +import 'dart:ffi'; -Pdfrx.pdfiumModulePath = 'somewhere/in/your/filesystem/libpdfium.so'; +// Load PDFium library manually +final pdfium = PDFium(DynamicLibrary.open('path/to/libpdfium.so')); pdfium.FPDF_InitLibrary(); // or pdfium.FPDF_InitLibraryWithConfig(...) ``` @@ -74,15 +66,17 @@ For more information about initialization, see [pdfrx Initialization](pdfrx-Init ### Basic PDFium Function Access ```dart -import 'package:pdfrx_engine/src/native/pdfium.dart'; -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfium_dart/pdfium_dart.dart'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; -void example() { +void example() async { + // Get PDFium bindings (downloads binaries if needed) + final pdfium = await getPdfium(); + // Use arena to automatically manage memory using((arena) { - // Access PDFium functions through the global pdfium instance + // Access PDFium functions final doc = pdfium.FPDF_LoadDocument( 'path/to/file.pdf'.toNativeUtf8(allocator: arena).cast(), nullptr, @@ -102,10 +96,9 @@ void example() { ### Working with Pages ```dart -import 'package:pdfrx_engine/src/native/pdfium.dart'; -import 'package:pdfrx_engine/src/native/pdfium_bindings.dart'; +import 'package:pdfium_dart/pdfium_dart.dart'; -void workWithPage(FPDF_DOCUMENT doc) { +void workWithPage(PDFium pdfium, FPDF_DOCUMENT doc) { final page = pdfium.FPDF_LoadPage(doc, 0); // Load first page if (page != nullptr) { @@ -123,11 +116,13 @@ void workWithPage(FPDF_DOCUMENT doc) { When working with raw bindings, you're responsible for proper memory management. Always use `Arena` to ensure allocated memory is properly released: ```dart -import 'package:pdfrx_engine/src/native/pdfium.dart'; +import 'package:pdfium_dart/pdfium_dart.dart'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; -void memoryExample() { +void memoryExample() async { + final pdfium = await getPdfium(); + // Use arena for automatic memory management using((arena) { final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena); @@ -146,7 +141,8 @@ void memoryExample() { } // Alternative: Manual memory management (not recommended) -void manualMemoryExample() { +void manualMemoryExample() async { + final pdfium = await getPdfium(); final pathPtr = 'path/to/file.pdf'.toNativeUtf8(); try { @@ -177,6 +173,8 @@ PDFium is not thread-safe. Ensure all PDFium calls are made from the same thread Always check return values from PDFium functions: ```dart +final pdfium = await getPdfium(); + using((arena) { final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena); final doc = pdfium.FPDF_LoadDocument(pathPtr.cast(), nullptr); diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 231532d2..e4c1287d 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -4,7 +4,7 @@ [pdfrx_engine](https://pub.dartlang.org/packages/pdfrx_engine) is a platform-agnostic PDF rendering and manipulation engine built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). It provides low-level PDF document APIs for viewing, editing, combining PDF documents, and importing images without any Flutter dependencies, making it suitable for use in pure Dart applications, CLI tools, or server-side PDF processing. -This package is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. +This package depends on [pdfium_dart](https://pub.dartlang.org/packages/pdfium_dart) for PDFium FFI bindings and is a part of [pdfrx](https://pub.dartlang.org/packages/pdfrx) Flutter plugin, which adds UI widgets and Flutter-specific features on top of this engine. ## Multi-platform support @@ -57,7 +57,8 @@ You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure t - [PdfPage.loadText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content from page - [PdfPage.loadLinks](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html) - Extract links from page - PDFium bindings - - For advanced use cases, you can access the raw PDFium bindings via `package:pdfrx_engine/src/native/pdfium_bindings.dart` + - For advanced use cases, you can access the raw PDFium bindings via `package:pdfium_dart/pdfium_dart.dart` + - The bindings are provided by the [pdfium_dart](https://pub.dartlang.org/packages/pdfium_dart) package - Note: Direct use of PDFium bindings is not recommended for most use cases ## When to Use pdfrx_engine vs. pdfrx From 81c1a2b79c672a24ee6be6b614a334130943eec1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 17:52:04 +0900 Subject: [PATCH 544/663] WIP: updating notes for pdfrx_engine and lower packages --- README.md | 2 +- doc/Importing-Images-to-PDF.md | 188 +++++++++--------- packages/pdfium_dart/CHANGELOG.md | 3 + packages/pdfium_dart/pubspec.yaml | 6 +- packages/pdfium_flutter/CHANGELOG.md | 3 + packages/pdfium_flutter/pubspec.yaml | 4 +- packages/pdfrx/example/pdf_combine/README.md | 9 +- .../linux/flutter/generated_plugins.cmake | 2 +- .../windows/flutter/generated_plugins.cmake | 2 +- .../linux/flutter/generated_plugins.cmake | 2 +- .../windows/flutter/generated_plugins.cmake | 2 +- packages/pdfrx_engine/CHANGELOG.md | 7 + packages/pdfrx_engine/README.md | 2 +- packages/pdfrx_engine/pubspec.yaml | 2 +- 14 files changed, 126 insertions(+), 108 deletions(-) create mode 100644 packages/pdfium_dart/CHANGELOG.md create mode 100644 packages/pdfium_flutter/CHANGELOG.md diff --git a/README.md b/README.md index 19d73421..168505c7 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Add `pdfrx_engine` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx_engine: ^0.2.4 + pdfrx_engine: ^0.3.0 ``` ## Documentation diff --git a/doc/Importing-Images-to-PDF.md b/doc/Importing-Images-to-PDF.md index fe0a0a4a..d987fefd 100644 --- a/doc/Importing-Images-to-PDF.md +++ b/doc/Importing-Images-to-PDF.md @@ -7,31 +7,42 @@ pdfrx provides powerful APIs for importing images into PDF documents. This featu The image import feature allows you to: - Convert images (JPEG, PNG, etc.) to PDF format -- Create PDF documents from images +- Create PDF documents from JPEG images - Insert images as new pages into existing PDFs - Build PDFs from scanned documents or photos - Control image dimensions and placement in the PDF ## Creating PDF Documents from Images -### PdfDocument.createFromImage +### PdfDocument.createFromJpegData -[PdfDocument.createFromImage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromImage.html) creates a new PDF document containing a single page with the specified image: +[PdfDocument.createFromJpegData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromJpegData.html) creates a new PDF document containing a single page with the specified JPEG image: ```dart import 'dart:ui' as ui; import 'package:pdfrx/pdfrx.dart'; +import 'package:image/image.dart' as img; // Load and decode an image final imageBytes = await File('photo.jpg').readAsBytes(); -final ui.Image image = await decodeImageFromList(imageBytes); -// Convert to PdfImage -final pdfImage = await image.toPdfImage(); +// If the image is already JPEG, you can use it directly +// Otherwise, convert it to JPEG format +Uint8List jpegData; +if (imagePath.toLowerCase().endsWith('.jpg') || imagePath.toLowerCase().endsWith('.jpeg')) { + jpegData = imageBytes; +} else { + // Decode the image + final image = img.decodeImage(imageBytes); + if (image == null) throw Exception('Failed to decode image'); + + // Encode as JPEG with quality 90 + jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90)); +} -// Create PDF with image (width and height in PDF units: 1/72 inch) -final imageDoc = await PdfDocument.createFromImage( - pdfImage, +// Create PDF with JPEG data (width and height in PDF units: 1/72 inch) +final imageDoc = await PdfDocument.createFromJpegData( + jpegData, width: 595, // A4 width in points (8.27 inches) height: 842, // A4 height in points (11.69 inches) sourceName: 'photo.pdf', @@ -42,12 +53,10 @@ final pdfBytes = await imageDoc.encodePdf(); await File('output.pdf').writeAsBytes(pdfBytes); // Clean up -pdfImage.dispose(); -image.dispose(); imageDoc.dispose(); ``` -**Important Note:** The `width` and `height` parameters only control the visible page size in the PDF document. They do NOT resize or compress the actual image data. The full-resolution image is embedded in the PDF regardless of these dimensions. If you need to reduce the PDF file size, you must resize the image before passing it to `createFromImage`. +**Important Note:** The `width` and `height` parameters only control the visible page size in the PDF document. They do NOT resize or compress the actual image data. The JPEG image is embedded in the PDF as-is. If you need to reduce the PDF file size, you must resize the image before converting it to JPEG. ### Understanding Image Dimensions @@ -71,18 +80,24 @@ width: 842, height: 1191 #### Calculating Dimensions from Image Size -If you want to preserve the image's aspect ratio and size based on DPI: +If you want to preserve the image's aspect ratio and size based on DPI, you need to decode the JPEG to get its dimensions: ```dart +import 'package:image/image.dart' as img; + +// Decode JPEG to get dimensions +final jpegImage = img.decodeJpg(jpegData); +if (jpegImage == null) throw Exception('Failed to decode JPEG'); + // Assume image DPI (common values: 72, 96, 150, 300) const double assumedDpi = 300; // Calculate PDF page size from image dimensions -final width = image.width * 72 / assumedDpi; -final height = image.height * 72 / assumedDpi; +final width = jpegImage.width * 72 / assumedDpi; +final height = jpegImage.height * 72 / assumedDpi; -final imageDoc = await PdfDocument.createFromImage( - pdfImage, +final imageDoc = await PdfDocument.createFromJpegData( + jpegData, width: width, height: height, sourceName: 'image.pdf', @@ -93,19 +108,24 @@ final imageDoc = await PdfDocument.createFromImage( ## Inserting Images into Existing PDFs -You can combine `PdfDocument.createFromImage` with [page manipulation](PDF-Page-Manipulation.md) to insert images into existing PDF documents: +You can combine `PdfDocument.createFromJpegData` with [page manipulation](PDF-Page-Manipulation.md) to insert images into existing PDF documents: ```dart +import 'package:image/image.dart' as img; + // Load existing PDF final doc = await PdfDocument.openFile('document.pdf'); -// Load and convert image to PDF -final imageBytes = await File('photo.jpg').readAsBytes(); -final ui.Image image = await decodeImageFromList(imageBytes); -final pdfImage = await image.toPdfImage(); +// Load and convert image to JPEG +final imageBytes = await File('photo.png').readAsBytes(); +final image = img.decodeImage(imageBytes); +if (image == null) throw Exception('Failed to decode image'); + +// Encode as JPEG +final jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90)); -final imageDoc = await PdfDocument.createFromImage( - pdfImage, +final imageDoc = await PdfDocument.createFromJpegData( + jpegData, width: 595, height: 842, sourceName: 'temp-image.pdf', @@ -123,8 +143,6 @@ final pdfBytes = await doc.encodePdf(); await File('output.pdf').writeAsBytes(pdfBytes); // Clean up -pdfImage.dispose(); -image.dispose(); doc.dispose(); imageDoc.dispose(); ``` @@ -134,30 +152,35 @@ imageDoc.dispose(); You can create a PDF document containing multiple images by combining pages from multiple image-based PDFs: ```dart +import 'package:image/image.dart' as img; + // Create a new PDF document final combinedDoc = await PdfDocument.createNew(sourceName: 'multi-image.pdf'); // List to store image documents final List imageDocs = []; -final List pdfImages = []; -final List images = []; try { // Load and convert multiple images - for (final imagePath in ['image1.jpg', 'image2.jpg', 'image3.jpg']) { + for (final imagePath in ['image1.jpg', 'image2.png', 'image3.jpg']) { final imageBytes = await File(imagePath).readAsBytes(); - final ui.Image image = await decodeImageFromList(imageBytes); - images.add(image); - final pdfImage = await image.toPdfImage(); - pdfImages.add(pdfImage); + // Decode the image + final image = img.decodeImage(imageBytes); + if (image == null) { + print('Failed to decode $imagePath'); + continue; + } + + // Encode as JPEG + final jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90)); // Calculate dimensions (assuming 300 DPI) final width = image.width * 72 / 300; final height = image.height * 72 / 300; - final imageDoc = await PdfDocument.createFromImage( - pdfImage, + final imageDoc = await PdfDocument.createFromJpegData( + jpegData, width: width, height: height, sourceName: imagePath, @@ -174,12 +197,6 @@ try { } finally { // Clean up resources - for (final img in images) { - img.dispose(); - } - for (final pdfImg in pdfImages) { - pdfImg.dispose(); - } for (final doc in imageDocs) { doc.dispose(); } @@ -191,18 +208,20 @@ try { ### Image Resolution and File Size -The `width` and `height` parameters in `createFromImage()` **only control the page dimensions**, not the image resolution. The full-resolution image data is embedded in the PDF file regardless of these values. +The `width` and `height` parameters in `createFromJpegData()` **only control the page dimensions**, not the image resolution. The JPEG image data is embedded in the PDF file as-is. **Example:** ```dart +import 'package:image/image.dart' as img; + // This creates a small page, but the PDF file will still contain -// the full 4000x3000 pixel image data -final largeImage = await decodeImageFromList(bytes); // 4000x3000 pixels -final pdfImage = await largeImage.toPdfImage(); +// the full 4000x3000 pixel JPEG data +final largeImage = img.decodeImage(bytes); // 4000x3000 pixels +final jpegData = Uint8List.fromList(img.encodeJpg(largeImage!, quality: 90)); -final doc = await PdfDocument.createFromImage( - pdfImage, +final doc = await PdfDocument.createFromJpegData( + jpegData, width: 200, // Small page width height: 150, // Small page height sourceName: 'small-page-large-file.pdf', @@ -212,84 +231,67 @@ final doc = await PdfDocument.createFromImage( ### How to Reduce PDF File Size -To reduce the PDF file size, you must resize the image **before** passing it to pdfrx: +To reduce the PDF file size, you must resize the image **before** converting it to JPEG: ```dart -import 'dart:ui' as ui; -import 'dart:math'; +import 'package:image/image.dart' as img; -// Load original image -final originalImage = await decodeImageFromList(imageBytes); +// Load and decode original image +final originalImage = img.decodeImage(imageBytes); +if (originalImage == null) throw Exception('Failed to decode image'); // Target page size at desired DPI (e.g., A4 at 150 DPI) const targetDpi = 150.0; const pageWidthInches = 8.27; // A4 width in inches const pageHeightInches = 11.69; // A4 height in inches -const pageWidth = pageWidthInches * targetDpi; // 1240 pixels -const pageHeight = pageHeightInches * targetDpi; // 1754 pixels +const pageWidthPixels = (pageWidthInches * targetDpi).round(); // 1240 pixels +const pageHeightPixels = (pageHeightInches * targetDpi).round(); // 1754 pixels // Calculate aspect ratios final imageAspect = originalImage.width / originalImage.height; -final pageAspect = pageWidth / pageHeight; +final pageAspect = pageWidthPixels / pageHeightPixels; // Calculate target dimensions that fit within the page while preserving aspect ratio int targetWidth, targetHeight; if (imageAspect > pageAspect) { // Image is wider than page - fit to width - targetWidth = pageWidth.round(); - targetHeight = (pageWidth / imageAspect).round(); + targetWidth = pageWidthPixels; + targetHeight = (pageWidthPixels / imageAspect).round(); } else { // Image is taller than page - fit to height - targetHeight = pageHeight.round(); - targetWidth = (pageHeight * imageAspect).round(); + targetHeight = pageHeightPixels; + targetWidth = (pageHeightPixels * imageAspect).round(); } -// Create a canvas with full page dimensions and center the resized image on it -final pageWidthPixels = pageWidth.round(); -final pageHeightPixels = pageHeight.round(); - -final recorder = ui.PictureRecorder(); -final canvas = Canvas(recorder); - -// Fill background with white -canvas.drawRect( - Rect.fromLTWH(0, 0, pageWidthPixels.toDouble(), pageHeightPixels.toDouble()), - Paint()..color = const Color(0xFFFFFFFF), -); - -// Calculate offset to center the image -final offsetX = (pageWidthPixels - targetWidth) / 2; -final offsetY = (pageHeightPixels - targetHeight) / 2; - -// Draw the resized image centered on the page -canvas.drawImageRect( +// Resize the image +final resizedImage = img.copyResize( originalImage, - Rect.fromLTWH(0, 0, originalImage.width.toDouble(), originalImage.height.toDouble()), - Rect.fromLTWH(offsetX, offsetY, targetWidth.toDouble(), targetHeight.toDouble()), - Paint(), + width: targetWidth, + height: targetHeight, + interpolation: img.Interpolation.linear, ); -final picture = recorder.endRecording(); -final finalImage = await picture.toImage(pageWidthPixels, pageHeightPixels); -originalImage.dispose(); - -// Now convert to PDF - this will have a much smaller file size -final pdfImage = await finalImage.toPdfImage(); +// Encode as JPEG with quality setting (1-100) +// Lower quality = smaller file size but lower image quality +final jpegData = Uint8List.fromList(img.encodeJpg(resizedImage, quality: 85)); // Calculate PDF page dimensions -final pdfPageWidth = pageWidthInches * 72; // A4 width in points (595) -final pdfPageHeight = pageHeightInches * 72; // A4 height in points (842) +const pdfPageWidth = pageWidthInches * 72; // A4 width in points (595) +const pdfPageHeight = pageHeightInches * 72; // A4 height in points (842) -final doc = await PdfDocument.createFromImage( - pdfImage, +final doc = await PdfDocument.createFromJpegData( + jpegData, width: pdfPageWidth, height: pdfPageHeight, sourceName: 'optimized.pdf', ); +// Encode to PDF +final pdfBytes = await doc.encodePdf(); +await File('output.pdf').writeAsBytes(pdfBytes); + // Clean up -picture.dispose(); -finalImage.dispose(); +doc.dispose(); ``` ### Recommended Image Resolutions diff --git a/packages/pdfium_dart/CHANGELOG.md b/packages/pdfium_dart/CHANGELOG.md new file mode 100644 index 00000000..ec928660 --- /dev/null +++ b/packages/pdfium_dart/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- First release. diff --git a/packages/pdfium_dart/pubspec.yaml b/packages/pdfium_dart/pubspec.yaml index f8e73dac..cdc105ec 100644 --- a/packages/pdfium_dart/pubspec.yaml +++ b/packages/pdfium_dart/pubspec.yaml @@ -10,8 +10,10 @@ environment: resolution: workspace dependencies: - ffi: - path: + archive: ^4.0.7 + ffi: ^2.1.4 + http: ^1.5.0 + path: ^1.9.1 dev_dependencies: ffigen: ^19.1.0 diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md new file mode 100644 index 00000000..ec928660 --- /dev/null +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- First release. diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index b81780da..766c2f14 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -12,10 +12,10 @@ resolution: workspace dependencies: pdfium_dart: ^0.1.0 - ffi: + ffi: ^2.1.4 flutter: sdk: flutter - path: + path: ^1.9.1 dev_dependencies: flutter_test: diff --git a/packages/pdfrx/example/pdf_combine/README.md b/packages/pdfrx/example/pdf_combine/README.md index 9b2c2306..99370e3f 100644 --- a/packages/pdfrx/example/pdf_combine/README.md +++ b/packages/pdfrx/example/pdf_combine/README.md @@ -16,7 +16,7 @@ A Flutter desktop application that demonstrates how to combine multiple PDF file ## How It Works -The app uses the [`PdfDocument.createFromImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromImage.html) and [`encodePdf()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/encodePdf.html) APIs from pdfrx_engine: +The app uses the [`PdfDocument.createFromJpegData()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromJpegData.html) and [`encodePdf()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/encodePdf.html) APIs from pdfrx_engine: ### PDF Files @@ -25,9 +25,10 @@ The app uses the [`PdfDocument.createFromImage()`](https://pub.dev/documentation ### Image Files -1. **Load Images**: Read image bytes and decode using Flutter's [`decodeImageFromList()`](https://api.flutter.dev/flutter/dart-ui/decodeImageFromList.html) -2. **Convert to PDF**: Use [`image.toPdfImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfImageGeneratorOnImage/toPdfImage.html) and [`PdfDocument.createFromImage()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromImage.html) to convert images to single-page PDF documents -3. **DPI Handling**: Images are converted with an assumed DPI of 300 for optimal page sizing +1. **Load Images**: Read image bytes and decode/resize as needed +2. **Convert to JPEG**: Compress images to JPEG format with quality control and optional resizing +3. **Convert to PDF**: Use [`PdfDocument.createFromJpegData()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/createFromJpegData.html) to convert JPEG data to single-page PDF documents +4. **Size Control**: Images are resized to fit within A4 page dimensions while maintaining aspect ratio ### Combining diff --git a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake index 198caa95..0e00fb81 100644 --- a/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/linux/flutter/generated_plugins.cmake @@ -9,7 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - pdfrx + pdfium_flutter ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake index 4c198f09..9a807d5f 100644 --- a/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/pdf_combine/windows/flutter/generated_plugins.cmake @@ -10,7 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - pdfrx + pdfium_flutter ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake b/packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake index 409ae740..1647b76a 100644 --- a/packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/viewer/linux/flutter/generated_plugins.cmake @@ -8,7 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - pdfrx + pdfium_flutter ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake b/packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake index 3f90e6e6..399e6936 100644 --- a/packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake +++ b/packages/pdfrx/example/viewer/windows/flutter/generated_plugins.cmake @@ -8,7 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - pdfrx + pdfium_flutter ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index d7508260..5a05b76a 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.3.0 + +- NEW: `PdfDocument.createFromJpegData()` - Create PDF documents from JPEG image data +- CHANGED: Now uses `pdfium_dart` package for PDFium FFI bindings instead of bundled bindings +- CHANGED: File structure refactoring - moved from monolithic API file to separate files for better organization +- Dependency updates + ## 0.2.4 - NEW: `PdfDocument` now supports page re-arrangement and accepts `PdfPage` instances from other documents, enabling PDF combine/merge functionality diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index e4c1287d..c0bff9bc 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -51,7 +51,7 @@ You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure t - [PdfDocument.openUri](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openUri.html) - Open PDF from stream (advanced use case) - [PdfDocument.openAsset](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openAsset.html) - Open PDF from Flutter asset - [PdfDocument.createNew](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createNew.html) - Create new empty PDF document - - [PdfDocument.createFromImage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromImage.html) - Create PDF from image + - [PdfDocument.createFromJpegData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromJpegData.html) - Create PDF from JPEG data - [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation and rendering - [PdfPage.render](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap - [PdfPage.loadText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content from page diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 4ebd214c..9923c531 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.2.4 +version: 0.3.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From b6a4679555fde7720ed061b98665d6f4cbba3e93 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 18:21:22 +0900 Subject: [PATCH 545/663] WIP --- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 3c091587..3cb9e464 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.4 + pdfrx_engine: ^0.3.0 pdfium_flutter: ^0.1.0 collection: crypto: ^3.0.6 diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 6a78799f..55ef2da0 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.2.4 + pdfrx_engine: ^0.3.0 http: dev_dependencies: From 736b73c0a2be14e75fdc2e158817820642a9a22b Mon Sep 17 00:00:00 2001 From: james Date: Mon, 10 Nov 2025 21:50:49 +1030 Subject: [PATCH 546/663] Magnifier enhancements to support customization --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 137 +++++++++++++++--- .../lib/src/widgets/pdf_viewer_params.dart | 127 ++++++++++++++-- 2 files changed, 233 insertions(+), 31 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index bca8aad0..63ca5a79 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2267,20 +2267,20 @@ class _PdfViewerState extends State Offset? calcPosition( Size? widgetSize, - _TextSelectionPart part, { + PdfTextSelectionAnchor? textAnchor, + Rect? anchorLocalRect, + Offset pointerOffset, { double margin = defMargin, double? marginOnTop, double? marginOnBottom, }) { - if (widgetSize == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { + if (widgetSize == null || textAnchor == null) { return null; } - final textAnchor = part == _TextSelectionPart.a ? _textSelA : _textSelB; - if (textAnchor == null) return null; late double left, top; - final rect0 = (part == _TextSelectionPart.a ? rectA : rectB); - final rect1 = (part == _TextSelectionPart.a ? _anchorARect : _anchorBRect); + final rect0 = textAnchor.rect; + final rect1 = anchorLocalRect; final pt = rect0.center; final rectTop = rect1 == null ? rect0.top : min(rect0.top, rect1.top); final rectBottom = rect1 == null ? rect0.bottom : max(rect0.bottom, rect1.bottom); @@ -2304,7 +2304,7 @@ class _PdfViewerState extends State } break; case PdfTextDirection.vrtl: - if (part == _TextSelectionPart.a) { + if (textAnchor.type == PdfTextSelectionAnchorType.a) { left = rectRight + margin; if (left + widgetSize.width + margin > viewSize.width) { left = rectLeft - widgetSize.width - margin; @@ -2327,8 +2327,17 @@ class _PdfViewerState extends State } Widget? magnifier; - if (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) { - final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA! : _textSelB!; + + final shouldShowMagnifier = widget.params.textSelectionParams?.magnifier?.shouldShowMagnifier?.call(); + // Show magnifier if dragging + if (((textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) && + shouldShowMagnifier != false) || + shouldShowMagnifier == true) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a + ? _textSelA! + : textAnchorMoving == _TextSelectionPart.b + ? _textSelB! + : (_selPartLastMoved == _TextSelectionPart.a ? _textSelA! : _textSelB!); final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = @@ -2339,15 +2348,50 @@ class _PdfViewerState extends State magnifierParams, ); if (magnifierEnabled) { - final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)(textAnchor, magnifierParams); + final textAnchorLocalRect = (textAnchor.type == PdfTextSelectionAnchorType.a ? _anchorARect : _anchorBRect); + // Calculate final magnifier position before calling builder + final magnifierPosition = + (magnifierParams.calcPosition ?? calcPosition).call( + _magnifierRect?.size, + textAnchor, + textAnchorLocalRect, + _pointerOffset, + margin: defMargin, + marginOnTop: 20, + marginOnBottom: 80, + ) ?? + Offset.zero; + + // Calculate clamped pointer position for magnifier content + final clampedPointerPosition = _calcClampedPointerPosition( + _pointerOffset, + magnifierPosition, + _magnifierRect?.size, + textAnchor, + ); + + final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)( + textAnchor, + magnifierParams, + clampedPointerPosition, + ); final magnifierMain = _buildMagnifier(context, magRect, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = builder(context, textAnchor, magnifierParams, magnifierMain, magRect.size); + magnifier = builder( + context, + textAnchor, + magnifierParams, + magnifierMain, + magRect.size, + clampedPointerPosition, + magnifierPosition, + ); if (magnifier != null && !isPositionalWidget(magnifier)) { - final offset = - calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; + // Use calculated values + final offset = magnifierPosition; + magnifier = AnimatedPositioned( - duration: _previousMagnifierRect != null ? const Duration(milliseconds: 100) : Duration.zero, + duration: _previousMagnifierRect != null ? magnifierParams.animationDuration : Duration.zero, left: offset.dx, top: offset.dy, child: WidgetSizeSniffer( @@ -2450,9 +2494,18 @@ class _PdfViewerState extends State contextMenu = createContextMenu(a, b, _contextMenuFor); if (contextMenu != null && !isPositionalWidget(contextMenu)) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a + ? _textSelA! + : textAnchorMoving == _TextSelectionPart.b + ? _textSelB! + : null; + final textAnchorLocalRect = textAnchor == null + ? null + : (textAnchor.type == PdfTextSelectionAnchorType.a ? _anchorARect : _anchorBRect); + final offset = localOffset != null ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) - : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); + : (calcPosition(_contextMenuRect?.size, textAnchor, textAnchorLocalRect, _pointerOffset) ?? Offset.zero); contextMenu = Positioned( left: offset.dx, top: offset.dy, @@ -2531,6 +2584,33 @@ class _PdfViewerState extends State ]; } + /// Calculate clamped pointer position for magnifier content. + /// + /// When the magnifier widget is clamped to viewport edges (by calcPosition), + /// we adjust the pointer position by the same amount. This enables [PdfViewerGetMagnifierRectForAnchor] + /// and [PdfViewerMagnifierBuilder] to use the clamped pointer position to effectively "freeze" the + /// magnifier content, preventing it from sliding inside the magnifier. + Offset _calcClampedPointerPosition( + Offset pointerOffset, + Offset magnifierPosition, + Size? magnifierSize, + PdfTextSelectionAnchor textAnchor, + ) { + var clampedPointerOffset = pointerOffset; + + if (magnifierSize != null) { + // What the magnifier X position would be without clamping (centered on pointer) + final unclampedLeft = pointerOffset.dx - magnifierSize.width / 2; + // How much it was actually clamped by calcPosition + final clampAmount = magnifierPosition.dx - unclampedLeft; + // Adjust pointer position by the same clamp amount to freeze content + clampedPointerOffset = Offset(pointerOffset.dx + clampAmount, pointerOffset.dy); + } + + // Convert to document coordinates + return _globalToDocument(clampedPointerOffset) ?? textAnchor.anchorPoint; + } + bool _shouldBeShownForAnchor( PdfTextSelectionAnchor textAnchor, PdfViewerController controller, @@ -2630,8 +2710,11 @@ class _PdfViewerState extends State } } - /// Calculate the rectangle shown in the magnifier for the given text anchor. - Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { + Rect _getMagnifierRect( + PdfTextSelectionAnchor textAnchor, + PdfViewerSelectionMagnifierParams params, + Offset pointerPosition, + ) { final c = textAnchor.page.charRects[textAnchor.index]; final (width, height) = switch (_document!.pages[textAnchor.page.pageNumber - 1].rotation.index & 1) { @@ -2688,6 +2771,8 @@ class _PdfViewerState extends State PdfViewerSelectionMagnifierParams params, Widget child, Size childSize, + Offset pointerPosition, + Offset magnifierPosition, ) { final scale = 80 / min(childSize.width, childSize.height); return Container( @@ -2746,11 +2831,15 @@ class _PdfViewerState extends State final a = _findTextAndIndexForPoint(_textSelA!.rect.center); if (a == null) return; _selA = a; + // Notify drag start callback + widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelA!); } else if (_selPartMoving == _TextSelectionPart.b) { _textSelectAnchor = anchor + _textSelB!.rect.bottomRight - position!; final b = _findTextAndIndexForPoint(_textSelB!.rect.center); if (b == null) return; _selB = b; + // Notify drag start callback + widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelB!); } else { return; } @@ -2789,11 +2878,22 @@ class _PdfViewerState extends State if (_isInteractionGoingOn) return; _contextMenuDocumentPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + // Notify drag update callback + final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; + if (anchor != null) { + widget.params.textSelectionParams?.onSelectionHandlePanUpdate?.call(anchor, details.delta); + } } void _onSelectionHandlePanEnd(_TextSelectionPart handle, DragEndDetails details) { if (_isInteractionGoingOn) return; final result = _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + // Notify drag end callback before clearing state + final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; + if (anchor != null) { + widget.params.textSelectionParams?.onSelectionHandlePanEnd?.call(anchor); + } + _selPartMoving = _TextSelectionPart.none; _isSelectingAllText = false; if (!result) { @@ -3824,6 +3924,9 @@ class PdfViewerController extends ValueListenable { /// Converts the local position in the PDF document structure to the global position. Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); + /// Converts a local position in the PDF document to the local position in the widget. + Offset? offsetToLocal(BuildContext context, Offset? position) => _state.offsetToLocal(context, position); + /// Converts document coordinates to local coordinates. PdfViewerCoordinateConverter get doc2local => _state; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2ade36a7..2fd7a558 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -743,6 +743,9 @@ class PdfTextSelectionParams { this.buildSelectionHandle, this.calcSelectionHandleOffset, this.onTextSelectionChange, + this.onSelectionHandlePanStart, + this.onSelectionHandlePanUpdate, + this.onSelectionHandlePanEnd, this.magnifier, }); @@ -777,6 +780,15 @@ class PdfTextSelectionParams { /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; + /// Callback for when a selection handle pan starts. + final PdfViewerSelectionHandlePanStartCallback? onSelectionHandlePanStart; + + /// Callback for when a selection handle is being panned. + final PdfViewerSelectionHandlePanUpdateCallback? onSelectionHandlePanUpdate; + + /// Callback for when a selection handle pan ends. + final PdfViewerSelectionHandlePanEndCallback? onSelectionHandlePanEnd; + /// Parameters for the magnifier. final PdfViewerSelectionMagnifierParams? magnifier; @@ -787,6 +799,9 @@ class PdfTextSelectionParams { other.buildSelectionHandle == buildSelectionHandle && other.calcSelectionHandleOffset == calcSelectionHandleOffset && other.onTextSelectionChange == onTextSelectionChange && + other.onSelectionHandlePanStart == onSelectionHandlePanStart && + other.onSelectionHandlePanUpdate == onSelectionHandlePanUpdate && + other.onSelectionHandlePanEnd == onSelectionHandlePanEnd && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && other.magnifier == magnifier; @@ -797,6 +812,9 @@ class PdfTextSelectionParams { buildSelectionHandle.hashCode ^ calcSelectionHandleOffset.hashCode ^ onTextSelectionChange.hashCode ^ + onSelectionHandlePanStart.hashCode ^ + onSelectionHandlePanUpdate.hashCode ^ + onSelectionHandlePanEnd.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ magnifier.hashCode; @@ -915,7 +933,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, @@ -940,6 +958,15 @@ typedef PdfViewerCalcSelectionAnchorHandleOffsetFunction = /// [textSelection] contains the selected text range on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection textSelection); +/// Callback for when a selection handle pan starts +typedef PdfViewerSelectionHandlePanStartCallback = void Function(PdfTextSelectionAnchor anchor); + +/// Callback for when a selection handle is being panned +typedef PdfViewerSelectionHandlePanUpdateCallback = void Function(PdfTextSelectionAnchor anchor, Offset delta); + +/// Callback for when a selection handle pan ends +typedef PdfViewerSelectionHandlePanEndCallback = void Function(PdfTextSelectionAnchor anchor); + /// Text selection abstract class PdfTextSelection { /// Whether the text selection is enabled by the configuration. @@ -1018,6 +1045,9 @@ class PdfViewerSelectionMagnifierParams { this.builder, this.shouldBeShownForAnchor, this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, + this.calcPosition, + this.shouldShowMagnifier, + this.animationDuration = const Duration(milliseconds: 100), }); /// The default maximum image bytes cached on memory is 256 KB. @@ -1049,6 +1079,33 @@ class PdfViewerSelectionMagnifierParams { /// The default is 256 * 1024 bytes (256 KB). final int maxImageBytesCachedOnMemory; + /// Optional callback to calculate the magnifier widget position. + /// + /// When provided, this function will be used to determine where to place + /// the magnifier widget in the viewport. If null, pdfrx uses its default + /// positioning logic. + /// + /// This can also be used for context menu positioning or other overlay widgets. + final PdfViewerCalcMagnifierPositionFunction? calcPosition; + + /// Optional callback to control magnifier visibility. + /// + /// This allows for more fine grained control of when the magnifier should be + /// shown during text selection, for example to coordinate with custom animations. + /// If null, the magnifier is shown whenever a selection handle is being dragged. + /// + /// Return true to show the magnifier, false to hide it. + final bool Function()? shouldShowMagnifier; + + /// Duration for the magnifier position animation. + /// + /// This controls the animation duration when the magnifier position changes + /// as the user drags the selection handle. Set to [Duration.zero] to disable + /// the position animation. + /// + /// Default is 100 milliseconds. + final Duration animationDuration; + @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1059,7 +1116,10 @@ class PdfViewerSelectionMagnifierParams { other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && other.shouldBeShownForAnchor == shouldBeShownForAnchor && - other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; + other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory && + other.calcPosition == calcPosition && + other.shouldShowMagnifier == shouldShowMagnifier && + other.animationDuration == animationDuration; } @override @@ -1069,30 +1129,43 @@ class PdfViewerSelectionMagnifierParams { getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ shouldBeShownForAnchor.hashCode ^ - maxImageBytesCachedOnMemory.hashCode; + maxImageBytesCachedOnMemory.hashCode ^ + calcPosition.hashCode ^ + shouldShowMagnifier.hashCode ^ + animationDuration.hashCode; } /// Function to get the magnifier rectangle for the anchor. /// -/// The following fragment illustrates one example of the code to calculate where on the document the magnifier should -/// be shown for: +/// This function determines what part of the PDF document to show in the magnifier. +/// +/// Parameters: +/// - [anchor]: The text selection anchor with character information +/// - [params]: Magnifier parameters +/// - [pointerPosition]: The clamped pointer position in document coordinates. +/// This is the raw pointer position adjusted for viewport edge clamping to prevent +/// content sliding when the magnifier widget reaches the viewport edge. +/// +/// Returns a [Rect] in document coordinates representing the area to magnify. /// +/// Example: ///```dart -/// getMagnifierRectForAnchor: (textAnchor, params) { +/// getMagnifierRectForAnchor: (textAnchor, params, pointerPosition) { /// final c = textAnchor.page.charRects[textAnchor.index]; /// final baseUnit = switch (textAnchor.direction) { /// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, /// PdfTextDirection.vrtl => c.width, /// }; /// return Rect.fromLTRB( -/// textAnchor.rect.left - baseUnit * 2, -/// textAnchor.rect.top - baseUnit * .2, -/// textAnchor.rect.right + baseUnit * 2, -/// textAnchor.rect.bottom + baseUnit * .2, -/// ); +/// pointerPosition.dx - baseUnit * 2.5, +/// textAnchor.rect.top - baseUnit * 0.5, +/// pointerPosition.dx + baseUnit * 2.5, +/// textAnchor.rect.bottom + baseUnit * 0.5, +/// ); +/// } ///``` typedef PdfViewerGetMagnifierRectForAnchor = - Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params); + Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params, Offset pointerPosition); /// Function to build the magnifier widget. /// @@ -1136,6 +1209,8 @@ typedef PdfViewerMagnifierBuilder = PdfViewerSelectionMagnifierParams params, Widget magnifierContent, Size magnifierContentSize, + Offset pointerPosition, + Offset magnifierPosition, ); /// Function to determine whether the magnifier should be shown or not. @@ -1157,6 +1232,31 @@ typedef PdfViewerMagnifierShouldBeShownFunction = PdfViewerSelectionMagnifierParams params, ); +/// Function to calculate the position of the magnifier widget in viewport coordinates. +/// +/// This callback allows custom positioning logic for the magnifier. +/// If null, pdfrx uses its default positioning algorithm that handles different text +/// directions (LTR, RTL, VRTL) and viewport edge cases. +/// +/// Parameters: +/// - [widgetSize]: The size of the magnifier widget (null if not yet measured) +/// - [textAnchor]: The text selection anchor with character information (may be null) +/// - [anchorLocalRect]: The anchor's character rectangle in viewport coordinates (may be null) +/// - [pointerPosition]: The pointer/finger position in viewport coordinates +/// - [margin]: Default margin from viewport edges +/// - [marginOnTop]: Optional custom margin when magnifier is positioned above text +/// - [marginOnBottom]: Optional custom margin when magnifier is positioned below text +typedef PdfViewerCalcMagnifierPositionFunction = + Offset? Function( + Size? widgetSize, + PdfTextSelectionAnchor? textAnchor, + Rect? anchorLocalRect, + Offset pointerPosition, { + double margin, + double? marginOnTop, + double? marginOnBottom, + }); + /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); @@ -1448,8 +1548,7 @@ class PdfViewerKeyHandlerParams { } @override - int get hashCode => - enabled.hashCode ^ autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; + int get hashCode => enabled.hashCode ^ autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; } enum PdfViewerGeneralTapType { From ef4d90b6dce175e11e16e5055756296e7bc01b16 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 18:21:22 +0900 Subject: [PATCH 547/663] Release pdfrx_engine v0.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 3c091587..3cb9e464 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.2.4 + pdfrx_engine: ^0.3.0 pdfium_flutter: ^0.1.0 collection: crypto: ^3.0.6 diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 6a78799f..55ef2da0 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.2.4 + pdfrx_engine: ^0.3.0 http: dev_dependencies: From b34bd349ece9e9e1ff2c3e4c1a64942959671a45 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 21:11:17 +0900 Subject: [PATCH 548/663] Release pdfrx v2.2.9 and pdfrx_coregraphics v0.1.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/CHANGELOG.md | 4 ++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 168505c7..9f2edcab 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.8 + pdfrx: ^2.2.9 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 469a706f..1e5b5162 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.9 + +- Updated to pdfrx_engine 0.3.0 + # 2.2.8 - NEW: `PdfDocument` now supports page re-arrangement and accepts `PdfPage` instances from other documents, enabling PDF combine/merge functionality diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index d9a47fd2..c5745abb 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.8 + pdfrx: ^2.2.9 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 3cb9e464..15894563 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.8 +version: 2.2.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index f059f6c2..729ca6f1 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.8 + +- Updated to pdfrx_engine 0.3.0 + ## 0.1.7 - Updated to pdfrx_engine 0.2.4 diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 18114c4f..62d96fad 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.0 - pdfrx_coregraphics: ^0.1.5 + pdfrx: ^2.2.9 + pdfrx_coregraphics: ^0.1.8 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 55ef2da0..c31e476e 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.7 +version: 0.1.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 21e387731e63620e5b8f951b636e69302d70014b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 22:41:54 +0900 Subject: [PATCH 549/663] WIP: fixes for iOS/macOS builds --- .../darwin/pdfium_flutter/Package.swift | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 88 +++++++------- .../macos/Runner.xcodeproj/project.pbxproj | 107 +----------------- 3 files changed, 51 insertions(+), 146 deletions(-) diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index 5c4073b1..5fc83230 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -9,7 +9,7 @@ let package = Package( ], products: [ .library( - name: "pdfium_flutter", + name: "pdfium-flutter", targets: ["pdfium_flutter"] ), ], diff --git a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj index 270e0634..74dcd276 100644 --- a/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/ios/Runner.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 06B83D6F6618DC7CCC4F8EC4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; @@ -16,7 +15,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - DB7AD93E01BB8E95A58E41D0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E52033D095D41B805F376 /* Pods_Runner.framework */; }; + B7491F1FA5813A72C5DB815E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A71E10111F6AE7ED2B3E748 /* Pods_Runner.framework */; }; + DDBE1588174F910263C39C4A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DB1D065852B0FEB77E57673 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,17 +45,20 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 312E52033D095D41B805F376 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3295B794FD54924048C1CD50 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 36C1BD909E9326B5FE0920A8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 3773DAD9E81D0A04DD41B8C0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 50E2E216D9E444E0C49E84D3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 5F4A74F28605FCB19004871A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 4DB1D065852B0FEB77E57673 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5A71E10111F6AE7ED2B3E748 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67B7D4760433250E4C3975AF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 896939C7E339590C9E807639 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -63,10 +66,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - C967F79C0E5199682A53E2C1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 9A55819EB52450C0121C39BC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 06B83D6F6618DC7CCC4F8EC4 /* Pods_RunnerTests.framework in Frameworks */, + DDBE1588174F910263C39C4A /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -83,7 +83,7 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - DB7AD93E01BB8E95A58E41D0 /* Pods_Runner.framework in Frameworks */, + B7491F1FA5813A72C5DB815E /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -93,12 +93,12 @@ 2C0A7AC090CFDFC89385173D /* Pods */ = { isa = PBXGroup; children = ( - 5F4A74F28605FCB19004871A /* Pods-Runner.debug.xcconfig */, - 50E2E216D9E444E0C49E84D3 /* Pods-Runner.release.xcconfig */, - C967F79C0E5199682A53E2C1 /* Pods-Runner.profile.xcconfig */, - 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */, - E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */, - E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */, + 896939C7E339590C9E807639 /* Pods-Runner.debug.xcconfig */, + 67B7D4760433250E4C3975AF /* Pods-Runner.release.xcconfig */, + 3773DAD9E81D0A04DD41B8C0 /* Pods-Runner.profile.xcconfig */, + 9A55819EB52450C0121C39BC /* Pods-RunnerTests.debug.xcconfig */, + 3295B794FD54924048C1CD50 /* Pods-RunnerTests.release.xcconfig */, + 36C1BD909E9326B5FE0920A8 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -131,7 +131,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 2C0A7AC090CFDFC89385173D /* Pods */, - F1B23DD2AC5484B2739268AA /* Frameworks */, + ED9A0663962D5B9F096B4B34 /* Frameworks */, ); sourceTree = ""; }; @@ -159,11 +159,11 @@ path = Runner; sourceTree = ""; }; - F1B23DD2AC5484B2739268AA /* Frameworks */ = { + ED9A0663962D5B9F096B4B34 /* Frameworks */ = { isa = PBXGroup; children = ( - 312E52033D095D41B805F376 /* Pods_Runner.framework */, - 767508ADDEEB5111FB1CCA12 /* Pods_RunnerTests.framework */, + 5A71E10111F6AE7ED2B3E748 /* Pods_Runner.framework */, + 4DB1D065852B0FEB77E57673 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -175,7 +175,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 373523437F138D4BB02F2C05 /* [CP] Check Pods Manifest.lock */, + 10CB03BA201C2D8083212310 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 8CA26D04F87237D4AA4DC2CF /* Frameworks */, @@ -194,14 +194,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - E003A8C4FC0CE09C63D52588 /* [CP] Check Pods Manifest.lock */, + EF8B5360E6E6BF4A239F5F56 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - FAEC9DAC8178CC76D4BE498C /* [CP] Embed Pods Frameworks */, + 98169DAAFFC178DEC77A818A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -279,7 +279,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 373523437F138D4BB02F2C05 /* [CP] Check Pods Manifest.lock */ = { + 10CB03BA201C2D8083212310 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -332,43 +332,43 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - E003A8C4FC0CE09C63D52588 /* [CP] Check Pods Manifest.lock */ = { + 98169DAAFFC178DEC77A818A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - FAEC9DAC8178CC76D4BE498C /* [CP] Embed Pods Frameworks */ = { + EF8B5360E6E6BF4A239F5F56 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -499,7 +499,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9F72CBDC1A2C39B133E1DA64 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 9A55819EB52450C0121C39BC /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -517,7 +517,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E1D09914098FEF5CB5760318 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 3295B794FD54924048C1CD50 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -533,7 +533,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E8D552749409F2EB339F0108 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 36C1BD909E9326B5FE0920A8 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj index 59878f87..e8ff76d2 100644 --- a/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pdfrx/example/viewer/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXAggregateTarget section */ @@ -27,9 +27,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 4F04007BD71D9E24E68CAA20 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF58CB124081E3D64D45978F /* Pods_Runner.framework */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - A354CD3A0CF63DBE7FA95FE4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,7 +61,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 07F88847F7E0AD2AB6A18DBD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; @@ -80,16 +77,9 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 5ABE92F86B1527884947EFD9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 8BB77DD10EE76AAC0ED2FF18 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - AF58CB124081E3D64D45978F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,7 +87,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A354CD3A0CF63DBE7FA95FE4 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -106,31 +95,15 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 4F04007BD71D9E24E68CAA20 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 067233A8615F413EB50792B3 /* Frameworks */ = { - isa = PBXGroup; - children = ( - AF58CB124081E3D64D45978F /* Pods_Runner.framework */, - 8A5BF5877B807C3BF03EB8B0 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 2549777989FCB1E18D7F6133 /* Pods */ = { isa = PBXGroup; children = ( - 8BB77DD10EE76AAC0ED2FF18 /* Pods-Runner.debug.xcconfig */, - 07F88847F7E0AD2AB6A18DBD /* Pods-Runner.release.xcconfig */, - 5ABE92F86B1527884947EFD9 /* Pods-Runner.profile.xcconfig */, - 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */, - 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */, - 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -162,7 +135,6 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, 2549777989FCB1E18D7F6133 /* Pods */, - 067233A8615F413EB50792B3 /* Frameworks */, ); sourceTree = ""; }; @@ -218,7 +190,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 95E89CCDE9488642DADD7E81 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -237,13 +208,11 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 8DA07DC84C90D2B5192A3EAA /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 03BECD5C4820C9F3BD651D30 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -297,7 +266,7 @@ ); mainGroup = 33CC10E42044A3C60003C045; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; @@ -330,23 +299,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 03BECD5C4820C9F3BD651D30 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -385,50 +337,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 8DA07DC84C90D2B5192A3EAA /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 95E89CCDE9488642DADD7E81 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -480,7 +388,6 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 63F365AED9BFC7B856BAB3B2 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -495,7 +402,6 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F0626F2E240C023E7ACC1A8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -510,7 +416,6 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4E203DD96071B03D5565457F /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -577,7 +482,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = XRDM278W3T; @@ -707,7 +612,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = XRDM278W3T; @@ -731,7 +636,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = XRDM278W3T; @@ -808,7 +713,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; From 522182a27eeaec7bc0e90595434559447cea3fda Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 23:40:54 +0900 Subject: [PATCH 550/663] Release pdfium_dart v0.1.1, pdfium_flutter v0.1.1, pdfrx_engine v0.3.1, and pdfrx v2.2.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_dart/CHANGELOG.md | 5 +++++ .../pdfium_dart/lib/src/pdfium_bindings.dart | 1 + .../lib/src/pdfium_downloader.dart | 22 +++++++++---------- packages/pdfium_dart/pubspec.yaml | 3 ++- packages/pdfium_flutter/CHANGELOG.md | 5 +++++ packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/pubspec.yaml | 6 ++--- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ .../lib/src/pdfrx_initialize_dart.dart | 4 ++-- packages/pdfrx_engine/pubspec.yaml | 2 +- 11 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/pdfium_dart/CHANGELOG.md b/packages/pdfium_dart/CHANGELOG.md index ec928660..1fd4a427 100644 --- a/packages/pdfium_dart/CHANGELOG.md +++ b/packages/pdfium_dart/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.1 + +- Add comments on PDFium class. +- Several PDFium capitalization fixes affecting API names. + ## 0.1.0 - First release. diff --git a/packages/pdfium_dart/lib/src/pdfium_bindings.dart b/packages/pdfium_dart/lib/src/pdfium_bindings.dart index bb585d08..11ea8646 100644 --- a/packages/pdfium_dart/lib/src/pdfium_bindings.dart +++ b/packages/pdfium_dart/lib/src/pdfium_bindings.dart @@ -6,6 +6,7 @@ // Generated by `package:ffigen`. // ignore_for_file: type=lint import 'dart:ffi' as ffi; +/// Bindings for PDFium C API class PDFium{ /// Holds the symbol lookup function. final ffi.Pointer Function(String symbolName) _lookup; diff --git a/packages/pdfium_dart/lib/src/pdfium_downloader.dart b/packages/pdfium_dart/lib/src/pdfium_downloader.dart index 3bd9c7b6..d6ac9aae 100644 --- a/packages/pdfium_dart/lib/src/pdfium_downloader.dart +++ b/packages/pdfium_dart/lib/src/pdfium_downloader.dart @@ -10,11 +10,11 @@ import 'pdfium_bindings.dart' as pdfium_bindings; /// The release of pdfium to download. /// /// The actual binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. -const currentPdfiumRelease = 'chromium%2F7506'; +const currentPDFiumRelease = 'chromium%2F7506'; -/// Helper function to get Pdfium instance. +/// Helper function to get PDFium instance. /// -/// This function downloads the Pdfium module if necessary. +/// This function downloads the PDFium module if necessary. /// /// For macOS, the downloaded library is not codesigned. If you encounter issues loading the library, /// you may need to manually codesign it using the following command: @@ -24,7 +24,7 @@ const currentPdfiumRelease = 'chromium%2F7506'; /// ``` Future getPdfium({ String? tmpPath, - String? pdfiumRelease = currentPdfiumRelease, + String? pdfiumRelease = currentPDFiumRelease, }) async { tmpPath ??= path.join( Directory.systemTemp.path, @@ -36,7 +36,7 @@ Future getPdfium({ if (!await File(tmpPath).exists()) { await Directory(tmpPath).create(recursive: true); } - final modulePath = await PDFiumDownloader.downloadAndGetPdfiumModulePath( + final modulePath = await PDFiumDownloader.downloadAndGetPDFiumModulePath( tmpPath, pdfiumRelease: pdfiumRelease, ); @@ -60,15 +60,15 @@ class PDFiumDownloader { /// - macOS x64, arm64 /// /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. - static Future downloadAndGetPdfiumModulePath( + static Future downloadAndGetPDFiumModulePath( String tmpPath, { - String? pdfiumRelease = currentPdfiumRelease, + String? pdfiumRelease = currentPDFiumRelease, }) async { final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; final platform = pa[1]!; final arch = pa[2]!; if (platform == 'windows' && arch == 'x64') { - return await downloadPdfium( + return await downloadPDFium( tmpPath, 'win', arch, @@ -77,7 +77,7 @@ class PDFiumDownloader { ); } if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { - return await downloadPdfium( + return await downloadPDFium( tmpPath, platform, arch, @@ -86,7 +86,7 @@ class PDFiumDownloader { ); } if (platform == 'macos') { - return await downloadPdfium( + return await downloadPDFium( tmpPath, 'mac', arch, @@ -99,7 +99,7 @@ class PDFiumDownloader { } /// Downloads the pdfium module for the given platform and architecture. - static Future downloadPdfium( + static Future downloadPDFium( String tmpRoot, String platform, String arch, diff --git a/packages/pdfium_dart/pubspec.yaml b/packages/pdfium_dart/pubspec.yaml index cdc105ec..e7eccf23 100644 --- a/packages/pdfium_dart/pubspec.yaml +++ b/packages/pdfium_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_dart description: Dart FFI bindings for PDFium library. Provides low-level access to PDFium's C API from Dart. -version: 0.1.0 +version: 0.1.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_dart issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -27,6 +27,7 @@ dev_dependencies: ffigen: output: bindings: 'lib/src/pdfium_bindings.dart' + description: 'Bindings for PDFium C API' headers: entry-points: - 'test/.tmp/**/fpdf_signature.h' diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index ec928660..1b8345ab 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.1 + +- Fixed SwiftPM package name inconsistency. +- Several PDFium capitalization affecting API names + ## 0.1.0 - First release. diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 766c2f14..4ca4e934 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.0 +version: 0.1.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 1e5b5162..47236a38 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.10 + +- Updated to pdfrx_engine 0.3.1 and pdfium_flutter 0.1.1 + # 2.2.9 - Updated to pdfrx_engine 0.3.0 diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 15894563..8bc4193c 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.9 +version: 2.2.10 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,8 +14,8 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.0 - pdfium_flutter: ^0.1.0 + pdfrx_engine: ^0.3.1 + pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 dart_pubspec_licenses: ^3.0.12 diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 5a05b76a..b35832ed 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.1 + +- Updated to pdfium_dart 0.1.1 + ## 0.3.0 - NEW: `PdfDocument.createFromJpegData()` - Create PDF documents from JPEG image data diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 8fe65353..4d499b62 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -19,7 +19,7 @@ bool _isInitialized = false; /// - Calls [PdfrxEntryFunctions.init] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. -Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease = pdfium_dart.currentPdfiumRelease}) async { +Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease = pdfium_dart.currentPDFiumRelease}) async { if (_isInitialized) return; Pdfrx.loadAsset ??= (name) async { @@ -33,7 +33,7 @@ Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease = pdfium_da if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await pdfium_dart.PDFiumDownloader.downloadAndGetPdfiumModulePath(pdfiumPath.path); + Pdfrx.pdfiumModulePath = await pdfium_dart.PDFiumDownloader.downloadAndGetPDFiumModulePath(pdfiumPath.path); } await PdfrxEntryFunctions.instance.init(); diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 9923c531..a5900a25 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.0 +version: 0.3.1 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 79fb469e6ca3dd0c5dc9abcce25b02aba8f6f255 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 10 Nov 2025 23:47:12 +0900 Subject: [PATCH 551/663] PdfViewerScrollThumb sometime show `null` --- packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart index 274d28dd..02507af1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_scroll_thumb.dart @@ -89,7 +89,7 @@ class _PdfViewerScrollThumbState extends State { ), ], ), - child: Center(child: Text(widget.controller.pageNumber.toString())), + child: Center(child: Text(widget.controller.pageNumber?.toString() ?? '')), ), onPanStart: (details) { _panStartOffset = top - details.localPosition.dy; @@ -144,7 +144,7 @@ class _PdfViewerScrollThumbState extends State { ), ], ), - child: Center(child: Text(widget.controller.pageNumber.toString())), + child: Center(child: Text(widget.controller.pageNumber?.toString() ?? '')), ), onPanStart: (details) { _panStartOffset = left - details.localPosition.dx; From d14633958fe1d361bf7d65b19852ed0eb5805d52 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 11 Nov 2025 00:11:49 +0900 Subject: [PATCH 552/663] Revert "Magnifier enhancements to support customization" --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 137 +++--------------- .../lib/src/widgets/pdf_viewer_params.dart | 127 ++-------------- 2 files changed, 31 insertions(+), 233 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 9a17c8f2..6f71f2f7 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2350,20 +2350,20 @@ class _PdfViewerState extends State Offset? calcPosition( Size? widgetSize, - PdfTextSelectionAnchor? textAnchor, - Rect? anchorLocalRect, - Offset pointerOffset, { + _TextSelectionPart part, { double margin = defMargin, double? marginOnTop, double? marginOnBottom, }) { - if (widgetSize == null || textAnchor == null) { + if (widgetSize == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { return null; } + final textAnchor = part == _TextSelectionPart.a ? _textSelA : _textSelB; + if (textAnchor == null) return null; late double left, top; - final rect0 = textAnchor.rect; - final rect1 = anchorLocalRect; + final rect0 = (part == _TextSelectionPart.a ? rectA : rectB); + final rect1 = (part == _TextSelectionPart.a ? _anchorARect : _anchorBRect); final pt = rect0.center; final rectTop = rect1 == null ? rect0.top : min(rect0.top, rect1.top); final rectBottom = rect1 == null ? rect0.bottom : max(rect0.bottom, rect1.bottom); @@ -2387,7 +2387,7 @@ class _PdfViewerState extends State } break; case PdfTextDirection.vrtl: - if (textAnchor.type == PdfTextSelectionAnchorType.a) { + if (part == _TextSelectionPart.a) { left = rectRight + margin; if (left + widgetSize.width + margin > viewSize.width) { left = rectLeft - widgetSize.width - margin; @@ -2410,17 +2410,8 @@ class _PdfViewerState extends State } Widget? magnifier; - - final shouldShowMagnifier = widget.params.textSelectionParams?.magnifier?.shouldShowMagnifier?.call(); - // Show magnifier if dragging - if (((textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) && - shouldShowMagnifier != false) || - shouldShowMagnifier == true) { - final textAnchor = textAnchorMoving == _TextSelectionPart.a - ? _textSelA! - : textAnchorMoving == _TextSelectionPart.b - ? _textSelB! - : (_selPartLastMoved == _TextSelectionPart.a ? _textSelA! : _textSelB!); + if (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA! : _textSelB!; final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = @@ -2431,50 +2422,15 @@ class _PdfViewerState extends State magnifierParams, ); if (magnifierEnabled) { - final textAnchorLocalRect = (textAnchor.type == PdfTextSelectionAnchorType.a ? _anchorARect : _anchorBRect); - // Calculate final magnifier position before calling builder - final magnifierPosition = - (magnifierParams.calcPosition ?? calcPosition).call( - _magnifierRect?.size, - textAnchor, - textAnchorLocalRect, - _pointerOffset, - margin: defMargin, - marginOnTop: 20, - marginOnBottom: 80, - ) ?? - Offset.zero; - - // Calculate clamped pointer position for magnifier content - final clampedPointerPosition = _calcClampedPointerPosition( - _pointerOffset, - magnifierPosition, - _magnifierRect?.size, - textAnchor, - ); - - final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)( - textAnchor, - magnifierParams, - clampedPointerPosition, - ); + final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)(textAnchor, magnifierParams); final magnifierMain = _buildMagnifier(context, magRect, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = builder( - context, - textAnchor, - magnifierParams, - magnifierMain, - magRect.size, - clampedPointerPosition, - magnifierPosition, - ); + magnifier = builder(context, textAnchor, magnifierParams, magnifierMain, magRect.size); if (magnifier != null && !isPositionalWidget(magnifier)) { - // Use calculated values - final offset = magnifierPosition; - + final offset = + calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; magnifier = AnimatedPositioned( - duration: _previousMagnifierRect != null ? magnifierParams.animationDuration : Duration.zero, + duration: _previousMagnifierRect != null ? const Duration(milliseconds: 100) : Duration.zero, left: offset.dx, top: offset.dy, child: WidgetSizeSniffer( @@ -2577,18 +2533,9 @@ class _PdfViewerState extends State contextMenu = createContextMenu(a, b, _contextMenuFor); if (contextMenu != null && !isPositionalWidget(contextMenu)) { - final textAnchor = textAnchorMoving == _TextSelectionPart.a - ? _textSelA! - : textAnchorMoving == _TextSelectionPart.b - ? _textSelB! - : null; - final textAnchorLocalRect = textAnchor == null - ? null - : (textAnchor.type == PdfTextSelectionAnchorType.a ? _anchorARect : _anchorBRect); - final offset = localOffset != null ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) - : (calcPosition(_contextMenuRect?.size, textAnchor, textAnchorLocalRect, _pointerOffset) ?? Offset.zero); + : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); contextMenu = Positioned( left: offset.dx, top: offset.dy, @@ -2667,33 +2614,6 @@ class _PdfViewerState extends State ]; } - /// Calculate clamped pointer position for magnifier content. - /// - /// When the magnifier widget is clamped to viewport edges (by calcPosition), - /// we adjust the pointer position by the same amount. This enables [PdfViewerGetMagnifierRectForAnchor] - /// and [PdfViewerMagnifierBuilder] to use the clamped pointer position to effectively "freeze" the - /// magnifier content, preventing it from sliding inside the magnifier. - Offset _calcClampedPointerPosition( - Offset pointerOffset, - Offset magnifierPosition, - Size? magnifierSize, - PdfTextSelectionAnchor textAnchor, - ) { - var clampedPointerOffset = pointerOffset; - - if (magnifierSize != null) { - // What the magnifier X position would be without clamping (centered on pointer) - final unclampedLeft = pointerOffset.dx - magnifierSize.width / 2; - // How much it was actually clamped by calcPosition - final clampAmount = magnifierPosition.dx - unclampedLeft; - // Adjust pointer position by the same clamp amount to freeze content - clampedPointerOffset = Offset(pointerOffset.dx + clampAmount, pointerOffset.dy); - } - - // Convert to document coordinates - return _globalToDocument(clampedPointerOffset) ?? textAnchor.anchorPoint; - } - bool _shouldBeShownForAnchor( PdfTextSelectionAnchor textAnchor, PdfViewerController controller, @@ -2793,11 +2713,8 @@ class _PdfViewerState extends State } } - Rect _getMagnifierRect( - PdfTextSelectionAnchor textAnchor, - PdfViewerSelectionMagnifierParams params, - Offset pointerPosition, - ) { + /// Calculate the rectangle shown in the magnifier for the given text anchor. + Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { final c = textAnchor.page.charRects[textAnchor.index]; final (width, height) = switch (_document!.pages[textAnchor.page.pageNumber - 1].rotation.index & 1) { @@ -2854,8 +2771,6 @@ class _PdfViewerState extends State PdfViewerSelectionMagnifierParams params, Widget child, Size childSize, - Offset pointerPosition, - Offset magnifierPosition, ) { final scale = 80 / min(childSize.width, childSize.height); return Container( @@ -2914,15 +2829,11 @@ class _PdfViewerState extends State final a = _findTextAndIndexForPoint(_textSelA!.rect.center); if (a == null) return; _selA = a; - // Notify drag start callback - widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelA!); } else if (_selPartMoving == _TextSelectionPart.b) { _textSelectAnchor = anchor + _textSelB!.rect.bottomRight - position!; final b = _findTextAndIndexForPoint(_textSelB!.rect.center); if (b == null) return; _selB = b; - // Notify drag start callback - widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelB!); } else { return; } @@ -2961,22 +2872,11 @@ class _PdfViewerState extends State if (_isInteractionGoingOn) return; _contextMenuDocumentPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); - // Notify drag update callback - final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; - if (anchor != null) { - widget.params.textSelectionParams?.onSelectionHandlePanUpdate?.call(anchor, details.delta); - } } void _onSelectionHandlePanEnd(_TextSelectionPart handle, DragEndDetails details) { if (_isInteractionGoingOn) return; final result = _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); - // Notify drag end callback before clearing state - final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; - if (anchor != null) { - widget.params.textSelectionParams?.onSelectionHandlePanEnd?.call(anchor); - } - _selPartMoving = _TextSelectionPart.none; _isSelectingAllText = false; if (!result) { @@ -4007,9 +3907,6 @@ class PdfViewerController extends ValueListenable { /// Converts the local position in the PDF document structure to the global position. Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); - /// Converts a local position in the PDF document to the local position in the widget. - Offset? offsetToLocal(BuildContext context, Offset? position) => _state.offsetToLocal(context, position); - /// Converts document coordinates to local coordinates. PdfViewerCoordinateConverter get doc2local => _state; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2fd7a558..2ade36a7 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -743,9 +743,6 @@ class PdfTextSelectionParams { this.buildSelectionHandle, this.calcSelectionHandleOffset, this.onTextSelectionChange, - this.onSelectionHandlePanStart, - this.onSelectionHandlePanUpdate, - this.onSelectionHandlePanEnd, this.magnifier, }); @@ -780,15 +777,6 @@ class PdfTextSelectionParams { /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; - /// Callback for when a selection handle pan starts. - final PdfViewerSelectionHandlePanStartCallback? onSelectionHandlePanStart; - - /// Callback for when a selection handle is being panned. - final PdfViewerSelectionHandlePanUpdateCallback? onSelectionHandlePanUpdate; - - /// Callback for when a selection handle pan ends. - final PdfViewerSelectionHandlePanEndCallback? onSelectionHandlePanEnd; - /// Parameters for the magnifier. final PdfViewerSelectionMagnifierParams? magnifier; @@ -799,9 +787,6 @@ class PdfTextSelectionParams { other.buildSelectionHandle == buildSelectionHandle && other.calcSelectionHandleOffset == calcSelectionHandleOffset && other.onTextSelectionChange == onTextSelectionChange && - other.onSelectionHandlePanStart == onSelectionHandlePanStart && - other.onSelectionHandlePanUpdate == onSelectionHandlePanUpdate && - other.onSelectionHandlePanEnd == onSelectionHandlePanEnd && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && other.magnifier == magnifier; @@ -812,9 +797,6 @@ class PdfTextSelectionParams { buildSelectionHandle.hashCode ^ calcSelectionHandleOffset.hashCode ^ onTextSelectionChange.hashCode ^ - onSelectionHandlePanStart.hashCode ^ - onSelectionHandlePanUpdate.hashCode ^ - onSelectionHandlePanEnd.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ magnifier.hashCode; @@ -933,7 +915,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, @@ -958,15 +940,6 @@ typedef PdfViewerCalcSelectionAnchorHandleOffsetFunction = /// [textSelection] contains the selected text range on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection textSelection); -/// Callback for when a selection handle pan starts -typedef PdfViewerSelectionHandlePanStartCallback = void Function(PdfTextSelectionAnchor anchor); - -/// Callback for when a selection handle is being panned -typedef PdfViewerSelectionHandlePanUpdateCallback = void Function(PdfTextSelectionAnchor anchor, Offset delta); - -/// Callback for when a selection handle pan ends -typedef PdfViewerSelectionHandlePanEndCallback = void Function(PdfTextSelectionAnchor anchor); - /// Text selection abstract class PdfTextSelection { /// Whether the text selection is enabled by the configuration. @@ -1045,9 +1018,6 @@ class PdfViewerSelectionMagnifierParams { this.builder, this.shouldBeShownForAnchor, this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, - this.calcPosition, - this.shouldShowMagnifier, - this.animationDuration = const Duration(milliseconds: 100), }); /// The default maximum image bytes cached on memory is 256 KB. @@ -1079,33 +1049,6 @@ class PdfViewerSelectionMagnifierParams { /// The default is 256 * 1024 bytes (256 KB). final int maxImageBytesCachedOnMemory; - /// Optional callback to calculate the magnifier widget position. - /// - /// When provided, this function will be used to determine where to place - /// the magnifier widget in the viewport. If null, pdfrx uses its default - /// positioning logic. - /// - /// This can also be used for context menu positioning or other overlay widgets. - final PdfViewerCalcMagnifierPositionFunction? calcPosition; - - /// Optional callback to control magnifier visibility. - /// - /// This allows for more fine grained control of when the magnifier should be - /// shown during text selection, for example to coordinate with custom animations. - /// If null, the magnifier is shown whenever a selection handle is being dragged. - /// - /// Return true to show the magnifier, false to hide it. - final bool Function()? shouldShowMagnifier; - - /// Duration for the magnifier position animation. - /// - /// This controls the animation duration when the magnifier position changes - /// as the user drags the selection handle. Set to [Duration.zero] to disable - /// the position animation. - /// - /// Default is 100 milliseconds. - final Duration animationDuration; - @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1116,10 +1059,7 @@ class PdfViewerSelectionMagnifierParams { other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && other.shouldBeShownForAnchor == shouldBeShownForAnchor && - other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory && - other.calcPosition == calcPosition && - other.shouldShowMagnifier == shouldShowMagnifier && - other.animationDuration == animationDuration; + other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; } @override @@ -1129,43 +1069,30 @@ class PdfViewerSelectionMagnifierParams { getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ shouldBeShownForAnchor.hashCode ^ - maxImageBytesCachedOnMemory.hashCode ^ - calcPosition.hashCode ^ - shouldShowMagnifier.hashCode ^ - animationDuration.hashCode; + maxImageBytesCachedOnMemory.hashCode; } /// Function to get the magnifier rectangle for the anchor. /// -/// This function determines what part of the PDF document to show in the magnifier. -/// -/// Parameters: -/// - [anchor]: The text selection anchor with character information -/// - [params]: Magnifier parameters -/// - [pointerPosition]: The clamped pointer position in document coordinates. -/// This is the raw pointer position adjusted for viewport edge clamping to prevent -/// content sliding when the magnifier widget reaches the viewport edge. -/// -/// Returns a [Rect] in document coordinates representing the area to magnify. +/// The following fragment illustrates one example of the code to calculate where on the document the magnifier should +/// be shown for: /// -/// Example: ///```dart -/// getMagnifierRectForAnchor: (textAnchor, params, pointerPosition) { +/// getMagnifierRectForAnchor: (textAnchor, params) { /// final c = textAnchor.page.charRects[textAnchor.index]; /// final baseUnit = switch (textAnchor.direction) { /// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, /// PdfTextDirection.vrtl => c.width, /// }; /// return Rect.fromLTRB( -/// pointerPosition.dx - baseUnit * 2.5, -/// textAnchor.rect.top - baseUnit * 0.5, -/// pointerPosition.dx + baseUnit * 2.5, -/// textAnchor.rect.bottom + baseUnit * 0.5, -/// ); -/// } +/// textAnchor.rect.left - baseUnit * 2, +/// textAnchor.rect.top - baseUnit * .2, +/// textAnchor.rect.right + baseUnit * 2, +/// textAnchor.rect.bottom + baseUnit * .2, +/// ); ///``` typedef PdfViewerGetMagnifierRectForAnchor = - Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params, Offset pointerPosition); + Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params); /// Function to build the magnifier widget. /// @@ -1209,8 +1136,6 @@ typedef PdfViewerMagnifierBuilder = PdfViewerSelectionMagnifierParams params, Widget magnifierContent, Size magnifierContentSize, - Offset pointerPosition, - Offset magnifierPosition, ); /// Function to determine whether the magnifier should be shown or not. @@ -1232,31 +1157,6 @@ typedef PdfViewerMagnifierShouldBeShownFunction = PdfViewerSelectionMagnifierParams params, ); -/// Function to calculate the position of the magnifier widget in viewport coordinates. -/// -/// This callback allows custom positioning logic for the magnifier. -/// If null, pdfrx uses its default positioning algorithm that handles different text -/// directions (LTR, RTL, VRTL) and viewport edge cases. -/// -/// Parameters: -/// - [widgetSize]: The size of the magnifier widget (null if not yet measured) -/// - [textAnchor]: The text selection anchor with character information (may be null) -/// - [anchorLocalRect]: The anchor's character rectangle in viewport coordinates (may be null) -/// - [pointerPosition]: The pointer/finger position in viewport coordinates -/// - [margin]: Default margin from viewport edges -/// - [marginOnTop]: Optional custom margin when magnifier is positioned above text -/// - [marginOnBottom]: Optional custom margin when magnifier is positioned below text -typedef PdfViewerCalcMagnifierPositionFunction = - Offset? Function( - Size? widgetSize, - PdfTextSelectionAnchor? textAnchor, - Rect? anchorLocalRect, - Offset pointerPosition, { - double margin, - double? marginOnTop, - double? marginOnBottom, - }); - /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); @@ -1548,7 +1448,8 @@ class PdfViewerKeyHandlerParams { } @override - int get hashCode => enabled.hashCode ^ autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; + int get hashCode => + enabled.hashCode ^ autofocus.hashCode ^ canRequestFocus.hashCode ^ focusNode.hashCode ^ parentNode.hashCode; } enum PdfViewerGeneralTapType { From af062eba656f1d3fe7682b086a30b758002a736b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:49:42 +0000 Subject: [PATCH 553/663] Bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index fa10ebd8..87bb2cb7 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -42,7 +42,7 @@ jobs: flutter build web --wasm --release --base-href "/pdfrx/${{ matrix.example }}/" --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" build/web/index.html - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.example }}-web path: packages/pdfrx/example/${{ matrix.example }}/build/web/ From 423db2aff4c6d20b5345ae9ab92510d48867c644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:49:45 +0000 Subject: [PATCH 554/663] Bump softprops/action-gh-release from 2.4.1 to 2.4.2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.1 to 2.4.2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/6da8fa9354ddfdc4aeace5fc48d7f679b5214090...5be0e66d93ac7ed76da52eca8bb058f665c3a5fe) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index cfbccd86..6b8d8ce7 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -35,7 +35,7 @@ jobs: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v0.1.15 + uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From 5e8c73e80fa8faac30516a4d51a68bce9f0e1c54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:49:51 +0000 Subject: [PATCH 555/663] Bump actions/download-artifact from 4 to 6 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github-pages.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index fa10ebd8..674aed46 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -59,7 +59,7 @@ jobs: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: path: artifacts/ - name: Prepare deployment directory diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index cfbccd86..d8cefd96 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -25,12 +25,12 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@f093f21ca4cfa7c75ccbbc2be54da76a0c7e1f05 # v6.0.0 with: name: pdfium-ios path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@f093f21ca4cfa7c75ccbbc2be54da76a0c7e1f05 # v6.0.0 with: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ From f6614c3c3f831f955e86a72f1333c034d4dfe1cc Mon Sep 17 00:00:00 2001 From: james Date: Tue, 11 Nov 2025 22:20:35 +1030 Subject: [PATCH 556/663] Magnifier customization enhancements --- packages/pdfrx/example/viewer/lib/main.dart | 176 +++++++++++++++++- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 135 ++++++++++++-- .../lib/src/widgets/pdf_viewer_params.dart | 137 ++++++++++++-- 3 files changed, 412 insertions(+), 36 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index fba3bb1b..72ea2baa 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -1,4 +1,4 @@ -import 'dart:math'; +import 'dart:math' as math; import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart'; @@ -41,7 +41,7 @@ class MainPage extends StatefulWidget { State createState() => _MainPageState(); } -class _MainPageState extends State with WidgetsBindingObserver { +class _MainPageState extends State with WidgetsBindingObserver, SingleTickerProviderStateMixin { final documentRef = ValueNotifier(null); final controller = PdfViewerController(); final showLeftPane = ValueNotifier(false); @@ -50,6 +50,10 @@ class _MainPageState extends State with WidgetsBindingObserver { final _markers = >{}; List? textSelections; + final bool _isDraggingHandle = false; // True while actively dragging, false on release + // Magnifier animation controller + late final AnimationController _magnifierAnimController; + void _update() { if (mounted) { setState(() {}); @@ -59,12 +63,14 @@ class _MainPageState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); + _magnifierAnimController = AnimationController(duration: const Duration(milliseconds: 250), vsync: this); WidgetsBinding.instance.addObserver(this); openInitialFile(); } @override void dispose() { + _magnifierAnimController.dispose(); WidgetsBinding.instance.removeObserver(this); textSearcher.value?.dispose(); textSearcher.dispose(); @@ -307,11 +313,148 @@ class _MainPageState extends State with WidgetsBindingObserver { onTextSelectionChange: (textSelection) async { textSelections = await textSelection.getSelectedTextRanges(); }, + /*magnifier: PdfViewerSelectionMagnifierParams( + shouldBeShownForAnchor: (textAnchor, controller, params) => true, + getMagnifierRectForAnchor: (textAnchor, params, clampedPointerPosition) { + final c = textAnchor.page.charRects[textAnchor.index]; + final baseUnit = switch (textAnchor.direction) { + PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, + PdfTextDirection.vrtl => c.width, + }; + + // Convert clamped pointer position from viewport to document coordinates + final pointerInDocument = + controller.globalToDocument(clampedPointerPosition) ?? textAnchor.anchorPoint; + + return Rect.fromLTRB( + pointerInDocument.dx - baseUnit * 2.5, + textAnchor.rect.top - baseUnit * 0.5, + pointerInDocument.dx + baseUnit * 2.5, + textAnchor.rect.bottom + baseUnit * 0.5, + ); + }, + builder: + ( + context, + textAnchor, + params, + magnifierContent, + magnifierContentSize, + pointerPosition, + magnifierPosition, + ) { + // calculate the scale to fit the magnifier content fit into 80x80 box + final contentScale = + 80 / math.min(magnifierContentSize.width, magnifierContentSize.height); + + // Calculate the actual magnifier widget size (with border radius padding) + final magnifierWidgetSize = Size( + magnifierContentSize.width * contentScale, + magnifierContentSize.height * contentScale, + ); + + // Start animation when magnifier first appears and capture initial pointer position + if (_magnifierAnimController.status == AnimationStatus.dismissed) { + _magnifierAnimationStartPositionViewport = pointerPosition; + _magnifierAnimController.forward(); + } + + final centeredStartOffset = + pointerPosition - + Offset(magnifierWidgetSize.width / 2, magnifierWidgetSize.height / 2); + final delta = centeredStartOffset - magnifierPosition; + + return AnimatedBuilder( + animation: _magnifierAnimController, + builder: (context, child) { + final currentProgress = _magnifierAnimController.value; + return Transform.translate( + offset: delta * (1 - currentProgress), + child: Transform.scale( + scale: currentProgress, + alignment: Alignment.center, + child: child!, + ), + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25), + boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(25), + child: SizedBox( + width: magnifierContentSize.width * contentScale, + height: magnifierContentSize.height * contentScale, + child: magnifierContent, + ), + ), + ), + ); + }, + calcPosition: + ( + widgetSize, + anchorLocalRect, + handleLocalRect, + textAnchor, + pointerPosition, { + margin = 10.0, + marginOnTop, + marginOnBottom, + }) { + if (widgetSize == null) return null; + + final viewSize = controller.viewSize; + + // Center magnifier horizontally on pointer for smooth tracking + var left = pointerPosition.dx - widgetSize.width / 2; + + // Clamp to viewport bounds + if (left < margin) { + left = margin; + } else if (left + widgetSize.width + margin > viewSize.width) { + left = viewSize.width - widgetSize.width - margin; + } + + var top = anchorLocalRect.top - widgetSize.height - (marginOnTop ?? margin); + + // If too close to top, place below instead + if (top < margin) { + top = anchorLocalRect.bottom + (marginOnBottom ?? margin); + } + + return Offset(left, top); + }, + shouldShowMagnifier: () => + _isDraggingHandle || + _magnifierAnimController.status == AnimationStatus.reverse || + _magnifierAnimController.status == AnimationStatus.forward, + animationDuration: Duration.zero, + ), + onSelectionHandlePanStart: (anchor) { + setState(() { + _isDraggingHandle = true; + }); + }, + + onSelectionHandlePanEnd: (anchor) { + // Animate out, then reset for next drag + if (mounted) { + setState(() { + _isDraggingHandle = false; + }); + } + _magnifierAnimController.reverse().then((_) { + _magnifierAnimController.reset(); + }); + }, */ ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, maxScale: 8, - //scrollPhysics: PdfViewerParams.getScrollPhysics(context), + scrollPhysics: PdfViewerParams.getScrollPhysics(context), viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures @@ -478,7 +621,7 @@ class _MainPageState extends State with WidgetsBindingObserver { null, // Horizontal layout (pages, params) { - final height = pages.fold(0.0, (prev, page) => max(prev, page.height)) + params.margin * 2; + final height = pages.fold(0.0, (prev, page) => math.max(prev, page.height)) + params.margin * 2; final pageLayouts = []; double x = params.margin; for (var page in pages) { @@ -496,7 +639,7 @@ class _MainPageState extends State with WidgetsBindingObserver { }, // Facing pages layout (pages, params) { - final width = pages.fold(0.0, (prev, page) => max(prev, page.width)); + final width = pages.fold(0.0, (prev, page) => math.max(prev, page.width)); final pageLayouts = []; final offset = needCoverPage ? 1 : 0; @@ -507,7 +650,9 @@ class _MainPageState extends State with WidgetsBindingObserver { final isLeft = isRightToLeftReadingOrder ? (pos & 1) == 1 : (pos & 1) == 0; final otherSide = (pos ^ 1) - offset; - final h = 0 <= otherSide && otherSide < pages.length ? max(page.height, pages[otherSide].height) : page.height; + final h = 0 <= otherSide && otherSide < pages.length + ? math.max(page.height, pages[otherSide].height) + : page.height; pageLayouts.add( Rect.fromLTWH( @@ -660,3 +805,22 @@ class _MainPageState extends State with WidgetsBindingObserver { return parts.isEmpty ? path : parts.last; } } + +/// Create a [CustomPainter] from a paint function. +class _CustomPainter extends CustomPainter { + /// Create a [CustomPainter] from a paint function. + const _CustomPainter.fromFunctions(this.paintFunction, {this.hitTestFunction}); + final void Function(Canvas canvas, Size size) paintFunction; + final bool Function(Offset position)? hitTestFunction; + @override + void paint(Canvas canvas, Size size) => paintFunction(canvas, size); + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + + @override + bool hitTest(Offset position) { + if (hitTestFunction == null) return false; + return hitTestFunction!(position); + } +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6f71f2f7..0a7fbc44 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2350,20 +2350,21 @@ class _PdfViewerState extends State Offset? calcPosition( Size? widgetSize, - _TextSelectionPart part, { + Rect anchorLocalRect, + Rect? handleLocalRect, + PdfTextSelectionAnchor? textAnchor, + Offset pointerPosition, { double margin = defMargin, double? marginOnTop, double? marginOnBottom, }) { - if (widgetSize == null || (part != _TextSelectionPart.a && part != _TextSelectionPart.b)) { + if (widgetSize == null || textAnchor == null) { return null; } - final textAnchor = part == _TextSelectionPart.a ? _textSelA : _textSelB; - if (textAnchor == null) return null; late double left, top; - final rect0 = (part == _TextSelectionPart.a ? rectA : rectB); - final rect1 = (part == _TextSelectionPart.a ? _anchorARect : _anchorBRect); + final rect0 = anchorLocalRect; + final rect1 = handleLocalRect; final pt = rect0.center; final rectTop = rect1 == null ? rect0.top : min(rect0.top, rect1.top); final rectBottom = rect1 == null ? rect0.bottom : max(rect0.bottom, rect1.bottom); @@ -2387,7 +2388,7 @@ class _PdfViewerState extends State } break; case PdfTextDirection.vrtl: - if (part == _TextSelectionPart.a) { + if (textAnchor.type == PdfTextSelectionAnchorType.a) { left = rectRight + margin; if (left + widgetSize.width + margin > viewSize.width) { left = rectLeft - widgetSize.width - margin; @@ -2410,8 +2411,17 @@ class _PdfViewerState extends State } Widget? magnifier; - if (textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) { - final textAnchor = textAnchorMoving == _TextSelectionPart.a ? _textSelA! : _textSelB!; + + final shouldShowMagnifier = widget.params.textSelectionParams?.magnifier?.shouldShowMagnifier?.call(); + // Show magnifier if dragging + if (((textAnchorMoving == _TextSelectionPart.a || textAnchorMoving == _TextSelectionPart.b) && + shouldShowMagnifier != false) || + shouldShowMagnifier == true) { + final textAnchor = textAnchorMoving == _TextSelectionPart.a + ? _textSelA! + : textAnchorMoving == _TextSelectionPart.b + ? _textSelB! + : (_selPartLastMoved == _TextSelectionPart.a ? _textSelA! : _textSelB!); final magnifierParams = widget.params.textSelectionParams?.magnifier ?? const PdfViewerSelectionMagnifierParams(); final magnifierEnabled = @@ -2422,15 +2432,50 @@ class _PdfViewerState extends State magnifierParams, ); if (magnifierEnabled) { - final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)(textAnchor, magnifierParams); + final anchorLocalRect = textAnchorMoving == _TextSelectionPart.a ? rectA : rectB; + final handleLocalRect = textAnchorMoving == _TextSelectionPart.a ? _anchorARect! : _anchorBRect!; + // Calculate final magnifier position before calling builder + final magnifierPosition = + (magnifierParams.calcPosition ?? calcPosition)( + _magnifierRect?.size, + anchorLocalRect, + handleLocalRect, + textAnchor, + _pointerOffset, + margin: 10, + marginOnTop: 20, + marginOnBottom: 80, + ) ?? + Offset.zero; + + // Calculate clamped pointer position for magnifier content + final clampedPointerPosition = _calcClampedPointerPosition( + _pointerOffset, + magnifierPosition, + _magnifierRect?.size, + textAnchor, + ); + + final magRect = (magnifierParams.getMagnifierRectForAnchor ?? _getMagnifierRect)( + textAnchor, + magnifierParams, + clampedPointerPosition, + ); final magnifierMain = _buildMagnifier(context, magRect, magnifierParams); final builder = magnifierParams.builder ?? _buildMagnifierDecoration; - magnifier = builder(context, textAnchor, magnifierParams, magnifierMain, magRect.size); + magnifier = builder( + context, + textAnchor, + magnifierParams, + magnifierMain, + magRect.size, + _pointerOffset, + magnifierPosition, + ); if (magnifier != null && !isPositionalWidget(magnifier)) { - final offset = - calcPosition(_magnifierRect?.size, textAnchorMoving, marginOnTop: 20, marginOnBottom: 80) ?? Offset.zero; + final offset = magnifierPosition; magnifier = AnimatedPositioned( - duration: _previousMagnifierRect != null ? const Duration(milliseconds: 100) : Duration.zero, + duration: _previousMagnifierRect != null ? magnifierParams.animationDuration : Duration.zero, left: offset.dx, top: offset.dy, child: WidgetSizeSniffer( @@ -2533,9 +2578,18 @@ class _PdfViewerState extends State contextMenu = createContextMenu(a, b, _contextMenuFor); if (contextMenu != null && !isPositionalWidget(contextMenu)) { + final textAnchor = _selPartLastMoved == _TextSelectionPart.a + ? _textSelA + : _selPartLastMoved == _TextSelectionPart.b + ? _textSelB + : null; + final anchorLocalRect = _selPartLastMoved == _TextSelectionPart.a ? rectA : rectB; + final handleLocalRect = _selPartLastMoved == _TextSelectionPart.a ? _anchorARect : _anchorBRect; final offset = localOffset != null ? normalizeWidgetPosition(localOffset, _contextMenuRect?.size) - : (calcPosition(_contextMenuRect?.size, _selPartLastMoved) ?? Offset.zero); + : (calcPosition(_contextMenuRect?.size, anchorLocalRect, handleLocalRect, textAnchor, _pointerOffset) ?? + Offset.zero); + contextMenu = Positioned( left: offset.dx, top: offset.dy, @@ -2614,6 +2668,33 @@ class _PdfViewerState extends State ]; } + /// Calculate the clamped pointer position for the magnifier content. + /// + /// When the magnifier widget is clamped to stay within viewport bounds (e.g., near screen edges), + /// we adjust the pointer position by the same amount. This enables [PdfViewerGetMagnifierRectForAnchor] + /// and [PdfViewerMagnifierBuilder] to use the clamped pointer position to effectively "freeze" the + /// magnifier content, preventing it from sliding inside the magnifier. + /// + /// Returns the clamped pointer position in viewport coordinates. + Offset _calcClampedPointerPosition( + Offset pointerOffset, + Offset magnifierPosition, + Size? magnifierSize, + PdfTextSelectionAnchor textAnchor, + ) { + var clampedPointerOffset = pointerOffset; + + if (magnifierSize != null) { + // What the magnifier X position would be without clamping (centered on pointer) + final unclampedLeft = pointerOffset.dx - magnifierSize.width / 2; + // How much it was actually clamped by calcPosition + final clampAmount = magnifierPosition.dx - unclampedLeft; + // Adjust pointer position by the same clamp amount to freeze content + clampedPointerOffset = Offset(pointerOffset.dx + clampAmount, pointerOffset.dy); + } + return clampedPointerOffset; + } + bool _shouldBeShownForAnchor( PdfTextSelectionAnchor textAnchor, PdfViewerController controller, @@ -2713,8 +2794,11 @@ class _PdfViewerState extends State } } - /// Calculate the rectangle shown in the magnifier for the given text anchor. - Rect _getMagnifierRect(PdfTextSelectionAnchor textAnchor, PdfViewerSelectionMagnifierParams params) { + Rect _getMagnifierRect( + PdfTextSelectionAnchor textAnchor, + PdfViewerSelectionMagnifierParams params, + Offset clampedPointerPosition, + ) { final c = textAnchor.page.charRects[textAnchor.index]; final (width, height) = switch (_document!.pages[textAnchor.page.pageNumber - 1].rotation.index & 1) { @@ -2771,6 +2855,8 @@ class _PdfViewerState extends State PdfViewerSelectionMagnifierParams params, Widget child, Size childSize, + Offset pointerPosition, + Offset magnifierPosition, ) { final scale = 80 / min(childSize.width, childSize.height); return Container( @@ -2829,11 +2915,15 @@ class _PdfViewerState extends State final a = _findTextAndIndexForPoint(_textSelA!.rect.center); if (a == null) return; _selA = a; + // Notify drag start callback + widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelA!); } else if (_selPartMoving == _TextSelectionPart.b) { _textSelectAnchor = anchor + _textSelB!.rect.bottomRight - position!; final b = _findTextAndIndexForPoint(_textSelB!.rect.center); if (b == null) return; _selB = b; + // Notify drag start callback + widget.params.textSelectionParams?.onSelectionHandlePanStart?.call(_textSelB!); } else { return; } @@ -2872,11 +2962,22 @@ class _PdfViewerState extends State if (_isInteractionGoingOn) return; _contextMenuDocumentPosition = null; _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + // Notify drag update callback + final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; + if (anchor != null) { + widget.params.textSelectionParams?.onSelectionHandlePanUpdate?.call(anchor, details.delta); + } } void _onSelectionHandlePanEnd(_TextSelectionPart handle, DragEndDetails details) { if (_isInteractionGoingOn) return; final result = _updateSelectionHandlesPan(_globalToDocument(details.globalPosition)); + // Notify drag end callback before clearing state + final anchor = handle == _TextSelectionPart.a ? _textSelA : _textSelB; + if (anchor != null) { + widget.params.textSelectionParams?.onSelectionHandlePanEnd?.call(anchor); + } + _selPartMoving = _TextSelectionPart.none; _isSelectingAllText = false; if (!result) { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2ade36a7..a39307f6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -743,6 +743,9 @@ class PdfTextSelectionParams { this.buildSelectionHandle, this.calcSelectionHandleOffset, this.onTextSelectionChange, + this.onSelectionHandlePanStart, + this.onSelectionHandlePanUpdate, + this.onSelectionHandlePanEnd, this.magnifier, }); @@ -777,6 +780,15 @@ class PdfTextSelectionParams { /// Function to be notified when the text selection is changed. final PdfViewerTextSelectionChangeCallback? onTextSelectionChange; + /// Callback for when a selection handle pan starts. + final PdfViewerSelectionHandlePanStartCallback? onSelectionHandlePanStart; + + /// Callback for when a selection handle is being panned. + final PdfViewerSelectionHandlePanUpdateCallback? onSelectionHandlePanUpdate; + + /// Callback for when a selection handle pan ends. + final PdfViewerSelectionHandlePanEndCallback? onSelectionHandlePanEnd; + /// Parameters for the magnifier. final PdfViewerSelectionMagnifierParams? magnifier; @@ -787,6 +799,9 @@ class PdfTextSelectionParams { other.buildSelectionHandle == buildSelectionHandle && other.calcSelectionHandleOffset == calcSelectionHandleOffset && other.onTextSelectionChange == onTextSelectionChange && + other.onSelectionHandlePanStart == onSelectionHandlePanStart && + other.onSelectionHandlePanUpdate == onSelectionHandlePanUpdate && + other.onSelectionHandlePanEnd == onSelectionHandlePanEnd && other.enableSelectionHandles == enableSelectionHandles && other.showContextMenuAutomatically == showContextMenuAutomatically && other.magnifier == magnifier; @@ -797,6 +812,9 @@ class PdfTextSelectionParams { buildSelectionHandle.hashCode ^ calcSelectionHandleOffset.hashCode ^ onTextSelectionChange.hashCode ^ + onSelectionHandlePanStart.hashCode ^ + onSelectionHandlePanUpdate.hashCode ^ + onSelectionHandlePanEnd.hashCode ^ enableSelectionHandles.hashCode ^ showContextMenuAutomatically.hashCode ^ magnifier.hashCode; @@ -915,7 +933,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, @@ -940,6 +958,15 @@ typedef PdfViewerCalcSelectionAnchorHandleOffsetFunction = /// [textSelection] contains the selected text range on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection textSelection); +/// Callback for when a selection handle pan starts +typedef PdfViewerSelectionHandlePanStartCallback = void Function(PdfTextSelectionAnchor anchor); + +/// Callback for when a selection handle is being panned +typedef PdfViewerSelectionHandlePanUpdateCallback = void Function(PdfTextSelectionAnchor anchor, Offset delta); + +/// Callback for when a selection handle pan ends +typedef PdfViewerSelectionHandlePanEndCallback = void Function(PdfTextSelectionAnchor anchor); + /// Text selection abstract class PdfTextSelection { /// Whether the text selection is enabled by the configuration. @@ -1018,6 +1045,9 @@ class PdfViewerSelectionMagnifierParams { this.builder, this.shouldBeShownForAnchor, this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, + this.calcPosition, + this.shouldShowMagnifier, + this.animationDuration = const Duration(milliseconds: 100), }); /// The default maximum image bytes cached on memory is 256 KB. @@ -1049,6 +1079,33 @@ class PdfViewerSelectionMagnifierParams { /// The default is 256 * 1024 bytes (256 KB). final int maxImageBytesCachedOnMemory; + /// Optional callback to calculate the magnifier widget position. + /// + /// When provided, this function will be used to determine where to place + /// the magnifier widget in the viewport. If null, pdfrx uses its default + /// positioning logic. + /// + /// This can also be used for context menu positioning or other overlay widgets. + final PdfViewerCalcMagnifierPositionFunction? calcPosition; + + /// Optional callback to control magnifier visibility. + /// + /// This allows for more fine grained control of when the magnifier should be + /// shown during text selection, for example to coordinate with custom animations. + /// If null, the magnifier is shown whenever a selection handle is being dragged. + /// + /// Return true to show the magnifier, false to hide it. + final bool Function()? shouldShowMagnifier; + + /// Duration for the magnifier position animation. + /// + /// This controls the animation duration when the magnifier position changes + /// as the user drags the selection handle. Set to [Duration.zero] to disable + /// the position animation. + /// + /// Default is 100 milliseconds. + final Duration animationDuration; + @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1059,7 +1116,10 @@ class PdfViewerSelectionMagnifierParams { other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && other.shouldBeShownForAnchor == shouldBeShownForAnchor && - other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory; + other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory && + other.calcPosition == calcPosition && + other.shouldShowMagnifier == shouldShowMagnifier && + other.animationDuration == animationDuration; } @override @@ -1069,30 +1129,50 @@ class PdfViewerSelectionMagnifierParams { getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ shouldBeShownForAnchor.hashCode ^ - maxImageBytesCachedOnMemory.hashCode; + maxImageBytesCachedOnMemory.hashCode ^ + calcPosition.hashCode ^ + shouldShowMagnifier.hashCode ^ + animationDuration.hashCode; } /// Function to get the magnifier rectangle for the anchor. /// -/// The following fragment illustrates one example of the code to calculate where on the document the magnifier should -/// be shown for: +/// This function determines what part of the PDF document to show in the magnifier. +/// +/// Parameters: +/// - [anchor]: The text selection anchor with character information +/// - [params]: Magnifier parameters +/// - [clampedPointerPosition]: The clamped pointer position in viewport coordinates. +/// This is the raw pointer position adjusted for viewport edge clamping to prevent +/// content sliding when the magnifier widget itself is clamped at the viewport edge. +/// Use [PdfViewerController.globalToDocument] to convert to document coordinates if needed. /// +/// Returns a [Rect] in document coordinates representing the area to magnify. +/// +/// Example: ///```dart -/// getMagnifierRectForAnchor: (textAnchor, params) { +/// getMagnifierRectForAnchor: (textAnchor, params, clampedPointerPosition) { /// final c = textAnchor.page.charRects[textAnchor.index]; /// final baseUnit = switch (textAnchor.direction) { /// PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, /// PdfTextDirection.vrtl => c.width, /// }; +/// // Convert to document coordinates for positioning +/// final pointerInDocument = controller.globalToDocument(clampedPointerPosition) ?? textAnchor.anchorPoint; /// return Rect.fromLTRB( -/// textAnchor.rect.left - baseUnit * 2, -/// textAnchor.rect.top - baseUnit * .2, -/// textAnchor.rect.right + baseUnit * 2, -/// textAnchor.rect.bottom + baseUnit * .2, -/// ); +/// pointerInDocument.dx - baseUnit * 2.5, +/// textAnchor.rect.top - baseUnit * 0.5, +/// pointerInDocument.dx + baseUnit * 2.5, +/// textAnchor.rect.bottom + baseUnit * 0.5, +/// ); +/// } ///``` typedef PdfViewerGetMagnifierRectForAnchor = - Rect Function(PdfTextSelectionAnchor anchor, PdfViewerSelectionMagnifierParams params); + Rect Function( + PdfTextSelectionAnchor anchor, + PdfViewerSelectionMagnifierParams params, + Offset clampedPointerPosition, + ); /// Function to build the magnifier widget. /// @@ -1107,11 +1187,13 @@ typedef PdfViewerGetMagnifierRectForAnchor = /// [magnifierContent] is the widget that contains the magnified content. And you can embed it into your widget tree. /// [magnifierContentSize] is the size of the magnified content in document coordinates; you can use the size to know /// the aspect ratio of the magnified content. +/// [pointerPosition] is the pointer/finger position in viewport coordinates. +/// [magnifierPosition] is the calculated position for the magnifier widget in viewport coordinates. /// /// The following fragment illustrates how to build a magnifier widget with a border and rounded corners: /// /// ```dart -/// builder: (context, textAnchor, params, magnifierContent, magnifierContentSize) { +/// builder: (context, textAnchor, params, magnifierContent, magnifierContentSize, pointerPosition, magnifierPosition) { /// // calculate the scale to fit the magnifier content fit into 80x80 box /// final scale = 80 / min(magnifierContentSize.width, magnifierContentSize.height); /// return Container( @@ -1136,6 +1218,8 @@ typedef PdfViewerMagnifierBuilder = PdfViewerSelectionMagnifierParams params, Widget magnifierContent, Size magnifierContentSize, + Offset pointerPosition, + Offset magnifierPosition, ); /// Function to determine whether the magnifier should be shown or not. @@ -1157,6 +1241,33 @@ typedef PdfViewerMagnifierShouldBeShownFunction = PdfViewerSelectionMagnifierParams params, ); +/// Function to calculate the position of the magnifier widget in viewport coordinates. +/// +/// This callback allows custom positioning logic for the magnifier. +/// If null, pdfrx uses its default positioning algorithm that handles different text +/// directions (LTR, RTL, VRTL) and viewport edge cases. +/// +/// Parameters: +/// - [widgetSize]: The size of the magnifier widget (null if not yet measured) +/// - [anchorLocalRect]: The anchor's character rectangle in viewport coordinates +/// - [handleLocalRect]: The selection handle rectangle in viewport coordinates (may be null) +/// - [textAnchor]: The text selection anchor with character information (may be null) +/// - [pointerPosition]: The pointer/finger position in viewport coordinates +/// - [margin]: Default margin from viewport edges +/// - [marginOnTop]: Optional custom margin when magnifier is positioned above text +/// - [marginOnBottom]: Optional custom margin when magnifier is positioned below text +typedef PdfViewerCalcMagnifierPositionFunction = + Offset? Function( + Size? widgetSize, + Rect anchorLocalRect, + Rect? handleLocalRect, + PdfTextSelectionAnchor textAnchor, + Offset pointerPosition, { + double margin, + double? marginOnTop, + double? marginOnBottom, + }); + /// Function to notify that the document is loaded/changed. typedef PdfViewerDocumentChangedCallback = void Function(PdfDocument? document); From 941c2abb3c1c608d628f0e824edfb61628768314 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 00:54:35 +0900 Subject: [PATCH 557/663] #513 Introducing PdfTextSelection.textSelectionPointRange/PdfTextSelectionDelegate.setTextSelectionPointRange --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 72 +++++++++++++------ .../lib/src/widgets/pdf_viewer_params.dart | 15 ++++ 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 6f71f2f7..fcde8e37 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -247,7 +247,7 @@ class _PdfViewerState extends State final double _hitTestMargin = 3.0; /// The starting/ending point of the text selection. - _PdfTextSelectionPoint? _selA, _selB; + PdfTextSelectionPoint? _selA, _selB; Offset? _textSelectAnchor; /// [_textSelA] is the rectangle of the first character in the selected paragraph and @@ -2172,7 +2172,7 @@ class _PdfViewerState extends State } /// [point] is in the document coordinates. - _PdfTextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { + PdfTextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { if (point == null) return null; for (var pageIndex = 0; pageIndex < _document!.pages.length; pageIndex++) { final pageRect = _layout!.pageLayouts[pageIndex]; @@ -2188,7 +2188,7 @@ class _PdfViewerState extends State for (var i = 0; i < text.charRects.length; i++) { final charRect = text.charRects[i]; if (charRect.containsPoint(pt)) { - return _PdfTextSelectionPoint(text, i); + return PdfTextSelectionPoint(text, i); } final d2 = charRect.distanceSquaredTo(pt); if (d2 < d2Min) { @@ -2197,7 +2197,7 @@ class _PdfViewerState extends State } } if (closestIndex != null && d2Min <= hitTestMargin * hitTestMargin) { - return _PdfTextSelectionPoint(text, closestIndex); + return PdfTextSelectionPoint(text, closestIndex); } } return null; @@ -2910,12 +2910,23 @@ class _PdfViewerState extends State @override Future clearTextSelection() async => _clearTextSelections(); - void _setTextSelection(_PdfTextSelectionPoint a, _PdfTextSelectionPoint b) { - if (!a.isValid || !b.isValid) { - throw ArgumentError('Both selection points must be valid.'); + @override + PdfTextSelectionRange? get textSelectionPointRange { + final a = _selA; + final b = _selB; + if (a == null || b == null) { + return null; } - _selA = a; - _selB = b; + return PdfTextSelectionRange.fromPoints(a, b); + } + + @override + Future setTextSelectionPointRange(PdfTextSelectionRange range) async { + if (_selA == range.start && _selB == range.end) { + return; + } + _selA = range.start; + _selB = range.end; if (_selA! > _selB!) { final temp = _selA; _selA = _selB; @@ -3058,8 +3069,8 @@ class _PdfViewerState extends State } final range = PdfPageTextRange(pageText: text, start: f.index, end: f.end); final selectionRect = f.bounds.toRectInDocument(page: page, pageRect: pageRect); - _selA = _PdfTextSelectionPoint(text, f.index); - _selB = _PdfTextSelectionPoint(text, f.end - 1); + _selA = PdfTextSelectionPoint(text, f.index); + _selB = PdfTextSelectionPoint(text, f.end - 1); _textSelA = PdfTextSelectionAnchor( selectionRect, range.pageText.getFragmentForTextIndex(range.start)?.direction ?? PdfTextDirection.ltr, @@ -3326,16 +3337,19 @@ class _PdfViewerTransformationController extends TransformationController { /// What selection part is moving by mouse-dragging/finger-panning. enum _TextSelectionPart { none, free, a, b } -/// Represents a point in the text selection. +/// Represents a point (combination of page and character index) in the text selection. /// It contains the [PdfPageText] and the index of the character in that text. @immutable -class _PdfTextSelectionPoint { - const _PdfTextSelectionPoint(this.text, this.index); +class PdfTextSelectionPoint { + const PdfTextSelectionPoint(this.text, this.index); /// The page text associated with this selection point. final PdfPageText text; /// The index of the character in the [text]. + /// + /// Similar to [PdfPageText.getRangeFromAB], this index is inclusive; even for the end point of the selection. + /// In other words, for the end of the selection, the index points to the last selected character. final int index; /// Whether the index is valid in the [text]. @@ -3344,33 +3358,51 @@ class _PdfTextSelectionPoint { @override bool operator ==(Object other) { if (identical(this, other)) return true; - if (other is! _PdfTextSelectionPoint) return false; + if (other is! PdfTextSelectionPoint) return false; return text == other.text && index == other.index; } - bool operator <(_PdfTextSelectionPoint other) { + bool operator <(PdfTextSelectionPoint other) { if (text.pageNumber != other.text.pageNumber) { return text.pageNumber < other.text.pageNumber; } return index < other.index; } - bool operator >(_PdfTextSelectionPoint other) => !(this <= other); + bool operator >(PdfTextSelectionPoint other) => !(this <= other); - bool operator <=(_PdfTextSelectionPoint other) { + bool operator <=(PdfTextSelectionPoint other) { if (text.pageNumber != other.text.pageNumber) { return text.pageNumber < other.text.pageNumber; } return index <= other.index; } - bool operator >=(_PdfTextSelectionPoint other) => !(this < other); + bool operator >=(PdfTextSelectionPoint other) => !(this < other); @override int get hashCode => text.hashCode ^ index.hashCode; @override - String toString() => '$_PdfTextSelectionPoint(text: $text, index: $index)'; + String toString() => '$PdfTextSelectionPoint(text: $text, index: $index)'; +} + +/// Represents a range of text selection between two points. +class PdfTextSelectionRange { + /// Creates a [PdfTextSelectionRange] from two selection points. + /// + /// The points can be in any order; the constructor will ensure that [start] is less than or equal to [end]. + PdfTextSelectionRange.fromPoints(PdfTextSelectionPoint a, PdfTextSelectionPoint b) + : start = a <= b ? a : b, + end = a <= b ? b : a; + + /// The start point of the text selection. + final PdfTextSelectionPoint start; + + /// The end point of the text selection. + /// + /// Please note that the index of this point is inclusive; it points to the last selected character. + final PdfTextSelectionPoint end; } /// Represents the anchor point of the text selection. diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2ade36a7..2eb58978 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -956,6 +956,11 @@ abstract class PdfTextSelection { /// Whether the viewer is currently selecting all text. bool get isSelectingAllText; + /// Get the text selection point range. + /// + /// null if there is no text selected. + PdfTextSelectionRange? get textSelectionPointRange; + /// Get the selected text. /// /// Although the use of this property is not restricted by [isCopyAllowed] @@ -963,6 +968,9 @@ abstract class PdfTextSelection { Future getSelectedText(); /// Get the selected text ranges. + /// + /// Although the use of this property is not restricted by [isCopyAllowed] + /// but you have to ensure that your use of the text does not violate [isCopyAllowed] condition. Future> getSelectedTextRanges(); } @@ -989,6 +997,13 @@ abstract class PdfTextSelectionDelegate implements PdfTextSelection { /// Please note that [position] is in document coordinates. Future selectWord(Offset position); + /// Set the text selection point range. + /// + /// This function will update the current text selection to the specified range. + /// + /// See also [textSelectionPointRange]. + Future setTextSelectionPointRange(PdfTextSelectionRange range); + /// Convert document coordinates to local coordinates and vice versa. PdfViewerCoordinateConverter get doc2local; } From 9e6ef831d134fbe9ec0802ee2a8bd00f3841665c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 00:59:23 +0900 Subject: [PATCH 558/663] Update document --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2eb58978..9ca05404 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -940,7 +940,9 @@ typedef PdfViewerCalcSelectionAnchorHandleOffsetFunction = /// [textSelection] contains the selected text range on each page. typedef PdfViewerTextSelectionChangeCallback = void Function(PdfTextSelection textSelection); -/// Text selection +/// Interface for text selection information. +/// +/// To perform text selection actions, use [PdfTextSelectionDelegate]. abstract class PdfTextSelection { /// Whether the text selection is enabled by the configuration. /// @@ -975,6 +977,8 @@ abstract class PdfTextSelection { } /// Delegate for text selection actions. +/// +/// You can obtain the instance via [PdfViewerController.textSelectionDelegate]. abstract class PdfTextSelectionDelegate implements PdfTextSelection { /// Copy the selected text. /// From 72d630602641648b32d1055321974265913d3f68 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 01:29:48 +0900 Subject: [PATCH 559/663] PdfViewerSelectionMagnifierParams.shouldBeShownForAnchor -> shouldShowMagnifierForAnchor --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- .../lib/src/widgets/pdf_viewer_params.dart | 55 +++++++++++-------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index abd3bbf0..f970619c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2426,7 +2426,7 @@ class _PdfViewerState extends State final magnifierEnabled = (magnifierParams.enabled ?? _selectionPointerDeviceKind == PointerDeviceKind.touch) && - (magnifierParams.shouldBeShownForAnchor ?? _shouldBeShownForAnchor)( + (magnifierParams.shouldShowMagnifierForAnchor ?? _shouldBeShownForAnchor)( textAnchor, _controller!, magnifierParams, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 5f15dfd8..dcb12f2f 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -933,7 +933,7 @@ enum PdfViewerPart { /// State of the text selection anchor handle. enum PdfViewerTextSelectionAnchorHandleState { normal, hover, dragging } -/// Function to build the text selection anchor handle. +/// Function to build the text selection anchor handle. typedef PdfViewerTextSelectionAnchorHandleBuilder = Widget? Function( BuildContext context, @@ -1062,11 +1062,11 @@ class PdfViewerSelectionMagnifierParams { this.magnifierSizeThreshold = 72, this.getMagnifierRectForAnchor, this.builder, - this.shouldBeShownForAnchor, - this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, - this.calcPosition, this.shouldShowMagnifier, + this.calcPosition, this.animationDuration = const Duration(milliseconds: 100), + this.shouldShowMagnifierForAnchor, + this.maxImageBytesCachedOnMemory = defaultMaxImageBytesCachedOnMemory, }); /// The default maximum image bytes cached on memory is 256 KB. @@ -1088,17 +1088,20 @@ class PdfViewerSelectionMagnifierParams { /// Function to build the magnifier widget. final PdfViewerMagnifierBuilder? builder; - /// Function to determine whether the magnifier should be shown based on conditions such as zoom level. + /// Function to control magnifier visibility. /// - /// If [enabled] is false, this function is not called. - final PdfViewerMagnifierShouldBeShownFunction? shouldBeShownForAnchor; - - /// The maximum number of image bytes to be cached on memory. + /// This allows for fine grained control of when the magnifier should be shown during text selection, for example + /// to coordinate with custom animations. + /// If null, the magnifier is shown whenever a selection handle is being dragged. /// - /// The default is 256 * 1024 bytes (256 KB). - final int maxImageBytesCachedOnMemory; + /// Return true to show the magnifier, false to hide it. + /// + /// Even if this function returns true, the magnifier may not be shown if other conditions are not met + /// (e.g., if [enabled] is false or if the character height is above [magnifierSizeThreshold], + /// or [shouldShowMagnifierForAnchor] returns false). + final bool Function()? shouldShowMagnifier; - /// Optional callback to calculate the magnifier widget position. + /// Function to calculate the magnifier widget position. /// /// When provided, this function will be used to determine where to place /// the magnifier widget in the viewport. If null, pdfrx uses its default @@ -1107,15 +1110,6 @@ class PdfViewerSelectionMagnifierParams { /// This can also be used for context menu positioning or other overlay widgets. final PdfViewerCalcMagnifierPositionFunction? calcPosition; - /// Optional callback to control magnifier visibility. - /// - /// This allows for more fine grained control of when the magnifier should be - /// shown during text selection, for example to coordinate with custom animations. - /// If null, the magnifier is shown whenever a selection handle is being dragged. - /// - /// Return true to show the magnifier, false to hide it. - final bool Function()? shouldShowMagnifier; - /// Duration for the magnifier position animation. /// /// This controls the animation duration when the magnifier position changes @@ -1125,6 +1119,21 @@ class PdfViewerSelectionMagnifierParams { /// Default is 100 milliseconds. final Duration animationDuration; + /// Function to determine whether the magnifier should be shown based on conditions such as zoom level. + /// + /// If [enabled] is false, this function is not called. + /// + /// If the function is null, the magnifier is shown if the character height is smaller than + /// [magnifierSizeThreshold]. + /// + /// Please note that the function is called after evaluating [enabled] and [shouldShowMagnifier]. + final PdfViewerMagnifierShouldBeShownFunction? shouldShowMagnifierForAnchor; + + /// The maximum number of image bytes to be cached on memory. + /// + /// The default is 256 * 1024 bytes (256 KB). + final int maxImageBytesCachedOnMemory; + @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -1134,7 +1143,7 @@ class PdfViewerSelectionMagnifierParams { other.magnifierSizeThreshold == magnifierSizeThreshold && other.getMagnifierRectForAnchor == getMagnifierRectForAnchor && other.builder == builder && - other.shouldBeShownForAnchor == shouldBeShownForAnchor && + other.shouldShowMagnifierForAnchor == shouldShowMagnifierForAnchor && other.maxImageBytesCachedOnMemory == maxImageBytesCachedOnMemory && other.calcPosition == calcPosition && other.shouldShowMagnifier == shouldShowMagnifier && @@ -1147,7 +1156,7 @@ class PdfViewerSelectionMagnifierParams { magnifierSizeThreshold.hashCode ^ getMagnifierRectForAnchor.hashCode ^ builder.hashCode ^ - shouldBeShownForAnchor.hashCode ^ + shouldShowMagnifierForAnchor.hashCode ^ maxImageBytesCachedOnMemory.hashCode ^ calcPosition.hashCode ^ shouldShowMagnifier.hashCode ^ From 9728d188cac0d79163c7d8e78890450750b56d82 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 02:41:08 +0900 Subject: [PATCH 560/663] Update document. --- doc/Text-Selection.md | 83 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index 01741665..329d1709 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -2,6 +2,8 @@ On pdfrx 2.1.X, text selection related parameters are moved to [PdfViewerParams.textSelectionParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/textSelectionParams.html). +## Enabling/Disabling Text Selection + Text selection feature is enabled by default and if you want to disable it, do like the following fragment: ```dart @@ -17,6 +19,8 @@ PdfViewer.asset( ), ``` +## Handling Text Selection Changes + If you want to handle text selection changes, you can use [PdfTextSelectionParams.onTextSelectionChange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionParams/onTextSelectionChange.html). The handler function receives a parameter of [PdfTextSelection](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection-class.html) and you can obtain the current text selection and its associated text ranges: @@ -38,3 +42,82 @@ PdfViewer.asset( ), ``` +## Programmatic Text Selection Control + +Starting from commit [941c2ab](https://github.com/espresso3389/pdfrx/commit/941c2abb3c1c608d628f0e824edfb61628768314), you can programmatically control text selection using [PdfTextSelectionDelegate](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionDelegate-class.html). This is particularly useful for implementing save/restore functionality for text selections (see [#513](https://github.com/espresso3389/pdfrx/issues/513)). + +### Getting Current Text Selection + +You can obtain the current text selection range using [textSelectionPointRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection/textSelectionPointRange.html): + +```dart +final controller = PdfViewerController(); + +// Get the current text selection point range +final PdfTextSelectionRange? range = controller.textSelection.textSelectionPointRange; + +if (range != null) { + // Access start and end points + final PdfTextSelectionPoint start = range.start; + final PdfTextSelectionPoint end = range.end; + + // Each point contains: + // - text: The PdfPageText object for the page + // - index: The character index within that page's text + print('Selection from page ${start.text.pageNumber}, char ${start.index} ' + 'to page ${end.text.pageNumber}, char ${end.index}'); +} +``` + +### Setting/Restoring Text Selection + +You can programmatically set the text selection using [setTextSelectionPointRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionDelegate/setTextSelectionPointRange.html). To create a text selection, you need the page number and character indices: + +```dart +// The code below assumes that the controller is associated to a PdfViewer +final controller = PdfViewerController(); +... + +// First, load the page text for the target page +final page = await controller.document?.getPage(pageNumber); +final pageText = await page?.loadText(); + +if (pageText != null) { + // Create selection points with page text and character indices + final startPoint = PdfTextSelectionPoint(pageText, startCharIndex); + final endPoint = PdfTextSelectionPoint(pageText, endCharIndex); + + // Create range and set the selection + final range = PdfTextSelectionRange.fromPoints(startPoint, endPoint); + await controller.textSelection.setTextSelectionPointRange(range); +} +``` + +**Note:** Text selection can span across multiple pages. The start and end points can be on different pages: + +```dart +// Example: Select from the beginning of page 1 to the end of page 3 +final startPage = await controller.document?.getPage(1); +final startPageText = await startPage?.loadText(); + +final endPage = await controller.document?.getPage(3); +final endPageText = await endPage?.loadText(); + +if (startPageText != null && endPageText != null && endPageText.fullText.isNotEmpty) { + final startPoint = PdfTextSelectionPoint(startPageText, 0); + // NOTE: The index is inclusive - it points to the last selected character. + // To select to the end of page, use (fullText.length - 1). + // This assumes the page has text (fullText.length > 0). + final endPoint = PdfTextSelectionPoint(endPageText, endPageText.fullText.length - 1); + final range = PdfTextSelectionRange.fromPoints(startPoint, endPoint); + await controller.textSelection.setTextSelectionPointRange(range); +} +``` + +After obtaining [textSelectionPointRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelection/textSelectionPointRange.html), you can use it with [setTextSelectionPointRange](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionDelegate/setTextSelectionPointRange.html) to restore the text selection unless the PDF structure is modified; i.e. page insertion/modification and so on. + +### Important Notes + +- [PdfTextSelectionPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionPoint-class.html) represents a point in the document's text, combining a [PdfPageText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageText-class.html) object and a character index +- The character index in both start and end points is **inclusive**; for the end point, it points to the last selected character (not one past it) +- [PdfTextSelectionRange.fromPoints](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSelectionRange/PdfTextSelectionRange.fromPoints.html) automatically ensures that `start` comes before `end`, regardless of the order you pass the points From 33c9c22ad2b6fdc99ee715ea118fadc82257da16 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 02:44:14 +0900 Subject: [PATCH 561/663] Release pdfium_flutter v0.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/darwin/pdfium_flutter.podspec | 6 +++--- packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift | 4 ++-- packages/pdfium_flutter/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index 1b8345ab..67707278 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +- Updated PDFium to version 144.0.7520.0 (build 20251111-173323). + ## 0.1.1 - Fixed SwiftPM package name inconsistency. diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec index 9bce2a2f..9a349dd0 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec +++ b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec @@ -1,11 +1,11 @@ # PDFium xcframework configuration # https://github.com/espresso3389/pdfium-xcframework/releases -PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-180742.xcframework.zip" -PDFIUM_HASH = "0a900bb5b5d66c4caaaaef1cf291dd1ef34639069baa12c565eda296aee878ec" +PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-173323/PDFium-chromium-7520-20251111-173323.xcframework.zip" +PDFIUM_HASH = "fb1419cadda3fbae9953bdcb1cc358fdeb7d10f80141e3c3d03be4dfd1f18b53" Pod::Spec.new do |s| s.name = 'pdfium_flutter' - s.version = '0.1.0' + s.version = '0.1.2' s.summary = 'Flutter FFI plugin for loading PDFium native libraries.' s.description = <<-DESC Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for iOS and macOS. diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index 5fc83230..255852a5 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -23,8 +23,8 @@ let package = Package( ), .binaryTarget( name: "PDFium", - url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7506.0/PDFium-chromium-7506-20251109-180742.xcframework.zip", - checksum: "0a900bb5b5d66c4caaaaef1cf291dd1ef34639069baa12c565eda296aee878ec" + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-173323/PDFium-chromium-7520-20251111-173323.xcframework.zip", + checksum: "fb1419cadda3fbae9953bdcb1cc358fdeb7d10f80141e3c3d03be4dfd1f18b53" ), ] ) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 4ca4e934..7c899237 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues From e56331c06f755e20113acec56057abaace1d6d5f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 03:16:24 +0900 Subject: [PATCH 562/663] Release pdfium_flutter v0.1.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/darwin/pdfium_flutter.podspec | 6 +++--- packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift | 4 ++-- packages/pdfium_flutter/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index 67707278..799d2af7 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.3 + +- Updated PDFium to version 144.0.7520.0 (build 20251111-181257). + ## 0.1.2 - Updated PDFium to version 144.0.7520.0 (build 20251111-173323). diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec index 9a349dd0..1c16df85 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec +++ b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec @@ -1,11 +1,11 @@ # PDFium xcframework configuration # https://github.com/espresso3389/pdfium-xcframework/releases -PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-173323/PDFium-chromium-7520-20251111-173323.xcframework.zip" -PDFIUM_HASH = "fb1419cadda3fbae9953bdcb1cc358fdeb7d10f80141e3c3d03be4dfd1f18b53" +PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-181257/PDFium-chromium-7520-20251111-181257.xcframework.zip" +PDFIUM_HASH = "1190e2034a34f1f4a462e3564516750d6652b05064c545978306032bcd19be42" Pod::Spec.new do |s| s.name = 'pdfium_flutter' - s.version = '0.1.2' + s.version = '0.1.3' s.summary = 'Flutter FFI plugin for loading PDFium native libraries.' s.description = <<-DESC Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for iOS and macOS. diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index 255852a5..cbd3d312 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -23,8 +23,8 @@ let package = Package( ), .binaryTarget( name: "PDFium", - url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-173323/PDFium-chromium-7520-20251111-173323.xcframework.zip", - checksum: "fb1419cadda3fbae9953bdcb1cc358fdeb7d10f80141e3c3d03be4dfd1f18b53" + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-181257/PDFium-chromium-7520-20251111-181257.xcframework.zip", + checksum: "1190e2034a34f1f4a462e3564516750d6652b05064c545978306032bcd19be42" ), ] ) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 7c899237..70e12a71 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.2 +version: 0.1.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues From b3743fbe35320a5fd8a4d5d53847ba250860c187 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 03:36:04 +0900 Subject: [PATCH 563/663] Release pdfium_flutter v0.1.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/darwin/pdfium_flutter.podspec | 6 +++--- packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift | 4 ++-- packages/pdfium_flutter/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index 799d2af7..83a23f5f 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4 + +- Updated PDFium to version 144.0.7520.0 (build 20251111-183119). + ## 0.1.3 - Updated PDFium to version 144.0.7520.0 (build 20251111-181257). diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec index 1c16df85..b05c0f28 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec +++ b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec @@ -1,11 +1,11 @@ # PDFium xcframework configuration # https://github.com/espresso3389/pdfium-xcframework/releases -PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-181257/PDFium-chromium-7520-20251111-181257.xcframework.zip" -PDFIUM_HASH = "1190e2034a34f1f4a462e3564516750d6652b05064c545978306032bcd19be42" +PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-183119/PDFium-chromium-7520-20251111-183119.xcframework.zip" +PDFIUM_HASH = "f7b1ac0b78aa24a909850b9347cb019d4feef7da242ad28a258a49f274dddd81" Pod::Spec.new do |s| s.name = 'pdfium_flutter' - s.version = '0.1.3' + s.version = '0.1.4' s.summary = 'Flutter FFI plugin for loading PDFium native libraries.' s.description = <<-DESC Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for iOS and macOS. diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index cbd3d312..070905fe 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -23,8 +23,8 @@ let package = Package( ), .binaryTarget( name: "PDFium", - url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-181257/PDFium-chromium-7520-20251111-181257.xcframework.zip", - checksum: "1190e2034a34f1f4a462e3564516750d6652b05064c545978306032bcd19be42" + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-183119/PDFium-chromium-7520-20251111-183119.xcframework.zip", + checksum: "f7b1ac0b78aa24a909850b9347cb019d4feef7da242ad28a258a49f274dddd81" ), ] ) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 70e12a71..242ff504 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.3 +version: 0.1.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues From 1461393dba756a382528291bd9d3b501c227f4b9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 04:09:23 +0900 Subject: [PATCH 564/663] Release pdfium_flutter v0.1.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/darwin/pdfium_flutter.podspec | 6 +++--- packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift | 4 ++-- packages/pdfium_flutter/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index 83a23f5f..bce21f97 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.5 + +- Updated PDFium to version 144.0.7520.0 (build 20251111-190355). + ## 0.1.4 - Updated PDFium to version 144.0.7520.0 (build 20251111-183119). diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec index b05c0f28..00f4c02e 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter.podspec +++ b/packages/pdfium_flutter/darwin/pdfium_flutter.podspec @@ -1,11 +1,11 @@ # PDFium xcframework configuration # https://github.com/espresso3389/pdfium-xcframework/releases -PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-183119/PDFium-chromium-7520-20251111-183119.xcframework.zip" -PDFIUM_HASH = "f7b1ac0b78aa24a909850b9347cb019d4feef7da242ad28a258a49f274dddd81" +PDFIUM_URL = "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-190355/PDFium-chromium-7520-20251111-190355.xcframework.zip" +PDFIUM_HASH = "bd2a9542f13c78b06698c7907936091ceee2713285234cbda2e16bc03c64810b" Pod::Spec.new do |s| s.name = 'pdfium_flutter' - s.version = '0.1.4' + s.version = '0.1.5' s.summary = 'Flutter FFI plugin for loading PDFium native libraries.' s.description = <<-DESC Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for iOS and macOS. diff --git a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift index 070905fe..dc59a5e2 100644 --- a/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift +++ b/packages/pdfium_flutter/darwin/pdfium_flutter/Package.swift @@ -23,8 +23,8 @@ let package = Package( ), .binaryTarget( name: "PDFium", - url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-183119/PDFium-chromium-7520-20251111-183119.xcframework.zip", - checksum: "f7b1ac0b78aa24a909850b9347cb019d4feef7da242ad28a258a49f274dddd81" + url: "https://github.com/espresso3389/pdfium-xcframework/releases/download/v144.0.7520.0-20251111-190355/PDFium-chromium-7520-20251111-190355.xcframework.zip", + checksum: "bd2a9542f13c78b06698c7907936091ceee2713285234cbda2e16bc03c64810b" ), ] ) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 242ff504..37366d91 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.4 +version: 0.1.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues From 11974d0edf0848d8c08ef0203fc889a710385883 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 15:13:49 +0900 Subject: [PATCH 565/663] PDFium chromium/7520 and updates on shared module caching mechanism --- .../lib/src/pdfium_downloader.dart | 44 ++++++++++------- packages/pdfium_dart/test/pdfium_test.dart | 2 +- .../pdfium_flutter/android/CMakeLists.txt | 2 +- packages/pdfium_flutter/linux/CMakeLists.txt | 2 +- .../pdfium_flutter/windows/CMakeLists.txt | 2 +- .../lib/src/native/pthread/pthread.dart | 2 + .../lib/src/pdfrx_initialize_dart.dart | 48 ++++++++++++++++--- 7 files changed, 75 insertions(+), 27 deletions(-) diff --git a/packages/pdfium_dart/lib/src/pdfium_downloader.dart b/packages/pdfium_dart/lib/src/pdfium_downloader.dart index d6ac9aae..8095dbf0 100644 --- a/packages/pdfium_dart/lib/src/pdfium_downloader.dart +++ b/packages/pdfium_dart/lib/src/pdfium_downloader.dart @@ -10,12 +10,15 @@ import 'pdfium_bindings.dart' as pdfium_bindings; /// The release of pdfium to download. /// /// The actual binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. -const currentPDFiumRelease = 'chromium%2F7506'; +const currentPDFiumRelease = 'chromium%2F7520'; /// Helper function to get PDFium instance. /// /// This function downloads the PDFium module if necessary. /// +/// - [cacheRootPath]: The root directory to cache the downloaded PDFium module. +/// - [pdfiumRelease]: The release of PDFium to download. Defaults to [currentPDFiumRelease]. +/// /// For macOS, the downloaded library is not codesigned. If you encounter issues loading the library, /// you may need to manually codesign it using the following command: /// @@ -23,21 +26,21 @@ const currentPDFiumRelease = 'chromium%2F7506'; /// codesign --force --sign - /// ``` Future getPdfium({ - String? tmpPath, + String? cacheRootPath, String? pdfiumRelease = currentPDFiumRelease, }) async { - tmpPath ??= path.join( + cacheRootPath ??= path.join( Directory.systemTemp.path, 'pdfium_dart', 'cache', pdfiumRelease, ); - if (!await File(tmpPath).exists()) { - await Directory(tmpPath).create(recursive: true); + if (!await File(cacheRootPath).exists()) { + await Directory(cacheRootPath).create(recursive: true); } final modulePath = await PDFiumDownloader.downloadAndGetPDFiumModulePath( - tmpPath, + cacheRootPath, pdfiumRelease: pdfiumRelease, ); @@ -61,15 +64,15 @@ class PDFiumDownloader { /// /// The binaries are downloaded from https://github.com/bblanchon/pdfium-binaries. static Future downloadAndGetPDFiumModulePath( - String tmpPath, { - String? pdfiumRelease = currentPDFiumRelease, + String cacheRootPath, { + String? pdfiumRelease, }) async { final pa = RegExp(r'"([^_]+)_([^_]+)"').firstMatch(Platform.version)!; final platform = pa[1]!; final arch = pa[2]!; if (platform == 'windows' && arch == 'x64') { return await downloadPDFium( - tmpPath, + cacheRootPath, 'win', arch, 'bin/pdfium.dll', @@ -78,7 +81,7 @@ class PDFiumDownloader { } if (platform == 'linux' && (arch == 'x64' || arch == 'arm64')) { return await downloadPDFium( - tmpPath, + cacheRootPath, platform, arch, 'lib/libpdfium.so', @@ -87,7 +90,7 @@ class PDFiumDownloader { } if (platform == 'macos') { return await downloadPDFium( - tmpPath, + cacheRootPath, 'mac', arch, 'lib/libpdfium.dylib', @@ -100,14 +103,21 @@ class PDFiumDownloader { /// Downloads the pdfium module for the given platform and architecture. static Future downloadPDFium( - String tmpRoot, + String cacheRootPath, String platform, String arch, String modulePath, String? pdfiumRelease, ) async { - final tmpDir = Directory('$tmpRoot/$platform-$arch'); - final targetPath = '${tmpDir.path}/$modulePath'; + pdfiumRelease ??= currentPDFiumRelease; + final pdfiumReleaseDirName = pdfiumRelease.replaceAll( + RegExp(r'[^A-Za-z0-9_]+'), + '_', + ); + final cacheDir = Directory( + '$cacheRootPath/$pdfiumReleaseDirName/$platform-$arch', + ); + final targetPath = '${cacheDir.path}/$modulePath'; if (await File(targetPath).exists()) return targetPath; final uri = @@ -116,13 +126,15 @@ class PDFiumDownloader { if (tgz.statusCode != 200) { throw Exception('Failed to download pdfium: $uri'); } + + await cacheDir.create(recursive: true); final archive = TarDecoder().decodeBytes( GZipDecoder().decodeBytes(tgz.bodyBytes), ); try { - await tmpDir.delete(recursive: true); + await cacheDir.delete(recursive: true); } catch (_) {} - await extractArchiveToDisk(archive, tmpDir.path); + await extractArchiveToDisk(archive, cacheDir.path); return targetPath; } } diff --git a/packages/pdfium_dart/test/pdfium_test.dart b/packages/pdfium_dart/test/pdfium_test.dart index 545c03db..e148634c 100644 --- a/packages/pdfium_dart/test/pdfium_test.dart +++ b/packages/pdfium_dart/test/pdfium_test.dart @@ -10,7 +10,7 @@ PDFium? _pdfium; void main() { setUp(() async { - _pdfium = await getPdfium(tmpPath: tmpRoot.path); + _pdfium = await getPdfium(cacheRootPath: tmpRoot.path); }); test('PDFium Initialization', () { diff --git a/packages/pdfium_flutter/android/CMakeLists.txt b/packages/pdfium_flutter/android/CMakeLists.txt index a9d6333b..6a17fa8f 100644 --- a/packages/pdfium_flutter/android/CMakeLists.txt +++ b/packages/pdfium_flutter/android/CMakeLists.txt @@ -8,7 +8,7 @@ project(${PROJECT_NAME} LANGUAGES CXX) # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7506) +set(PDFIUM_RELEASE chromium%2F7520) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfium_flutter/linux/CMakeLists.txt b/packages/pdfium_flutter/linux/CMakeLists.txt index 860d2b77..e5ef65f8 100644 --- a/packages/pdfium_flutter/linux/CMakeLists.txt +++ b/packages/pdfium_flutter/linux/CMakeLists.txt @@ -25,7 +25,7 @@ message( STATUS "Target CPU Name: ${CPU_NAME}" ) # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7506) +set(PDFIUM_RELEASE chromium%2F7520) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfium_flutter/windows/CMakeLists.txt b/packages/pdfium_flutter/windows/CMakeLists.txt index 6f0230bb..beade1ef 100644 --- a/packages/pdfium_flutter/windows/CMakeLists.txt +++ b/packages/pdfium_flutter/windows/CMakeLists.txt @@ -12,7 +12,7 @@ project(${PROJECT_NAME} LANGUAGES CXX) # versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(PDFIUM_RELEASE chromium%2F7506) +set(PDFIUM_RELEASE chromium%2F7520) set(PDFIUM_DIR ${CMAKE_BINARY_DIR}/pdfium) set(PDFIUM_RELEASE_DIR ${PDFIUM_DIR}/${PDFIUM_RELEASE}) diff --git a/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart b/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart index 0c01abe2..04dfc5c2 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/pthread.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'dart:ffi'; import 'dart:io'; diff --git a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart index 4d499b62..d9f576ad 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_initialize_dart.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:path/path.dart' as path; import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_dart; import 'pdfrx.dart'; @@ -19,24 +20,57 @@ bool _isInitialized = false; /// - Calls [PdfrxEntryFunctions.init] to initialize the PDFium library. /// /// For Flutter, you should call `pdfrxFlutterInitialize` instead of the function. -Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease = pdfium_dart.currentPDFiumRelease}) async { +Future pdfrxInitialize({String? tmpPath, String? pdfiumRelease}) async { if (_isInitialized) return; Pdfrx.loadAsset ??= (name) async { throw UnimplementedError('By default, Pdfrx.loadAsset is not implemented for Dart.'); }; - final tmpDir = Directory.systemTemp; + final tmpDir = tmpPath != null ? Directory(tmpPath) : _getPdfrxCacheDirectory(); Pdfrx.getCacheDirectory ??= () => tmpDir.path; - final pdfiumPath = Directory(Platform.environment['PDFIUM_PATH'] ?? '${tmpDir.path}/pdfrx.cache/pdfium'); - Pdfrx.pdfiumModulePath ??= pdfiumPath.path; - if (!File(Pdfrx.pdfiumModulePath!).existsSync()) { - pdfiumPath.createSync(recursive: true); - Pdfrx.pdfiumModulePath = await pdfium_dart.PDFiumDownloader.downloadAndGetPDFiumModulePath(pdfiumPath.path); + final pdfiumPath = Platform.environment['PDFIUM_PATH']; + if (pdfiumPath != null && await File(pdfiumPath).exists()) { + Pdfrx.pdfiumModulePath ??= pdfiumPath; + await PdfrxEntryFunctions.instance.init(); + _isInitialized = true; + return; + } else { + final moduleDir = Directory(tmpDir.path); + await moduleDir.create(recursive: true); + Pdfrx.pdfiumModulePath = await pdfium_dart.PDFiumDownloader.downloadAndGetPDFiumModulePath( + moduleDir.path, + pdfiumRelease: pdfiumRelease, + ); } await PdfrxEntryFunctions.instance.init(); _isInitialized = true; } + +/// Gets the Pdfrx cache directory. +/// +/// If the environment variable `PDFRX_CACHE_DIR` is set, it uses that directory. +/// Otherwise, it defaults to: +/// - On Windows: `%LOCALAPPDATA%\pdfrx` +/// - On other platforms: `~/.pdfrx` +Directory _getPdfrxCacheDirectory() { + final pdfrxCacheDir = Platform.environment['PDFRX_CACHE_DIR']; + if (pdfrxCacheDir != null && pdfrxCacheDir.isNotEmpty) { + return Directory(pdfrxCacheDir); + } + + final tmp = path.join(Directory.systemTemp.path, 'pdfrx.cache'); + final tmpDir = Directory(tmp); + if (tmpDir.existsSync()) { + return tmpDir; + } + + if (Platform.isWindows) { + return Directory(path.join(Platform.environment['LOCALAPPDATA']!, 'pdfrx')); + } else { + return Directory(path.join(Platform.environment['HOME']!, '.pdfrx')); + } +} From be6db798efd0c4ebb80a1d447c21ab614f66f250 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 15:21:09 +0900 Subject: [PATCH 566/663] Update pdfrx:remove_darwin_pdfium_modules to correctly handle pdfium_flutter --- .../bin/remove_darwin_pdfium_modules.dart | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart index e6944ef1..aaba46eb 100644 --- a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart +++ b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart @@ -29,7 +29,7 @@ Future main(List args) async { } final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); - final pdfrxPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfrx'); + final pdfrxPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfium_flutter'); print('Found: ${pdfrxPackage.name} ${pdfrxPackage.version}: ${pdfrxPackage.pubspecYamlPath}'); final pubspecPath = pdfrxPackage.pubspecYamlPath; @@ -62,19 +62,13 @@ Future main(List args) async { String _commentPlatforms(String yaml) { // Comment out iOS platform configuration yaml = yaml.replaceAllMapped( - RegExp( - r'^(\s*ios:\s*\n\s*pluginClass:\s*PdfrxPlugin\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', - multiLine: true, - ), + RegExp(r'^(\s*ios:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', ); // Comment out macOS platform configuration yaml = yaml.replaceAllMapped( - RegExp( - r'^(\s*macos:\s*\n\s*pluginClass:\s*PdfrxPlugin\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', - multiLine: true, - ), + RegExp(r'^(\s*macos:\s*\n\s*ffiPlugin:\s*true\n\s*sharedDarwinSource:\s*true)', multiLine: true), (match) => '# ${match[1]!.replaceAll('\n', '\n# ')}', ); @@ -84,20 +78,14 @@ String _commentPlatforms(String yaml) { String _uncommentPlatforms(String yaml) { // Uncomment iOS platform configuration yaml = yaml.replaceAllMapped( - RegExp( - r'^# (\s*ios:\s*\n)# (\s*pluginClass:\s*PdfrxPlugin\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', - multiLine: true, - ), - (match) => '${match[1]}${match[2]}${match[3]}${match[4]}', + RegExp(r'^# (\s*ios:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '${match[1]}${match[2]}${match[3]}', ); // Uncomment macOS platform configuration yaml = yaml.replaceAllMapped( - RegExp( - r'^# (\s*macos:\s*\n)# (\s*pluginClass:\s*PdfrxPlugin\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', - multiLine: true, - ), - (match) => '${match[1]}${match[2]}${match[3]}${match[4]}', + RegExp(r'^# (\s*macos:\s*\n)# (\s*ffiPlugin:\s*true\n)# (\s*sharedDarwinSource:\s*true)', multiLine: true), + (match) => '${match[1]}${match[2]}${match[3]}', ); return yaml; From 6ad4f94c762c400628f3c6569daa938bb34e4582 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 15:54:18 +0900 Subject: [PATCH 567/663] Release pdfium_dart v0.1.2, pdfium_flutter v0.1.6, and pdfrx_engine v0.3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_dart/CHANGELOG.md | 7 +++++++ packages/pdfium_dart/pubspec.yaml | 2 +- packages/pdfium_flutter/CHANGELOG.md | 5 +++++ packages/pdfium_flutter/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 7 +++++++ packages/pdfrx_engine/pubspec.yaml | 4 ++-- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/pdfium_dart/CHANGELOG.md b/packages/pdfium_dart/CHANGELOG.md index 1fd4a427..4f9e911c 100644 --- a/packages/pdfium_dart/CHANGELOG.md +++ b/packages/pdfium_dart/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.2 + +- Updated PDFium to version 144.0.7520.0. +- Improved cache directory structure to support multiple PDFium releases. +- Changed `tmpPath` parameter to `cacheRootPath` in `getPdfium()` for better clarity. +- Enhanced documentation for PDFium download and caching mechanism. + ## 0.1.1 - Add comments on PDFium class. diff --git a/packages/pdfium_dart/pubspec.yaml b/packages/pdfium_dart/pubspec.yaml index e7eccf23..5aa63f4b 100644 --- a/packages/pdfium_dart/pubspec.yaml +++ b/packages/pdfium_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_dart description: Dart FFI bindings for PDFium library. Provides low-level access to PDFium's C API from Dart. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_dart issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index bce21f97..1b456c06 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.6 + +- Updated PDFium to version 144.0.7520.0 for Android, Linux, and Windows platforms. +- Updated `pdfium_dart` dependency to ^0.1.2. + ## 0.1.5 - Updated PDFium to version 144.0.7520.0 (build 20251111-190355). diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 37366d91..1e2da6b0 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.5 +version: 0.1.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -11,7 +11,7 @@ environment: resolution: workspace dependencies: - pdfium_dart: ^0.1.0 + pdfium_dart: ^0.1.2 ffi: ^2.1.4 flutter: sdk: flutter diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index b35832ed..a375dd1f 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.3.2 + +- Updated to `pdfium_dart` 0.1.2. +- Improved cache directory management with support for `PDFRX_CACHE_DIR` environment variable. +- Better default cache locations: `~/.pdfrx` on Unix-like systems, `%LOCALAPPDATA%\pdfrx` on Windows. +- Fallback to system temp directory for backward compatibility. + ## 0.3.1 - Updated to pdfium_dart 0.1.1 diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index a5900a25..79de6be4 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.1 +version: 0.3.2 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -12,7 +12,7 @@ resolution: workspace # Some of the dependencies are intentionally not pinned to a specific version # to allow for more flexibility in resolving versions when used in larger projects. dependencies: - pdfium_dart: ^0.1.0 + pdfium_dart: ^0.1.2 collection: crypto: ^3.0.6 ffi: From f11859735d88da4a30780e26c5ddd8f9d113d0ad Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 16:27:49 +0900 Subject: [PATCH 568/663] WIP --- packages/pdfium_flutter/lib/src/pdfium_loader.dart | 3 ++- packages/pdfrx_engine/lib/src/native/pdfium.dart | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/pdfium_flutter/lib/src/pdfium_loader.dart b/packages/pdfium_flutter/lib/src/pdfium_loader.dart index ba7e26bf..79baf593 100644 --- a/packages/pdfium_flutter/lib/src/pdfium_loader.dart +++ b/packages/pdfium_flutter/lib/src/pdfium_loader.dart @@ -67,8 +67,9 @@ PdfiumNativeFunctionLookup? createPdfiumNativeFunctionLookup< if (nativeBindings != null) { ffi.Pointer lookup(String symbolName) { final ptr = nativeBindings[symbolName]; - if (ptr == null) + if (ptr == null) { throw Exception('Failed to find binding for $symbolName'); + } return ffi.Pointer.fromAddress(ptr); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index e00bd046..fde9f866 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -48,7 +48,9 @@ PdfrxNativeFunctionLookup? createPdfrxNativeFunctionLookup lookup(String symbolName) { final ptr = bindings[symbolName]; - if (ptr == null) throw Exception('Failed to find binding for $symbolName'); + if (ptr == null) { + throw Exception('Failed to find binding for $symbolName'); + } return ffi.Pointer.fromAddress(ptr); } From 1801b31d0ed864cab79a304f04d0cf30f83b6baf Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 12 Nov 2025 17:57:31 +0900 Subject: [PATCH 569/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 4 ++-- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index f970619c..fa98b84e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2426,7 +2426,7 @@ class _PdfViewerState extends State final magnifierEnabled = (magnifierParams.enabled ?? _selectionPointerDeviceKind == PointerDeviceKind.touch) && - (magnifierParams.shouldShowMagnifierForAnchor ?? _shouldBeShownForAnchor)( + (magnifierParams.shouldShowMagnifierForAnchor ?? _shouldShowMagnifierForAnchor)( textAnchor, _controller!, magnifierParams, @@ -2695,7 +2695,7 @@ class _PdfViewerState extends State return clampedPointerOffset; } - bool _shouldBeShownForAnchor( + bool _shouldShowMagnifierForAnchor( PdfTextSelectionAnchor textAnchor, PdfViewerController controller, PdfViewerSelectionMagnifierParams params, diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index dcb12f2f..2df68a12 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1055,6 +1055,14 @@ abstract class PdfViewerCoordinateConverter { } /// Parameters for the text selection magnifier. +/// +/// The text selection magnifier is used with text selection handles to help users select text precisely, +/// especially on touch devices. It shows a magnified view of the text around the selection handle +/// as the user drags the handle. +/// +/// Because of this, the magnifier is typically enabled only on touch devices by default and if you want to +/// show the magnifier on non-touch devices, you need to set [enabled] to true and also set +/// [PdfTextSelectionParams.enableSelectionHandles] to true to enable selection handles. @immutable class PdfViewerSelectionMagnifierParams { const PdfViewerSelectionMagnifierParams({ @@ -1075,6 +1083,8 @@ class PdfViewerSelectionMagnifierParams { /// Whether the magnifier is enabled. /// /// null to determine the behavior based on pointing device. + /// + /// To show magnifier on non-touch devices, set this and [PdfTextSelectionParams.enableSelectionHandles] to true. final bool? enabled; /// If the character size (in pt.) is smaller than this value, the magnifier will be shown. From 53e8f26f370013910e74177b8265a3f14c7c10c6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 09:52:39 +0900 Subject: [PATCH 570/663] WIP: use shortened syntax for Allocator.allocate --- ...operability-with-other-PDFium-Libraries.md | 4 +- .../lib/src/native/pdfrx_pdfium.dart | 46 +++++++++---------- .../lib/src/native/pthread/file_access.dart | 4 +- .../lib/src/native/pthread/file_write.dart | 4 +- .../lib/src/native/win32/file_access.dart | 4 +- .../lib/src/native/win32/file_write.dart | 4 +- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md index ecec5d9c..b46fb88b 100644 --- a/doc/Interoperability-with-other-PDFium-Libraries.md +++ b/doc/Interoperability-with-other-PDFium-Libraries.md @@ -152,7 +152,7 @@ class LowLevelPdfiumAccess { return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { return using((arena) { - final dataPtr = arena.allocate(pdfData.length); + final dataPtr = arena(pdfData.length); dataPtr.asTypedList(pdfData.length).setAll(0, pdfData); // Load document using low-level API @@ -176,7 +176,7 @@ class LowLevelPdfiumAccess { // Get document metadata tags for (final tag in ['Title', 'Author', 'Subject', 'Keywords', 'Creator']) { - final buffer = arena.allocate(256); + final buffer = arena(256); final tagPtr = tag.toNativeUtf8(allocator: arena); final len = pdfium.FPDF_GetMetaText( doc, diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index f248e91e..78a1943a 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:ffi/ffi.dart'; +import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; @@ -31,7 +32,6 @@ import '../utils/shuffle_in_place.dart'; import 'native_utils.dart'; import 'pdf_file_cache.dart'; import 'pdfium.dart'; -import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import 'pdfium_file_access.dart'; import 'worker.dart'; @@ -49,13 +49,13 @@ Future _init() async { _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); (await BackgroundWorker.instance).computeWithArena((arena, params) { - final config = arena.allocate(sizeOf()); + final config = arena(); config.ref.version = 2; final fontPaths = [?params.appLocalFontPath?.path, ...params.fontPaths]; if (fontPaths.isNotEmpty) { // NOTE: m_pUserFontPaths must not be freed until FPDF_DestroyLibrary is called; on pdfrx, it's never freed. - final fontPathArray = malloc>(sizeOf>() * (fontPaths.length + 1)); + final fontPathArray = malloc>(fontPaths.length + 1); for (var i = 0; i < fontPaths.length; i++) { fontPathArray[i] = fontPaths[i] .toNativeUtf8() @@ -311,7 +311,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { // If the file size is smaller than the specified size, load the file on memory if (fileSize <= maxSizeToCacheOnMemory) { - final buffer = malloc.allocate(fileSize); + final buffer = malloc(fileSize); try { await read(buffer.asTypedList(fileSize), 0, fileSize); return _openByFunc( @@ -458,7 +458,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { (arena, params) { final document = pdfium.FPDF_CreateNewDocument(); final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); - final newPages = arena.allocate(sizeOf>()); + final newPages = arena(); newPages.value = newPage; final imageObj = pdfium.FPDFPageObj_NewImageObj(document); @@ -591,7 +591,7 @@ class _PdfDocumentPdfium extends PdfDocument { final permissions = pdfium.FPDF_GetDocPermissions(doc); final securityHandlerRevision = pdfium.FPDF_GetSecurityHandlerRevision(doc); - formInfo = calloc.allocate(sizeOf()); + formInfo = calloc(); formInfo.ref.version = 1; formHandle = pdfium.FPDFDOC_InitFormFillEnvironment(doc, formInfo); return ( @@ -700,7 +700,7 @@ class _PdfDocumentPdfium extends PdfDocument { for (var i = params.pagesCountLoadedSoFar; i < end; i++) { final page = pdfium.FPDF_LoadPage(doc, i); try { - final rect = arena.allocate(sizeOf()); + final rect = arena(); pdfium.FPDF_GetPageBoundingBox(page, rect); pages.add(( width: pdfium.FPDF_GetPageWidthF(page), @@ -872,7 +872,7 @@ class _PdfDocumentPdfium extends PdfDocument { final nativeWriteCallable = _NativeFileWriteCallable.isolateLocal(write, exceptionalReturn: 0); try { final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); - final fw = arena.allocate(sizeOf()); + final fw = arena(); fw.ref.version = 1; fw.ref.WriteBlock = nativeWriteCallable.nativeFunction; final int flags; @@ -966,7 +966,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { @override void move(int fromIndex, int toIndex, int count) { using((arena) { - final pageIndices = arena.allocate(sizeOf() * count); + final pageIndices = arena(count); for (var i = 0; i < count; i++) { pageIndices[i] = fromIndex + i; } @@ -984,7 +984,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { @override void duplicate(int fromIndex, int toIndex, int count) { using((arena) { - final pageIndices = arena.allocate(sizeOf() * count); + final pageIndices = arena(count); for (var i = 0; i < count; i++) { pageIndices[i] = fromIndex + i; } @@ -997,7 +997,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { final page = items[negativeItemIndex]!; final src = pdfium_bindings.FPDF_DOCUMENT.fromAddress(page.document); using((arena) { - final pageIndices = arena.allocate(sizeOf()); + final pageIndices = arena(); pageIndices.value = page.pageNumber - 1; pdfium.FPDF_ImportPagesByIndex(document, src, pageIndices, 1, index); }); @@ -1067,9 +1067,9 @@ class _PdfPagePdfium extends PdfPage { const rgbaSize = 4; Pointer buffer = nullptr; try { - buffer = malloc.allocate(width * height * rgbaSize); + buffer = malloc(width * height * rgbaSize); final isSucceeded = await using((arena) async { - final cancelFlag = arena.allocate(sizeOf()); + final cancelFlag = arena(); ct?.attach(cancelFlag); if (cancelFlag.value || document.isDisposed) return false; @@ -1190,7 +1190,7 @@ class _PdfPagePdfium extends PdfPage { if (document.isDisposed || !isLoaded) return null; return await (await BackgroundWorker.instance).computeWithArena((arena, params) { final doubleSize = sizeOf(); - final rectBuffer = arena.allocate(4 * sizeOf()); + final rectBuffer = arena(4); final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1); final textPage = pdfium.FPDFText_LoadPage(page); @@ -1248,7 +1248,7 @@ class _PdfPagePdfium extends PdfPage { if (linkPage == nullptr) return []; final doubleSize = sizeOf(); - final rectBuffer = arena.allocate(4 * doubleSize); + final rectBuffer = arena(4); return List.generate(pdfium.FPDFLink_CountWebLinks(linkPage), (index) { final rects = List.generate(pdfium.FPDFLink_CountRects(linkPage, index), (rectIndex) { pdfium.FPDFLink_GetRect( @@ -1273,26 +1273,26 @@ class _PdfPagePdfium extends PdfPage { static String _getLinkUrl(pdfium_bindings.FPDF_PAGELINK linkPage, int linkIndex, Arena arena) { final urlLength = pdfium.FPDFLink_GetURL(linkPage, linkIndex, nullptr, 0); - final urlBuffer = arena.allocate(urlLength * sizeOf()); + final urlBuffer = arena(urlLength); pdfium.FPDFLink_GetURL(linkPage, linkIndex, urlBuffer, urlLength); return urlBuffer.cast().toDartString(); } static String? _getAnnotationContent(pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { - final contentLength = pdfium.FPDFAnnot_GetStringValue( + final contentLengthInBytes = pdfium.FPDFAnnot_GetStringValue( annot, 'Contents'.toNativeUtf8(allocator: arena).cast(), nullptr, 0, ); - if (contentLength > 0) { - final contentBuffer = arena.allocate(contentLength); + if (contentLengthInBytes > 0) { + final contentBuffer = arena.allocate(contentLengthInBytes); // NOTE: size is in bytes pdfium.FPDFAnnot_GetStringValue( annot, 'Contents'.toNativeUtf8(allocator: arena).cast(), contentBuffer, - contentLength, + contentLengthInBytes, ); return contentBuffer.cast().toDartString(); } @@ -1307,7 +1307,7 @@ class _PdfPagePdfium extends PdfPage { final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); try { final count = pdfium.FPDFPage_GetAnnotCount(page); - final rectf = arena.allocate(sizeOf()); + final rectf = arena(); final links = []; for (var i = 0; i < count; i++) { final annot = pdfium.FPDFPage_GetAnnot(page, i); @@ -1444,8 +1444,8 @@ extension _PointerExt on Pointer { PdfDest? _pdfDestFromDest(pdfium_bindings.FPDF_DEST dest, pdfium_bindings.FPDF_DOCUMENT document, Arena arena) { if (dest == nullptr) return null; - final pul = arena.allocate(sizeOf()); - final values = arena.allocate(sizeOf() * 4); + final pul = arena(); + final values = arena(4); final pageIndex = pdfium.FPDFDest_GetDestPageIndex(document, dest); final type = pdfium.FPDFDest_GetView(dest, pul, values); if (type != 0) { diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart index f6200c04..1e22345b 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart @@ -3,8 +3,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; - import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; + import '../pdfium_file_access.dart'; import '../worker.dart'; import 'pthread.dart'; @@ -12,7 +12,7 @@ import 'pthread.dart'; class PdfiumFileAccessHelperPthread implements PdfiumFileAccessHelper { @override Future create(int fileSize, int readBlock) async { - final buffer = malloc.allocate( + final buffer = malloc( sizeOf() + sizeOfPthreadMutex + sizeOfPthreadCond + sizeOf() * 2, ); final fa = buffer.cast(); diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart index 2d91f73a..62d3fb54 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart @@ -3,8 +3,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; - import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; + import '../pdfium_file_write.dart'; import '../worker.dart'; import 'pthread.dart'; @@ -12,7 +12,7 @@ import 'pthread.dart'; class PdfiumFileWriteHelperPthread implements PdfiumFileWriteHelper { @override Future create(int writeBlock) async { - final buffer = malloc.allocate( + final buffer = malloc( sizeOf() + sizeOfPthreadMutex + sizeOfPthreadCond + sizeOf() * 2, ); final fw = buffer.cast(); diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart index 1e00315c..6a9b1ff5 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart @@ -3,8 +3,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; - import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; + import '../pdfium_file_access.dart'; import '../worker.dart'; import 'kernel32.dart'; @@ -12,7 +12,7 @@ import 'kernel32.dart'; class PdfiumFileAccessHelperWin32 implements PdfiumFileAccessHelper { @override Future create(int fileSize, int readBlock) async { - final buffer = malloc.allocate( + final buffer = malloc( sizeOf() + sizeOfCriticalSection + sizeOfConditionVariable + diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart index 41634190..2e50f49d 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart @@ -3,8 +3,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; - import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; + import '../pdfium_file_write.dart'; import '../worker.dart'; import 'kernel32.dart'; @@ -12,7 +12,7 @@ import 'kernel32.dart'; class PdfiumFileWriteHelperWin32 implements PdfiumFileWriteHelper { @override Future create(int writeBlock) async { - final buffer = malloc.allocate( + final buffer = calloc( sizeOf() + sizeOfCriticalSection + sizeOfConditionVariable + sizeOf() * 2, ); final fw = buffer.cast(); From cccbba2f0936af217eff833c034e2e1bff19761f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 09:53:05 +0900 Subject: [PATCH 571/663] WIP: update example viewer code --- packages/pdfrx/example/viewer/lib/main.dart | 36 +++++---------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 72ea2baa..418dd2a5 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -50,9 +50,12 @@ class _MainPageState extends State with WidgetsBindingObserver, Single final _markers = >{}; List? textSelections; - final bool _isDraggingHandle = false; // True while actively dragging, false on release + bool _isDraggingHandle = false; // True while actively dragging, false on release // Magnifier animation controller - late final AnimationController _magnifierAnimController; + late final AnimationController _magnifierAnimController = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + ); void _update() { if (mounted) { @@ -63,7 +66,6 @@ class _MainPageState extends State with WidgetsBindingObserver, Single @override void initState() { super.initState(); - _magnifierAnimController = AnimationController(duration: const Duration(milliseconds: 250), vsync: this); WidgetsBinding.instance.addObserver(this); openInitialFile(); } @@ -309,12 +311,11 @@ class _MainPageState extends State with WidgetsBindingObserver, Single pageAnchor: isHorizontalLayout ? PdfPageAnchor.left : PdfPageAnchor.top, pageAnchorEnd: isHorizontalLayout ? PdfPageAnchor.right : PdfPageAnchor.bottom, textSelectionParams: PdfTextSelectionParams( - enabled: true, onTextSelectionChange: (textSelection) async { textSelections = await textSelection.getSelectedTextRanges(); }, - /*magnifier: PdfViewerSelectionMagnifierParams( - shouldBeShownForAnchor: (textAnchor, controller, params) => true, + magnifier: PdfViewerSelectionMagnifierParams( + shouldShowMagnifierForAnchor: (textAnchor, controller, params) => true, getMagnifierRectForAnchor: (textAnchor, params, clampedPointerPosition) { final c = textAnchor.page.charRects[textAnchor.index]; final baseUnit = switch (textAnchor.direction) { @@ -355,7 +356,6 @@ class _MainPageState extends State with WidgetsBindingObserver, Single // Start animation when magnifier first appears and capture initial pointer position if (_magnifierAnimController.status == AnimationStatus.dismissed) { - _magnifierAnimationStartPositionViewport = pointerPosition; _magnifierAnimController.forward(); } @@ -449,7 +449,7 @@ class _MainPageState extends State with WidgetsBindingObserver, Single _magnifierAnimController.reverse().then((_) { _magnifierAnimController.reset(); }); - }, */ + }, ), keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, @@ -567,7 +567,6 @@ class _MainPageState extends State with WidgetsBindingObserver, Single if (count > 0) { await PdfrxEntryFunctions.instance.reloadFonts(); await controller.documentRef.resolveListenable().load(forceReload: true); - //controller.forceRepaintAllPageImages(); } }); } @@ -805,22 +804,3 @@ class _MainPageState extends State with WidgetsBindingObserver, Single return parts.isEmpty ? path : parts.last; } } - -/// Create a [CustomPainter] from a paint function. -class _CustomPainter extends CustomPainter { - /// Create a [CustomPainter] from a paint function. - const _CustomPainter.fromFunctions(this.paintFunction, {this.hitTestFunction}); - final void Function(Canvas canvas, Size size) paintFunction; - final bool Function(Offset position)? hitTestFunction; - @override - void paint(Canvas canvas, Size size) => paintFunction(canvas, size); - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => true; - - @override - bool hitTest(Offset position) { - if (hitTestFunction == null) return false; - return hitTestFunction!(position); - } -} From f12ffd3e8549965005f983ae9896d4fd5f6994d5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 10:51:10 +0900 Subject: [PATCH 572/663] Add PdfViewerController.localToDocument/documentToLocal --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index fa98b84e..c877687c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -271,7 +271,11 @@ class _PdfViewerState extends State bool _isInteractionGoingOn = false; BuildContext? _contextForFocusNode; + + /// last pointer location in viewer local coordinates Offset _pointerOffset = Offset.zero; + + /// last pointer device kind to differentiate between mouse and touch PointerDeviceKind? _pointerDeviceKind; // boundary margins adjusted to center content that's smaller than @@ -1995,16 +1999,26 @@ class _PdfViewerState extends State /// Converts the global position to the local position in the PDF document structure. Offset? _globalToDocument(Offset global) { - final ratio = 1 / _currentZoom; - return _globalToLocal( - global, - )?.translate(-_txController.value.xZoomed, -_txController.value.yZoomed).scale(ratio, ratio); + final local = _globalToLocal(global); + if (local == null) return null; + return _localToDocument(local); } /// Converts the local position in the PDF document structure to the global position. - Offset? _documentToGlobal(Offset document) => _localToGlobal( - document.scale(_currentZoom, _currentZoom).translate(_txController.value.xZoomed, _txController.value.yZoomed), - ); + Offset? _documentToGlobal(Offset document) => _localToGlobal((_documentToLocal(document))); + + /// Converts the local position in the widget to the local position in the PDF document structure. + Offset _localToDocument(Offset local) { + final ratio = 1 / _currentZoom; + return local.translate(-_txController.value.xZoomed, -_txController.value.yZoomed).scale(ratio, ratio); + } + + /// Converts the local position in the PDF document structure to the local position in the widget. + Offset _documentToLocal(Offset document) { + return document + .scale(_currentZoom, _currentZoom) + .translate(_txController.value.xZoomed, _txController.value.yZoomed); + } FocusNode? _getFocusNode() { return _contextForFocusNode != null ? Focus.of(_contextForFocusNode!) : null; @@ -4040,6 +4054,12 @@ class PdfViewerController extends ValueListenable { /// Converts the local position in the PDF document structure to the global position. Offset? documentToGlobal(Offset document) => _state._documentToGlobal(document); + /// Converts local coordinates to document coordinates. + Offset localToDocument(Offset local) => _state._localToDocument(local); + + /// Converts document coordinates to local coordinates. + Offset documentToLocal(Offset document) => _state._documentToLocal(document); + /// Converts document coordinates to local coordinates. PdfViewerCoordinateConverter get doc2local => _state; From 47bfac19fa310609e6851a37f617794cbf93dbd9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 10:52:42 +0900 Subject: [PATCH 573/663] FIXED: #532 --- packages/pdfrx/example/viewer/lib/main.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 418dd2a5..559fdbe5 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -324,9 +324,7 @@ class _MainPageState extends State with WidgetsBindingObserver, Single }; // Convert clamped pointer position from viewport to document coordinates - final pointerInDocument = - controller.globalToDocument(clampedPointerPosition) ?? textAnchor.anchorPoint; - + final pointerInDocument = controller.localToDocument(clampedPointerPosition); return Rect.fromLTRB( pointerInDocument.dx - baseUnit * 2.5, textAnchor.rect.top - baseUnit * 0.5, From 607088002b78863dbf5b670efaf751ed15f4f840 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 10:57:20 +0900 Subject: [PATCH 574/663] Anyway comment-out the magnifier control example --- packages/pdfrx/example/viewer/lib/main.dart | 236 ++++++++++---------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 559fdbe5..5f3f8420 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -50,7 +50,7 @@ class _MainPageState extends State with WidgetsBindingObserver, Single final _markers = >{}; List? textSelections; - bool _isDraggingHandle = false; // True while actively dragging, false on release + bool _isDraggingHandle = false; // Magnifier animation controller late final AnimationController _magnifierAnimController = AnimationController( duration: const Duration(milliseconds: 250), @@ -314,123 +314,123 @@ class _MainPageState extends State with WidgetsBindingObserver, Single onTextSelectionChange: (textSelection) async { textSelections = await textSelection.getSelectedTextRanges(); }, - magnifier: PdfViewerSelectionMagnifierParams( - shouldShowMagnifierForAnchor: (textAnchor, controller, params) => true, - getMagnifierRectForAnchor: (textAnchor, params, clampedPointerPosition) { - final c = textAnchor.page.charRects[textAnchor.index]; - final baseUnit = switch (textAnchor.direction) { - PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, - PdfTextDirection.vrtl => c.width, - }; - - // Convert clamped pointer position from viewport to document coordinates - final pointerInDocument = controller.localToDocument(clampedPointerPosition); - return Rect.fromLTRB( - pointerInDocument.dx - baseUnit * 2.5, - textAnchor.rect.top - baseUnit * 0.5, - pointerInDocument.dx + baseUnit * 2.5, - textAnchor.rect.bottom + baseUnit * 0.5, - ); - }, - builder: - ( - context, - textAnchor, - params, - magnifierContent, - magnifierContentSize, - pointerPosition, - magnifierPosition, - ) { - // calculate the scale to fit the magnifier content fit into 80x80 box - final contentScale = - 80 / math.min(magnifierContentSize.width, magnifierContentSize.height); - - // Calculate the actual magnifier widget size (with border radius padding) - final magnifierWidgetSize = Size( - magnifierContentSize.width * contentScale, - magnifierContentSize.height * contentScale, - ); - - // Start animation when magnifier first appears and capture initial pointer position - if (_magnifierAnimController.status == AnimationStatus.dismissed) { - _magnifierAnimController.forward(); - } - - final centeredStartOffset = - pointerPosition - - Offset(magnifierWidgetSize.width / 2, magnifierWidgetSize.height / 2); - final delta = centeredStartOffset - magnifierPosition; - - return AnimatedBuilder( - animation: _magnifierAnimController, - builder: (context, child) { - final currentProgress = _magnifierAnimController.value; - return Transform.translate( - offset: delta * (1 - currentProgress), - child: Transform.scale( - scale: currentProgress, - alignment: Alignment.center, - child: child!, - ), - ); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(25), - boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(25), - child: SizedBox( - width: magnifierContentSize.width * contentScale, - height: magnifierContentSize.height * contentScale, - child: magnifierContent, - ), - ), - ), - ); - }, - calcPosition: - ( - widgetSize, - anchorLocalRect, - handleLocalRect, - textAnchor, - pointerPosition, { - margin = 10.0, - marginOnTop, - marginOnBottom, - }) { - if (widgetSize == null) return null; - - final viewSize = controller.viewSize; - - // Center magnifier horizontally on pointer for smooth tracking - var left = pointerPosition.dx - widgetSize.width / 2; - - // Clamp to viewport bounds - if (left < margin) { - left = margin; - } else if (left + widgetSize.width + margin > viewSize.width) { - left = viewSize.width - widgetSize.width - margin; - } - - var top = anchorLocalRect.top - widgetSize.height - (marginOnTop ?? margin); - - // If too close to top, place below instead - if (top < margin) { - top = anchorLocalRect.bottom + (marginOnBottom ?? margin); - } - - return Offset(left, top); - }, - shouldShowMagnifier: () => - _isDraggingHandle || - _magnifierAnimController.status == AnimationStatus.reverse || - _magnifierAnimController.status == AnimationStatus.forward, - animationDuration: Duration.zero, - ), + // magnifier: PdfViewerSelectionMagnifierParams( + // shouldShowMagnifierForAnchor: (textAnchor, controller, params) => true, + // getMagnifierRectForAnchor: (textAnchor, params, clampedPointerPosition) { + // final c = textAnchor.page.charRects[textAnchor.index]; + // final baseUnit = switch (textAnchor.direction) { + // PdfTextDirection.ltr || PdfTextDirection.rtl || PdfTextDirection.unknown => c.height, + // PdfTextDirection.vrtl => c.width, + // }; + + // // Convert clamped pointer position from viewport to document coordinates + // final pointerInDocument = controller.localToDocument(clampedPointerPosition); + // return Rect.fromLTRB( + // pointerInDocument.dx - baseUnit * 2.5, + // textAnchor.rect.top - baseUnit * 0.5, + // pointerInDocument.dx + baseUnit * 2.5, + // textAnchor.rect.bottom + baseUnit * 0.5, + // ); + // }, + // builder: + // ( + // context, + // textAnchor, + // params, + // magnifierContent, + // magnifierContentSize, + // pointerPosition, + // magnifierPosition, + // ) { + // // calculate the scale to fit the magnifier content fit into 80x80 box + // final contentScale = + // 80 / math.min(magnifierContentSize.width, magnifierContentSize.height); + + // // Calculate the actual magnifier widget size (with border radius padding) + // final magnifierWidgetSize = Size( + // magnifierContentSize.width * contentScale, + // magnifierContentSize.height * contentScale, + // ); + + // // Start animation when magnifier first appears and capture initial pointer position + // if (_magnifierAnimController.status == AnimationStatus.dismissed) { + // _magnifierAnimController.forward(); + // } + + // final centeredStartOffset = + // pointerPosition - + // Offset(magnifierWidgetSize.width / 2, magnifierWidgetSize.height / 2); + // final delta = centeredStartOffset - magnifierPosition; + + // return AnimatedBuilder( + // animation: _magnifierAnimController, + // builder: (context, child) { + // final currentProgress = _magnifierAnimController.value; + // return Transform.translate( + // offset: delta * (1 - currentProgress), + // child: Transform.scale( + // scale: currentProgress, + // alignment: Alignment.center, + // child: child!, + // ), + // ); + // }, + // child: Container( + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(25), + // boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8, spreadRadius: 2)], + // ), + // child: ClipRRect( + // borderRadius: BorderRadius.circular(25), + // child: SizedBox( + // width: magnifierContentSize.width * contentScale, + // height: magnifierContentSize.height * contentScale, + // child: magnifierContent, + // ), + // ), + // ), + // ); + // }, + // calcPosition: + // ( + // widgetSize, + // anchorLocalRect, + // handleLocalRect, + // textAnchor, + // pointerPosition, { + // margin = 10.0, + // marginOnTop, + // marginOnBottom, + // }) { + // if (widgetSize == null) return null; + + // final viewSize = controller.viewSize; + + // // Center magnifier horizontally on pointer for smooth tracking + // var left = pointerPosition.dx - widgetSize.width / 2; + + // // Clamp to viewport bounds + // if (left < margin) { + // left = margin; + // } else if (left + widgetSize.width + margin > viewSize.width) { + // left = viewSize.width - widgetSize.width - margin; + // } + + // var top = anchorLocalRect.top - widgetSize.height - (marginOnTop ?? margin); + + // // If too close to top, place below instead + // if (top < margin) { + // top = anchorLocalRect.bottom + (marginOnBottom ?? margin); + // } + + // return Offset(left, top); + // }, + // shouldShowMagnifier: () => + // _isDraggingHandle || + // _magnifierAnimController.status == AnimationStatus.reverse || + // _magnifierAnimController.status == AnimationStatus.forward, + // animationDuration: Duration.zero, + // ), onSelectionHandlePanStart: (anchor) { setState(() { _isDraggingHandle = true; From 52eacb338ead1c2618c0ae5ce62387ddd80217b2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 11:10:06 +0900 Subject: [PATCH 575/663] Release pdfrx_engine v0.3.3 and pdfrx v2.2.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 6 ++++++ packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 47236a38..e097a2ab 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.11 + +- FIXED: Magnifier content location calculated incorrectly ([#532](https://github.com/espresso3389/pdfrx/issues/532)) +- NEW: Added `PdfViewerController.localToDocument()` and `PdfViewerController.documentToLocal()` methods for coordinate conversion +- Updated to pdfrx_engine 0.3.3 + # 2.2.10 - Updated to pdfrx_engine 0.3.1 and pdfium_flutter 0.1.1 diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8bc4193c..87ccb8ee 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.10 +version: 2.2.11 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.1 + pdfrx_engine: ^0.3.3 pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index a375dd1f..307e07ae 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.3 + +- Code refactoring and maintenance updates. +- Use shortened syntax for `Allocator.allocate`. + ## 0.3.2 - Updated to `pdfium_dart` 0.1.2. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 79de6be4..fc6b3533 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.2 +version: 0.3.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 40ed41b2ae77027af161555202ec10ebeb879add Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 11:15:00 +0900 Subject: [PATCH 576/663] Update RELEASING.md to include all packages in monorepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added release procedures for: - pdfium_dart - pdfium_flutter - pdfrx_coregraphics Also added dependency order section to clarify the sequence for releasing multiple packages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- RELEASING.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index eb0451e2..c6292457 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,6 +1,15 @@ -# Releasing pdfrx and pdfrx_engine +# Releasing Packages -This guide covers the full release checklist for both packages in the monorepo. Follow the steps that apply to the package you are releasing. +This guide covers the full release checklist for all packages in the monorepo. Follow the steps that apply to the package you are releasing. + +## Package Overview + +The monorepo contains five packages: +- **pdfium_dart** - Low-level Dart FFI bindings for PDFium +- **pdfium_flutter** - Flutter plugin for loading PDFium native libraries +- **pdfrx_engine** - Platform-agnostic PDF rendering API +- **pdfrx** - Cross-platform PDF viewer plugin for Flutter +- **pdfrx_coregraphics** - CoreGraphics-backed renderer for iOS/macOS (experimental) ## pdfrx_engine Releases @@ -51,7 +60,67 @@ This guide covers the full release checklist for both packages in the monorepo. - Focus on release notes and what changed; link to the version-specific changelog entry. +## pdfium_dart Releases + +1. Update the version in `packages/pdfium_dart/pubspec.yaml`. + - Follow semantic versioning based on the scope of changes. +2. Update `packages/pdfium_dart/CHANGELOG.md` with user-facing changes. + - Include PDFium version updates if applicable. + - Document any changes to the FFI bindings or `getPdfium()` functionality. +3. Update `packages/pdfium_dart/README.md` if necessary. +4. Run tests: `dart test` inside `packages/pdfium_dart/`. +5. Run `pana` inside `packages/pdfium_dart/` to validate the package. +6. Commit changes with `Release pdfium_dart vX.Y.Z`. +7. Tag the commit with `git tag pdfium_dart-vX.Y.Z`. +8. Push commits and tags. +9. Publish with `dart pub publish` inside `packages/pdfium_dart/`. + +## pdfium_flutter Releases + +1. Update the version in `packages/pdfium_flutter/pubspec.yaml`. + - If `pdfium_dart` was updated, update the dependency version here as well. +2. Update `packages/pdfium_flutter/CHANGELOG.md` with user-facing changes. + - Include PDFium binary version updates if applicable. + - Document platform-specific changes (iOS, Android, Windows, macOS, Linux). +3. Update `packages/pdfium_flutter/README.md` if necessary. +4. Update platform-specific build configurations if PDFium binaries changed: + - `darwin/pdfium_flutter.podspec` for iOS/macOS (CocoaPods) + - `darwin/pdfium_flutter/Package.swift` for Swift Package Manager + - `android/CMakeLists.txt` for Android + - `windows/CMakeLists.txt` for Windows + - `linux/CMakeLists.txt` for Linux +5. Run tests: `flutter test` inside `packages/pdfium_flutter/`. +6. Run `pana` inside `packages/pdfium_flutter/` to validate the package. +7. Commit changes with `Release pdfium_flutter vX.Y.Z`. +8. Tag the commit with `git tag pdfium_flutter-vX.Y.Z`. +9. Push commits and tags. +10. Publish with `flutter pub publish` inside `packages/pdfium_flutter/`. + +## pdfrx_coregraphics Releases + +1. Update the version in `packages/pdfrx_coregraphics/pubspec.yaml`. +2. Update `packages/pdfrx_coregraphics/CHANGELOG.md` with user-facing changes. +3. Update `packages/pdfrx_coregraphics/README.md` if necessary. +4. Run tests on macOS/iOS devices if possible. +5. Run `pana` inside `packages/pdfrx_coregraphics/` to validate the package. +6. Commit changes with `Release pdfrx_coregraphics vX.Y.Z`. +7. Tag the commit with `git tag pdfrx_coregraphics-vX.Y.Z`. +8. Push commits and tags. +9. Publish with `flutter pub publish` inside `packages/pdfrx_coregraphics/`. + +## Dependency Order + +When releasing multiple packages, follow this order to respect dependencies: + +1. **pdfium_dart** (no dependencies on other packages in monorepo) +2. **pdfium_flutter** (depends on pdfium_dart) +3. **pdfrx_engine** (depends on pdfium_dart) +4. **pdfrx** (depends on pdfrx_engine and pdfium_flutter) +5. **pdfrx_coregraphics** (depends on pdfrx_engine, independent of pdfium_flutter) + ## General Notes - Keep `CHANGELOG.md` entries user-focused and concise. - Coordinate with the repository owner if any release blockers appear. +- When releasing multiple packages together, create a single commit with all version changes. +- Tag format: `-vX.Y.Z` (e.g., `pdfium_dart-v0.1.3`, `pdfrx-v2.2.11`). From 2a7b9510b921bda6482d7be248cd421e8e1b0a3e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 20:36:28 +0900 Subject: [PATCH 577/663] FIXED: #535 --- packages/pdfrx/pubspec.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 87ccb8ee..8e371d8e 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -45,6 +45,11 @@ executables: flutter: plugin: platforms: + android: + ios: + macos: + windows: + linux: web: assets: From a6b48e824fc44f01f452d25aaf52e73b4e351fed Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 20:43:00 +0900 Subject: [PATCH 578/663] FIXED: #535 --- packages/pdfrx/pubspec.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8e371d8e..649fce37 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -43,15 +43,5 @@ executables: remove_darwin_pdfium_modules: flutter: - plugin: - platforms: - android: - ios: - macos: - windows: - linux: - web: - assets: - assets/ - From 16dfbc4491000545d59869ab174a37ca8e5f0e75 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 21:00:01 +0900 Subject: [PATCH 579/663] Release pdfrx v2.2.12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9f2edcab..0433a0f3 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.9 + pdfrx: ^2.2.12 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index e097a2ab..1bd03496 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.12 + +- FIXED: Package incorrectly showing as web-only on pub.dev due to incorrect Flutter plugin platform declarations ([#535](https://github.com/espresso3389/pdfrx/issues/535)) + # 2.2.11 - FIXED: Magnifier content location calculated incorrectly ([#532](https://github.com/espresso3389/pdfrx/issues/532)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index c5745abb..cca2724e 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.9 + pdfrx: ^2.2.12 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 649fce37..8b848656 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.11 +version: 2.2.12 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From edaea667e5ca949ed066144ef8378008a225dcfd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 23:07:19 +0900 Subject: [PATCH 580/663] Feature Request: Add loadingBuilder and errorBuilder to PdfDocumentViewBuilder #536 --- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 62e16a91..26c815a0 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -38,11 +38,19 @@ import '../../pdfrx.dart'; /// ), /// ``` class PdfDocumentViewBuilder extends StatefulWidget { - const PdfDocumentViewBuilder({required this.documentRef, required this.builder, super.key}); + const PdfDocumentViewBuilder({ + required this.documentRef, + required this.builder, + this.loadingBuilder, + this.errorBuilder, + super.key, + }); PdfDocumentViewBuilder.asset( String assetName, { required this.builder, + this.loadingBuilder, + this.errorBuilder, super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, @@ -59,11 +67,12 @@ class PdfDocumentViewBuilder extends StatefulWidget { PdfDocumentViewBuilder.file( String filePath, { required this.builder, + this.loadingBuilder, + this.errorBuilder, super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, bool useProgressiveLoading = false, - bool autoDispose = true, }) : documentRef = PdfDocumentRefFile( filePath, @@ -76,6 +85,8 @@ class PdfDocumentViewBuilder extends StatefulWidget { PdfDocumentViewBuilder.uri( Uri uri, { required this.builder, + this.loadingBuilder, + this.errorBuilder, super.key, PdfPasswordProvider? passwordProvider, bool firstAttemptByEmptyPassword = true, @@ -95,18 +106,15 @@ class PdfDocumentViewBuilder extends StatefulWidget { withCredentials: withCredentials, ); - /// A reference to the PDF document. final PdfDocumentRef documentRef; - - /// A builder that builds a widget tree with the PDF document. final PdfDocumentViewBuilderFunction builder; + /// Optional builders + final WidgetBuilder? loadingBuilder; + final Widget Function(BuildContext context, Object error, StackTrace? stackTrace)? errorBuilder; + @override State createState() => _PdfDocumentViewBuilderState(); - - static PdfDocumentViewBuilder? maybeOf(BuildContext context) { - return context.findAncestorWidgetOfExactType(); - } } class _PdfDocumentViewBuilderState extends State { @@ -151,15 +159,26 @@ class _PdfDocumentViewBuilderState extends State { } }); document?.loadPagesProgressively(); - if (mounted) { - setState(() {}); - } + if (mounted) setState(() {}); } } @override Widget build(BuildContext context) { - return widget.builder(context, widget.documentRef.resolveListenable().document); + final listenable = widget.documentRef.resolveListenable(); + + // Handle error + if (listenable.error != null && widget.errorBuilder != null) { + return widget.errorBuilder!(context, listenable.error!, listenable.stackTrace); + } + + // Handle loading + if (listenable.document == null && widget.loadingBuilder != null) { + return widget.loadingBuilder!(context); + } + + // Render document + return widget.builder(context, listenable.document); } } From 54cd362bbc519221f9d3290602cfb76c18de7728 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 13 Nov 2025 23:18:26 +0900 Subject: [PATCH 581/663] Add more comments. --- .../pdfrx/lib/src/widgets/pdf_widgets.dart | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 26c815a0..8021a8fe 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -38,6 +38,7 @@ import '../../pdfrx.dart'; /// ), /// ``` class PdfDocumentViewBuilder extends StatefulWidget { + /// Creates a widget that loads PDF document. const PdfDocumentViewBuilder({ required this.documentRef, required this.builder, @@ -46,6 +47,18 @@ class PdfDocumentViewBuilder extends StatefulWidget { super.key, }); + /// Creates a widget that loads PDF document from an asset. + /// + /// - [assetName] is the name of the asset. + /// - [builder] is the builder that builds the widget tree with the PDF document. + /// - [loadingBuilder] is the builder that builds the loading widget. + /// - [errorBuilder] is the builder that builds the error widget on error. + /// - [passwordProvider] is the provider for the password of the PDF document. + /// - [firstAttemptByEmptyPassword] indicates whether to try to open the document with an empty password first. + /// - [useProgressiveLoading] indicates whether to use progressive loading. + /// - [autoDispose] indicates whether to automatically dispose the document when the widget is disposed. + /// + /// Returns the created widget. PdfDocumentViewBuilder.asset( String assetName, { required this.builder, @@ -64,6 +77,18 @@ class PdfDocumentViewBuilder extends StatefulWidget { autoDispose: autoDispose, ); + /// Creates a widget that loads PDF document from a file. + /// + /// - [filePath] is the path of the file. + /// - [builder] is the builder that builds the widget tree with the PDF document. + /// - [loadingBuilder] is the builder that builds the loading widget. + /// - [errorBuilder] is the builder that builds the error widget on error. + /// - [passwordProvider] is the provider for the password of the PDF document. + /// - [firstAttemptByEmptyPassword] indicates whether to try to open the document with an empty password first. + /// - [useProgressiveLoading] indicates whether to use progressive loading. + /// - [autoDispose] indicates whether to automatically dispose the document when the widget is disposed. + /// + /// Returns the created widget. PdfDocumentViewBuilder.file( String filePath, { required this.builder, @@ -82,6 +107,18 @@ class PdfDocumentViewBuilder extends StatefulWidget { autoDispose: autoDispose, ); + /// Creates a widget that loads PDF document from a URI. + /// + /// - [uri] is the URI of the PDF document. + /// - [builder] is the builder that builds the widget tree with the PDF document. + /// - [loadingBuilder] is the builder that builds the loading widget. + /// - [errorBuilder] is the builder that builds the error widget on error. + /// - [passwordProvider] is the provider for the password of the PDF document. + /// - [firstAttemptByEmptyPassword] indicates whether to try to open the document with an empty password first. + /// - [useProgressiveLoading] indicates whether to use progressive loading. + /// - [autoDispose] indicates whether to automatically dispose the document when the widget is disposed. + /// + /// Returns the created widget. PdfDocumentViewBuilder.uri( Uri uri, { required this.builder, @@ -106,17 +143,30 @@ class PdfDocumentViewBuilder extends StatefulWidget { withCredentials: withCredentials, ); + /// The reference to the PDF document. final PdfDocumentRef documentRef; + + /// The builder that builds the widget tree with the PDF document. final PdfDocumentViewBuilderFunction builder; - /// Optional builders + /// The builder that builds the loading widget. final WidgetBuilder? loadingBuilder; - final Widget Function(BuildContext context, Object error, StackTrace? stackTrace)? errorBuilder; + + /// The builder that builds the error widget on error. + final PdfDocumentViewBuilderErrorBuilder? errorBuilder; @override State createState() => _PdfDocumentViewBuilderState(); } +/// A function that builds a widget tree when an error occurs while loading the PDF document. +/// +/// [context] is the build context. +/// [error] is the error that occurred. +/// [stackTrace] is the stack trace of the error, which may be null. +typedef PdfDocumentViewBuilderErrorBuilder = + Widget Function(BuildContext context, Object error, StackTrace? stackTrace); + class _PdfDocumentViewBuilderState extends State { StreamSubscription? _updateSubscription; From 35041eba0fc3faa756c95a9df34bdaeee813d917 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 17 Nov 2025 16:05:46 +0900 Subject: [PATCH 582/663] FIXED: #540 wire _CanvasLinkPainter._handleTapUp to _handleGeneralTap --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index c877687c..ced14291 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -530,8 +530,9 @@ class _PdfViewerState extends State if (_initialized && _canvasLinkPainter.isEnabled) _canvasLinkPainter.linkHandlingOverlay(viewSize), if (_initialized && widget.params.viewerOverlayBuilder != null) - ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleLinkTap) - .map((e) => e), + ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleTapUp).map( + (e) => e, + ), if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), ], ), @@ -4263,7 +4264,7 @@ class _CanvasLinkPainter { return null; } - bool _handleLinkTap(Offset tapPosition) { + bool _handleTapUp(Offset tapPosition) { _state._requestFocus(); _cursor = MouseCursor.defer; final link = _findLinkAtPosition(tapPosition); @@ -4274,7 +4275,8 @@ class _CanvasLinkPainter { return true; } } - _state._clearTextSelections(); + final globalPosition = _state._localToGlobal(tapPosition)!; + _state._handleGeneralTap(globalPosition, PdfViewerGeneralTapType.tap); return false; } @@ -4283,7 +4285,7 @@ class _CanvasLinkPainter { return GestureDetector( behavior: HitTestBehavior.translucent, // link taps - onTapUp: (details) => _handleLinkTap(details.localPosition), + onTapUp: (details) => _handleTapUp(details.localPosition), child: StatefulBuilder( builder: (context, setState) { return MouseRegion( From 80ebbd8b13abf6c025cad7fa38d007e661e08e15 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 17 Nov 2025 16:43:19 +0900 Subject: [PATCH 583/663] Introduces PdfLinkHandlerParams.laidOverPageOverlays --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 14 ++++++++--- .../lib/src/widgets/pdf_viewer_params.dart | 25 ++++++++++++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index ced14291..08a2ddd9 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -526,13 +526,13 @@ class _PdfViewerState extends State ), ), ), + if (_initialized && _canvasLinkPainter.isLaidUnderPageOverlays) + _canvasLinkPainter.linkHandlingOverlay(viewSize), if (_initialized) ..._buildPageOverlayWidgets(context), - if (_initialized && _canvasLinkPainter.isEnabled) + if (_initialized && _canvasLinkPainter.isLaidOverPageOverlays) _canvasLinkPainter.linkHandlingOverlay(viewSize), if (_initialized && widget.params.viewerOverlayBuilder != null) - ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleTapUp).map( - (e) => e, - ), + ...widget.params.viewerOverlayBuilder!(context, viewSize, _canvasLinkPainter._handleTapUp), if (_initialized) ..._placeTextSelectionWidgets(context, viewSize, isCopyTextEnabled), ], ), @@ -4220,6 +4220,12 @@ class _CanvasLinkPainter { bool get isEnabled => _state.widget.params.linkHandlerParams != null; + bool get isLaidOverPageOverlays => + _state.widget.params.linkHandlerParams != null && _state.widget.params.linkHandlerParams!.laidOverPageOverlays; + + bool get isLaidUnderPageOverlays => + _state.widget.params.linkHandlerParams != null && !_state.widget.params.linkHandlerParams!.laidOverPageOverlays; + /// Reset all the internal data. void resetAll() { _cursor = MouseCursor.defer; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 2df68a12..33186ea1 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -428,6 +428,10 @@ class PdfViewerParams { /// /// This function is used to decorate each page with overlay widgets. /// + /// But placing widgets over the page may make the viewer heavier, especially when + /// the document has many pages. So please use this function with care. + /// To draw simple decorations such as page number footer, consider using [pageBackgroundPaintCallbacks]. + /// /// The return value of the function is a list of widgets to be laid out on the page; /// they are actually laid out on the page using [Stack]. /// @@ -481,8 +485,8 @@ class PdfViewerParams { /// /// If [linkHandlerParams] is specified, it is ignored. /// - /// Basically, handling links with widgets are not recommended because it is not efficient. - /// [linkHandlerParams] is the recommended way to handle links. + /// Basically, handling links with widgets are not recommended because it makes the viewer heavier. + /// If you just handle simple link taps or want to customize link visuals, use [linkHandlerParams]. final PdfLinkWidgetBuilder? linkWidgetBuilder; /// Callback to paint over the rendered page. @@ -1496,6 +1500,7 @@ class PdfLinkHandlerParams { this.linkColor, this.customPainter, this.enableAutoLinkDetection = true, + this.laidOverPageOverlays = true, }); /// Function to be called when the link is tapped. @@ -1531,6 +1536,13 @@ class PdfLinkHandlerParams { /// The default is true. final bool enableAutoLinkDetection; + /// Whether the link widgets are laid over page overlays or not. + /// + /// If true, the link widgets are laid over page overlays built by [PdfViewerParams.pageOverlaysBuilder]. + /// If false, the link widgets are laid under page overlays. + /// The default is true. + final bool laidOverPageOverlays; + @override bool operator ==(covariant PdfLinkHandlerParams other) { if (identical(this, other)) return true; @@ -1538,12 +1550,17 @@ class PdfLinkHandlerParams { return other.onLinkTap == onLinkTap && other.linkColor == linkColor && other.customPainter == customPainter && - other.enableAutoLinkDetection == enableAutoLinkDetection; + other.enableAutoLinkDetection == enableAutoLinkDetection && + other.laidOverPageOverlays == laidOverPageOverlays; } @override int get hashCode { - return onLinkTap.hashCode ^ linkColor.hashCode ^ customPainter.hashCode ^ enableAutoLinkDetection.hashCode; + return onLinkTap.hashCode ^ + linkColor.hashCode ^ + customPainter.hashCode ^ + enableAutoLinkDetection.hashCode ^ + laidOverPageOverlays.hashCode; } } From c90208629505525368e7431f6bb1f6606cef7c2f Mon Sep 17 00:00:00 2001 From: Utsav Date: Mon, 17 Nov 2025 13:57:22 +0530 Subject: [PATCH 584/663] FIX: update document reference comparison to use key for consistency --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 08a2ddd9..24ff6bf2 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -301,7 +301,7 @@ class _PdfViewerState extends State return; } - if (oldWidget?.documentRef == widget.documentRef) { + if (oldWidget?.documentRef.key == widget.documentRef.key) { if (widget.params.doChangesRequireReload(oldWidget?.params)) { if (widget.params.annotationRenderingMode != oldWidget?.params.annotationRenderingMode) { _imageCache.releaseAllImages(); From d06822e7c8ec11502ddaf4dc8e1034f934632400 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Mon, 17 Nov 2025 22:13:36 +0900 Subject: [PATCH 585/663] Update docs. --- doc/Coordinate-Conversion.md | 384 ++++++++++++++++++ ...rOverlayBuilder-and-pageOverlaysBuilder.md | 2 +- doc/README.md | 1 + 3 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 doc/Coordinate-Conversion.md diff --git a/doc/Coordinate-Conversion.md b/doc/Coordinate-Conversion.md new file mode 100644 index 00000000..8877c722 --- /dev/null +++ b/doc/Coordinate-Conversion.md @@ -0,0 +1,384 @@ +# Coordinate Conversion + +This guide explains how to work with different coordinate systems in pdfrx and convert between them. + +## Understanding Coordinate Systems + +pdfrx uses four distinct coordinate systems: + +1. **Global Coordinates** - Screen/window coordinates (origin at top-left of the screen) +2. **Local Coordinates** - Widget's local coordinate system (origin at top-left of the widget) +3. **Document Coordinates** - PDF document layout coordinates (72 DPI, unzoomed, with pages laid out according to the layout mode) +4. **Page Coordinates** - Individual PDF page coordinates (origin at bottom-left corner, following PDF standard) + +### Coordinate System Diagram + +``` +Global (Screen) Local (Widget) Document (Layout) Page (PDF) +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ (0,0) │ │ (0,0) │ │ (0,0) │ │ │ +│ │ │ │ │ ┌────┐ │ │ │ +│ ┌──────┐ │ │ Content │ │ │Page│ │ │ (0,h) │ +│ │Widget│ │ → │ │ → │ └────┘ │ → │ │ +│ └──────┘ │ │ │ │ ┌────┐ │ └─────────────┘ +│ │ │ │ │ │Page│ │ (0,0) (w,0) +└─────────────┘ └─────────────┘ └──┴────┴─────┘ +``` + +## Converting Between Coordinate Systems + +### Widget Local ↔ Global Coordinates + +Use [PdfViewerController.globalToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToLocal.html) and [PdfViewerController.localToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToGlobal.html): + +```dart +final controller = PdfViewerController(); +... + +// Global to local (e.g., from GestureDetector.onTapDown) +final globalPos = details.globalPosition; +final localPos = controller.globalToLocal(globalPos); + +// Local to global +final localPos = Offset(100, 200); +final globalPos = controller.localToGlobal(localPos); +``` + +### Global ↔ Document Coordinates + +Use [PdfViewerController.globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) and [PdfViewerController.documentToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToGlobal.html): + +```dart +// Global to document (accounts for zoom and pan) +final globalPos = details.globalPosition; +final docPos = controller.globalToDocument(globalPos); + +// Document to global +final docPos = Offset(100, 200); +final globalPos = controller.documentToGlobal(docPos); +``` + +### Widget Local ↔ Document Coordinates + +Use [PdfViewerController.localToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToDocument.html) and [PdfViewerController.documentToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToLocal.html): + +```dart +// Local to document +final localPos = Offset(100, 200); +final docPos = controller.localToDocument(localPos); + +// Document to local +final docPos = Offset(100, 200); +final localPos = controller.documentToLocal(docPos); +``` + +### Document ↔ Page Coordinates + +Convert between document layout coordinates and individual page coordinates: + +```dart +import 'package:pdfrx/pdfrx.dart'; + +// Get the page layout rectangles +final pageLayouts = controller.layout.pageLayouts; +final pageRect = pageLayouts[pageIndex]; // Document coordinates + +// Document position to page position +final docPos = Offset(150, 300); +if (pageRect.contains(docPos)) { + // Offset within the page (top-left origin) + final pageOffset = docPos - pageRect.topLeft; + + // Convert to PDF page coordinates (bottom-left origin) + // IMPORTANT: This automatically handles page rotation + final pdfPoint = pageOffset.toPdfPoint( + page: page, + scaledPageSize: pageRect.size, + ); +} + +// Page coordinates to document position +final pdfPoint = PdfPoint(100, 200); // PDF coordinates (bottom-left origin) +// IMPORTANT: This automatically handles page rotation +final pageOffset = pdfPoint.toOffset( + page: page, + scaledPageSize: pageRect.size, +); // Top-left origin +final docPos = pageOffset.translate(pageRect.left, pageRect.top); +``` + +**Important Note about Page Rotation**: PDF pages can have rotation metadata (0°, 90°, 180°, or 270°). When converting between document and page coordinates, the conversion methods automatically account for the page's rotation. The coordinate system is rotated so that (0,0) in page coordinates always represents the bottom-left corner of the page as it appears when rendered (after rotation is applied). + +## Common Use Cases + +### Example 1: Finding Which Page Was Tapped + +```dart +PdfViewerController controller; + +void onTapDown(TapDownDetails details) { + // Convert global position to document coordinates + final docPos = controller.globalToDocument(details.globalPosition); + if (docPos == null) return; + + // Find which page contains this position + final pageLayouts = controller.layout.pageLayouts; + final pageIndex = pageLayouts.indexWhere((rect) => rect.contains(docPos)); + + if (pageIndex >= 0) { + final pageNumber = pageIndex + 1; + print('Tapped on page $pageNumber'); + + // Get position within the page + final pageRect = pageLayouts[pageIndex]; + final offsetInPage = docPos - pageRect.topLeft; + print('Position in page: $offsetInPage'); + } +} +``` + +### Example 2: Highlighting a Specific PDF Rectangle + +```dart +Widget buildPageOverlay(BuildContext context, Rect pageRect, PdfPage page) { + // Rectangle in PDF page coordinates (bottom-left origin) + final pdfRect = PdfRect( + left: 100, + top: 200, + right: 300, + bottom: 100, + ); + + // Convert to document coordinates (top-left origin, scaled) + final rect = pdfRect.toRect( + page: page, + scaledPageSize: pageRect.size, + ); + + // Position it within the page overlay + return Positioned( + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.yellow, width: 2), + ), + ), + ); +} +``` + +### Example 3: Converting Search Results to Overlay Positions + +```dart +// Search for text in the PDF +final matches = await controller.document.pages[pageNumber - 1].loadText(); +final searchResults = matches.searchText('keyword'); + +// Build overlays for search results +Widget buildSearchHighlights(BuildContext context, Rect pageRect, PdfPage page) { + return Stack( + children: searchResults.map((result) { + // result.bounds is in PDF page coordinates + final rect = result.bounds.toRect( + page: page, + scaledPageSize: pageRect.size, + ); + + return Positioned( + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + child: Container( + color: Colors.yellow.withOpacity(0.3), + ), + ); + }).toList(), + ); +} +``` + +### Example 4: Getting Visible Document Area + +```dart +// Get the currently visible area in document coordinates +final visibleRect = controller.visibleRect; + +// Check if a specific position is visible +final docPos = Offset(100, 200); +final isVisible = visibleRect.contains(docPos); + +// Scroll to make a position visible +final targetPos = Offset(500, 1000); +if (!visibleRect.contains(targetPos)) { + controller.goToPosition(targetPos); +} +``` + +## Understanding Zoom and Scale + +When working with coordinates, it's important to understand how zoom affects positioning: + +- **Document coordinates** are always at 72 DPI (unzoomed), regardless of the current zoom level +- **Page overlay coordinates** must be scaled by the current zoom factor when positioning widgets + +```dart +// In pageOverlaysBuilder +Widget buildOverlay(BuildContext context, Rect pageRect, PdfPage page) { + // pageRect is already zoomed (in widget coordinates) + // To position something at a specific document coordinate: + final docOffset = Offset(100, 200); // In document coordinates + + // Method 1: Use documentToLocal (recommended) + final localOffset = controller.documentToLocal( + docOffset.translate(pageRect.left, pageRect.top) + ); + + // Method 2: Manual scaling + final zoom = controller.currentZoom; + final scaledOffset = docOffset * zoom; + + return Positioned( + left: scaledOffset.dx, + top: scaledOffset.dy, + child: YourWidget(), + ); +} +``` + +## Important Notes + +### Y-Axis Orientation + +- **Flutter/Widget coordinates**: Y increases downward (top to bottom) +- **PDF page coordinates**: Y increases upward (bottom to top) + +The conversion extensions ([PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html) and [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html)) handle this Y-axis flipping automatically. + +### Page Rotation + +PDF pages can have rotation metadata (0°, 90°, 180°, or 270°) that affects how the page is displayed. This rotation is stored in the PDF file and accessible via [PdfPage.rotation](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPage/rotation.html). + +#### How Rotation Affects Coordinates + +When a page is rotated: + +- The page's width and height are **swapped** for 90° and 270° rotations +- The coordinate system is **rotated** so that (0,0) in page coordinates represents the bottom-left corner of the page **as it appears after rotation** +- In-page offsets must account for this rotation when converting to/from PDF page coordinates + +#### Automatic Rotation Handling + +The conversion methods automatically account for page rotation when you use the extension methods: + +```dart +// Automatic rotation handling (uses page.rotation) +final pdfPoint = PdfPoint(100, 200); +final offset = pdfPoint.toOffset(page: page); // Rotation applied automatically + +// Converting back also handles rotation +final pageOffset = Offset(50, 100); +final pdfPoint = pageOffset.toPdfPoint(page: page); // Rotation reversed automatically +``` + +#### Manual Rotation Override + +You can override the rotation if needed: + +```dart +// Force a specific rotation (useful for preview or testing) +final offset = pdfPoint.toOffset(page: page, rotation: 90); +final pdfPoint = pageOffset.toPdfPoint(page: page, rotation: 0); +``` + +#### Example: Handling Rotated Pages + +```dart +void highlightAreaOnRotatedPage(PdfPage page, PdfRect area) { + // Get page layout in document + final pageRect = controller.layout.pageLayouts[page.pageNumber - 1]; + + // Check the page rotation + print('Page rotation: ${page.rotation.index * 90}°'); // 0, 90, 180, or 270 + + // Convert PDF rect to document coordinates (rotation handled automatically) + final rect = area.toRect( + page: page, + scaledPageSize: pageRect.size, + ); // rotation is automatically applied + + // For 90° or 270° rotations, note that width and height are swapped + if (page.rotation.index == 1 || page.rotation.index == 3) { + print('Page is rotated 90° or 270° - dimensions are swapped'); + print('Original page size: ${page.width} x ${page.height}'); + print('Rendered page size: ${pageRect.width} x ${pageRect.height}'); + } +} +``` + +**Key Points**: + +- Always use the conversion extension methods ([PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html), [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html), [PdfRect.toRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRect.html)) instead of manual calculations +- These methods handle rotation, Y-axis flipping, and scaling automatically +- The page layout rectangle (`pageRect`) already reflects the rotated dimensions +- In-page offsets calculated by subtracting `pageRect.topLeft` are relative to the **rotated** page as displayed + +### Null Safety + +Some conversion methods return nullable values because they may fail if: + +- The widget is not yet rendered (`globalToLocal`, `localToGlobal`) +- The position is outside the document bounds + +Always check for null: + +```dart +final docPos = controller.globalToDocument(globalPos); +if (docPos == null) { + // Widget not ready or position invalid + return; +} +// Proceed with docPos +``` + +## Coordinate Converter Interface + +For advanced use cases where you need to perform conversions from within custom builders, use [PdfViewerCoordinateConverter](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerCoordinateConverter-class.html): + +```dart +// Available in viewerOverlayBuilder and pageOverlaysBuilder +final converter = controller.doc2local; + +// Convert with BuildContext +final localPos = converter.offsetToLocal(context, docPos); +final localRect = converter.rectToLocal(context, docRect); +``` + +## API Reference + +### PdfViewerController Methods + +- [globalToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToLocal.html) - Global → Local +- [localToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToGlobal.html) - Local → Global +- [globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) - Global → Document +- [documentToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToGlobal.html) - Document → Global +- [localToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToDocument.html) - Local → Document +- [documentToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToLocal.html) - Document → Local +- [currentZoom](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/currentZoom.html) - Get current zoom factor +- [visibleRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/visibleRect.html) - Get visible area in document coordinates + +### Extension Methods + +- [PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html) - PDF page → Document offset +- [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html) - Document offset → PDF page +- [PdfRect.toRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRect.html) - PDF page rect → Document rect +- [PdfRect.toRectInDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRectInDocument.html) - PDF page rect → Full document position + +## See Also + +- [Deal with viewerOverlayBuilder and pageOverlaysBuilder](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Practical example using coordinate conversion +- [Text Selection](Text-Selection.md) - Uses coordinate conversion for selection rectangles +- [PDF Link Handling](PDF-Link-Handling.md) - Uses coordinate conversion for link areas diff --git a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md index 3c7941b0..d50032c7 100644 --- a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md +++ b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md @@ -46,7 +46,7 @@ pageOverlaysBuilder: (context, pageRect, page) { On [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html); - Convert the global tap position to in-document position using [PdfViewerController.globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) - - The in-document position is position in document structure (i.e., page layout in 72-dpi). + - The in-document position is position in document structure (i.e., page layout in 72-dpi). - Determine which page contains the position using [PdfViewerController.layout.pageLayouts](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageLayout/pageLayouts.html) - Convert the in-document position to the in-page position (just subtract the page's top-left position) diff --git a/doc/README.md b/doc/README.md index 9d3d03c8..3c69b308 100644 --- a/doc/README.md +++ b/doc/README.md @@ -29,6 +29,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ### Advanced Topics +- [Coordinate Conversion](Coordinate-Conversion.md) - Understanding and converting between coordinate systems - [Customizing Key Handling](Customizing-Key-Handling-on-PdfViewer.md) - Keyboard shortcuts - [Password Protected PDFs](Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md) - Handle encrypted PDFs - [Overlay Builders](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Custom overlays From d586b5791b689b23e086f6ecec7b7c366d3416f1 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 00:58:53 +0900 Subject: [PATCH 586/663] #544: Treat empty PDF as a valid PDF to keep consistency with existing editing feature --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 24ff6bf2..340c65e5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -627,7 +627,9 @@ class _PdfViewerState extends State ) ?? _coverScale!; await _setZoom(Offset.zero, zoom, duration: Duration.zero); - await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); + if (_pageNumber! <= _layout!.pageLayouts.length) { + await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); + } if (mounted && _document != null && _controller != null) { widget.params.onViewerReady?.call(_document!, _controller!); } @@ -1030,7 +1032,7 @@ class _PdfViewerState extends State _coverScale = max(s1, s2); } final pageNumber = _pageNumber ?? _gotoTargetPageNumber; - if (pageNumber != null) { + if (pageNumber != null && pageNumber >= 1 && pageNumber <= _layout!.pageLayouts.length) { final rect = _layout!.pageLayouts[pageNumber - 1]; final m2 = params.margin * 2; _alternativeFitScale = min( From 8240f1af2a6e3e7cdfc921c4bd5141868a15717e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 02:04:22 +0900 Subject: [PATCH 587/663] FIXED: #518 Focus.of -> Focus.maybeOf to not throw exceptions --- .../lib/src/widgets/internals/pdf_viewer_key_handler.dart | 6 +++++- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index 848fb35c..e3bd96f8 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -25,8 +25,12 @@ class PdfViewerKeyHandler extends StatelessWidget { @override Widget build(BuildContext context) { + final focusNode = Focus.maybeOf(context); + if (focusNode == null) { + return child; + } final childBuilder = Builder( - builder: (context) => ListenableBuilder(listenable: Focus.of(context), builder: (context, _) => child), + builder: (context) => ListenableBuilder(listenable: focusNode, builder: (context, _) => child), ); if (!params.enabled) { return childBuilder; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 340c65e5..652dbdbc 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -2024,7 +2024,7 @@ class _PdfViewerState extends State } FocusNode? _getFocusNode() { - return _contextForFocusNode != null ? Focus.of(_contextForFocusNode!) : null; + return _contextForFocusNode != null ? Focus.maybeOf(_contextForFocusNode!) : null; } void _requestFocus() { From 808ef704f86814f41836efafae015914a2959657 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 02:55:51 +0900 Subject: [PATCH 588/663] #515: PdfTextSearcher updates to deal with document changes --- .../lib/src/widgets/pdf_text_searcher.dart | 104 ++++++++++++------ .../lib/src/pdf_page_status_change.dart | 18 +++ packages/pdfrx_engine/lib/src/pdf_text.dart | 2 +- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index e6671c3e..8c6a89c8 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -10,7 +10,9 @@ import '../../pdfrx.dart'; /// To be notified when the search status change, use [addListener]. class PdfTextSearcher extends Listenable { /// Creates a new instance of [PdfTextSearcher]. - PdfTextSearcher(this._controller); + PdfTextSearcher(this._controller) { + _registerForDocumentChanges(); + } final PdfViewerController _controller; @@ -21,13 +23,14 @@ class PdfTextSearcher extends Listenable { int _searchSession = 0; // current search session List _matches = const []; List _matchesPageStartIndices = const []; - Pattern? _lastSearchPattern; + _SearchCondition? _lastSearchCondition; int? _currentIndex; PdfPageTextRange? _currentMatch; int? _searchingPageNumber; int? _totalPageCount; bool _isSearching = false; final _cachedText = {}; + StreamSubscription? _documentEventSubscription; /// The current match index in [matches] if available. int? get currentIndex => _currentIndex; @@ -51,7 +54,7 @@ class PdfTextSearcher extends Listenable { return _searchingPageNumber! / _totalPageCount!; } - Pattern? get pattern => _lastSearchPattern; + Pattern? get pattern => _lastSearchCondition?.pattern; int get searchSession => _searchSession; @@ -81,13 +84,17 @@ class PdfTextSearcher extends Listenable { final searchSession = ++_searchSession; void search() { - if (_isIdenticalPattern(_lastSearchPattern, pattern)) return; - _lastSearchPattern = pattern; + if (_isIdenticalPattern(_lastSearchCondition?.pattern, pattern)) return; + _lastSearchCondition = _SearchCondition( + pattern: pattern, + caseInsensitive: caseInsensitive, + goToFirstMatch: goToFirstMatch, + ); if (pattern.isEmpty) { _resetTextSearch(); return; } - _startTextSearchInternal(pattern, searchSession, caseInsensitive, goToFirstMatch); + _startTextSearchInternal(_lastSearchCondition!, searchSession); } if (searchImmediately) { @@ -97,34 +104,18 @@ class PdfTextSearcher extends Listenable { } } - bool _isIdenticalPattern(Pattern? a, Pattern? b) { - if (a is String && b is String) { - return a == b; - } - if (a is RegExp && b is RegExp) { - return a.pattern == b.pattern && - a.isCaseSensitive == b.isCaseSensitive && - a.isMultiLine == b.isMultiLine && - a.isUnicode == b.isUnicode && - a.isDotAll == b.isDotAll; - } - if (a == null && b == null) { - return true; - } - return false; - } - /// Reset the current search. void resetTextSearch() => _resetTextSearch(); /// Almost identical to [resetTextSearch], but does not notify listeners. void dispose() { + _documentEventSubscription?.cancel(); _listeners.clear(); _cachedText.clear(); _resetTextSearch(notify: false); } - void _resetTextSearch({bool notify = true}) { + void _resetTextSearch({bool notify = true, bool clearSearchCondition = true}) { _cancelTextSearch(); _matches = const []; _matchesPageStartIndices = const []; @@ -132,7 +123,9 @@ class PdfTextSearcher extends Listenable { _currentIndex = null; _currentMatch = null; _isSearching = false; - _lastSearchPattern = null; + if (clearSearchCondition) { + _lastSearchCondition = null; + } if (notify) { notifyListeners(); } @@ -143,12 +136,7 @@ class PdfTextSearcher extends Listenable { ++_searchSession; } - Future _startTextSearchInternal( - Pattern text, - int searchSession, - bool caseInsensitive, - bool goToFirstMatch, - ) async { + Future _startTextSearchInternal(_SearchCondition condition, int searchSession) async { await controller?.useDocument((document) async { final textMatches = []; final textMatchesPageStartIndex = []; @@ -159,9 +147,10 @@ class PdfTextSearcher extends Listenable { _searchingPageNumber = page.pageNumber; if (searchSession != _searchSession) return; final pageText = await loadText(pageNumber: page.pageNumber); + if (searchSession != _searchSession) return; if (pageText == null) continue; textMatchesPageStartIndex.add(textMatches.length); - await for (final f in pageText.allMatches(text, caseInsensitive: caseInsensitive)) { + for (final f in pageText.allMatches(condition.pattern, caseInsensitive: condition.caseInsensitive)) { if (searchSession != _searchSession) return; textMatches.add(f); } @@ -172,7 +161,7 @@ class PdfTextSearcher extends Listenable { if (_matches.isNotEmpty && first) { first = false; - if (goToFirstMatch) { + if (condition.goToFirstMatch) { _currentIndex = 0; _currentMatch = null; goToMatchOfIndex(_currentIndex!); @@ -182,6 +171,31 @@ class PdfTextSearcher extends Listenable { }); } + void _registerForDocumentChanges() { + _documentEventSubscription?.cancel(); + _documentEventSubscription = controller!.document.events.listen((event) { + if (event is PdfDocumentPageStatusChangedEvent) { + final changedPages = event.changes.keys.toSet(); + final needRestart = _matches.any((m) => changedPages.contains(m.pageNumber)); + if (needRestart) { + _restartSearch(); + } + } + }); + } + + void _restartSearch() { + _resetTextSearch(clearSearchCondition: false); + _cachedText.clear(); + if (_lastSearchCondition != null) { + startTextSearch( + _lastSearchCondition!.pattern, + caseInsensitive: _lastSearchCondition!.caseInsensitive, + goToFirstMatch: _lastSearchCondition!.goToFirstMatch, + ); + } + } + /// Just a helper function to load the text of a page. Future loadText({required int pageNumber}) async { final cached = _cachedText[pageNumber]; @@ -284,3 +298,27 @@ extension _PatternExts on Pattern { } } } + +class _SearchCondition { + const _SearchCondition({required this.pattern, required this.caseInsensitive, required this.goToFirstMatch}); + final Pattern pattern; + final bool caseInsensitive; + final bool goToFirstMatch; +} + +bool _isIdenticalPattern(Pattern? a, Pattern? b) { + if (a is String && b is String) { + return a == b; + } + if (a is RegExp && b is RegExp) { + return a.pattern == b.pattern && + a.isCaseSensitive == b.isCaseSensitive && + a.isMultiLine == b.isMultiLine && + a.isUnicode == b.isUnicode && + a.isDotAll == b.isDotAll; + } + if (a == null && b == null) { + return true; + } + return false; +} diff --git a/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart index f4b1b930..41e6c20f 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart @@ -1,7 +1,19 @@ +/// Enum representing the type of PDF page status change. +enum PdfPageStatusChangeType { + /// Page has been moved inside the same document. + moved, + + /// Page has been newly added or modified. + modified, +} + /// Base class for PDF page status change. abstract class PdfPageStatusChange { const PdfPageStatusChange(); + /// Type of the status change. + PdfPageStatusChangeType get type; + /// Create [PdfPageStatusMoved]. static PdfPageStatusChange moved({required int oldPageNumber}) => PdfPageStatusMoved(oldPageNumber: oldPageNumber); @@ -14,6 +26,9 @@ class PdfPageStatusMoved extends PdfPageStatusChange { const PdfPageStatusMoved({required this.oldPageNumber}); final int oldPageNumber; + @override + PdfPageStatusChangeType get type => PdfPageStatusChangeType.moved; + @override int get hashCode => oldPageNumber.hashCode; @@ -31,6 +46,9 @@ class PdfPageStatusMoved extends PdfPageStatusChange { class PdfPageStatusModified extends PdfPageStatusChange { const PdfPageStatusModified(); + @override + PdfPageStatusChangeType get type => PdfPageStatusChangeType.modified; + @override int get hashCode => 0; diff --git a/packages/pdfrx_engine/lib/src/pdf_text.dart b/packages/pdfrx_engine/lib/src/pdf_text.dart index 94eb9db3..7d2cd54b 100644 --- a/packages/pdfrx_engine/lib/src/pdf_text.dart +++ b/packages/pdfrx_engine/lib/src/pdf_text.dart @@ -104,7 +104,7 @@ class PdfPageText { /// /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { + Iterable allMatches(Pattern pattern, {bool caseInsensitive = true}) sync* { final String text; if (pattern is RegExp) { caseInsensitive = pattern.isCaseSensitive; From 7406469fef41a6b308b1c1117b6bac08a1e3ffae Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:16:17 +0900 Subject: [PATCH 589/663] Release pdfrx v2.2.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 8 ++++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0433a0f3..a6f90acb 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.12 + pdfrx: ^2.2.13 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 1bd03496..ca0940c4 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.2.13 + +- FIXED: [PdfTextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) not in sync on [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) update ([#515](https://github.com/espresso3389/pdfrx/issues/515)) +- FIXED: Focus.of -> Focus.maybeOf to prevent exceptions when FocusNode is not available ([#518](https://github.com/espresso3389/pdfrx/issues/518)) +- FIXED: Crash when opening an empty PDF - now treated as a valid PDF to keep consistency with existing editing feature ([#544](https://github.com/espresso3389/pdfrx/issues/544)) +- FIXED: [PdfViewerParams.onGeneralTap](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onGeneralTap.html) one-click not working ([#540](https://github.com/espresso3389/pdfrx/issues/540)) +- IMPROVED: Document reference comparison now uses key for better consistency ([#543](https://github.com/espresso3389/pdfrx/pull/543)) + # 2.2.12 - FIXED: Package incorrectly showing as web-only on pub.dev due to incorrect Flutter plugin platform declarations ([#535](https://github.com/espresso3389/pdfrx/issues/535)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index cca2724e..b0b67c42 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.12 + pdfrx: ^2.2.13 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 8b848656..4a1e8153 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.12 +version: 2.2.13 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 20cc1c93d7057f5849ed9a4bd41a03eb838d8e93 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:17:39 +0900 Subject: [PATCH 590/663] Update text loading method to loadStructuredText --- doc/Text-Selection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index 329d1709..04459845 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -80,7 +80,7 @@ final controller = PdfViewerController(); // First, load the page text for the target page final page = await controller.document?.getPage(pageNumber); -final pageText = await page?.loadText(); +final pageText = await page?.loadStructuredText(); if (pageText != null) { // Create selection points with page text and character indices @@ -98,10 +98,10 @@ if (pageText != null) { ```dart // Example: Select from the beginning of page 1 to the end of page 3 final startPage = await controller.document?.getPage(1); -final startPageText = await startPage?.loadText(); +final startPageText = await startPage?.loadStructuredText(); final endPage = await controller.document?.getPage(3); -final endPageText = await endPage?.loadText(); +final endPageText = await endPage?.loadStructuredText(); if (startPageText != null && endPageText != null && endPageText.fullText.isNotEmpty) { final startPoint = PdfTextSelectionPoint(startPageText, 0); From ea6ebdf5f43a3dfeaf5c7560b71295eb281a7374 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:21:12 +0900 Subject: [PATCH 591/663] Fix page indexing for text selection example --- doc/Text-Selection.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index 04459845..fd203a35 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -79,7 +79,7 @@ final controller = PdfViewerController(); ... // First, load the page text for the target page -final page = await controller.document?.getPage(pageNumber); +final page = await controller.document![pageNumber - 1]; final pageText = await page?.loadStructuredText(); if (pageText != null) { @@ -97,11 +97,11 @@ if (pageText != null) { ```dart // Example: Select from the beginning of page 1 to the end of page 3 -final startPage = await controller.document?.getPage(1); -final startPageText = await startPage?.loadStructuredText(); +final startPage = await controller.document.pages[0]; +final startPageText = await startPage.loadStructuredText(); -final endPage = await controller.document?.getPage(3); -final endPageText = await endPage?.loadStructuredText(); +final endPage = await controller.document.pages[2]; +final endPageText = await endPage.loadStructuredText(); if (startPageText != null && endPageText != null && endPageText.fullText.isNotEmpty) { final startPoint = PdfTextSelectionPoint(startPageText, 0); From 6fb187b45a2d5a24b1b4cc68501bda8a7b7be4aa Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:26:25 +0900 Subject: [PATCH 592/663] DOCS: Fix API inconsistencies in documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed incorrect package reference (pdf_render -> pdfrx) in Page-Layout-Customization.md - Fixed typo PPdfrxEntryFunctions -> PdfrxEntryFunctions in Loading-Fonts-Dynamically.md - Removed non-existent enableTextSelection parameter in Text-Selection.md - Updated deprecated initPdfium -> init in pdfrx-Initialization.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- doc/Loading-Fonts-Dynamically.md | 2 +- doc/Page-Layout-Customization.md | 2 +- doc/Text-Selection.md | 1 - doc/pdfrx-Initialization.md | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/Loading-Fonts-Dynamically.md b/doc/Loading-Fonts-Dynamically.md index 33602c74..2cf12282 100644 --- a/doc/Loading-Fonts-Dynamically.md +++ b/doc/Loading-Fonts-Dynamically.md @@ -24,7 +24,7 @@ Future loadFont() async { final fontData = response.bodyBytes; // Add font data to PDFium - await PPdfrxEntryFunctions.instance.addFontData( + await PdfrxEntryFunctions.instance.addFontData( face: 'MyFont', // font name should be unique but don't have to be meaningful name data: fontData, ); diff --git a/doc/Page-Layout-Customization.md b/doc/Page-Layout-Customization.md index 412a2121..e0912556 100644 --- a/doc/Page-Layout-Customization.md +++ b/doc/Page-Layout-Customization.md @@ -1,6 +1,6 @@ # Page Layout Customization -> NOTE: setting [PdfViewerParams.layoutPages](https://pub.dev/documentation/pdf_render/latest/pdf_render_widgets/PdfViewerParams/layoutPages.html) dynamically does not refresh the viewer. You should call [PdfViewerController.invalidate](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/invalidate.html), [setState](https://api.flutter.dev/flutter/widgets/State/setState.html) or some equivalent function. +> NOTE: setting [PdfViewerParams.layoutPages](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/layoutPages.html) dynamically does not refresh the viewer. You should call [PdfViewerController.invalidate](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/invalidate.html), [setState](https://api.flutter.dev/flutter/widgets/State/setState.html) or some equivalent function. ## Horizontal Scroll View diff --git a/doc/Text-Selection.md b/doc/Text-Selection.md index fd203a35..ea4c26ef 100644 --- a/doc/Text-Selection.md +++ b/doc/Text-Selection.md @@ -29,7 +29,6 @@ The handler function receives a parameter of [PdfTextSelection](https://pub.dev/ PdfViewer.asset( 'assets/test.pdf', params: PdfViewerParams( - enableTextSelection: true, textSelectionParams: PdfTextSelectionParams( onTextSelectionChange: (selections) async { // Get the selected string diff --git a/doc/pdfrx-Initialization.md b/doc/pdfrx-Initialization.md index 983414c6..4a268ebc 100644 --- a/doc/pdfrx-Initialization.md +++ b/doc/pdfrx-Initialization.md @@ -21,7 +21,7 @@ Basically, these initialization functions do the following things: - Map PdfDocument [factory/interop functions](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions-class.html) to actual platform ones - Set [Pdfrx.loadAsset](https://pub.dev/documentation/pdfrx/latest/pdfrx/Pdfrx/loadAsset.html) (Flutter only) - Download PDFium binary on-demand ([pdfrxInitialize](https://pub.dev/documentation/pdfrx/latest/pdfrx/pdfrxInitialize.html) only) -- Call [PdfrxEntryFunctions.initPdfium](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfrxEntryFunctions/initPdfium.html) to initialize the PDFium library (internally calls `FPDF_InitLibraryWithConfig`) +- Call [PdfrxEntryFunctions.init](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfrxEntryFunctions/init.html) to initialize the PDFium library (internally calls `FPDF_InitLibraryWithConfig`) ## Cache Directory From 44df633e30025ea19a0a3c2504dab55c1175b477 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:38:33 +0900 Subject: [PATCH 593/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_text.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index 8c6a89c8..15c587bf 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -150,7 +150,7 @@ class PdfTextSearcher extends Listenable { if (searchSession != _searchSession) return; if (pageText == null) continue; textMatchesPageStartIndex.add(textMatches.length); - for (final f in pageText.allMatches(condition.pattern, caseInsensitive: condition.caseInsensitive)) { + await for (final f in pageText.allMatches(condition.pattern, caseInsensitive: condition.caseInsensitive)) { if (searchSession != _searchSession) return; textMatches.add(f); } diff --git a/packages/pdfrx_engine/lib/src/pdf_text.dart b/packages/pdfrx_engine/lib/src/pdf_text.dart index 7d2cd54b..94eb9db3 100644 --- a/packages/pdfrx_engine/lib/src/pdf_text.dart +++ b/packages/pdfrx_engine/lib/src/pdf_text.dart @@ -104,7 +104,7 @@ class PdfPageText { /// /// Just work like [Pattern.allMatches] but it returns stream of [PdfPageTextRange]. /// [caseInsensitive] is used to specify case-insensitive search only if [pattern] is [String]. - Iterable allMatches(Pattern pattern, {bool caseInsensitive = true}) sync* { + Stream allMatches(Pattern pattern, {bool caseInsensitive = true}) async* { final String text; if (pattern is RegExp) { caseInsensitive = pattern.isCaseSensitive; From 49e57c6f578b7c71084a26c7f8168e03b0f9b41a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 03:45:47 +0900 Subject: [PATCH 594/663] Release pdfrx v2.2.14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6f90acb..37c2a52d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.13 + pdfrx: ^2.2.14 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index ca0940c4..9c91756e 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.14 + +- Minor changes. + # 2.2.13 - FIXED: [PdfTextSearcher](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfTextSearcher-class.html) not in sync on [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) update ([#515](https://github.com/espresso3389/pdfrx/issues/515)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index b0b67c42..ed844515 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.13 + pdfrx: ^2.2.14 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 4a1e8153..6e469ec0 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.13 +version: 2.2.14 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 4dd492ffe6272718fa390e273f9c45dde238dea9 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 16:07:57 +0900 Subject: [PATCH 595/663] #518 The context to get focus is wrong. --- .../widgets/internals/pdf_viewer_key_handler.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index e3bd96f8..c6994530 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -25,12 +25,14 @@ class PdfViewerKeyHandler extends StatelessWidget { @override Widget build(BuildContext context) { - final focusNode = Focus.maybeOf(context); - if (focusNode == null) { - return child; - } final childBuilder = Builder( - builder: (context) => ListenableBuilder(listenable: focusNode, builder: (context, _) => child), + builder: (context) { + final focusNode = Focus.maybeOf(context); + if (focusNode == null) { + return child; + } + return ListenableBuilder(listenable: focusNode, builder: (context, _) => child); + }, ); if (!params.enabled) { return childBuilder; From 8ea96e96b51785bfa6c721a8d107710345728516 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 16:42:26 +0900 Subject: [PATCH 596/663] Release pdfrx v2.2.15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 37c2a52d..9e2d9873 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Add `pdfrx` to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.14 + pdfrx: ^2.2.15 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 9c91756e..7c0c061f 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.15 + +- FIXED: Focus context retrieval issue in [PdfViewerKeyHandler](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerKeyHandler-class.html) ([#518](https://github.com/espresso3389/pdfrx/issues/518)) + # 2.2.14 - Minor changes. diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index ed844515..06658f5d 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.14 + pdfrx: ^2.2.15 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 6e469ec0..b1753fe4 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.14 +version: 2.2.15 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 1eeff4c87e39d72b9ecfda0068d812445812e67c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 18 Nov 2025 16:43:03 +0900 Subject: [PATCH 597/663] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6ed509df..0e4acccb 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ pubspec_overrides.yaml **/macos/**/*.xcodeproj/project.pbxproj build/ios/ + +.flutter-plugins-dependencies From 5f09bb1516a66d427fe3141813f1cfb930ffac2d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 00:16:10 +0900 Subject: [PATCH 598/663] Removed: l10nDelegate --- packages/pdfrx/example/viewer/lib/main.dart | 17 ++++++++++ .../pdfrx/lib/src/widgets/pdf_viewer.dart | 19 +---------- .../lib/src/widgets/pdf_viewer_params.dart | 34 +++++-------------- 3 files changed, 27 insertions(+), 43 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 5f3f8420..a18e3bf5 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -453,6 +453,23 @@ class _MainPageState extends State with WidgetsBindingObserver, Single useAlternativeFitScaleAsMinScale: false, maxScale: 8, scrollPhysics: PdfViewerParams.getScrollPhysics(context), + customizeContextMenuItems: (params, items) { + // Example: add custom menu item to show page number + + items.add( + ContextMenuButtonItem( + type: ContextMenuButtonType.searchWeb, + onPressed: () async { + final text = await controller.textSelectionDelegate.getSelectedText(); + if (text.isNotEmpty && text.length < 100) { + final query = Uri.encodeComponent(text); + final url = Uri.parse('https://www.google.com/search?q=$query'); + await launchUrl(url); + } + }, + ), + ); + }, viewerOverlayBuilder: (context, size, handleLinkTap) => [ // // Example use of GestureDetector to handle custom gestures diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 652dbdbc..ba5669a7 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -444,6 +444,7 @@ class _PdfViewerState extends State color: widget.params.backgroundColor, child: PdfViewerKeyHandler( onKeyRepeat: _onKey, + // NOTE: When the PdfViewer gets focus, we report it to prevent the default context menu on Web browser. onFocusChange: (hasFocus) => focusReportForPreventingContextMenuWeb(this, hasFocus), params: widget.params.keyHandlerParams, child: StreamBuilder( @@ -2895,13 +2896,11 @@ class _PdfViewerState extends State params.textSelectionDelegate.hasSelectedText) ContextMenuButtonItem( onPressed: () => params.textSelectionDelegate.copyTextSelection(), - label: _l10n(PdfViewerL10nKey.copy), type: ContextMenuButtonType.copy, ), if (params.isTextSelectionEnabled && !params.textSelectionDelegate.isSelectingAllText) ContextMenuButtonItem( onPressed: () => params.textSelectionDelegate.selectAllText(), - label: _l10n(PdfViewerL10nKey.selectAll), type: ContextMenuButtonType.selectAll, ), ]; @@ -3276,22 +3275,6 @@ class _PdfViewerState extends State _magnifierImageCache.releaseAllImages(); _invalidate(); } - - /// Get the localized string for the given key. - /// - /// If a custom localization delegate is provided in the widget parameters, it will be used. - /// Otherwise, default English strings will be returned. - String _l10n(PdfViewerL10nKey key, [List? args]) { - var result = widget.params.l10nDelegate?.call(key, args); - if (result != null) return result; - - switch (key) { - case PdfViewerL10nKey.copy: - return 'Copy'; - case PdfViewerL10nKey.selectAll: - return 'Select All'; - } - } } class _PdfPageImageCache { diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 33186ea1..482ad4a6 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -70,7 +70,6 @@ class PdfViewerParams { this.onKey, this.keyHandlerParams = const PdfViewerKeyHandlerParams(), this.behaviorControlParams = const PdfViewerBehaviorControlParams(), - this.l10nDelegate, this.forceReload = false, this.scrollPhysics, this.scrollPhysicsScale, @@ -536,9 +535,6 @@ class PdfViewerParams { /// Parameters to control viewer behaviors. final PdfViewerBehaviorControlParams behaviorControlParams; - /// Delegate for localization. - final PdfViewerL10nDelegate? l10nDelegate; - /// Force reload the viewer. /// /// Normally whether to reload the viewer is determined by the changes of the parameters but @@ -671,7 +667,6 @@ class PdfViewerParams { other.onKey == onKey && other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && - other.l10nDelegate == l10nDelegate && other.forceReload == forceReload && other.scrollPhysics == scrollPhysics; } @@ -731,7 +726,6 @@ class PdfViewerParams { onKey.hashCode ^ keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ - l10nDelegate.hashCode ^ forceReload.hashCode ^ scrollPhysics.hashCode; } @@ -840,13 +834,11 @@ class PdfTextSelectionParams { /// params.textSelectionDelegate.hasSelectedText) /// ContextMenuButtonItem( /// onPressed: () => params.textSelectionDelegate.copyTextSelection(), -/// label: 'Copy', /// type: ContextMenuButtonType.copy, /// ), /// if (params.isTextSelectionEnabled && !params.textSelectionDelegate.isSelectingAllText) /// ContextMenuButtonItem( /// onPressed: () => params.textSelectionDelegate.selectAllText(), -/// label: 'Select All', /// type: ContextMenuButtonType.selectAll, /// ), /// ]; @@ -870,6 +862,15 @@ class PdfTextSelectionParams { /// See [PdfViewerParams.customizeContextMenuItems] for more. typedef PdfViewerContextMenuBuilder = Widget? Function(BuildContext context, PdfViewerContextMenuBuilderParams params); +/// Function to customize the context menu items. +/// +/// This function is called when the context menu is built and can be used to customize the context menu items. +/// This function may not be called if the context menu is build using [PdfViewerContextMenuBuilder]. +/// [PdfViewerContextMenuBuilder] is responsible for building the context menu items +/// (i.e. it should decide whether to call this function internally or not). +/// +/// - [params] contains the parameters for building the context menu. +/// - [items] is the list of context menu items to be customized. You can add, remove, or modify the items in this list. typedef PdfViewerContextMenuUpdateMenuItemsFunction = void Function(PdfViewerContextMenuBuilderParams params, List items); @@ -1675,20 +1676,3 @@ class PdfViewerBehaviorControlParams { pageImageCachingDelay.hashCode ^ partialImageLoadingDelay.hashCode; } - -/// Delegate for localization. -/// -/// The [key] is the localization key. See [PdfViewerL10nKey] for more details. -/// The [args] are the arguments for the localization string. -/// -/// If the function returns null, the default localization string will be used. -typedef PdfViewerL10nDelegate = String? Function(PdfViewerL10nKey key, [List? args]); - -/// Localization keys for the PDF viewer. -enum PdfViewerL10nKey { - /// "Copy" action label. - copy, - - /// "Select All" action label. - selectAll, -} From 754cb0459744926e92ba7df32b43d756fe883c8e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 00:19:46 +0900 Subject: [PATCH 599/663] Example: viewer now has search-web context menu --- packages/pdfrx/example/viewer/lib/main.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index a18e3bf5..5cfddb25 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -454,17 +454,17 @@ class _MainPageState extends State with WidgetsBindingObserver, Single maxScale: 8, scrollPhysics: PdfViewerParams.getScrollPhysics(context), customizeContextMenuItems: (params, items) { - // Example: add custom menu item to show page number - + // Example: add custom menu item to search selected text on web items.add( ContextMenuButtonItem( type: ContextMenuButtonType.searchWeb, onPressed: () async { final text = await controller.textSelectionDelegate.getSelectedText(); - if (text.isNotEmpty && text.length < 100) { - final query = Uri.encodeComponent(text); - final url = Uri.parse('https://www.google.com/search?q=$query'); - await launchUrl(url); + if (text.isNotEmpty) { + final shortened = text.length > 100 ? text.substring(0, 100) : text; + await launchUrl( + Uri.parse('https://www.google.com/search?q=${Uri.encodeComponent(shortened)}'), + ); } }, ), From 82e297f9d02f9a06836cb9559df78b089c40fcbc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 00:55:50 +0900 Subject: [PATCH 600/663] WIP: consistency updates for _pages use --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 16 ++++++++-------- .../lib/src/native/pdfrx_pdfium.dart | 14 +++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 066502f8..510765e5 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -419,7 +419,8 @@ class _PdfDocumentWasm extends PdfDocument { var firstPageIndex = pages.indexWhere((page) => !page.isLoaded); if (firstPageIndex < 0) return; // All pages are already loaded - for (; firstPageIndex < pages.length;) { + final newPages = pages.toList(growable: false); + for (; firstPageIndex < newPages.length;) { if (isDisposed) return; final result = await _sendCommand( 'loadPagesProgressively', @@ -432,13 +433,9 @@ class _PdfDocumentWasm extends PdfDocument { final pagesLoaded = parsePages(this, result['pages'] as List); firstPageIndex += pagesLoaded.length; for (final page in pagesLoaded) { - pages[page.pageNumber - 1] = page; // Update the existing page - } - - if (!subject.isClosed) { - final changes = {for (var p in pagesLoaded) p.pageNumber: PdfPageStatusModified()}; - subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); + newPages[page.pageNumber - 1] = page; // Update the existing page } + pages = newPages; updateMissingFonts(result['missingFonts']); @@ -452,6 +449,9 @@ class _PdfDocumentWasm extends PdfDocument { }); } + /// Don't handle [_pages] directly unless you really understand what you're doing; use [pages] getter/setter instead. + /// + /// [pages] automatically keeps consistency and also notifies page changes. late List _pages; @override @@ -486,7 +486,7 @@ class _PdfDocumentWasm extends PdfDocument { } } - _pages = pages; + _pages = List.unmodifiable(pages); subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 78a1943a..e88b978c 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -659,14 +659,7 @@ class _PdfDocumentPdfium extends PdfDocument { timeout: loadUnitDuration, ); if (isDisposed) return; - _pages = List.unmodifiable(loaded.pages); - - // notify pages changed - final changes = { - for (var p in _pages.skip(firstUnloadedPageIndex).take(_pages.length - firstUnloadedPageIndex)) - p.pageNumber: PdfPageStatusModified(), - }; - subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); + pages = loaded.pages; if (onPageLoadProgress != null) { final result = await onPageLoadProgress(loaded.pageCountLoadedTotal, loaded.pages.length, data); @@ -797,10 +790,13 @@ class _PdfDocumentPdfium extends PdfDocument { } } - _pages = pages; + _pages = List.unmodifiable(pages); subject.add(PdfDocumentPageStatusChangedEvent(this, changes: changes)); } + /// Don't handle [_pages] directly unless you really understand what you're doing; use [pages] getter/setter instead. + /// + /// [pages] automatically keeps consistency and also notifies page changes. List _pages = []; @override From 06d3525ba5fab58475e42a167e293645dc55686e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 02:28:19 +0900 Subject: [PATCH 601/663] - Introduces PdfPage helper functions to wait for page loaded. - PdfPage.loadStructuredText is now an extension method. --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 3 +- packages/pdfrx_engine/lib/src/pdf_page.dart | 91 +++++++++++++++++-- .../lib/src/pdf_page_proxies.dart | 8 -- 3 files changed, 83 insertions(+), 19 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index ba5669a7..e860660e 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -407,11 +407,12 @@ class _PdfViewerState extends State void _onDocumentEvent(PdfDocumentEvent event) { if (event is PdfDocumentPageStatusChangedEvent) { - // TODO: we can reuse images for moved pages + // FIXME: We can handle the event more efficiently by only updating the affected pages. for (final change in event.changes.entries) { _imageCache.removeCacheImagesForPage(change.key); _magnifierImageCache.removeCacheImagesForPage(change.key); } + // very conservative approach: just clear all caches; we can optimize this later _canvasLinkPainter.resetAll(); _textCache.clear(); _clearTextSelections(invalidate: false); diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index d38b2708..b8785351 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -1,7 +1,13 @@ +import 'dart:async'; + +import 'package:rxdart/rxdart.dart'; + import 'pdf_document.dart'; +import 'pdf_document_event.dart'; import 'pdf_image.dart'; import 'pdf_link.dart'; import 'pdf_page_proxies.dart'; +import 'pdf_page_status_change.dart'; import 'pdf_text.dart'; import 'pdf_text_formatter.dart'; @@ -27,7 +33,9 @@ abstract class PdfPage { /// Whether the page is really loaded or not. /// /// If the value is false, the page's [width], [height], and [rotation] are just guessed values and - /// will be updated when the page is really loaded. + /// will be updated when the page is really loaded (progressive loading case only). + /// + /// If you want to wait until the page is really loaded, use [PdfPageBaseExtensions.ensureLoaded]. bool get isLoaded; /// Render a sub-area or full image of specified PDF file. @@ -42,6 +50,9 @@ abstract class PdfPage { /// - [flags] is used to specify additional rendering flags. The default is [PdfPageRenderFlags.none]. /// - [cancellationToken] can be used to cancel the rendering process. It must be created by [createCancellationToken]. /// + /// If the page is not loaded yet (progressive loading case only), the function renders empty page with specified + /// background color. + /// /// The following code extract the area of (20,30)-(120,130) from the page image rendered at 1000x1500 pixels: /// ```dart /// final image = await page.render( @@ -70,17 +81,11 @@ abstract class PdfPage { /// Create [PdfPageRenderCancellationToken] to cancel the rendering process. PdfPageRenderCancellationToken createCancellationToken(); - /// Load structured text with character bounding boxes. - /// - /// The function internally does test flow analysis (reading order) and line segmentation to detect - /// text direction and line breaks. - /// - /// To access the raw text, use [loadText]. - Future loadStructuredText() => PdfTextFormatter.loadStructuredText(this, pageNumberOverride: pageNumber); - /// Load plain text for the page. /// - /// For text with character bounding boxes, use [loadStructuredText]. + /// For text with character bounding boxes, use [PdfPageBaseExtensions.loadStructuredText]. + /// + /// If the page is not loaded yet (progressive loading case only), this function returns null. Future loadText(); /// Load links. @@ -91,9 +96,75 @@ abstract class PdfPage { /// If [enableAutoLinkDetection] is true, the function tries to detect Web links automatically. /// This is useful if the PDF file contains text that looks like Web links but not defined as links in the PDF. /// The default is true. + /// + /// If the page is not loaded yet (progressive loading case only), this function returns an empty list. Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}); } +/// Extension methods for [PdfPage]. +extension PdfPageBaseExtensions on PdfPage { + /// Load structured text with character bounding boxes. + /// + /// The function internally does test flow analysis (reading order) and line segmentation to detect + /// text direction and line breaks. + /// + /// To access the raw text, use [loadText]. + /// + /// If the page is not loaded yet (progressive loading case only), this function returns null. + Future loadStructuredText({bool ensureLoaded = true}) => + PdfTextFormatter.loadStructuredText(this, pageNumberOverride: pageNumber); + + /// Stream of page status change events for this page. + /// + /// The event is based on the page position (page number), so the page instance identity may change if the page is + /// replaced by another instance with the same page number. + Stream get events { + return document.events + .where((event) => event is PdfDocumentPageStatusChangedEvent && event.changes.containsKey(pageNumber)) + .map((event) => (event as PdfDocumentPageStatusChangedEvent).changes[pageNumber]!); + } + + /// Stream of latest page instances when the page status changes. + /// + /// The page instance identity may change if the page is replaced by another instance with the same page number. + /// For example, when the page is loaded, the instance may be replaced with a fully loaded page instance. + /// This stream emits the latest instance of the page whenever a status change event occurs for this page. + /// Note that this stream may emit the same instance multiple times if the page is not replaced. + Stream get latestPageStream => + Stream.value(this).concatWith([events.map((event) => document.pages[pageNumber - 1])]); + + /// Wait until the page is really loaded. + /// + /// Returns the latest instance of the page once it is loaded. + /// If [timeout] is specified, it returns null if the page is not loaded within the duration. otherwise, + /// it waits indefinitely and never returns null. + Future ensureLoaded({Duration? timeout}) async { + final newPage = document.pages[pageNumber - 1]; + if (newPage.isLoaded) { + return newPage; + } + final completer = Completer(); + late StreamSubscription subscription; + subscription = events.listen((event) { + final newPage = document.pages[pageNumber - 1]; + if (newPage.isLoaded) { + subscription.cancel(); + completer.complete(newPage); // get the latest instance + } + }); + if (timeout != null) { + return completer.future.timeout( + timeout, + onTimeout: () { + subscription.cancel(); + return null; + }, + ); + } + return completer.future; + } +} + /// Extension to add rotation capability to [PdfPage]. /// /// Use these functions to create rotated pages when reorganizing or combining PDFs. diff --git a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart index cd462521..eb0fa5d6 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page_proxies.dart @@ -3,7 +3,6 @@ import 'pdf_image.dart'; import 'pdf_link.dart'; import 'pdf_page.dart'; import 'pdf_text.dart'; -import 'pdf_text_formatter.dart'; /// Proxy interface for [PdfPage]. /// @@ -104,9 +103,6 @@ class PdfPageRenumbered implements PdfPageProxy { cancellationToken: cancellationToken, rotationOverride: rotationOverride, ); - - @override - Future loadStructuredText() => PdfTextFormatter.loadStructuredText(this, pageNumberOverride: pageNumber); } /// PDF page wrapper that applies an absolute rotation to the base page. @@ -183,8 +179,4 @@ class PdfPageRotated implements PdfPageProxy { @override Future> loadLinks({bool compact = false, bool enableAutoLinkDetection = true}) => basePage.loadLinks(compact: compact, enableAutoLinkDetection: enableAutoLinkDetection); - - // Text methods don't depend on rotation - just delegate to source - @override - Future loadStructuredText() => basePage.loadStructuredText(); } From 27cf99d2f090d8e9f3f86913021c67dd8c16b1ed Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 03:07:27 +0900 Subject: [PATCH 602/663] PdfPageStatusChange now has page parameter to notify the new page. --- doc/Progressive-Loading.md | 401 ++++++++++++++++++ doc/README.md | 1 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 7 +- .../lib/pdfrx_coregraphics.dart | 6 +- .../lib/src/native/pdfrx_pdfium.dart | 7 +- packages/pdfrx_engine/lib/src/pdf_page.dart | 19 +- .../lib/src/pdf_page_status_change.dart | 23 +- 7 files changed, 447 insertions(+), 17 deletions(-) create mode 100644 doc/Progressive-Loading.md diff --git a/doc/Progressive-Loading.md b/doc/Progressive-Loading.md new file mode 100644 index 00000000..0490640d --- /dev/null +++ b/doc/Progressive-Loading.md @@ -0,0 +1,401 @@ +# Progressive Loading + +Progressive loading is a feature that allows PDF documents to be loaded page-by-page instead of loading all pages at once. This is particularly useful for large PDF files, as it significantly reduces initial load time and memory usage. + +## Overview + +When you open a PDF document, pdfrx can operate in two modes: + +1. **Standard Loading** (default): All pages are loaded immediately when the document is opened +2. **Progressive Loading**: Only the first page is loaded initially, and additional pages are loaded on-demand + +Progressive loading is especially beneficial when: + +- Working with large PDF files (hundreds of pages) +- Memory is constrained +- You want faster initial document load times +- Users typically don't view all pages in a session + +**Note**: [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) uses progressive loading by default, while [`PdfDocument`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) requires explicit opt-in. + +## Understanding Page Loading States + +When progressive loading is enabled, pages can be in one of two states: + +### Loaded Pages + +Pages that are fully loaded have complete information: + +- Accurate [`width`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/width.html), [`height`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/height.html), and [`rotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/rotation.html) values +- Can be rendered properly +- Text extraction works ([`loadText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html), [`loadStructuredText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/loadStructuredText.html)) +- Link extraction works ([`loadLinks()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html)) +- [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) property returns `true` + +### Unloaded Pages (Progressive Loading Only) + +Pages that haven't been loaded yet have limited functionality: + +- [`width`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/width.html), [`height`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/height.html), and [`rotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/rotation.html) are **estimated values** (may be incorrect) +- Rendering produces an empty page with the specified background color +- Text extraction returns `null` +- Link extraction returns an empty list +- [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) property returns `false` + +## Enabling Progressive Loading + +### For PdfViewer + +[`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) uses progressive loading **by default** (`useProgressiveLoading: true`). This means PDF documents are loaded page-by-page automatically as you scroll, providing optimal performance for large files. + +All [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) constructors support the `useProgressiveLoading` parameter: + +```dart +// Uses progressive loading by default +PdfViewer.file('path/to/document.pdf') + +// Explicitly enable progressive loading (same as default) +PdfViewer.asset( + 'assets/large-document.pdf', + useProgressiveLoading: true, +) + +// Disable progressive loading (load all pages at once) +PdfViewer.uri( + Uri.parse('https://example.com/document.pdf'), + useProgressiveLoading: false, +) +``` + +When using [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html), progressive loading happens automatically in the background as you scroll through the document. You don't need to manually call [`loadPagesProgressively()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/loadPagesProgressively.html). + +### For PdfDocument (Engine-Level API) + +When using [`PdfDocument`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) directly (without [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html)), progressive loading is **disabled by default** (`useProgressiveLoading: false`). You need to explicitly enable it: + +```dart +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +// Open a document with progressive loading enabled +final document = await PdfDocument.openFile( + 'path/to/document.pdf', + useProgressiveLoading: true, +); + +// At this point, only the first page is loaded +print('First page loaded: ${document.pages[0].isLoaded}'); // true +print('Second page loaded: ${document.pages[1].isLoaded}'); // false +``` + +## Working with Progressively Loaded Documents + +### Checking Page Load Status + +Use the [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) property to check if a page is fully loaded: + +```dart +final page = document.pages[5]; + +if (page.isLoaded) { + // Page is fully loaded - all operations work normally + final text = await page.loadText(); + print(text?.text); +} else { + // Page is not loaded yet - dimensions may be estimates + print('Page not loaded yet'); +} +``` + +### Waiting for a Page to Load + +Use the [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) extension method to wait for a specific page to load. This method waits indefinitely and always returns a loaded page: + +```dart +final page = document.pages[10]; + +// Wait for the page to load (waits indefinitely, never returns null) +final loadedPage = await page.ensureLoaded(); +final text = await loadedPage.loadText(); +print(text?.text); +``` + +If you need to set a timeout, use [`waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) instead. This method returns `null` if the timeout occurs: + +```dart +final page = document.pages[10]; + +// Wait for the page to load with a timeout +final loadedPage = await page.waitForLoaded( + timeout: Duration(seconds: 5), +); + +if (loadedPage != null) { + // Page loaded successfully + final text = await loadedPage.loadText(); + print(text?.text); +} else { + // Timeout occurred + print('Page failed to load within timeout'); +} +``` + +**Important**: The [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) method may return a **different instance** of [`PdfPage`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) than the original. Always use the returned instance: + +```dart +// ❌ WRONG - using the old page instance +final page = document.pages[10]; +await page.ensureLoaded(); +final text = await page.loadText(); // May not work as expected + +// ✅ CORRECT - using the returned loaded page instance +final page = document.pages[10]; +final loadedPage = await page.ensureLoaded(); +final text = await loadedPage.loadText(); // Works correctly +``` + +### Monitoring Page Load Events + +You can listen to page status changes using the [`events`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/events.html) stream. The event provides the latest page instance directly via the `page` property: + +```dart +final page = document.pages[5]; + +// Listen for status changes on this specific page +page.events.listen((change) { + // The change.page property provides the newest page instance + final updatedPage = change.page; + print('Page ${updatedPage.pageNumber} status changed'); + print('Is loaded: ${updatedPage.isLoaded}'); + + if (updatedPage.isLoaded) { + print('Page dimensions: ${updatedPage.width} x ${updatedPage.height}'); + } +}); +``` + +### Getting Latest Page Instance + +The [`latestPageStream`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/latestPageStream.html) provides the most recent page instance whenever the page status changes: + +```dart +final page = document.pages[10]; + +page.latestPageStream.listen((latestPage) { + print('Page updated, isLoaded: ${latestPage.isLoaded}'); + if (latestPage.isLoaded) { + // Use the latest loaded instance + } +}); +``` + +## Manually Triggering Progressive Loading + +When using [`PdfDocument`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) directly (not [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html)), you need to manually trigger progressive loading using [`loadPagesProgressively()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/loadPagesProgressively.html): + +```dart +final document = await PdfDocument.openFile( + 'path/to/document.pdf', + useProgressiveLoading: true, +); + +// Load pages progressively with progress callback +await document.loadPagesProgressively( + onPageLoadProgress: (data, loadedPageCount, totalPageCount) { + print('Loaded $loadedPageCount of $totalPageCount pages'); + + // Return true to continue loading, false to stop + return true; + }, + loadUnitDuration: Duration(milliseconds: 250), +); +``` + +The callback is invoked periodically (every `loadUnitDuration`) as pages are loaded. Return `false` from the callback to stop the loading process early. + +## Common Pitfalls and Solutions + +### Issue: Text Extraction Returns Null + +**Problem**: Trying to extract text from an unloaded page returns `null`. + +```dart +final page = document.pages[50]; +final text = await page.loadText(); // Returns null if page not loaded +``` + +**Solution**: Always wait for the page to load first: + +```dart +final page = document.pages[50]; +final loadedPage = await page.ensureLoaded(); +final text = await loadedPage.loadText(); +print(text?.text); +``` + +### Issue: Page Dimensions Are Incorrect + +**Problem**: Using [`width`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/width.html), [`height`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/height.html), or [`rotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/rotation.html) values from unloaded pages gives estimated values that may be wrong. + +```dart +final page = document.pages[20]; +print('Width: ${page.width}'); // May be an estimate if page is not loaded +``` + +**Solution**: Check [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) or use [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html): + +```dart +final page = document.pages[20]; +final loadedPage = await page.ensureLoaded(); +print('Actual width: ${loadedPage.width}'); +print('Actual height: ${loadedPage.height}'); +``` + +### Issue: Processing All Pages Fails Silently + +**Problem**: Iterating through all pages without waiting for them to load: + +```dart +// ❌ WRONG - pages may not be loaded yet +for (final page in document.pages) { + final text = await page.loadText(); // May return null + processText(text?.text); // Silently skips unloaded pages +} +``` + +**Solution**: Ensure pages are loaded before processing: + +```dart +// ✅ CORRECT - ensure each page is loaded +for (final page in document.pages) { + final loadedPage = await page.ensureLoaded(); + final text = await loadedPage.loadText(); + processText(text?.text); +} + +// Alternative: Load all pages first using loadPagesProgressively() +await document.loadPagesProgressively(); +for (final page in document.pages) { + final text = await page.loadText(); + processText(text?.text); +} +``` + +## Performance Considerations + +### When to Use Progressive Loading + +**Use progressive loading when:** + +- Working with large PDFs (100+ pages) +- Initial load time is critical +- Users typically view only a few pages +- Memory usage is a concern +- Loading from network (reduces initial bandwidth) + +**Avoid progressive loading when:** + +- Working with small PDFs (< 20 pages) +- You need to process all pages immediately +- All pages will be accessed anyway +- Simplicity is preferred over optimization + +### Memory Management + +Progressive loading reduces initial memory usage but doesn't automatically unload pages. Once a page is loaded, it stays in memory until the document is disposed. For very large documents, consider: + +- Loading and processing pages in batches +- Disposing and reopening the document periodically if processing thousands of pages +- Using [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) which handles page lifecycle automatically + +## API Reference + +### PdfPage Properties and Methods + +- [`PdfPage`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation class +- [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) - Check if page is fully loaded +- [`width`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/width.html) - Page width in points +- [`height`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/height.html) - Page height in points +- [`rotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/rotation.html) - Page rotation +- [`render()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap +- [`loadText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content +- [`loadLinks()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html) - Extract links + +### PdfPageBaseExtensions Methods + +- [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) - Wait for page to load (waits indefinitely, never returns null) +- [`waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) - Wait for page to load with optional timeout (may return null on timeout) +- [`loadStructuredText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/loadStructuredText.html) - Extract structured text with bounding boxes +- [`events`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/events.html) - Stream of page status changes (events include the newest page instance) +- [`latestPageStream`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/latestPageStream.html) - Stream of latest page instances + +### PdfPageStatusChange + +- [`PdfPageStatusChange`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange-class.html) - Base class for page status change events +- [`page`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/page.html) - The newest instance of the page after the change +- [`type`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/type.html) - Type of status change (moved or modified) + +### PdfDocument Methods + +- [`PdfDocument`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document class +- [`openFile()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) - Open PDF from file +- [`openAsset()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openAsset.html) - Open PDF from asset +- [`openData()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory +- [`loadPagesProgressively()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/loadPagesProgressively.html) - Manually trigger progressive loading +- [`events`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/events.html) - Stream of document-level events +- [`pages`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/pages.html) - List of pages + +### PdfViewer Class + +- [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) - Flutter PDF viewer widget +- [`PdfViewer.file()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html) - Load from file +- [`PdfViewer.asset()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html) - Load from asset +- [`PdfViewer.uri()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - Load from URI +- [`PdfViewer.data()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.data.html) - Load from memory + +## Example: Processing Pages with Progress Indicator + +Here's a complete example showing how to process all pages in a large PDF with a progress indicator: + +```dart +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +Future processPdfPages(String filePath) async { + // Open document with progressive loading + final document = await PdfDocument.openFile( + filePath, + useProgressiveLoading: true, + ); + + try { + // Load pages progressively with progress reporting + await document.loadPagesProgressively( + onPageLoadProgress: (_, loadedCount, totalCount) { + final progress = (loadedCount / totalCount * 100).toStringAsFixed(1); + print('Loading pages: $progress% ($loadedCount/$totalCount)'); + return true; // Continue loading + }, + ); + + // Now all pages are loaded, safe to process + for (int i = 0; i < document.pages.length; i++) { + final page = document.pages[i]; + + // Extract text from page + final text = await page.loadText(); + print('Page ${i + 1}: ${text?.text.substring(0, 100)}...'); + + // Extract links + final links = await page.loadLinks(); + print('Page ${i + 1} has ${links.length} links'); + } + } finally { + await document.dispose(); + } +} +``` + +## Related Documentation + +- [Document Loading Indicator](Document-Loading-Indicator.md) - Show loading progress in UI +- [Low-Level PDFium Bindings Access](Low-Level-PDFium-Bindings-Access.md) - Advanced PDFium usage +- [pdfrx Initialization](pdfrx-Initialization.md) - Setting up pdfrx diff --git a/doc/README.md b/doc/README.md index 3c69b308..812ce55f 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,6 +5,7 @@ Welcome to the pdfrx documentation! This guide provides comprehensive informatio ## Getting Started - [pdfrx Initialization](pdfrx-Initialization.md) - How to properly initialize pdfrx in your app +- [Progressive Loading](Progressive-Loading.md) - Understanding and using progressive page loading for large PDFs ## Core Features diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 510765e5..82ad7ba4 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -476,13 +476,14 @@ class _PdfDocumentWasm extends PdfDocument { } final newPageNumber = pages.length + 1; - pages.add(newPage.withPageNumber(newPageNumber)); + final updated = newPage.withPageNumber(newPageNumber); + pages.add(updated); final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); if (oldPageIndex != -1) { - changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); + changes[newPageNumber] = PdfPageStatusChange.moved(page: updated, oldPageNumber: oldPageIndex + 1); } else { - changes[newPageNumber] = PdfPageStatusChange.modified; + changes[newPageNumber] = PdfPageStatusChange.modified(page: updated); } } diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index f4bd2f03..e383a65f 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -467,15 +467,17 @@ class _CoreGraphicsPdfDocument extends PdfDocument { } final newPageNumber = pages.length + 1; - pages.add(newPage.withPageNumber(newPageNumber)); + final updated = newPage.withPageNumber(newPageNumber); + pages.add(updated); final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); if (oldPageIndex != -1) { changes[newPageNumber] = PdfPageStatusChange.moved( + page: updated, oldPageNumber: oldPageIndex + 1, ); } else { - changes[newPageNumber] = PdfPageStatusChange.modified; + changes[newPageNumber] = PdfPageStatusChange.modified(page: updated); } } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index e88b978c..bb0c66fa 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -780,13 +780,14 @@ class _PdfDocumentPdfium extends PdfDocument { } final newPageNumber = pages.length + 1; - pages.add(newPage.withPageNumber(newPageNumber)); + final updated = newPage.withPageNumber(newPageNumber); + pages.add(updated); final oldPageIndex = _pages.indexWhere((p) => identical(p, newPage)); if (oldPageIndex != -1) { - changes[newPageNumber] = PdfPageStatusChange.moved(oldPageNumber: oldPageIndex + 1); + changes[newPageNumber] = PdfPageStatusChange.moved(page: updated, oldPageNumber: oldPageIndex + 1); } else { - changes[newPageNumber] = PdfPageStatusChange.modified; + changes[newPageNumber] = PdfPageStatusChange.modified(page: updated); } } diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index b8785351..4095df18 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -35,7 +35,8 @@ abstract class PdfPage { /// If the value is false, the page's [width], [height], and [rotation] are just guessed values and /// will be updated when the page is really loaded (progressive loading case only). /// - /// If you want to wait until the page is really loaded, use [PdfPageBaseExtensions.ensureLoaded]. + /// If you want to wait until the page is really loaded, use [PdfPageBaseExtensions.ensureLoaded] or + /// [PdfPageBaseExtensions.waitForLoaded]. bool get isLoaded; /// Render a sub-area or full image of specified PDF file. @@ -133,12 +134,21 @@ extension PdfPageBaseExtensions on PdfPage { Stream get latestPageStream => Stream.value(this).concatWith([events.map((event) => document.pages[pageNumber - 1])]); + /// Ensure the page is really loaded. + /// + /// Returns the latest instance of the page once it is loaded. + /// + /// If you want to specify a timeout, use [waitForLoaded] instead. + Future ensureLoaded() async { + return (await waitForLoaded())!; + } + /// Wait until the page is really loaded. /// /// Returns the latest instance of the page once it is loaded. /// If [timeout] is specified, it returns null if the page is not loaded within the duration. otherwise, /// it waits indefinitely and never returns null. - Future ensureLoaded({Duration? timeout}) async { + Future waitForLoaded({Duration? timeout}) async { final newPage = document.pages[pageNumber - 1]; if (newPage.isLoaded) { return newPage; @@ -146,10 +156,9 @@ extension PdfPageBaseExtensions on PdfPage { final completer = Completer(); late StreamSubscription subscription; subscription = events.listen((event) { - final newPage = document.pages[pageNumber - 1]; - if (newPage.isLoaded) { + if (event.page.isLoaded) { subscription.cancel(); - completer.complete(newPage); // get the latest instance + completer.complete(event.page); // get the latest instance } }); if (timeout != null) { diff --git a/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart index 41e6c20f..40f2d3d1 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page_status_change.dart @@ -1,3 +1,5 @@ +import 'pdf_page.dart'; + /// Enum representing the type of PDF page status change. enum PdfPageStatusChangeType { /// Page has been moved inside the same document. @@ -14,16 +16,26 @@ abstract class PdfPageStatusChange { /// Type of the status change. PdfPageStatusChangeType get type; + /// The page that has changed. + /// + /// This is a new instance of the page after the change. + PdfPage get page; + /// Create [PdfPageStatusMoved]. - static PdfPageStatusChange moved({required int oldPageNumber}) => PdfPageStatusMoved(oldPageNumber: oldPageNumber); + static PdfPageStatusChange moved({required PdfPage page, required int oldPageNumber}) => + PdfPageStatusMoved(page: page, oldPageNumber: oldPageNumber); /// Return [PdfPageStatusModified]. - static const modified = PdfPageStatusModified(); + static PdfPageStatusChange modified({required PdfPage page}) => PdfPageStatusModified(page: page); } /// Event that is triggered when a PDF page is moved inside the same document. class PdfPageStatusMoved extends PdfPageStatusChange { - const PdfPageStatusMoved({required this.oldPageNumber}); + const PdfPageStatusMoved({required this.page, required this.oldPageNumber}); + + @override + final PdfPage page; + final int oldPageNumber; @override @@ -44,7 +56,10 @@ class PdfPageStatusMoved extends PdfPageStatusChange { /// Event that is triggered when a PDF page is modified or newly added. class PdfPageStatusModified extends PdfPageStatusChange { - const PdfPageStatusModified(); + const PdfPageStatusModified({required this.page}); + + @override + final PdfPage page; @override PdfPageStatusChangeType get type => PdfPageStatusChangeType.modified; From f621e619144c950b409c9bba7ee96c62e4f03771 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 03:09:51 +0900 Subject: [PATCH 603/663] Remove API refs. from progressive loading doc. --- doc/Progressive-Loading.md | 45 -------------------------------------- 1 file changed, 45 deletions(-) diff --git a/doc/Progressive-Loading.md b/doc/Progressive-Loading.md index 0490640d..d3c85054 100644 --- a/doc/Progressive-Loading.md +++ b/doc/Progressive-Loading.md @@ -307,51 +307,6 @@ Progressive loading reduces initial memory usage but doesn't automatically unloa - Disposing and reopening the document periodically if processing thousands of pages - Using [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) which handles page lifecycle automatically -## API Reference - -### PdfPage Properties and Methods - -- [`PdfPage`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) - Page representation class -- [`isLoaded`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/isLoaded.html) - Check if page is fully loaded -- [`width`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/width.html) - Page width in points -- [`height`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/height.html) - Page height in points -- [`rotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/rotation.html) - Page rotation -- [`render()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap -- [`loadText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content -- [`loadLinks()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html) - Extract links - -### PdfPageBaseExtensions Methods - -- [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) - Wait for page to load (waits indefinitely, never returns null) -- [`waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) - Wait for page to load with optional timeout (may return null on timeout) -- [`loadStructuredText()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/loadStructuredText.html) - Extract structured text with bounding boxes -- [`events`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/events.html) - Stream of page status changes (events include the newest page instance) -- [`latestPageStream`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/latestPageStream.html) - Stream of latest page instances - -### PdfPageStatusChange - -- [`PdfPageStatusChange`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange-class.html) - Base class for page status change events -- [`page`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/page.html) - The newest instance of the page after the change -- [`type`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/type.html) - Type of status change (moved or modified) - -### PdfDocument Methods - -- [`PdfDocument`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document class -- [`openFile()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) - Open PDF from file -- [`openAsset()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openAsset.html) - Open PDF from asset -- [`openData()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory -- [`loadPagesProgressively()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/loadPagesProgressively.html) - Manually trigger progressive loading -- [`events`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/events.html) - Stream of document-level events -- [`pages`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/pages.html) - List of pages - -### PdfViewer Class - -- [`PdfViewer`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) - Flutter PDF viewer widget -- [`PdfViewer.file()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html) - Load from file -- [`PdfViewer.asset()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html) - Load from asset -- [`PdfViewer.uri()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - Load from URI -- [`PdfViewer.data()`](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.data.html) - Load from memory - ## Example: Processing Pages with Progress Indicator Here's a complete example showing how to process all pages in a large PDF with a progress indicator: From 814912da9fee08201e620574d7e100879f40bf8e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:12:29 +0900 Subject: [PATCH 604/663] Release pdfrx 2.2.16, pdfrx_engine 0.3.4, pdfrx_coregraphics 0.1.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NEW: Progressive loading helper functions - ensureLoaded() and waitForLoaded() - NEW: PdfPageStatusChange.page property for easier access to updated page instances - NEW: Added comprehensive Progressive Loading documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfrx/CHANGELOG.md | 7 +++++++ packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_coregraphics/CHANGELOG.md | 6 ++++++ packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 8 ++++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 7c0c061f..7a2147a8 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.2.16 + +- Updated to pdfrx_engine 0.3.4 +- NEW: Progressive loading helper functions - [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) and [`waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) +- NEW: [`PdfPageStatusChange.page`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/page.html) property for easier access to updated page instances +- NEW: Added comprehensive [Progressive Loading documentation](https://github.com/espresso3389/pdfrx/blob/master/doc/Progressive-Loading.md) + # 2.2.15 - FIXED: Focus context retrieval issue in [PdfViewerKeyHandler](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerKeyHandler-class.html) ([#518](https://github.com/espresso3389/pdfrx/issues/518)) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b1753fe4..7e31c626 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.15 +version: 2.2.16 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.3 + pdfrx_engine: ^0.3.4 pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 729ca6f1..70227a84 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.9 + +- Updated to pdfrx_engine 0.3.4 +- NEW: Progressive loading helper functions - [`ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) and [`waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) +- NEW: [`PdfPageStatusChange.page`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/page.html) property for easier access to updated page instances + ## 0.1.8 - Updated to pdfrx_engine 0.3.0 diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index c31e476e..280a10e7 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.8 +version: 0.1.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.3.0 + pdfrx_engine: ^0.3.4 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 307e07ae..e0c58b4b 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.3.4 + +- NEW: Added [`PdfPageBaseExtensions.ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) - Wait for page to load (waits indefinitely, never returns null) +- NEW: Added [`PdfPageBaseExtensions.waitForLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/waitForLoaded.html) - Wait for page to load with optional timeout (may return null on timeout) +- NEW: [`PdfPageStatusChange.page`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageStatusChange/page.html) property now provides the newest page instance directly +- NEW: Added comprehensive [Progressive Loading documentation](https://github.com/espresso3389/pdfrx/blob/master/doc/Progressive-Loading.md) +- IMPROVED: Better API for progressive loading - `ensureLoaded()` simplifies common use cases, `waitForLoaded()` for timeout scenarios + ## 0.3.3 - Code refactoring and maintenance updates. diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index fc6b3533..ff2b0c37 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.3 +version: 0.3.4 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 3464ac14da896635c0e5f56aa4fc07aa670b27b7 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:12:57 +0900 Subject: [PATCH 605/663] WIP --- packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 482ad4a6..77d07678 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -1424,9 +1424,10 @@ class PdfViewerGeneralTapHandlerDetails { /// Function to build page overlays. /// -/// [pageRect] is the rectangle of the page in the viewer. +/// [pageRectInViewer] is the rectangle of the page in the viewer; it represents where the page is drawn in the viewer and +/// not the page size in the document. /// [page] is the page. -typedef PdfPageOverlaysBuilder = List Function(BuildContext context, Rect pageRect, PdfPage page); +typedef PdfPageOverlaysBuilder = List Function(BuildContext context, Rect pageRectInViewer, PdfPage page); /// Function to build loading banner. /// From 84a22b7f4046d1db30d5a9b6f4f6e271aaacca8b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:15:16 +0900 Subject: [PATCH 606/663] WIP: PdfViewer.network??? --- packages/pdfrx/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 06658f5d..a0c1a9ef 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -138,7 +138,7 @@ For opening PDF files from various sources, there are several constructors avail - [PdfViewer.asset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html) - Load from Flutter assets - [PdfViewer.file](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html) - Load from local file - [PdfViewer.data](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.data.html) - Load from memory (Uint8List) -- [PdfViewer.network](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - Load from network URL +- [PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) - Load from network URL ## Customizations/Features From a0d296a8064a867399c46d39dd9584e7516043d0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:25:43 +0900 Subject: [PATCH 607/663] WIP --- README.md | 59 ++++++++++++++++++------------- packages/pdfium_dart/README.md | 23 ++++++------ packages/pdfium_flutter/README.md | 16 ++++----- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 9e2d9873..97ade28f 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ This repository contains multiple Dart/Flutter packages for PDF rendering, viewi ## Packages -### [pdfrx_engine](packages/pdfrx_engine/) +### [pdfrx_engine](https://pub.dev/packages/pdfrx_engine) -A platform-agnostic PDF rendering and manipulation API built on top of PDFium. +A platform-agnostic PDF rendering and manipulation API built on top of [PDFium](https://pdfium.googlesource.com/pdfium/). - Pure Dart package (no Flutter dependencies) - Provides low-level PDF document API for viewing and editing @@ -14,56 +14,66 @@ A platform-agnostic PDF rendering and manipulation API built on top of PDFium. - Can be used in CLI applications or non-Flutter Dart projects - Supports all platforms: Android, iOS, Windows, macOS, Linux -### [pdfrx](packages/pdfrx/) +[View documentation](packages/pdfrx_engine/) | [API reference](https://pub.dev/documentation/pdfrx_engine/latest/) + +### [pdfrx](https://pub.dev/packages/pdfrx) A cross-platform PDF viewer and manipulation plugin for Flutter. - Flutter plugin with UI widgets -- Built on top of pdfrx_engine +- Built on top of [pdfrx_engine](https://pub.dev/packages/pdfrx_engine) - Provides high-level viewer widgets and overlays - Includes text selection, search, zoom controls, and more - Supports PDF editing features like page manipulation, document combining, and image import -### [pdfrx_coregraphics](packages/pdfrx_coregraphics/) +[View documentation](packages/pdfrx/) | [API reference](https://pub.dev/documentation/pdfrx/latest/) + +### [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) -**⚠️ EXPERIMENTAL** - CoreGraphics-backed renderer for pdfrx on iOS/macOS. +**⚠️ EXPERIMENTAL** - CoreGraphics-backed renderer for [pdfrx](https://pub.dev/packages/pdfrx) on iOS/macOS. -- Uses PDFKit/CoreGraphics instead of PDFium on Apple platforms +- Uses PDFKit/CoreGraphics instead of [PDFium](https://pdfium.googlesource.com/pdfium/) on Apple platforms - Drop-in replacement for teams preferring the system PDF stack -- Maintains full compatibility with pdfrx widget API +- Maintains full compatibility with [pdfrx](https://pub.dev/packages/pdfrx) widget API - iOS and macOS only -### [pdfium_dart](packages/pdfium_dart/) +[View documentation](packages/pdfrx_coregraphics/) | [API reference](https://pub.dev/documentation/pdfrx_coregraphics/latest/) + +### [pdfium_dart](https://pub.dev/packages/pdfium_dart) -Low-level Dart FFI bindings for the PDFium library. +Low-level Dart FFI bindings for the [PDFium](https://pdfium.googlesource.com/pdfium/) library. -- Pure Dart package with auto-generated FFI bindings using `ffigen` +- Pure Dart package with auto-generated FFI bindings using [ffigen](https://pub.dev/packages/ffigen) - Provides direct access to PDFium's C API from Dart -- Includes `getPdfium()` function that downloads PDFium binaries on demand +- Includes [getPdfium()](https://pub.dev/documentation/pdfium_dart/latest/pdfium_dart/getPdfium.html) function that downloads PDFium binaries on demand - Used as a foundation by higher-level packages -### [pdfium_flutter](packages/pdfium_flutter/) +[View documentation](packages/pdfium_dart/) | [API reference](https://pub.dev/documentation/pdfium_dart/latest/) -Flutter FFI plugin for loading PDFium native libraries. +### [pdfium_flutter](https://pub.dev/packages/pdfium_flutter) + +Flutter FFI plugin for loading [PDFium](https://pdfium.googlesource.com/pdfium/) native libraries. - Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux) - Provides utilities for loading PDFium at runtime -- Re-exports `pdfium_dart` FFI bindings +- Re-exports [pdfium_dart](https://pub.dev/packages/pdfium_dart) FFI bindings - Simplifies PDFium integration in Flutter applications +[View documentation](packages/pdfium_flutter/) | [API reference](https://pub.dev/documentation/pdfium_flutter/latest/) + ## When to Use Which Package -- **Use `pdfrx`** if you're building a Flutter application and need PDF viewing and manipulation capabilities with UI -- **Use `pdfrx_engine`** if you need PDF rendering and manipulation without Flutter dependencies (e.g., server-side PDF processing, CLI tools, PDF combining utilities) -- **Use `pdfrx_coregraphics`** (experimental) if you want to use CoreGraphics/PDFKit instead of PDFium on iOS/macOS -- **Use `pdfium_dart`** if you need low-level PDFium FFI bindings for Dart projects or want on-demand PDFium binary downloads -- **Use `pdfium_flutter`** if you're building a Flutter plugin that needs PDFium integration with bundled binaries +- **Use [pdfrx](https://pub.dev/packages/pdfrx)** if you're building a Flutter application and need PDF viewing and manipulation capabilities with UI +- **Use [pdfrx_engine](https://pub.dev/packages/pdfrx_engine)** if you need PDF rendering and manipulation without Flutter dependencies (e.g., server-side PDF processing, CLI tools, PDF combining utilities) +- **Use [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics)** (experimental) if you want to use CoreGraphics/PDFKit instead of [PDFium](https://pdfium.googlesource.com/pdfium/) on iOS/macOS +- **Use [pdfium_dart](https://pub.dev/packages/pdfium_dart)** if you need low-level PDFium FFI bindings for Dart projects or want on-demand PDFium binary downloads +- **Use [pdfium_flutter](https://pub.dev/packages/pdfium_flutter)** if you're building a Flutter plugin that needs PDFium integration with bundled binaries ## Getting Started ### For Flutter Applications -Add `pdfrx` to your `pubspec.yaml`: +Add [pdfrx](https://pub.dev/packages/pdfrx) to your `pubspec.yaml`: ```yaml dependencies: @@ -72,7 +82,7 @@ dependencies: ### For Pure Dart Applications -Add `pdfrx_engine` to your `pubspec.yaml`: +Add [pdfrx_engine](https://pub.dev/packages/pdfrx_engine) to your `pubspec.yaml`: ```yaml dependencies: @@ -82,6 +92,7 @@ dependencies: ## Documentation Comprehensive documentation is available in the [doc/](doc/) directory, including: + - Getting started guides - Feature tutorials - Platform-specific configurations @@ -95,7 +106,7 @@ This is a monorepo managed with pub workspaces. Just do `dart pub get` on some d ### PDF Viewer -The example viewer application is located in [packages/pdfrx/example/viewer/](packages/pdfrx/example/viewer/). It demonstrates the full capabilities of the pdfrx Flutter plugin. +The example viewer application is located in [packages/pdfrx/example/viewer/](packages/pdfrx/example/viewer/). It demonstrates the full capabilities of the [pdfrx](https://pub.dev/packages/pdfrx) Flutter plugin. ```bash cd packages/pdfrx/example/viewer @@ -122,4 +133,4 @@ Contributions are welcome! Please read the individual package READMEs for specif ## License -This project is licensed under the MIT License - see the LICENSE file for details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/packages/pdfium_dart/README.md b/packages/pdfium_dart/README.md index 46742bca..ac1690bc 100644 --- a/packages/pdfium_dart/README.md +++ b/packages/pdfium_dart/README.md @@ -2,15 +2,18 @@ Dart FFI bindings for the PDFium library. This package provides low-level access to PDFium's C API from Dart. +This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. + ## Overview -This package contains auto-generated FFI bindings for PDFium using `ffigen`. It is designed to be a minimal, pure Dart package that other packages can depend on to access PDFium functionality. +This package contains auto-generated FFI bindings for PDFium using [ffigen](https://pub.dev/packages/ffigen). It is designed to be a minimal, pure Dart package that other packages can depend on to access PDFium functionality. **Key Features:** + - Pure Dart package with no Flutter dependencies -- Auto-generated FFI bindings using `ffigen` +- Auto-generated FFI bindings using [ffigen](https://pub.dev/packages/ffigen) - Provides direct access to PDFium's C API -- Includes `getPdfium()` function for on-demand PDFium binary downloads +- Includes [getPdfium()](https://pub.dev/documentation/pdfium_dart/latest/pdfium_dart/getPdfium.html) function for on-demand PDFium binary downloads - Supports Windows (x64), Linux (x64, ARM64), and macOS (x64, ARM64) ## Usage @@ -29,7 +32,7 @@ final pdfium = PDFium(DynamicLibrary.open('/path/to/libpdfium.so')); ### On-Demand PDFium Downloads -The `getPdfium()` function automatically downloads PDFium binaries on demand, making it easy to use PDFium in CLI applications or for testing without bundling binaries: +The [getPdfium](https://pub.dev/documentation/pdfium_dart/latest/pdfium_dart/getPdfium.html) function automatically downloads PDFium binaries on demand, making it easy to use PDFium in CLI applications or for testing without bundling binaries: ```dart import 'package:pdfium_dart/pdfium_dart.dart'; @@ -55,14 +58,16 @@ The binaries are downloaded from [bblanchon/pdfium-binaries](https://github.com/ ### Prerequisites -The `ffigen` process requires LLVM/Clang to be installed for parsing C headers: +The [ffigen](https://pub.dev/packages/ffigen) process requires LLVM/Clang to be installed for parsing C headers: - **macOS**: Install via Homebrew + ```bash brew install llvm ``` - **Linux**: Install via package manager + ```bash # Ubuntu/Debian sudo apt-get install libclang-dev @@ -78,11 +83,13 @@ The `ffigen` process requires LLVM/Clang to be installed for parsing C headers: To regenerate the FFI bindings: 1. Run tests to download PDFium headers: + ```bash dart test ``` 2. Generate bindings: + ```bash dart run ffigen ``` @@ -97,8 +104,4 @@ The bindings are generated from PDFium headers using the configuration in `ffige | Linux | x64, ARM64 | ✅ | | macOS | x64, ARM64 | ✅ | -**Note:** For Flutter applications with bundled PDFium binaries, use the `pdfium_flutter` package instead. - -## License - -This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. +**Note:** For Flutter applications with bundled PDFium binaries, use the [pdfium_flutter](https://pub.dev/packages/pdfium_flutter) package instead. diff --git a/packages/pdfium_flutter/README.md b/packages/pdfium_flutter/README.md index cf1ea1be..9063e6f9 100644 --- a/packages/pdfium_flutter/README.md +++ b/packages/pdfium_flutter/README.md @@ -2,12 +2,15 @@ Flutter FFI plugin for loading PDFium native libraries. This package bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. +This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. + ## Overview This package provides: + - Pre-built PDFium native libraries for all supported platforms - Utilities for loading PDFium at runtime -- Re-exports of `pdfium_dart` FFI bindings +- Re-exports of [pdfium_dart](https://pub.dev/packages/pdfium_dart) FFI bindings ## Platform Support @@ -36,15 +39,10 @@ final customPdfium = loadPdfium(modulePath: '/custom/path/to/pdfium.so'); ## Native Libraries -### Android -PDFium binaries are downloaded during build from [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases). - -### iOS/macOS -PDFium XCFramework is downloaded using CocoaPods/SwiftPM install from [espresso3389/pdfium-xcframework](https://github.com/espresso3389/pdfium-xcframework/releases). +### Android/Windows/Linux -### Windows/Linux PDFium binaries are downloaded during build from [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases). -## License +### iOS/macOS -This package is part of the [pdfrx](https://github.com/espresso3389/pdfrx) project. +PDFium XCFramework is downloaded using CocoaPods/SwiftPM install from [espresso3389/pdfium-xcframework](https://github.com/espresso3389/pdfium-xcframework/releases). From 741e51a85d62ceeee1dc517167c656f5f54d823e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:27:26 +0900 Subject: [PATCH 608/663] WIP --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 97ade28f..72c08900 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A platform-agnostic PDF rendering and manipulation API built on top of [PDFium]( - Can be used in CLI applications or non-Flutter Dart projects - Supports all platforms: Android, iOS, Windows, macOS, Linux -[View documentation](packages/pdfrx_engine/) | [API reference](https://pub.dev/documentation/pdfrx_engine/latest/) +[View repo](packages/pdfrx_engine/) | [API reference](https://pub.dev/documentation/pdfrx_engine/latest/) ### [pdfrx](https://pub.dev/packages/pdfrx) @@ -26,7 +26,7 @@ A cross-platform PDF viewer and manipulation plugin for Flutter. - Includes text selection, search, zoom controls, and more - Supports PDF editing features like page manipulation, document combining, and image import -[View documentation](packages/pdfrx/) | [API reference](https://pub.dev/documentation/pdfrx/latest/) +[View repo](packages/pdfrx/) | [API reference](https://pub.dev/documentation/pdfrx/latest/) ### [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics) @@ -37,7 +37,7 @@ A cross-platform PDF viewer and manipulation plugin for Flutter. - Maintains full compatibility with [pdfrx](https://pub.dev/packages/pdfrx) widget API - iOS and macOS only -[View documentation](packages/pdfrx_coregraphics/) | [API reference](https://pub.dev/documentation/pdfrx_coregraphics/latest/) +[View repo](packages/pdfrx_coregraphics/) | [API reference](https://pub.dev/documentation/pdfrx_coregraphics/latest/) ### [pdfium_dart](https://pub.dev/packages/pdfium_dart) @@ -48,7 +48,7 @@ Low-level Dart FFI bindings for the [PDFium](https://pdfium.googlesource.com/pdf - Includes [getPdfium()](https://pub.dev/documentation/pdfium_dart/latest/pdfium_dart/getPdfium.html) function that downloads PDFium binaries on demand - Used as a foundation by higher-level packages -[View documentation](packages/pdfium_dart/) | [API reference](https://pub.dev/documentation/pdfium_dart/latest/) +[View repo](packages/pdfium_dart/) | [API reference](https://pub.dev/documentation/pdfium_dart/latest/) ### [pdfium_flutter](https://pub.dev/packages/pdfium_flutter) @@ -59,7 +59,7 @@ Flutter FFI plugin for loading [PDFium](https://pdfium.googlesource.com/pdfium/) - Re-exports [pdfium_dart](https://pub.dev/packages/pdfium_dart) FFI bindings - Simplifies PDFium integration in Flutter applications -[View documentation](packages/pdfium_flutter/) | [API reference](https://pub.dev/documentation/pdfium_flutter/latest/) +[View repo](packages/pdfium_flutter/) | [API reference](https://pub.dev/documentation/pdfium_flutter/latest/) ## When to Use Which Package From 0be0ee99c166c3af4eae54b70be7530ece557255 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:40:07 +0900 Subject: [PATCH 609/663] WIP --- packages/pdfrx_engine/README.md | 83 ++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index c0bff9bc..88c87214 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -15,7 +15,9 @@ This package depends on [pdfium_dart](https://pub.dartlang.org/packages/pdfium_d - Linux (even on Raspberry Pi) - Web (WASM) supported only on Flutter by [pdfrx](https://pub.dartlang.org/packages/pdfrx) -## Example Code +## Example Codes + +### Page Image Export The following fragment illustrates how to use the PDF engine to load and render a PDF file: @@ -40,7 +42,80 @@ void main() async { } ``` -You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure the native PDFium library is properly loaded. For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/blob/master/doc/pdfrx-Initialization.md) +You should call [pdfrxInitialize()](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/pdfrxInitialize.html) before using any PDF engine APIs to ensure the native PDFium library is properly loaded. For more information, see [pdfrx Initialization](https://github.com/espresso3389/pdfrx/blob/master/doc/pdfrx-Initialization.md) + +### Page Manipulation Example + +The following example demonstrates how to combine pages from multiple PDF documents: + +```dart +import 'dart:io'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +void main() async { + await pdfrxInitialize(); + + // Open source PDF documents + final doc1 = await PdfDocument.openFile('document1.pdf'); + final doc2 = await PdfDocument.openFile('document2.pdf'); + + // Create a new PDF document + final outputDoc = await PdfDocument.createNew(sourceName: 'combined.pdf'); + + // Combine pages: first 3 pages from doc1, all pages from doc2, last page from doc1 + outputDoc.pages = [ + ...doc1.pages.sublist(0, 3), + ...doc2.pages, + doc1.pages.last, + ]; + + // Save the combined PDF + final pdfData = await outputDoc.encodePdf(); + await File('combined.pdf').writeAsBytes(pdfData); + + // Clean up + doc1.dispose(); + doc2.dispose(); + outputDoc.dispose(); +} +``` + +### Image-to-PDF Conversion Example + +The following example shows how to convert JPEG images to PDF: + +```dart +import 'dart:io'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; + +void main() async { + await pdfrxInitialize(); + + // Load JPEG image data + final jpegData = await File('photo.jpg').readAsBytes(); + + // Create PDF from JPEG (A4 size: 595 x 842 points) + final doc = await PdfDocument.createFromJpegData( + jpegData, + width: 595, + height: 842, + sourceName: 'photo.pdf', + ); + + // Save to file + final pdfData = await doc.encodePdf(); + await File('output.pdf').writeAsBytes(pdfData); + + doc.dispose(); +} +``` + +For more complex examples including selective page combining with range specifications, see [pdfcombine.dart](https://github.com/espresso3389/pdfrx/blob/master/packages/pdfrx_engine/example/pdfcombine.dart). + +For detailed guides and tutorials, see the [documentation](https://github.com/espresso3389/pdfrx/tree/master/doc): + +- [PDF Page Manipulation](https://github.com/espresso3389/pdfrx/blob/master/doc/PDF-Page-Manipulation.md) - Re-arrange, combine, and extract PDF pages +- [Importing Images to PDF](https://github.com/espresso3389/pdfrx/blob/master/doc/Importing-Images-to-PDF.md) - Convert images to PDF and insert images into PDFs ## PDF API @@ -56,10 +131,6 @@ You should call `pdfrxInitialize()` before using any PDF engine APIs to ensure t - [PdfPage.render](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/render.html) - Render page to bitmap - [PdfPage.loadText](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadText.html) - Extract text content from page - [PdfPage.loadLinks](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage/loadLinks.html) - Extract links from page -- PDFium bindings - - For advanced use cases, you can access the raw PDFium bindings via `package:pdfium_dart/pdfium_dart.dart` - - The bindings are provided by the [pdfium_dart](https://pub.dartlang.org/packages/pdfium_dart) package - - Note: Direct use of PDFium bindings is not recommended for most use cases ## When to Use pdfrx_engine vs. pdfrx From 61a2954b9d604eeb8ebd510ba8de63c45c5f33dc Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:45:02 +0900 Subject: [PATCH 610/663] Release pdfium_dart v0.1.3, pdfium_flutter v0.1.7, pdfrx_engine v0.3.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation updates for all three packages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/pdfium_dart/CHANGELOG.md | 4 ++++ packages/pdfium_dart/pubspec.yaml | 2 +- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/pdfium_dart/CHANGELOG.md b/packages/pdfium_dart/CHANGELOG.md index 4f9e911c..8bacf531 100644 --- a/packages/pdfium_dart/CHANGELOG.md +++ b/packages/pdfium_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.3 + +- Documentation updates. + ## 0.1.2 - Updated PDFium to version 144.0.7520.0. diff --git a/packages/pdfium_dart/pubspec.yaml b/packages/pdfium_dart/pubspec.yaml index 5aa63f4b..d6044342 100644 --- a/packages/pdfium_dart/pubspec.yaml +++ b/packages/pdfium_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_dart description: Dart FFI bindings for PDFium library. Provides low-level access to PDFium's C API from Dart. -version: 0.1.2 +version: 0.1.3 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_dart issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index 1b456c06..e6c1b19b 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.7 + +- Documentation updates. + ## 0.1.6 - Updated PDFium to version 144.0.7520.0 for Android, Linux, and Windows platforms. diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 1e2da6b0..75ca5874 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.6 +version: 0.1.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index e0c58b4b..34f40632 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.5 + +- Documentation updates. + ## 0.3.4 - NEW: Added [`PdfPageBaseExtensions.ensureLoaded()`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageBaseExtensions/ensureLoaded.html) - Wait for page to load (waits indefinitely, never returns null) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index ff2b0c37..23140ae9 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.4 +version: 0.3.5 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 18a4fd94812aad974643caf16212fdd06be90a7e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 04:56:27 +0900 Subject: [PATCH 611/663] Update README.md files --- packages/pdfrx/README.md | 2 +- packages/pdfrx_coregraphics/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index a0c1a9ef..584bc3ac 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.15 + pdfrx: ^2.2.16 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 62d96fad..213db3df 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.9 - pdfrx_coregraphics: ^0.1.8 + pdfrx: ^2.2.16 + pdfrx_coregraphics: ^0.1.9 ``` Set the CoreGraphics entry functions before initializing pdfrx: From 37953b7610ad91a26bb2f6eac74d6bc063241d13 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 05:09:55 +0900 Subject: [PATCH 612/663] Throw PdfException rather than PlatformException --- packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index e383a65f..450f4e2c 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -288,7 +288,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { // try again with the next password continue; } - rethrow; + throw PdfException(e.message ?? 'Platform error: ${e.code}'); } } } From 00847e5cb7173f3d4b8928309c15d4c68c38cd5a Mon Sep 17 00:00:00 2001 From: Babek Teymurov Date: Wed, 19 Nov 2025 14:27:03 +0400 Subject: [PATCH 613/663] feat: Enhanced PDF annotation support with comprehensive metadata extraction --- packages/pdfrx/assets/pdfium_worker.js | 93 ++++++++++++---- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 22 ++-- .../Sources/PdfrxCoregraphicsPlugin.swift | 87 ++++++++++++--- .../lib/pdfrx_coregraphics.dart | 16 ++- packages/pdfrx_engine/lib/pdfrx_engine.dart | 1 + .../lib/src/native/pdfrx_pdfium.dart | 49 ++++++--- .../pdfrx_engine/lib/src/pdf_annotation.dart | 102 ++++++++++++++++++ packages/pdfrx_engine/lib/src/pdf_link.dart | 19 ++-- 8 files changed, 322 insertions(+), 67 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/pdf_annotation.dart diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 6828893a..3d7b6601 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -1297,22 +1297,22 @@ function _loadAnnotLinks(params) { const [l, t, r, b] = new Float32Array(Pdfium.memory.buffer, rectF, 4); const rect = [l, t > b ? t : b, r, t > b ? b : t]; - const content = _getAnnotationContent(annot); + const annotation = _getAnnotationContent(annot); const dest = _processAnnotDest(annot, docHandle); if (dest) { links.push({ rects: [rect], dest: _pdfDestFromDest(dest, docHandle), - annotationContent: content + annotation: annotation }); } else { const url = _processAnnotLink(annot, docHandle); - if (url || content) { + if (url || annotation) { links.push({ rects: [rect], url: url, - annotationContent: content + annotation: annotation }); } } @@ -1323,30 +1323,77 @@ function _loadAnnotLinks(params) { return links; } -function _getAnnotationContent(annot) { - const contentsKey = StringUtils.allocateUTF8("Contents"); - const contentLength = Pdfium.wasmExports.FPDFAnnot_GetStringValue( - annot, contentsKey, null, 0 - ); - let content = null; - if (contentLength > 0) { - const contentBuffer = Pdfium.wasmExports.malloc(contentLength * 2); - Pdfium.wasmExports.FPDFAnnot_GetStringValue( - annot, contentsKey, contentBuffer, contentLength - ); - content = StringUtils.utf16BytesToString( - new Uint8Array(Pdfium.memory.buffer, contentBuffer, contentLength * 2) - ); - Pdfium.wasmExports.free(contentBuffer); +/** + * Get annotation content with all metadata fields + * @param {number} annot Annotation handle + * @returns {Object|null} Annotation object with author, content, dates, and subject + */ +function _getAnnotationContent(annot) { + console.log('Getting annotation content'); + const author = _getAnnotField('T', annot); // Title (Author) + console.log('Author:', author); + const content = _getAnnotField('Contents', annot); // Content + console.log('Content:', content); + const modDate = _getAnnotField('M', annot); // Modification date + console.log('Modification Date:', modDate); + const creationDate = _getAnnotField('CreationDate', annot); // Creation date + console.log('Creation Date:', creationDate); + const subject = _getAnnotField('Subj', annot); // Subject + console.log('Subject:', subject); + + if (!author && !content && !modDate && !creationDate && !subject) { + return null; } - StringUtils.freeUTF8(contentsKey); - return content; + return { + author: author, + content: content, + modificationDate: modDate, + creationDate: creationDate, + subject: subject + }; } +/** + * Helper function to get annotation field value + * @param {string} fieldName PDF annotation field name + * @returns {string|null} + */ +function _getAnnotField(fieldName, annot) { + const key = StringUtils.allocateUTF8(fieldName); + try { + const length = Pdfium.wasmExports.FPDFAnnot_GetStringValue( + annot, + key, + null, + 0 + ); + + if (length <= 0) return null; + + const buffer = Pdfium.wasmExports.malloc(length * 2); + try { + Pdfium.wasmExports.FPDFAnnot_GetStringValue( + annot, + key, + buffer, + length + ); + const value = StringUtils.utf16BytesToString( + new Uint8Array(Pdfium.memory.buffer, buffer, length * 2) + ); + return value && value.trim() !== '' ? value : null; + } finally { + Pdfium.wasmExports.free(buffer); + } + } finally { + StringUtils.freeUTF8(key); + } +} + /** * * @param {number} annot @@ -1871,7 +1918,7 @@ function encodePdf(params) { */ function createNewDocument() { const docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); - return _loadDocument(docHandle, false, () => {}); + return _loadDocument(docHandle, false, () => { }); } /** @@ -1998,7 +2045,7 @@ function createDocumentFromJpegData(params) { Pdfium.wasmExports.FPDF_ClosePage(pageHandle); // Load and return the document - return _loadDocument(docHandle, false, () => {}); + return _loadDocument(docHandle, false, () => { }); } /** diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 82ad7ba4..9a67428e 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -664,21 +664,31 @@ class _PdfPageWasm extends PdfPage { final url = link['url']; final dest = link['dest']; - final annotationContent = link['annotationContent'] as String?; + + final annotationData = link['annotation'] as Map?; + final annotation = annotationData != null + ? PdfAnnotation( + author: annotationData['author'] as String?, + content: annotationData['content'] as String?, + subject: annotationData['subject'] as String?, + modificationDate: annotationData['modificationDate'] as String?, + creationDate: annotationData['creationDate'] as String?, + ) + : null; if (url is String) { - return PdfLink(rects, url: Uri.tryParse(url), annotationContent: annotationContent); + return PdfLink(rects, url: Uri.tryParse(url), annotation: annotation); } if (dest != null && dest is Map) { - return PdfLink(rects, dest: _pdfDestFromMap(dest), annotationContent: annotationContent); + return PdfLink(rects, dest: _pdfDestFromMap(dest), annotation: annotation); } - if (annotationContent != null) { - return PdfLink(rects, annotationContent: annotationContent); + if (annotation != null) { + return PdfLink(rects, annotation: annotation); } - return PdfLink(rects, annotationContent: annotationContent); + return PdfLink(rects); }).toList(); } diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index 5163fe6c..cb0df0aa 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -1288,6 +1288,9 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { var linkEntry: [String: Any] = ["rects": annotRects] + var annotation: [String: String] = [:] + var hasAnnotation = false + // Extract URL from action var actionDict: CGPDFDictionaryRef? if CGPDFDictionaryGetDictionary(annotDict, "A", &actionDict), let action = actionDict { @@ -1317,22 +1320,63 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } } - // Extract annotation content - var contentsString: CGPDFStringRef? - if CGPDFDictionaryGetString(annotDict, "Contents", &contentsString), let contents = contentsString { - if let cfString = CGPDFStringCopyTextString(contents) { - linkEntry["annotationContent"] = cfString as String - } + // Author (T field) + var authorString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "T", &authorString), let author = authorString { + if let cfString = CGPDFStringCopyTextString(author) { + annotation["author"] = cfString as String + hasAnnotation = true } - - // Only return if we have a URL or destination - guard linkEntry["url"] != nil || linkEntry["dest"] != nil else { - return nil + } + + // Contents + var contentsString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "Contents", &contentsString), let contents = contentsString { + if let cfString = CGPDFStringCopyTextString(contents) { + annotation["content"] = cfString as String + hasAnnotation = true + } + } + + // Subject + var subjString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "Subj", &subjString), let subj = subjString { + if let cfString = CGPDFStringCopyTextString(subj) { + annotation["subject"] = cfString as String + hasAnnotation = true + } + } + + // Modification date (M) + var modDateString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "M", &modDateString), let modDate = modDateString { + if let cfString = CGPDFStringCopyTextString(modDate) { + annotation["modificationDate"] = cfString as String + hasAnnotation = true + } + } + + // Creation date + var createDateString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "CreationDate", &createDateString), let createDate = createDateString { + if let cfString = CGPDFStringCopyTextString(createDate) { + annotation["creationDate"] = cfString as String + hasAnnotation = true } + } + + if hasAnnotation { + linkEntry["annotation"] = annotation + } - return linkEntry + // Only return if we have a URL, destination, or annotation + guard linkEntry["url"] != nil || linkEntry["dest"] != nil || linkEntry["annotation"] != nil else { + return nil } + return linkEntry +} + private func parseCGRect(_ rectArray: CGPDFArrayRef) -> [String: Double]? { let count = CGPDFArrayGetCount(rectArray) guard count >= 4 else { return nil } @@ -1466,9 +1510,26 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { .joined(separator: "|") return "rect:\(rectKey)" } - if let content = link["annotationContent"] as? String, !content.isEmpty { - return "content:\(content)" + + // Use annotation fields for key generation + if let annotation = link["annotation"] as? [String: String] { + var components: [String] = [] + + if let author = annotation["author"], !author.isEmpty { + components.append("author:\(author)") + } + if let content = annotation["content"], !content.isEmpty { + components.append("content:\(content)") + } + if let subject = annotation["subject"], !subject.isEmpty { + components.append("subject:\(subject)") + } + + if !components.isEmpty { + return components.joined(separator: "|") + } } + return UUID().uuidString } diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 450f4e2c..58395336 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -689,11 +689,25 @@ class _CoreGraphicsPdfPage extends PdfPage { const []; final url = map['url'] as String?; final destMap = map['dest'] as Map?; + + // Parse annotation from Swift + final annotationData = map['annotation'] as Map?; + final annotation = annotationData != null + ? PdfAnnotation( + author: annotationData['author'] as String?, + content: annotationData['content'] as String?, + subject: annotationData['subject'] as String?, + modificationDate: + annotationData['modificationDate'] as String?, + creationDate: annotationData['creationDate'] as String?, + ) + : null; + final link = PdfLink( rects, url: url == null ? null : Uri.tryParse(url), dest: _parseDest(destMap, defaultPageNumber: pageNumber), - annotationContent: map['annotationContent'] as String?, + annotation: annotation, ); return compact ? link.compact() : link; }) diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index f6dfa66a..15808302 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -1,6 +1,7 @@ library; export 'src/mock/pdfrx_initialize_mock.dart' if (dart.library.io) 'src/pdfrx_initialize_dart.dart'; +export 'src/pdf_annotation.dart'; export 'src/pdf_dest.dart'; export 'src/pdf_document.dart'; export 'src/pdf_document_event.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index bb0c66fa..b2f4568f 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -12,6 +12,7 @@ import 'package:pdfium_dart/pdfium_dart.dart' as pdfium_bindings; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; +import '../pdf_annotation.dart'; import '../pdf_dest.dart'; import '../pdf_document.dart'; import '../pdf_document_event.dart'; @@ -1275,26 +1276,40 @@ class _PdfPagePdfium extends PdfPage { return urlBuffer.cast().toDartString(); } - static String? _getAnnotationContent(pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { - final contentLengthInBytes = pdfium.FPDFAnnot_GetStringValue( + static String? getAnnotField(String fieldName, pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { + final length = pdfium.FPDFAnnot_GetStringValue( annot, - 'Contents'.toNativeUtf8(allocator: arena).cast(), + fieldName.toNativeUtf8(allocator: arena).cast(), nullptr, 0, ); - if (contentLengthInBytes > 0) { - final contentBuffer = arena.allocate(contentLengthInBytes); // NOTE: size is in bytes - pdfium.FPDFAnnot_GetStringValue( - annot, - 'Contents'.toNativeUtf8(allocator: arena).cast(), - contentBuffer, - contentLengthInBytes, - ); - return contentBuffer.cast().toDartString(); + if (length <= 0) return null; + + final buffer = arena.allocate(length); + pdfium.FPDFAnnot_GetStringValue(annot, fieldName.toNativeUtf8(allocator: arena).cast(), buffer, length); + final value = buffer.cast().toDartString(); + return value.isEmpty ? null : value; + } + + static PdfAnnotation? _getAnnotationContent(pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { + final author = getAnnotField('T', annot, arena); + final content = getAnnotField('Contents', annot, arena); + final modDate = getAnnotField('M', annot, arena); + final creationDate = getAnnotField('CreationDate', annot, arena); + final subject = getAnnotField('Subj', annot, arena); + + if (author == null && content == null && modDate == null && creationDate == null && subject == null) { + return null; } - return null; + return PdfAnnotation( + author: author, + content: content, + modificationDate: modDate, + creationDate: creationDate, + subject: subject, + ); } Future> _loadAnnotLinks() async => document.isDisposed @@ -1317,15 +1332,15 @@ class _PdfPagePdfium extends PdfPage { r.top > r.bottom ? r.bottom : r.top, ).translate(-params.bbLeft, -params.bbBottom); - final content = _getAnnotationContent(annot, arena); + final annotation = _getAnnotationContent(annot, arena); final dest = _processAnnotDest(annot, document, arena); if (dest != nullptr) { - links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena), annotationContent: content)); + links.add(PdfLink([rect], dest: _pdfDestFromDest(dest, document, arena), annotation: annotation)); } else { final uri = _processAnnotLink(annot, document, arena); - if (uri != null || content != null) { - links.add(PdfLink([rect], url: uri, annotationContent: content)); + if (uri != null || annotation != null) { + links.add(PdfLink([rect], url: uri, annotation: annotation)); } } pdfium.FPDFPage_CloseAnnot(annot); diff --git a/packages/pdfrx_engine/lib/src/pdf_annotation.dart b/packages/pdfrx_engine/lib/src/pdf_annotation.dart new file mode 100644 index 00000000..0294e3a8 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_annotation.dart @@ -0,0 +1,102 @@ +import 'package:flutter/foundation.dart'; + +/// PDF annotation information extracted from PDF links. +/// +/// Contains metadata about PDF annotations such as author, content, and dates. +@immutable +class PdfAnnotation { + const PdfAnnotation({this.author, this.content, this.subject, this.modificationDate, this.creationDate}); + + /// The author/creator of the annotation (PDF field: T - Title). + final String? author; + + /// The content/text of the annotation (PDF field: Contents). + final String? content; + + /// The subject of the annotation (PDF field: Subj). + final String? subject; + + /// The modification date of the annotation (PDF field: M). + final String? modificationDate; + + /// The creation date of the annotation (PDF field: CreationDate). + final String? creationDate; + + /// Returns true if all fields are null. + bool get isEmpty => + author == null && content == null && subject == null && modificationDate == null && creationDate == null; + + /// Returns true if at least one field is not null. + bool get isNotEmpty => !isEmpty; + + @override + String toString() { + final buffer = StringBuffer('PdfAnnotation{'); + final fields = []; + + if (author != null) fields.add('author: $author'); + if (content != null) fields.add('content: $content'); + if (subject != null) fields.add('subject: $subject'); + if (modificationDate != null) fields.add('modDate: $modificationDate'); + if (creationDate != null) fields.add('createDate: $creationDate'); + + buffer.write(fields.join(', ')); + buffer.write('}'); + return buffer.toString(); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PdfAnnotation && + other.author == author && + other.content == content && + other.subject == subject && + other.modificationDate == modificationDate && + other.creationDate == creationDate; + } + + @override + int get hashCode => + author.hashCode ^ content.hashCode ^ subject.hashCode ^ modificationDate.hashCode ^ creationDate.hashCode; + + /// Creates a copy with the given fields replaced with new values. + PdfAnnotation copyWith({ + String? author, + String? content, + String? subject, + String? modificationDate, + String? creationDate, + }) { + return PdfAnnotation( + author: author ?? this.author, + content: content ?? this.content, + subject: subject ?? this.subject, + modificationDate: modificationDate ?? this.modificationDate, + creationDate: creationDate ?? this.creationDate, + ); + } + + /// Converts to a map for serialization. + Map toJson() { + return { + if (author != null) 'author': author, + if (content != null) 'content': content, + if (subject != null) 'subject': subject, + if (modificationDate != null) 'modificationDate': modificationDate, + if (creationDate != null) 'creationDate': creationDate, + }; + } + + /// Creates from a map for deserialization. + factory PdfAnnotation.fromJson(Map json) { + return PdfAnnotation( + author: json['author'] as String?, + content: json['content'] as String?, + subject: json['subject'] as String?, + modificationDate: json['modificationDate'] as String?, + creationDate: json['creationDate'] as String?, + ); + } +} diff --git a/packages/pdfrx_engine/lib/src/pdf_link.dart b/packages/pdfrx_engine/lib/src/pdf_link.dart index 06a13fff..e5263632 100644 --- a/packages/pdfrx_engine/lib/src/pdf_link.dart +++ b/packages/pdfrx_engine/lib/src/pdf_link.dart @@ -1,3 +1,4 @@ +import 'pdf_annotation.dart'; import 'pdf_dest.dart'; import 'pdf_page.dart'; import 'pdf_rect.dart'; @@ -8,7 +9,7 @@ import 'utils/list_equals.dart'; /// Either one of [url] or [dest] is valid (not null). /// See [PdfPage.loadLinks]. class PdfLink { - const PdfLink(this.rects, {this.url, this.dest, this.annotationContent}); + const PdfLink(this.rects, {this.url, this.dest, this.annotation}); /// Link URL. final Uri? url; @@ -21,8 +22,8 @@ class PdfLink { /// Sometimes a link can span multiple rectangles, e.g., a link across multiple lines. final List rects; - /// Annotation content if available. - final String? annotationContent; + /// Annotation information if available. + final PdfAnnotation? annotation; /// Compact the link. /// @@ -30,21 +31,25 @@ class PdfLink { /// [rects] is typically growable and also modifiable. The method ensures that [rects] is unmodifiable. /// [dest] is also compacted by calling [PdfDest.compact]. PdfLink compact() { - return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact(), annotationContent: annotationContent); + return PdfLink(List.unmodifiable(rects), url: url, dest: dest?.compact(), annotation: annotation); } @override String toString() { - return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects, annotationContent: $annotationContent }'; + return 'PdfLink{${url?.toString() ?? dest?.toString()}, rects: $rects, annotation: $annotation}'; } @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is PdfLink && other.url == url && other.dest == dest && listEquals(other.rects, rects); + return other is PdfLink && + other.url == url && + other.dest == dest && + listEquals(other.rects, rects) && + other.annotation == annotation; } @override - int get hashCode => url.hashCode ^ dest.hashCode ^ rects.hashCode; + int get hashCode => url.hashCode ^ dest.hashCode ^ rects.hashCode ^ annotation.hashCode; } From 6e2442d00495eb37e23b9a17e445afff707a7c7a Mon Sep 17 00:00:00 2001 From: Babek Teymurov Date: Wed, 19 Nov 2025 14:49:19 +0400 Subject: [PATCH 614/663] fix: Remove Flutter dependency from PdfAnnotation class --- .../pdfrx_engine/lib/src/pdf_annotation.dart | 59 ++----------------- 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/pdf_annotation.dart b/packages/pdfrx_engine/lib/src/pdf_annotation.dart index 0294e3a8..c876bdc0 100644 --- a/packages/pdfrx_engine/lib/src/pdf_annotation.dart +++ b/packages/pdfrx_engine/lib/src/pdf_annotation.dart @@ -1,9 +1,6 @@ -import 'package:flutter/foundation.dart'; - /// PDF annotation information extracted from PDF links. /// /// Contains metadata about PDF annotations such as author, content, and dates. -@immutable class PdfAnnotation { const PdfAnnotation({this.author, this.content, this.subject, this.modificationDate, this.creationDate}); @@ -31,18 +28,8 @@ class PdfAnnotation { @override String toString() { - final buffer = StringBuffer('PdfAnnotation{'); - final fields = []; - - if (author != null) fields.add('author: $author'); - if (content != null) fields.add('content: $content'); - if (subject != null) fields.add('subject: $subject'); - if (modificationDate != null) fields.add('modDate: $modificationDate'); - if (creationDate != null) fields.add('createDate: $creationDate'); - - buffer.write(fields.join(', ')); - buffer.write('}'); - return buffer.toString(); + return 'PdfAnnotation{author: $author, content: $content, subject: $subject, ' + 'modificationDate: $modificationDate, creationDate: $creationDate}'; } @override @@ -58,45 +45,7 @@ class PdfAnnotation { } @override - int get hashCode => - author.hashCode ^ content.hashCode ^ subject.hashCode ^ modificationDate.hashCode ^ creationDate.hashCode; - - /// Creates a copy with the given fields replaced with new values. - PdfAnnotation copyWith({ - String? author, - String? content, - String? subject, - String? modificationDate, - String? creationDate, - }) { - return PdfAnnotation( - author: author ?? this.author, - content: content ?? this.content, - subject: subject ?? this.subject, - modificationDate: modificationDate ?? this.modificationDate, - creationDate: creationDate ?? this.creationDate, - ); - } - - /// Converts to a map for serialization. - Map toJson() { - return { - if (author != null) 'author': author, - if (content != null) 'content': content, - if (subject != null) 'subject': subject, - if (modificationDate != null) 'modificationDate': modificationDate, - if (creationDate != null) 'creationDate': creationDate, - }; - } - - /// Creates from a map for deserialization. - factory PdfAnnotation.fromJson(Map json) { - return PdfAnnotation( - author: json['author'] as String?, - content: json['content'] as String?, - subject: json['subject'] as String?, - modificationDate: json['modificationDate'] as String?, - creationDate: json['creationDate'] as String?, - ); + int get hashCode { + return author.hashCode ^ content.hashCode ^ subject.hashCode ^ modificationDate.hashCode ^ creationDate.hashCode; } } From eb9632aff6422d26792324547e246ece5fa0e000 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 19 Nov 2025 21:16:11 +0900 Subject: [PATCH 615/663] T should be "Title" though it's actually Author --- packages/pdfrx/assets/pdfium_worker.js | 77 +++++++------------ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 2 +- .../Sources/PdfrxCoregraphicsPlugin.swift | 26 +++---- .../lib/pdfrx_coregraphics.dart | 2 +- .../lib/src/native/pdfrx_pdfium.dart | 18 ++--- .../pdfrx_engine/lib/src/pdf_annotation.dart | 22 +++--- 6 files changed, 62 insertions(+), 85 deletions(-) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 3d7b6601..48cac450 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -632,7 +632,7 @@ const emEnv = { tm[7] = 0; // dst tm[8] = 0; // gmtoff }, - _tzset_js: function () { }, + _tzset_js: function () {}, emscripten_date_now: function () { return Date.now(); }, @@ -914,7 +914,7 @@ function _loadDocument(docHandle, useProgressiveLoading, onDispose) { } catch (e) { try { if (formHandle !== 0) Pdfium.wasmExports.FPDFDOC_ExitFormFillEnvironment(formHandle); - } catch (e) { } + } catch (e) {} Pdfium.wasmExports.free(formInfo); delete disposers[docHandle]; onDispose(); @@ -988,7 +988,7 @@ function closeDocument(params) { if (params.formHandle) { try { Pdfium.wasmExports.FPDFDOC_ExitFormFillEnvironment(params.formHandle); - } catch (e) { } + } catch (e) {} } Pdfium.wasmExports.free(params.formInfo); Pdfium.wasmExports.FPDF_CloseDocument(params.docHandle); @@ -1280,7 +1280,6 @@ function _getLinkUrl(linkPage, linkIndex) { return url; } - /** * @param {{docHandle: number, pageIndex: number}} params * @returns {Array} @@ -1304,7 +1303,7 @@ function _loadAnnotLinks(params) { links.push({ rects: [rect], dest: _pdfDestFromDest(dest, docHandle), - annotation: annotation + annotation: annotation, }); } else { const url = _processAnnotLink(annot, docHandle); @@ -1312,7 +1311,7 @@ function _loadAnnotLinks(params) { links.push({ rects: [rect], url: url, - annotation: annotation + annotation: annotation, }); } } @@ -1323,68 +1322,49 @@ function _loadAnnotLinks(params) { return links; } - +/** + * @typedef {{title: string|null, content: string|null, modificationDate: string|null, creationDate: string|null, subject: string|null}} PdfAnnotationContent + */ /** * Get annotation content with all metadata fields * @param {number} annot Annotation handle - * @returns {Object|null} Annotation object with author, content, dates, and subject + * @returns {PdfAnnotationContent|null} Annotation object or null if no content */ function _getAnnotationContent(annot) { - console.log('Getting annotation content'); - const author = _getAnnotField('T', annot); // Title (Author) - console.log('Author:', author); + const title = _getAnnotField('T', annot); // Title (Author) const content = _getAnnotField('Contents', annot); // Content - console.log('Content:', content); const modDate = _getAnnotField('M', annot); // Modification date - console.log('Modification Date:', modDate); const creationDate = _getAnnotField('CreationDate', annot); // Creation date - console.log('Creation Date:', creationDate); const subject = _getAnnotField('Subj', annot); // Subject - console.log('Subject:', subject); - - if (!author && !content && !modDate && !creationDate && !subject) { + if (!title && !content && !modDate && !creationDate && !subject) { return null; } return { - author: author, + title: title, content: content, modificationDate: modDate, creationDate: creationDate, - subject: subject + subject: subject, }; } - /** - * Helper function to get annotation field value - * @param {string} fieldName PDF annotation field name - * @returns {string|null} - */ + * Helper function to get annotation field value + * @param {string} fieldName PDF annotation field name + * @returns {string|null} + */ function _getAnnotField(fieldName, annot) { const key = StringUtils.allocateUTF8(fieldName); try { - const length = Pdfium.wasmExports.FPDFAnnot_GetStringValue( - annot, - key, - null, - 0 - ); - + const length = Pdfium.wasmExports.FPDFAnnot_GetStringValue(annot, key, null, 0); if (length <= 0) return null; const buffer = Pdfium.wasmExports.malloc(length * 2); try { - Pdfium.wasmExports.FPDFAnnot_GetStringValue( - annot, - key, - buffer, - length - ); - const value = StringUtils.utf16BytesToString( - new Uint8Array(Pdfium.memory.buffer, buffer, length * 2) - ); + Pdfium.wasmExports.FPDFAnnot_GetStringValue(annot, key, buffer, length); + const value = StringUtils.utf16BytesToString(new Uint8Array(Pdfium.memory.buffer, buffer, length * 2)); return value && value.trim() !== '' ? value : null; } finally { Pdfium.wasmExports.free(buffer); @@ -1918,7 +1898,7 @@ function encodePdf(params) { */ function createNewDocument() { const docHandle = Pdfium.wasmExports.FPDF_CreateNewDocument(); - return _loadDocument(docHandle, false, () => { }); + return _loadDocument(docHandle, false, () => {}); } /** @@ -1997,12 +1977,7 @@ function createDocumentFromJpegData(params) { new Int32Array(Pdfium.memory.buffer, pageArrayPtr, 1)[0] = pageHandle; // Load JPEG data into the image object - const loadResult = Pdfium.wasmExports.FPDFImageObj_LoadJpegFileInline( - pageArrayPtr, - 1, - imageObj, - fileAccessPtr - ); + const loadResult = Pdfium.wasmExports.FPDFImageObj_LoadJpegFileInline(pageArrayPtr, 1, imageObj, fileAccessPtr); Pdfium.wasmExports.free(pageArrayPtr); Pdfium.removeFunction(callbackIndex); Pdfium.wasmExports.free(fileAccessPtr); @@ -2045,7 +2020,7 @@ function createDocumentFromJpegData(params) { Pdfium.wasmExports.FPDF_ClosePage(pageHandle); // Load and return the document - return _loadDocument(docHandle, false, () => { }); + return _loadDocument(docHandle, false, () => {}); } /** @@ -2195,7 +2170,11 @@ async function initializePdfium(params = {}) { }); } catch (e) { // Fallback for browsers that do not support instantiateStreaming - console.warn('%cWebAssembly.instantiateStreaming failed, falling back to ArrayBuffer instantiation. Consider to configure your server to serve wasm files as application/wasm', 'background: red; color: white', e); + console.warn( + '%cWebAssembly.instantiateStreaming failed, falling back to ArrayBuffer instantiation. Consider to configure your server to serve wasm files as application/wasm', + 'background: red; color: white', + e + ); const response = await fetch(pdfiumWasmUrl, fetchOptions); const buffer = await response.arrayBuffer(); result = await WebAssembly.instantiate(buffer, { diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 9a67428e..5ec581db 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -668,7 +668,7 @@ class _PdfPageWasm extends PdfPage { final annotationData = link['annotation'] as Map?; final annotation = annotationData != null ? PdfAnnotation( - author: annotationData['author'] as String?, + title: annotationData['title'] as String?, content: annotationData['content'] as String?, subject: annotationData['subject'] as String?, modificationDate: annotationData['modificationDate'] as String?, diff --git a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift index cb0df0aa..253d861a 100644 --- a/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift +++ b/packages/pdfrx_coregraphics/darwin/pdfrx_coregraphics/Sources/PdfrxCoregraphicsPlugin.swift @@ -1321,14 +1321,14 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { } // Author (T field) - var authorString: CGPDFStringRef? - if CGPDFDictionaryGetString(annotDict, "T", &authorString), let author = authorString { - if let cfString = CGPDFStringCopyTextString(author) { - annotation["author"] = cfString as String + var titleString: CGPDFStringRef? + if CGPDFDictionaryGetString(annotDict, "T", &titleString), let title = titleString { + if let cfString = CGPDFStringCopyTextString(title) { + annotation["title"] = cfString as String hasAnnotation = true } } - + // Contents var contentsString: CGPDFStringRef? if CGPDFDictionaryGetString(annotDict, "Contents", &contentsString), let contents = contentsString { @@ -1337,7 +1337,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { hasAnnotation = true } } - + // Subject var subjString: CGPDFStringRef? if CGPDFDictionaryGetString(annotDict, "Subj", &subjString), let subj = subjString { @@ -1346,7 +1346,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { hasAnnotation = true } } - + // Modification date (M) var modDateString: CGPDFStringRef? if CGPDFDictionaryGetString(annotDict, "M", &modDateString), let modDate = modDateString { @@ -1355,7 +1355,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { hasAnnotation = true } } - + // Creation date var createDateString: CGPDFStringRef? if CGPDFDictionaryGetString(annotDict, "CreationDate", &createDateString), let createDate = createDateString { @@ -1364,7 +1364,7 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { hasAnnotation = true } } - + if hasAnnotation { linkEntry["annotation"] = annotation } @@ -1510,11 +1510,11 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { .joined(separator: "|") return "rect:\(rectKey)" } - + // Use annotation fields for key generation if let annotation = link["annotation"] as? [String: String] { var components: [String] = [] - + if let author = annotation["author"], !author.isEmpty { components.append("author:\(author)") } @@ -1524,12 +1524,12 @@ public class PdfrxCoregraphicsPlugin: NSObject, FlutterPlugin { if let subject = annotation["subject"], !subject.isEmpty { components.append("subject:\(subject)") } - + if !components.isEmpty { return components.joined(separator: "|") } } - + return UUID().uuidString } diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 58395336..15725578 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -694,7 +694,7 @@ class _CoreGraphicsPdfPage extends PdfPage { final annotationData = map['annotation'] as Map?; final annotation = annotationData != null ? PdfAnnotation( - author: annotationData['author'] as String?, + title: annotationData['title'] as String?, content: annotationData['content'] as String?, subject: annotationData['subject'] as String?, modificationDate: diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index b2f4568f..74f7362e 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -1276,14 +1276,13 @@ class _PdfPagePdfium extends PdfPage { return urlBuffer.cast().toDartString(); } - static String? getAnnotField(String fieldName, pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { + static String? _getAnnotField(String fieldName, pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { final length = pdfium.FPDFAnnot_GetStringValue( annot, fieldName.toNativeUtf8(allocator: arena).cast(), nullptr, 0, ); - if (length <= 0) return null; final buffer = arena.allocate(length); @@ -1293,18 +1292,17 @@ class _PdfPagePdfium extends PdfPage { } static PdfAnnotation? _getAnnotationContent(pdfium_bindings.FPDF_ANNOTATION annot, Arena arena) { - final author = getAnnotField('T', annot, arena); - final content = getAnnotField('Contents', annot, arena); - final modDate = getAnnotField('M', annot, arena); - final creationDate = getAnnotField('CreationDate', annot, arena); - final subject = getAnnotField('Subj', annot, arena); - - if (author == null && content == null && modDate == null && creationDate == null && subject == null) { + final title = _getAnnotField('T', annot, arena); + final content = _getAnnotField('Contents', annot, arena); + final modDate = _getAnnotField('M', annot, arena); + final creationDate = _getAnnotField('CreationDate', annot, arena); + final subject = _getAnnotField('Subj', annot, arena); + if (title == null && content == null && modDate == null && creationDate == null && subject == null) { return null; } return PdfAnnotation( - author: author, + title: title, content: content, modificationDate: modDate, creationDate: creationDate, diff --git a/packages/pdfrx_engine/lib/src/pdf_annotation.dart b/packages/pdfrx_engine/lib/src/pdf_annotation.dart index c876bdc0..3520c265 100644 --- a/packages/pdfrx_engine/lib/src/pdf_annotation.dart +++ b/packages/pdfrx_engine/lib/src/pdf_annotation.dart @@ -2,33 +2,33 @@ /// /// Contains metadata about PDF annotations such as author, content, and dates. class PdfAnnotation { - const PdfAnnotation({this.author, this.content, this.subject, this.modificationDate, this.creationDate}); + const PdfAnnotation({this.title, this.content, this.subject, this.modificationDate, this.creationDate}); - /// The author/creator of the annotation (PDF field: T - Title). - final String? author; + /// The author/creator of the annotation (PDF field: `T` - Title). + final String? title; - /// The content/text of the annotation (PDF field: Contents). + /// The content/text of the annotation (PDF field: `Contents`). final String? content; - /// The subject of the annotation (PDF field: Subj). + /// The subject of the annotation (PDF field: `Subj` - Subject). final String? subject; - /// The modification date of the annotation (PDF field: M). + /// The modification date of the annotation (PDF field: `M` - ModificationDate). final String? modificationDate; - /// The creation date of the annotation (PDF field: CreationDate). + /// The creation date of the annotation (PDF field: `CreationDate`). final String? creationDate; /// Returns true if all fields are null. bool get isEmpty => - author == null && content == null && subject == null && modificationDate == null && creationDate == null; + title == null && content == null && subject == null && modificationDate == null && creationDate == null; /// Returns true if at least one field is not null. bool get isNotEmpty => !isEmpty; @override String toString() { - return 'PdfAnnotation{author: $author, content: $content, subject: $subject, ' + return 'PdfAnnotation{author: $title, content: $content, subject: $subject, ' 'modificationDate: $modificationDate, creationDate: $creationDate}'; } @@ -37,7 +37,7 @@ class PdfAnnotation { if (identical(this, other)) return true; return other is PdfAnnotation && - other.author == author && + other.title == title && other.content == content && other.subject == subject && other.modificationDate == modificationDate && @@ -46,6 +46,6 @@ class PdfAnnotation { @override int get hashCode { - return author.hashCode ^ content.hashCode ^ subject.hashCode ^ modificationDate.hashCode ^ creationDate.hashCode; + return title.hashCode ^ content.hashCode ^ subject.hashCode ^ modificationDate.hashCode ^ creationDate.hashCode; } } From 6c631a027971d9a5dcea80ff65b72f551ac8d4cd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 20 Nov 2025 00:36:13 +0900 Subject: [PATCH 616/663] Add PdfDateTime class for PDF date parsing. --- .vscode/settings.json | 1 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 4 +- .../lib/pdfrx_coregraphics.dart | 9 +- packages/pdfrx_engine/lib/pdfrx_engine.dart | 1 + .../lib/src/native/pdfrx_pdfium.dart | 5 +- .../pdfrx_engine/lib/src/pdf_annotation.dart | 10 +- .../pdfrx_engine/lib/src/pdf_datetime.dart | 151 ++++++++++++++++++ 7 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 packages/pdfrx_engine/lib/src/pdf_datetime.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index c17f4a37..6f27314b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -222,6 +222,7 @@ "xobject", "YESNO", "YESNOCANCEL", + "YMDHMS", "ZapfDingbats" ], "editor.codeActionsOnSave": { diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 5ec581db..416dd45c 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -671,8 +671,8 @@ class _PdfPageWasm extends PdfPage { title: annotationData['title'] as String?, content: annotationData['content'] as String?, subject: annotationData['subject'] as String?, - modificationDate: annotationData['modificationDate'] as String?, - creationDate: annotationData['creationDate'] as String?, + modificationDate: PdfDateTime.fromPdfDateString(annotationData['modificationDate']), + creationDate: PdfDateTime.fromPdfDateString(annotationData['creationDate']), ) : null; diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 15725578..8c944a2b 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -697,9 +697,12 @@ class _CoreGraphicsPdfPage extends PdfPage { title: annotationData['title'] as String?, content: annotationData['content'] as String?, subject: annotationData['subject'] as String?, - modificationDate: - annotationData['modificationDate'] as String?, - creationDate: annotationData['creationDate'] as String?, + modificationDate: PdfDateTime.fromPdfDateString( + annotationData['modificationDate'] as String?, + ), + creationDate: PdfDateTime.fromPdfDateString( + annotationData['creationDate'] as String?, + ), ) : null; diff --git a/packages/pdfrx_engine/lib/pdfrx_engine.dart b/packages/pdfrx_engine/lib/pdfrx_engine.dart index 15808302..3446469b 100644 --- a/packages/pdfrx_engine/lib/pdfrx_engine.dart +++ b/packages/pdfrx_engine/lib/pdfrx_engine.dart @@ -2,6 +2,7 @@ library; export 'src/mock/pdfrx_initialize_mock.dart' if (dart.library.io) 'src/pdfrx_initialize_dart.dart'; export 'src/pdf_annotation.dart'; +export 'src/pdf_datetime.dart'; export 'src/pdf_dest.dart'; export 'src/pdf_document.dart'; export 'src/pdf_document_event.dart'; diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 74f7362e..a0ea3a3e 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -13,6 +13,7 @@ import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import '../pdf_annotation.dart'; +import '../pdf_datetime.dart'; import '../pdf_dest.dart'; import '../pdf_document.dart'; import '../pdf_document_event.dart'; @@ -1304,8 +1305,8 @@ class _PdfPagePdfium extends PdfPage { return PdfAnnotation( title: title, content: content, - modificationDate: modDate, - creationDate: creationDate, + modificationDate: PdfDateTime.fromPdfDateString(modDate), + creationDate: PdfDateTime.fromPdfDateString(creationDate), subject: subject, ); } diff --git a/packages/pdfrx_engine/lib/src/pdf_annotation.dart b/packages/pdfrx_engine/lib/src/pdf_annotation.dart index 3520c265..6032604b 100644 --- a/packages/pdfrx_engine/lib/src/pdf_annotation.dart +++ b/packages/pdfrx_engine/lib/src/pdf_annotation.dart @@ -1,8 +1,10 @@ +import 'pdf_datetime.dart'; + /// PDF annotation information extracted from PDF links. /// /// Contains metadata about PDF annotations such as author, content, and dates. class PdfAnnotation { - const PdfAnnotation({this.title, this.content, this.subject, this.modificationDate, this.creationDate}); + PdfAnnotation({this.title, this.content, this.subject, this.modificationDate, this.creationDate}); /// The author/creator of the annotation (PDF field: `T` - Title). final String? title; @@ -14,10 +16,10 @@ class PdfAnnotation { final String? subject; /// The modification date of the annotation (PDF field: `M` - ModificationDate). - final String? modificationDate; + final PdfDateTime? modificationDate; /// The creation date of the annotation (PDF field: `CreationDate`). - final String? creationDate; + final PdfDateTime? creationDate; /// Returns true if all fields are null. bool get isEmpty => @@ -28,7 +30,7 @@ class PdfAnnotation { @override String toString() { - return 'PdfAnnotation{author: $title, content: $content, subject: $subject, ' + return 'PdfAnnotation{title: $title, content: $content, subject: $subject, ' 'modificationDate: $modificationDate, creationDate: $creationDate}'; } diff --git a/packages/pdfrx_engine/lib/src/pdf_datetime.dart b/packages/pdfrx_engine/lib/src/pdf_datetime.dart new file mode 100644 index 00000000..64854078 --- /dev/null +++ b/packages/pdfrx_engine/lib/src/pdf_datetime.dart @@ -0,0 +1,151 @@ +/// Represents a PDF date/time string defined in [PDF 32000-1:2008, 7.9.4 Dates](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95) +class PdfDateTime { + /// Creates a [PdfDateTime] from a PDF date string. + const PdfDateTime(this.pdfDateString); + + /// Creates a [PdfDateTime] from a [DateTime] object. + PdfDateTime.fromDateTime(DateTime dateTime) + : pdfDateString = _pdfDateStringFromYMDHMS( + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + timeZoneOffset: dateTime.timeZoneOffset.inMinutes, + ); + + /// Creates a [PdfDateTime] from individual date and time components. + /// + /// - [year] (e.g., 2025) + /// - [month] (1-12) + /// - [day] (1-31) + /// - [hour] (0-23) + /// - [minute] (0-59) + /// - [second] (0-59) + /// - [timeZoneOffset] is in minutes and defaults to 0 (UTC) + PdfDateTime.fromYMDHMS(int year, int month, int day, int hour, int minute, int second, {int timeZoneOffset = 0}) + : pdfDateString = _pdfDateStringFromYMDHMS(year, month, day, hour, minute, second, timeZoneOffset: timeZoneOffset); + + /// The original PDF date string. + /// + /// The date string should be in `D:YYYYMMDDHHmmSSOHH'mm'` format as specified in the PDF standard. + /// But this class do permissive parsing and allows missing some of the components. + /// To validate the format, use [isValidFormat]. + final String pdfDateString; + + /// Creates a [PdfDateTime] from a nullable PDF date string. + /// + /// Returns null if [pdfDateString] is null. Otherwise, creates a [PdfDateTime] instance. + static PdfDateTime? fromPdfDateString(String? pdfDateString) => + pdfDateString != null ? PdfDateTime(pdfDateString) : null; + + /// Generates a PDF date string from individual date and time components. + /// + /// - [year] (e.g., 2025) + /// - [month] (1-12) + /// - [day] (1-31) + /// - [hour] (0-23) + /// - [minute] (0-59) + /// - [second] (0-59) + /// - [timeZoneOffset] is in minutes and defaults to 0 (UTC) + static String _pdfDateStringFromYMDHMS( + int year, + int month, + int day, + int hour, + int minute, + int second, { + int timeZoneOffset = 0, + }) { + final y = year.toString().padLeft(4, '0'); + final m = month.toString().padLeft(2, '0'); + final d = day.toString().padLeft(2, '0'); + final h = hour.toString().padLeft(2, '0'); + final min = minute.toString().padLeft(2, '0'); + final s = second.toString().padLeft(2, '0'); + final String tz; + if (timeZoneOffset == 0) { + tz = 'Z'; + } else { + final sign = timeZoneOffset > 0 ? '+' : '-'; + final absOffset = timeZoneOffset.abs(); + final hours = (absOffset ~/ 60).toString().padLeft(2, '0'); + final minutes = (absOffset % 60).toString().padLeft(2, '0'); + tz = "$sign$hours'$minutes'"; + } + return 'D:$y$m$d$h$min$s$tz'; + } + + /// Year (e.g., 2025) + int get year => _getNumber(2, 4); + + /// Month (1-12) + int get month => _getNumber(6, 2, 1); + + /// Day (1-31) + int get day => _getNumber(8, 2, 1); + + /// Hour (0-23). + int get hour => _getNumber(10, 2); + + /// Minute (0-59). + int get minute => _getNumber(12, 2); + + /// Second (0-59). + int get second => _getNumber(14, 2); + + int get timezoneOffset { + if (pdfDateString.length >= 17) { + final sign = pdfDateString[16]; + if (sign == 'Z') return 0; + final hours = _getNumber(17, 2); + final minutes = _getNumber(20, 2); + final offsetInMinutes = hours * 60 + minutes; + return sign == '+' + ? offsetInMinutes + : sign == '-' + ? -offsetInMinutes + : 0; + } + return 0; + } + + int _getNumber(int start, int length, [int defaultValue = 0]) { + if (pdfDateString.length >= start + length) { + return int.tryParse(pdfDateString.substring(start, start + length)) ?? defaultValue; + } + return 0; + } + + /// UTC [DateTime] representation of the PDF date/time string. + /// + /// The date/time is adjusted according to the timezone offset specified in the PDF date string so that the resulting + /// [DateTime] is in UTC. + DateTime toDateTime() => + DateTime.utc(year, month, day, hour, minute, second).subtract(Duration(minutes: timezoneOffset)); + + /// Checks if the PDF date/time string is in a valid format or not. + bool get isValidFormat { + return _dtRegex.hasMatch(pdfDateString); + } + + /// Regular expression to validate PDF date/time string format. + static final _dtRegex = RegExp(r"^D:\d{4}(\d{2}(\d{2}(\d{2}(\d{2}(\d{2}(Z|[+\-]\d{2}'\d{2}'?)?)?)?)?)?)?$"); + + /// Returns a canonicalized [PdfDateTime] with all components filled in. + PdfDateTime canonicalize() => + PdfDateTime.fromYMDHMS(year, month, day, hour, minute, second, timeZoneOffset: timezoneOffset); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is PdfDateTime && other.pdfDateString == pdfDateString; + } + + @override + int get hashCode => pdfDateString.hashCode; + + @override + String toString() => pdfDateString; +} From 589e8064322fc8c4c5d2b13ca4eb91393ea1e261 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 21 Nov 2025 01:07:12 +0900 Subject: [PATCH 617/663] _setZoom now properly set boundaryMargins --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index e860660e..8956589b 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1947,10 +1947,13 @@ class _PdfViewerState extends State double _getNextZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: true, loop: loop); double _getPreviousZoom({bool loop = true}) => _findNextZoomStop(_currentZoom, zoomUp: false, loop: loop); - Future _setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) => _goTo( - _calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!), - duration: duration, - ); + Future _setZoom(Offset position, double zoom, {Duration duration = const Duration(milliseconds: 200)}) { + _adjustBoundaryMargins(_viewSize!, zoom); + return _goTo( + _calcMatrixFor(position, zoom: zoom, viewSize: _viewSize!), + duration: duration, + ); + } Offset _localPositionToZoomCenter(Offset localPosition, double newZoom) { final toCenter = (_viewSize!.center(Offset.zero) - localPosition) / newZoom; @@ -1970,9 +1973,7 @@ class _PdfViewerState extends State bool loop = false, Offset? zoomCenter, Duration duration = const Duration(milliseconds: 200), - }) async { - await _setZoom(zoomCenter ?? _centerPosition, _getPreviousZoom(loop: loop), duration: duration); - } + }) => _setZoom(zoomCenter ?? _centerPosition, _getPreviousZoom(loop: loop), duration: duration); RenderBox? get _renderBox { final renderBox = context.findRenderObject(); From 624fcd456f0a8e8fe6ad2b28bf10b0c1aafe9985 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 21 Nov 2025 02:39:21 +0900 Subject: [PATCH 618/663] #547 --- packages/pdfrx/example/viewer/lib/main.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/example/viewer/lib/main.dart b/packages/pdfrx/example/viewer/lib/main.dart index 5cfddb25..cb0368f5 100644 --- a/packages/pdfrx/example/viewer/lib/main.dart +++ b/packages/pdfrx/example/viewer/lib/main.dart @@ -452,7 +452,9 @@ class _MainPageState extends State with WidgetsBindingObserver, Single keyHandlerParams: PdfViewerKeyHandlerParams(autofocus: true), useAlternativeFitScaleAsMinScale: false, maxScale: 8, - scrollPhysics: PdfViewerParams.getScrollPhysics(context), + // #547: At least, on Flutter Web on Windows, the default scroll physics + // seems to have some issues with zooming and we don't use it here. + //scrollPhysics: PdfViewerParams.getScrollPhysics(context), customizeContextMenuItems: (params, items) { // Example: add custom menu item to search selected text on web items.add( From 606b39a1b3327aabf4db8148dea63867cf60e981 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 21 Nov 2025 02:41:56 +0900 Subject: [PATCH 619/663] #538: Ctrl+wheel logic can be enabled on Web On Flutter Web on Windows, Ctrl+wheel is handled by Flutter engine and it never gets here; and if you set scrollPhysics to non-null, it causes layout issue on zooming out (see #547). --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8956589b..024a2627 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -1581,23 +1581,24 @@ class _PdfViewerState extends State void _onWheelDelta(PointerScrollEvent event) { _startInteraction(); try { - if (!kIsWeb) { - // To make the behavior consistent across platforms, we only handle zooming on web via Ctrl+wheel. - if (HardwareKeyboard.instance.isControlPressed) { - // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. - // So, I just add both values. - var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; - final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); - if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; - // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. - _controller!.zoomOnLocalPosition( - localPosition: _controller!.globalToLocal(event.position)!, - newZoom: newZoom, - duration: Duration.zero, - ); - return; - } + // Handle Ctrl+wheel for zooming + // NOTE: On Flutter Web on Windows, Ctrl+wheel is handled by Flutter engine and it never gets here; and if + // you set scrollPhysics to non-null, it causes layout issue on zooming out (see #547). + if (HardwareKeyboard.instance.isControlPressed) { + // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. + // So, I just add both values. + var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; + final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); + if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; + // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. + _controller!.zoomOnLocalPosition( + localPosition: _controller!.globalToLocal(event.position)!, + newZoom: newZoom, + duration: Duration.zero, + ); + return; } + final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; final m = _txController.value.clone(); From c08e0226aa0444307d29324ee1efed03fe347eef Mon Sep 17 00:00:00 2001 From: james Date: Fri, 21 Nov 2025 11:50:42 +1030 Subject: [PATCH 620/663] fix trackpad & wheel boundary issues on web --- packages/pdfrx/lib/src/widgets/interactive_viewer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index 6e9f6c36..a6f947b8 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -1110,7 +1110,7 @@ class InteractiveViewerState extends State with TickerProvide final focalPointScene = _transformer.toScene(local); final newFocalPointScene = _transformer.toScene(local - localDelta); - _transformer.value = _matrixTranslate(_transformer.value, newFocalPointScene - focalPointScene); + _transformer.value = _matrixClamp(_matrixTranslate(_transformer.value, newFocalPointScene - focalPointScene)); widget.onInteractionUpdate?.call( ScaleUpdateDetails( @@ -1155,7 +1155,7 @@ class InteractiveViewerState extends State with TickerProvide // After scaling, translate such that the event's position is at the // same scene point before and after the scale. final focalPointSceneScaled = _transformer.toScene(local); - _transformer.value = _matrixTranslate(_transformer.value, focalPointSceneScaled - focalPointScene); + _transformer.value = _matrixClamp(_matrixTranslate(_transformer.value, focalPointSceneScaled - focalPointScene)); widget.onInteractionUpdate?.call( ScaleUpdateDetails(focalPoint: global, localFocalPoint: local, scale: scaleChange), From a51894e5c480f149f79d0a0c8767de1c87a2da53 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 22 Nov 2025 04:10:32 +0900 Subject: [PATCH 621/663] PdfDateTime is now an extension type. --- .../pdfrx_engine/lib/src/pdf_datetime.dart | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/pdf_datetime.dart b/packages/pdfrx_engine/lib/src/pdf_datetime.dart index 64854078..354d75cd 100644 --- a/packages/pdfrx_engine/lib/src/pdf_datetime.dart +++ b/packages/pdfrx_engine/lib/src/pdf_datetime.dart @@ -1,8 +1,10 @@ /// Represents a PDF date/time string defined in [PDF 32000-1:2008, 7.9.4 Dates](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95) -class PdfDateTime { - /// Creates a [PdfDateTime] from a PDF date string. - const PdfDateTime(this.pdfDateString); - +/// +/// [pdfDateString] is a PDF date string. +/// The date string should be in `D:YYYYMMDDHHmmSSOHH'mm'` format as specified in the PDF standard. +/// But this class do permissive parsing and allows missing some of the components. +/// To validate the format, use [isValidFormat]. +extension type const PdfDateTime(String pdfDateString) { /// Creates a [PdfDateTime] from a [DateTime] object. PdfDateTime.fromDateTime(DateTime dateTime) : pdfDateString = _pdfDateStringFromYMDHMS( @@ -27,13 +29,6 @@ class PdfDateTime { PdfDateTime.fromYMDHMS(int year, int month, int day, int hour, int minute, int second, {int timeZoneOffset = 0}) : pdfDateString = _pdfDateStringFromYMDHMS(year, month, day, hour, minute, second, timeZoneOffset: timeZoneOffset); - /// The original PDF date string. - /// - /// The date string should be in `D:YYYYMMDDHHmmSSOHH'mm'` format as specified in the PDF standard. - /// But this class do permissive parsing and allows missing some of the components. - /// To validate the format, use [isValidFormat]. - final String pdfDateString; - /// Creates a [PdfDateTime] from a nullable PDF date string. /// /// Returns null if [pdfDateString] is null. Otherwise, creates a [PdfDateTime] instance. @@ -126,9 +121,7 @@ class PdfDateTime { DateTime.utc(year, month, day, hour, minute, second).subtract(Duration(minutes: timezoneOffset)); /// Checks if the PDF date/time string is in a valid format or not. - bool get isValidFormat { - return _dtRegex.hasMatch(pdfDateString); - } + bool get isValidFormat => _dtRegex.hasMatch(pdfDateString); /// Regular expression to validate PDF date/time string format. static final _dtRegex = RegExp(r"^D:\d{4}(\d{2}(\d{2}(\d{2}(\d{2}(\d{2}(Z|[+\-]\d{2}'\d{2}'?)?)?)?)?)?)?$"); @@ -136,16 +129,4 @@ class PdfDateTime { /// Returns a canonicalized [PdfDateTime] with all components filled in. PdfDateTime canonicalize() => PdfDateTime.fromYMDHMS(year, month, day, hour, minute, second, timeZoneOffset: timezoneOffset); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is PdfDateTime && other.pdfDateString == pdfDateString; - } - - @override - int get hashCode => pdfDateString.hashCode; - - @override - String toString() => pdfDateString; } From 6de1716f53dd2fd9065982f569d3e5937c11a4b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:08:18 +0000 Subject: [PATCH 622/663] Bump actions/checkout from 5.0.0 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 16 ++++++++-------- .github/workflows/claude.yml | 2 +- .github/workflows/github-pages.yml | 4 ++-- .github/workflows/pana-analysis.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c942e225..86423b8f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -61,7 +61,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Java uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 @@ -95,7 +95,7 @@ jobs: package_manager: [cocoapods, swiftpm] name: ios-${{ matrix.package_manager }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter run: | @@ -130,7 +130,7 @@ jobs: package_manager: [cocoapods, swiftpm] name: macos-${{ matrix.package_manager }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter run: | @@ -161,7 +161,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Linux dependencies run: | @@ -189,7 +189,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }} runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Linux dependencies run: | @@ -217,7 +217,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }} runs-on: windows-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter shell: pwsh @@ -241,7 +241,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} runs-on: windows-11-arm steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter (master branch for ARM64 support) shell: pwsh @@ -264,7 +264,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter run: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 85cd1aeb..d7dc32bb 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -25,7 +25,7 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 1 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index efd8c7a2..c10826dd 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -57,7 +57,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Download all artifacts uses: actions/download-artifact@v6 with: diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index 3e1095bc..2db20c0c 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -26,7 +26,7 @@ jobs: - package_name: pdfrx package_path: packages/pdfrx steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Linux dependencies run: | diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index c3de5335..6405d55c 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -11,7 +11,7 @@ jobs: target: [ios, macos] steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact From e9fc23b9cb6f68638d5c8b2817ac197824746925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:52:51 +0000 Subject: [PATCH 623/663] Bump softprops/action-gh-release from 2.4.2 to 2.5.0 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.2 to 2.5.0. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/5be0e66d93ac7ed76da52eca8bb058f665c3a5fe...a06a81a03ee405af7f2048a818ed3f03bbf83c7b) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/pdfium-apple-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 6405d55c..e397639b 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -35,7 +35,7 @@ jobs: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ - name: Release PDFium - uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v0.1.15 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v0.1.15 with: token: ${{ secrets.TOKEN_FOR_RELEASE }} tag_name: ${{ github.ref_name }} From a946a4cbd518057530551fbf722e5d961b65dbad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:25:21 +0000 Subject: [PATCH 624/663] Bump actions/checkout from 6.0.0 to 6.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...8e8c483db84b4bee98b60c0593521ed34d9990e8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 16 ++++++++-------- .github/workflows/claude.yml | 2 +- .github/workflows/github-pages.yml | 4 ++-- .github/workflows/pana-analysis.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 86423b8f..8875d6d3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -61,7 +61,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Java uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 @@ -95,7 +95,7 @@ jobs: package_manager: [cocoapods, swiftpm] name: ios-${{ matrix.package_manager }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter run: | @@ -130,7 +130,7 @@ jobs: package_manager: [cocoapods, swiftpm] name: macos-${{ matrix.package_manager }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter run: | @@ -161,7 +161,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Linux dependencies run: | @@ -189,7 +189,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }} runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Linux dependencies run: | @@ -217,7 +217,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }} runs-on: windows-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter shell: pwsh @@ -241,7 +241,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }} runs-on: windows-11-arm steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter (master branch for ARM64 support) shell: pwsh @@ -264,7 +264,7 @@ jobs: if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter run: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index d7dc32bb..def5c851 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -25,7 +25,7 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index c10826dd..9c4ee896 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Flutter run: | git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter @@ -57,7 +57,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download all artifacts uses: actions/download-artifact@v6 with: diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml index 2db20c0c..37f40d0b 100644 --- a/.github/workflows/pana-analysis.yml +++ b/.github/workflows/pana-analysis.yml @@ -26,7 +26,7 @@ jobs: - package_name: pdfrx package_path: packages/pdfrx steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Linux dependencies run: | diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index e397639b..88a9428e 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -11,7 +11,7 @@ jobs: target: [ios, macos] steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact From 8224fde43b6d1eedd4b7d27a83931f087e237156 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:25:26 +0000 Subject: [PATCH 625/663] Bump actions/setup-java from 5.0.0 to 5.1.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/dded0888837ed1f317902acf8a20df0ad188d165...f2beeb24e141e01a676f977032f5a29d81c9e27e) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 86423b8f..b8d9831e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -64,7 +64,7 @@ jobs: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Java - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 with: distribution: 'temurin' java-version: '18' From de1a3b854cd5c70d098423b061567025f997c532 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Dec 2025 22:05:19 +0900 Subject: [PATCH 626/663] Move dart_pubspec_licenses to dev_dependencies (#561). --- packages/pdfrx/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 7e31c626..45c217fb 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -18,7 +18,6 @@ dependencies: pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 - dart_pubspec_licenses: ^3.0.12 ffi: flutter: sdk: flutter @@ -32,6 +31,7 @@ dependencies: web: ^1.1.1 dev_dependencies: + dart_pubspec_licenses: ^3.0.14 ffigen: ^19.1.0 flutter_test: sdk: flutter From 1d2ed9eb9a1b8abcf6cc05b2f3e726c063419b8a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Dec 2025 22:57:12 +0900 Subject: [PATCH 627/663] Release pdfrx_engine v0.3.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NEW: PdfDateTime extension type for PDF date string parsing - NEW: PdfAnnotation class for PDF annotation metadata extraction (#546) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/README.md | 2 ++ packages/pdfrx_engine/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 34f40632..570c779a 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.6 + +- NEW: [`PdfDateTime`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) extension type for PDF date string parsing ([PDF 32000-1:2008, 7.9.4 Dates](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95)) +- NEW: [`PdfAnnotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfAnnotation-class.html) class for PDF annotation metadata extraction ([#546](https://github.com/espresso3389/pdfrx/pull/546)) + ## 0.3.5 - Documentation updates. diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 88c87214..68c3ce7f 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -120,6 +120,8 @@ For detailed guides and tutorials, see the [documentation](https://github.com/es ## PDF API - Easy to use PDF APIs + - [PdfDateTime](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) - PDF date/time string parsing ([PDF 32000-1:2008, 7.9.4](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95)) + - [PdfAnnotation](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfAnnotation-class.html) - PDF annotation metadata - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document interface - [PdfDocument.openFile](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) - Open PDF from file path - [PdfDocument.openData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory (Uint8List) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 23140ae9..2f1d6f4c 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.5 +version: 0.3.6 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From 91bd9f5d3493480230e0b39666b564690ea5570a Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Dec 2025 23:01:03 +0900 Subject: [PATCH 628/663] Release pdfrx v2.2.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated to pdfrx_engine 0.3.6 - FIXED: Trackpad and mouse wheel boundary issues on Web (#547, #548) - FIXED: _setZoom now properly sets boundaryMargins - NEW: Ctrl+wheel zoom logic can be enabled on Web (#538) - NEW: PdfDateTime extension type for PDF date string parsing - NEW: PdfAnnotation class for PDF annotation metadata extraction (#546) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx/CHANGELOG.md | 9 +++++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 6 +++--- packages/pdfrx_coregraphics/CHANGELOG.md | 6 ++++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/README.md | 2 -- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 7a2147a8..b65e23d5 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,12 @@ +# 2.2.17 + +- Updated to pdfrx_engine 0.3.6 +- FIXED: Trackpad and mouse wheel boundary issues on Web ([#547](https://github.com/espresso3389/pdfrx/issues/547), [#548](https://github.com/espresso3389/pdfrx/pull/548)) +- FIXED: `_setZoom` now properly sets `boundaryMargins` +- NEW: Ctrl+wheel zoom logic can be enabled on Web ([#538](https://github.com/espresso3389/pdfrx/issues/538)) +- NEW: [`PdfDateTime`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) extension type for PDF date string parsing +- NEW: [`PdfAnnotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfAnnotation-class.html) class for PDF annotation metadata extraction ([#546](https://github.com/espresso3389/pdfrx/pull/546)) + # 2.2.16 - Updated to pdfrx_engine 0.3.4 diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 584bc3ac..64d04823 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.16 + pdfrx: ^2.2.17 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 45c217fb..0f56b465 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.16 +version: 2.2.17 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,10 +14,11 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.4 + pdfrx_engine: ^0.3.6 pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 + dart_pubspec_licenses: ^3.0.14 ffi: flutter: sdk: flutter @@ -31,7 +32,6 @@ dependencies: web: ^1.1.1 dev_dependencies: - dart_pubspec_licenses: ^3.0.14 ffigen: ^19.1.0 flutter_test: sdk: flutter diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 70227a84..bfdd59ef 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.10 + +- Updated to pdfrx_engine 0.3.6 +- NEW: [`PdfDateTime`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) extension type for PDF date string parsing +- NEW: [`PdfAnnotation`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfAnnotation-class.html) class for PDF annotation metadata extraction ([#546](https://github.com/espresso3389/pdfrx/pull/546)) + ## 0.1.9 - Updated to pdfrx_engine 0.3.4 diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 213db3df..d2086cdb 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.16 - pdfrx_coregraphics: ^0.1.9 + pdfrx: ^2.2.17 + pdfrx_coregraphics: ^0.1.10 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 280a10e7..31b4d680 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.9 +version: 0.1.10 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.3.4 + pdfrx_engine: ^0.3.6 http: dev_dependencies: diff --git a/packages/pdfrx_engine/README.md b/packages/pdfrx_engine/README.md index 68c3ce7f..88c87214 100644 --- a/packages/pdfrx_engine/README.md +++ b/packages/pdfrx_engine/README.md @@ -120,8 +120,6 @@ For detailed guides and tutorials, see the [documentation](https://github.com/es ## PDF API - Easy to use PDF APIs - - [PdfDateTime](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) - PDF date/time string parsing ([PDF 32000-1:2008, 7.9.4](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95)) - - [PdfAnnotation](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfAnnotation-class.html) - PDF annotation metadata - [PdfDocument](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument-class.html) - Main document interface - [PdfDocument.openFile](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openFile.html) - Open PDF from file path - [PdfDocument.openData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/openData.html) - Open PDF from memory (Uint8List) From a190ac891f296037a5424b3f0012ef2b279a6840 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 9 Dec 2025 23:02:22 +0900 Subject: [PATCH 629/663] Add .tmp/ to .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0e4acccb..78c58218 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .history .svn/ migrate_working_dir/ +.tmp/ # IntelliJ related *.iml From c045f91c47abdc10c4b0822b29621b7d10e8e5f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:27:28 +0000 Subject: [PATCH 630/663] Bump actions/upload-artifact from 5.0.0 to 6.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github-pages.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 9c4ee896..3fac8e90 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -42,7 +42,7 @@ jobs: flutter build web --wasm --release --base-href "/pdfrx/${{ matrix.example }}/" --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" build/web/index.html - name: Upload build artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6.0.0 with: name: ${{ matrix.example }}-web path: packages/pdfrx/example/${{ matrix.example }}/build/web/ diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 88a9428e..5d56892f 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -15,7 +15,7 @@ jobs: - name: Build PDFium ${{ matrix.target }} run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }} - name: Upload PDFium artifact - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pdfium-${{ matrix.target }} path: ./packages/pdfrx/darwin/pdfium/pdfium-${{ matrix.target }}.zip From e639589ce238f90fb5b4200fb5dfabdf66814ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:27:35 +0000 Subject: [PATCH 631/663] Bump actions/download-artifact from 6 to 7 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github-pages.yml | 2 +- .github/workflows/pdfium-apple-release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 9c4ee896..b728dd4d 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -59,7 +59,7 @@ jobs: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download all artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: path: artifacts/ - name: Prepare deployment directory diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml index 88a9428e..57e502b0 100644 --- a/.github/workflows/pdfium-apple-release.yml +++ b/.github/workflows/pdfium-apple-release.yml @@ -25,12 +25,12 @@ jobs: runs-on: macos-latest steps: - name: Download iOS artifact - uses: actions/download-artifact@f093f21ca4cfa7c75ccbbc2be54da76a0c7e1f05 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v6.0.0 with: name: pdfium-ios path: ./packages/pdfrx/darwin/pdfium/ - name: Download macOS artifact - uses: actions/download-artifact@f093f21ca4cfa7c75ccbbc2be54da76a0c7e1f05 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v6.0.0 with: name: pdfium-macos path: ./packages/pdfrx/darwin/pdfium/ From 9d6fcf9468bc9c6bedd4d7dcc48463d1638dc0de Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 17 Dec 2025 04:21:09 +0900 Subject: [PATCH 632/663] #563, #570 dart_pubspec_licenses 3.0.15 to make it compatible with Flutter 3.35.x 03abcd --- packages/pdfrx/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 0f56b465..aa1c5a58 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 - dart_pubspec_licenses: ^3.0.14 + dart_pubspec_licenses: ^3.0.15 ffi: flutter: sdk: flutter From 534cf2659254f0e8b539ef6adf6560af6e5f9654 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 17 Dec 2025 04:26:40 +0900 Subject: [PATCH 633/663] Intentionally leave flutter_lints version open. --- packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx/example/pdf_combine/pubspec.yaml | 2 +- packages/pdfrx/example/viewer/pubspec.yaml | 2 +- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 75ca5874..800f0909 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + flutter_lints: flutter: plugin: diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index 60efcfb0..c602999e 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + flutter_lints: flutter: uses-material-design: true diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 902ed5ab..5614e00c 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + flutter_lints: flutter: uses-material-design: true diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index aa1c5a58..b0416f6a 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -35,7 +35,7 @@ dev_dependencies: ffigen: ^19.1.0 flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + flutter_lints: archive: ^4.0.7 executables: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 31b4d680..6107079c 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + flutter_lints: flutter: plugin: From 64a4960de4ee741958d6791d933f575edd3b9f38 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 17 Dec 2025 04:53:47 +0900 Subject: [PATCH 634/663] Release pdfrx v2.2.18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FIXED: Dependency conflicts with dart_pubspec_licenses causing version resolution failures (#563, #570) - Removed dart_pubspec_licenses dependency and reimplemented package path resolution internally 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx/CHANGELOG.md | 6 + packages/pdfrx/bin/find_package_path.dart | 170 ++++++++++++++++++ .../bin/remove_darwin_pdfium_modules.dart | 13 +- packages/pdfrx/bin/remove_wasm_modules.dart | 13 +- packages/pdfrx/pubspec.yaml | 4 +- 5 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 packages/pdfrx/bin/find_package_path.dart diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index b65e23d5..cd9e6a5e 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.18 + +- FIXED: Dependency conflicts with `dart_pubspec_licenses` causing version resolution failures ([#563](https://github.com/espresso3389/pdfrx/issues/563), [#570](https://github.com/espresso3389/pdfrx/issues/570)) + - Removed `dart_pubspec_licenses` dependency and reimplemented package path resolution internally + - This resolves conflicts with `pana`, `meta`, `lints`, and `analyzer` package versions + # 2.2.17 - Updated to pdfrx_engine 0.3.6 diff --git a/packages/pdfrx/bin/find_package_path.dart b/packages/pdfrx/bin/find_package_path.dart new file mode 100644 index 00000000..94092428 --- /dev/null +++ b/packages/pdfrx/bin/find_package_path.dart @@ -0,0 +1,170 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; + +/// Finds the pubspec.yaml path for a package by reading pubspec.lock. +/// +/// Searches for pubspec.lock starting from [projectRoot] and walking up +/// the directory tree (to support pub workspace). +/// +/// Returns null if the package is not found or pubspec.lock doesn't exist. +String? findPackagePubspecPath(String projectRoot, String packageName) { + final pubspecLockPath = _findPubspecLock(projectRoot); + if (pubspecLockPath == null) return null; + + final rootDir = p.dirname(pubspecLockPath); + + final pubspecLock = loadYaml(File(pubspecLockPath).readAsStringSync()); + final packages = pubspecLock['packages'] as YamlMap?; + + // First, check pubspec.lock for the package + if (packages != null) { + final package = packages[packageName] as YamlMap?; + if (package != null) { + final pubCacheDir = _guessPubCacheDir(); + if (pubCacheDir != null) { + final packageDir = _getPackageDirectory( + package: package, + pubCacheDir: pubCacheDir, + basePubspecLockPath: pubspecLockPath, + ); + if (packageDir != null) { + return p.join(packageDir, 'pubspec.yaml'); + } + } + } + } + + // If not found in pubspec.lock, check workspace packages + final rootPubspecYamlFile = File(p.join(rootDir, 'pubspec.yaml')); + if (rootPubspecYamlFile.existsSync()) { + final rootPubspecYaml = loadYaml(rootPubspecYamlFile.readAsStringSync()); + final workspace = rootPubspecYaml['workspace'] as YamlList?; + if (workspace != null) { + for (final entry in workspace.whereType()) { + final workspacePackageDir = p.normalize(p.join(rootDir, entry)); + final workspacePubspecPath = p.join(workspacePackageDir, 'pubspec.yaml'); + final workspacePubspecFile = File(workspacePubspecPath); + if (workspacePubspecFile.existsSync()) { + final workspacePubspec = loadYaml(workspacePubspecFile.readAsStringSync()); + if (workspacePubspec['name'] == packageName) { + return workspacePubspecPath; + } + } + } + } + } + + return null; +} + +/// Searches for pubspec.lock starting from [startDir] and walking up the directory tree. +String? _findPubspecLock(String startDir) { + var current = p.normalize(p.absolute(startDir)); + + while (true) { + final lockFile = File(p.join(current, 'pubspec.lock')); + if (lockFile.existsSync()) { + return lockFile.path; + } + + final parent = p.dirname(current); + if (parent == current) { + // Reached root + return null; + } + current = parent; + } +} + +/// Guesses the pub cache directory location. +String? _guessPubCacheDir() { + var pubCache = Platform.environment['PUB_CACHE']; + if (pubCache != null && Directory(pubCache).existsSync()) return pubCache; + + if (Platform.isWindows) { + final appData = Platform.environment['APPDATA']; + if (appData != null) { + pubCache = p.join(appData, 'Pub', 'Cache'); + if (Directory(pubCache).existsSync()) return pubCache; + } + final localAppData = Platform.environment['LOCALAPPDATA']; + if (localAppData != null) { + pubCache = p.join(localAppData, 'Pub', 'Cache'); + if (Directory(pubCache).existsSync()) return pubCache; + } + } + + final homeDir = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + if (homeDir != null) { + return p.join(homeDir, '.pub-cache'); + } + return null; +} + +/// Gets the package directory from a pubspec.lock package entry. +String? _getPackageDirectory({ + required YamlMap package, + required String pubCacheDir, + required String basePubspecLockPath, +}) { + final source = package['source'] as String?; + final desc = package['description']; + + if (source == 'hosted' && desc is YamlMap) { + final host = _removePrefix(desc['url'] as String? ?? 'pub.dev'); + final name = desc['name'] as String?; + final version = package['version'] as String?; + if (name == null || version == null) return null; + return p.join(pubCacheDir, 'hosted', host.replaceAll('/', '%47'), '$name-$version'); + } else if (source == 'git' && desc is YamlMap) { + final repo = _gitRepoName(desc['url'] as String? ?? ''); + final commit = desc['resolved-ref'] as String?; + final subPath = desc['path'] as String? ?? ''; + if (commit == null) return null; + return p.join(pubCacheDir, 'git', '$repo-$commit', subPath); + } else if (source == 'sdk') { + final flutterDir = Platform.environment['FLUTTER_ROOT']; + if (flutterDir == null) return null; + final sdkName = desc is YamlMap ? desc['sdk'] as String? : null; + if (sdkName == 'flutter') { + final name = package['description'] is YamlMap ? (package['description'] as YamlMap)['name'] : null; + if (name == null) return null; + return p.join(flutterDir, 'packages', name); + } + return null; + } else if (source == 'path' && desc is YamlMap) { + final relativePath = desc['path'] as String?; + if (relativePath == null) return null; + return p.normalize(p.join(p.dirname(basePubspecLockPath), relativePath)); + } + + return null; +} + +/// Removes the protocol prefix from a URL. +String _removePrefix(String url) { + if (url.startsWith('https://')) return url.substring(8); + if (url.startsWith('http://')) return url.substring(7); + return url; +} + +/// Extracts the repository name from a Git URL. +String _gitRepoName(String url) { + // Handle various Git URL formats + var name = url; + if (name.endsWith('.git')) { + name = name.substring(0, name.length - 4); + } + final lastSlash = name.lastIndexOf('/'); + if (lastSlash >= 0) { + name = name.substring(lastSlash + 1); + } + // Handle ssh URLs like git@github.com:user/repo + final colonIndex = name.lastIndexOf(':'); + if (colonIndex >= 0) { + name = name.substring(colonIndex + 1); + } + return name; +} diff --git a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart index aaba46eb..cc9694ba 100644 --- a/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart +++ b/packages/pdfrx/bin/remove_darwin_pdfium_modules.dart @@ -2,9 +2,10 @@ import 'dart:io'; -import 'package:dart_pubspec_licenses/dart_pubspec_licenses.dart' as oss; import 'package:path/path.dart' as path; +import 'find_package_path.dart'; + Future main(List args) async { try { // Parse arguments @@ -28,11 +29,13 @@ Future main(List args) async { return 2; } - final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); - final pdfrxPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfium_flutter'); - print('Found: ${pdfrxPackage.name} ${pdfrxPackage.version}: ${pdfrxPackage.pubspecYamlPath}'); + final pubspecPath = findPackagePubspecPath(projectRoot, 'pdfium_flutter'); + if (pubspecPath == null) { + print('pdfium_flutter package not found. Did you run "flutter pub get"?'); + return 3; + } + print('Found pdfium_flutter: $pubspecPath'); - final pubspecPath = pdfrxPackage.pubspecYamlPath; final pubspecFile = File(pubspecPath); if (!pubspecFile.existsSync()) { diff --git a/packages/pdfrx/bin/remove_wasm_modules.dart b/packages/pdfrx/bin/remove_wasm_modules.dart index 0744e1fb..dc07c719 100644 --- a/packages/pdfrx/bin/remove_wasm_modules.dart +++ b/packages/pdfrx/bin/remove_wasm_modules.dart @@ -2,9 +2,10 @@ import 'dart:io'; -import 'package:dart_pubspec_licenses/dart_pubspec_licenses.dart' as oss; import 'package:path/path.dart' as path; +import 'find_package_path.dart'; + Future main(List args) async { try { // Parse arguments @@ -28,11 +29,13 @@ Future main(List args) async { return 2; } - final deps = await oss.listDependencies(pubspecYamlPath: projectPubspecYaml.path); - final pdfrxWasmPackage = [...deps.allDependencies, deps.package].firstWhere((p) => p.name == 'pdfrx'); - print('Found: ${pdfrxWasmPackage.name} ${pdfrxWasmPackage.version}: ${pdfrxWasmPackage.pubspecYamlPath}'); + final pubspecPath = findPackagePubspecPath(projectRoot, 'pdfrx'); + if (pubspecPath == null) { + print('pdfrx package not found. Did you run "flutter pub get"?'); + return 3; + } + print('Found pdfrx: $pubspecPath'); - final pubspecPath = pdfrxWasmPackage.pubspecYamlPath; final pubspecFile = File(pubspecPath); if (!pubspecFile.existsSync()) { diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index b0416f6a..56a2fbf5 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.17 +version: 2.2.18 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -18,7 +18,6 @@ dependencies: pdfium_flutter: ^0.1.1 collection: crypto: ^3.0.6 - dart_pubspec_licenses: ^3.0.15 ffi: flutter: sdk: flutter @@ -30,6 +29,7 @@ dependencies: url_launcher: ^6.3.2 vector_math: ^2.2.0 web: ^1.1.1 + yaml: dev_dependencies: ffigen: ^19.1.0 From adb491746b5d41d6856b4a8dc19c5785b325f379 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 24 Dec 2025 17:58:01 +0900 Subject: [PATCH 635/663] PdfPage "proxy" is not a public feature --- doc/PDF-Page-Manipulation.md | 3 +-- packages/pdfrx_engine/lib/src/pdf_page.dart | 12 +++++++++++- .../pdfrx_engine/lib/src/pdf_text_formatter.dart | 3 +-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/PDF-Page-Manipulation.md b/doc/PDF-Page-Manipulation.md index 1cdf5617..97d44fed 100644 --- a/doc/PDF-Page-Manipulation.md +++ b/doc/PDF-Page-Manipulation.md @@ -101,12 +101,11 @@ doc.pages = [ ]; ``` -Technically, the functions on [PdfPageWithRotationExtension](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageWithRotationExtension.html) creates a ([PdfPageProxy](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageProxy-class.html)) with the specified rotation, which can be used in page manipulation operations. +Technically, the functions on [PdfPageWithRotationExtension](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPageWithRotationExtension.html) creates a proxy object with the specified rotation, which can be used in page manipulation operations. **Important Notes:** - If the specified rotation matches the page's current rotation, the original page is returned unchanged -- The proxy page can be used in any context where a regular [PdfPage](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfPage-class.html) is expected ### Encoding to PDF diff --git a/packages/pdfrx_engine/lib/src/pdf_page.dart b/packages/pdfrx_engine/lib/src/pdf_page.dart index 4095df18..92714722 100644 --- a/packages/pdfrx_engine/lib/src/pdf_page.dart +++ b/packages/pdfrx_engine/lib/src/pdf_page.dart @@ -193,6 +193,8 @@ extension PdfPageBaseExtensions on PdfPage { extension PdfPageWithRotationExtension on PdfPage { /// Rotates a page with the specified rotation. /// + /// The method returns a new page with rotation equal to [rotation]. + /// /// See usage example in [PdfPageWithRotationExtension]. PdfPage rotatedTo(PdfPageRotation rotation) { if (rotation == this.rotation) { @@ -203,6 +205,8 @@ extension PdfPageWithRotationExtension on PdfPage { /// Rotates a page with rotation added to the current rotation. /// + /// The method returns a new page with rotation equal to (current rotation + [delta]). + /// /// See usage example in [PdfPageWithRotationExtension]. PdfPage rotatedBy(PdfPageRotation delta) { final newRotation = PdfPageRotation.values[(rotation.index + delta.index) & 3]; @@ -211,17 +215,23 @@ extension PdfPageWithRotationExtension on PdfPage { /// Rotates a page clockwise by 90 degrees. /// + /// This method returns a new page with rotation equal to (current rotation + clockwise90). + /// /// See usage example in [PdfPageWithRotationExtension]. PdfPage rotatedCW90() => rotatedBy(PdfPageRotation.clockwise90); /// Rotates a page counter-clockwise by 90 degrees. /// + /// This method returns a new page with rotation equal to (current rotation + clockwise270). + /// /// See usage example in [PdfPageWithRotationExtension]. PdfPage rotatedCCW90() => rotatedBy(PdfPageRotation.clockwise270); /// Rotates a page clockwise by 180 degrees. /// - /// Returns newly created [PdfPageProxy] that applies the rotation. + /// This method returns a new page with rotation equal to (current rotation + clockwise180). + /// + /// See usage example in [PdfPageWithRotationExtension]. PdfPage rotated180() => rotatedBy(PdfPageRotation.clockwise180); } diff --git a/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart index ccf8eca9..7eccbf60 100644 --- a/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart +++ b/packages/pdfrx_engine/lib/src/pdf_text_formatter.dart @@ -4,7 +4,6 @@ import 'package:vector_math/vector_math_64.dart'; import './mock/string_buffer_wrapper.dart' if (dart.library.io) './native/string_buffer_wrapper.dart'; import 'pdf_page.dart'; -import 'pdf_page_proxies.dart'; import 'pdf_rect.dart'; import 'pdf_text.dart'; import 'utils/unmodifiable_list.dart'; @@ -23,7 +22,7 @@ final class PdfTextFormatter { /// /// To access the raw text, use [PdfPage.loadText]. /// - /// This implementation is shared among multiple [PdfPage] and [PdfPageProxy] implementations. + /// This implementation is shared among multiple [PdfPage] and [PdfPage] proxy implementations. static Future loadStructuredText(PdfPage page, {required int? pageNumberOverride}) async { pageNumberOverride ??= page.pageNumber; final raw = await _loadFormattedText(page); From 409d1733d75287d165a77ff2fb49d41154d7e184 Mon Sep 17 00:00:00 2001 From: quaaantumdev Date: Wed, 31 Dec 2025 00:26:07 +0100 Subject: [PATCH 636/663] Smooth scroll and zoom interactions (optional) --- packages/pdfrx/lib/pdfrx.dart | 5 +- .../lib/src/widgets/interactive_viewer.dart | 9 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 76 +++++- .../lib/src/widgets/pdf_viewer_params.dart | 31 ++- ...df_viewer_scroll_interaction_delegate.dart | 77 ++++++ ...r_scroll_interaction_delegate_instant.dart | 89 ++++++ ...r_scroll_interaction_delegate_physics.dart | 255 ++++++++++++++++++ 7 files changed, 524 insertions(+), 18 deletions(-) create mode 100644 packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart create mode 100644 packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart create mode 100644 packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index af1d294e..f13b2b8f 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -3,9 +3,12 @@ export 'package:pdfrx_engine/pdfrx_engine.dart'; export 'src/pdf_document_ref.dart'; export 'src/pdfrx_flutter.dart'; +export 'src/utils/fixed_overscroll_physics.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; export 'src/widgets/pdf_viewer_params.dart'; export 'src/widgets/pdf_viewer_scroll_thumb.dart'; export 'src/widgets/pdf_widgets.dart'; -export 'src/utils/fixed_overscroll_physics.dart'; +export 'src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart'; +export 'src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart'; +export 'src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart'; diff --git a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart index a6f947b8..6bf8f450 100644 --- a/packages/pdfrx/lib/src/widgets/interactive_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/interactive_viewer.dart @@ -83,6 +83,7 @@ class InteractiveViewer extends StatefulWidget { this.alignment, this.trackpadScrollCausesScale = false, this.onWheelDelta, + this.onPointerScale, this.scrollPhysics, this.scrollPhysicsScale, this.scrollPhysicsAutoAdjustBoundaries = true, @@ -131,6 +132,7 @@ class InteractiveViewer extends StatefulWidget { this.alignment, this.trackpadScrollCausesScale = false, this.onWheelDelta, + this.onPointerScale, this.scrollPhysics, this.scrollPhysicsScale, this.scrollPhysicsAutoAdjustBoundaries = true, @@ -394,6 +396,9 @@ class InteractiveViewer extends StatefulWidget { /// final void Function(PointerScrollEvent event)? onWheelDelta; + /// To override the default pointer scale behavior. + final void Function(PointerScaleEvent event)? onPointerScale; + // Used as the coefficient of friction in the inertial translation animation. // This value was eyeballed to give a feel similar to Google Photos. static const double _kDrag = 0.0000135; @@ -1135,6 +1140,10 @@ class InteractiveViewerState extends State with TickerProvide } scaleChange = math.exp(-event.scrollDelta.dy / widget.scaleFactor); } else if (event is PointerScaleEvent) { + if (widget.onPointerScale != null) { + widget.onPointerScale!(event); + return; + } scaleChange = event.scale; } else { return; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 024a2627..8b37933c 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -210,7 +210,7 @@ class PdfViewer extends StatefulWidget { } class _PdfViewerState extends State - with SingleTickerProviderStateMixin + with TickerProviderStateMixin implements PdfTextSelectionDelegate, PdfViewerCoordinateConverter { PdfViewerController? _controller; late final _txController = _PdfViewerTransformationController(this); @@ -282,18 +282,32 @@ class _PdfViewerState extends State // the viewport EdgeInsets _adjustedBoundaryMargins = EdgeInsets.zero; + PdfViewerScrollInteractionDelegate? _interactionDelegate; + @override void initState() { super.initState(); pdfrxFlutterInitialize(); _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); _widgetUpdated(null); + _updateInteractionDelegate(); } @override void didUpdateWidget(covariant PdfViewer oldWidget) { super.didUpdateWidget(oldWidget); _widgetUpdated(oldWidget); + if (widget.params.interactionDelegateProvider != oldWidget.params.interactionDelegateProvider) { + _updateInteractionDelegate(); + } + } + + void _updateInteractionDelegate() { + _interactionDelegate?.dispose(); + _interactionDelegate = widget.params.interactionDelegateProvider.create(); + if (_controller != null) { + _interactionDelegate!.init(_controller!, this); + } } Future _widgetUpdated(PdfViewer? oldWidget) async { @@ -316,6 +330,8 @@ class _PdfViewerState extends State ..load(); } + // we dont check/update `_interactionDelegate` here because we dont assume the user + _onDocumentChanged(); } @@ -352,6 +368,7 @@ class _PdfViewerState extends State _controller!._attach(this); _txController.addListener(_onMatrixChanged); _documentSubscription = document.events.listen(_onDocumentEvent); + _interactionDelegate?.init(_controller!, this); if (mounted) { setState(() {}); @@ -386,6 +403,7 @@ class _PdfViewerState extends State @override void dispose() { + _interactionDelegate?.dispose(); focusReportForPreventingContextMenuWeb(this, false); _documentSubscription?.cancel(); _textSelectionChangedDebounceTimer?.cancel(); @@ -483,6 +501,7 @@ class _PdfViewerState extends State onInteractionUpdate: widget.params.onInteractionUpdate, interactionEndFrictionCoefficient: widget.params.interactionEndFrictionCoefficient, onWheelDelta: widget.params.scrollByMouseWheel != null ? _onWheelDelta : null, + onPointerScale: _onPointerScale, scrollPhysics: widget.params.scrollPhysics, scrollPhysicsScale: widget.params.scrollPhysicsScale, scrollPhysicsAutoAdjustBoundaries: false, @@ -798,6 +817,7 @@ class _PdfViewerState extends State } void _onInteractionStart(ScaleStartDetails details) { + _interactionDelegate?.stop(); // Stop physics when user touches _startInteraction(); _requestFocus(); widget.params.onInteractionStart?.call(details); @@ -1588,26 +1608,54 @@ class _PdfViewerState extends State // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. // So, I just add both values. var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; - final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); - if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; + final rawScaleFactor = pow(1.2, zoomFactor).toDouble(); + + final dampening = widget.params.scaleByPointerScale; + final scaleFactor = (rawScaleFactor - 1.0) * dampening + 1.0; + // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. - _controller!.zoomOnLocalPosition( - localPosition: _controller!.globalToLocal(event.position)!, - newZoom: newZoom, - duration: Duration.zero, - ); + _interactionDelegate?.zoom(scaleFactor, _controller!.globalToLocal(event.position)!); return; } - final dx = -event.scrollDelta.dx * widget.params.scrollByMouseWheel! / _currentZoom; - final dy = -event.scrollDelta.dy * widget.params.scrollByMouseWheel! / _currentZoom; - final m = _txController.value.clone(); + final scrollMultiplier = widget.params.scrollByMouseWheel ?? 1.0; + + var rawDx = -event.scrollDelta.dx; + var rawDy = -event.scrollDelta.dy; + + // Shift + Vertical Scroll = Horizontal Scroll + // Standard behavior for desktop applications + if (HardwareKeyboard.instance.isShiftPressed && rawDy != 0 && rawDx == 0) { + rawDx = rawDy; + rawDy = 0; + } + + final dx = rawDx * scrollMultiplier; + final dy = rawDy * scrollMultiplier; + + final Offset delta; + // If the parameter forces horizontal scrolling, we swap the axes. + // Note: If user held SHIFT (already swapped to horizontal), this swap + // effectively turns it back to vertical, which is generally acceptable + // (axis inversion) if the user explicitly configured this param. if (widget.params.scrollHorizontallyByMouseWheel) { - m.translateByDouble(dy, dx, 0, 1); + delta = Offset(dy, dx); } else { - m.translateByDouble(dx, dy, 0, 1); + delta = Offset(dx, dy); } - _txController.value = _makeMatrixInSafeRange(m, forceClamp: true); + + _interactionDelegate?.pan(delta); + } finally { + _stopInteraction(); + } + } + + void _onPointerScale(PointerScaleEvent event) { + _startInteraction(); + try { + final dampening = widget.params.scaleByPointerScale; + final scaleFactor = (event.scale - 1.0) * dampening + 1.0; + _interactionDelegate?.zoom(scaleFactor, event.localPosition); } finally { _stopInteraction(); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 77d07678..f442abab 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -50,6 +50,7 @@ class PdfViewerParams { this.onPageChanged, this.getPageRenderingScale, this.scrollByMouseWheel = 0.2, + this.scaleByPointerScale = 1.0, this.scrollHorizontallyByMouseWheel = false, this.enableKeyboardNavigation = true, this.scrollByArrowKey = 25.0, @@ -73,6 +74,7 @@ class PdfViewerParams { this.forceReload = false, this.scrollPhysics, this.scrollPhysicsScale, + this.interactionDelegateProvider = const PdfViewerScrollInteractionDelegateProviderInstant(), }); /// Margin around the page. @@ -352,6 +354,15 @@ class PdfViewerParams { /// null to disable scroll-by-mouse-wheel. final double? scrollByMouseWheel; + /// Scale sensitivity for pointer scale events (e.g. Trackpad pinch) and Ctrl+Scroll zoom interactions. + /// + /// Defaults to 1.0. + /// * Values < 1.0 reduce the zoom speed (finer control). + /// * Values > 1.0 increase the zoom speed (faster). + /// + /// This factor is applied to the raw scale delta received from the platform to determine the target zoom level. + final double scaleByPointerScale; + /// If true, the scroll direction is horizontal when the mouse wheel is scrolled in primary direction. final bool scrollHorizontallyByMouseWheel; @@ -563,6 +574,14 @@ class PdfViewerParams { /// Scroll physics for scaling within the viewer. If null, it uses the same value as [scrollPhysics]. final ScrollPhysics? scrollPhysicsScale; + /// Provider to create a delegate that handles scroll/zoom interactions (Mouse Wheel / Trackpad). + /// + /// Defaults to [PdfViewerScrollInteractionDelegateProviderInstant] which provides + /// instant updates (legacy behavior). + /// + /// To enable smooth, physics-based animations, use [PdfViewerScrollInteractionDelegateProviderPhysics]. + final PdfViewerScrollInteractionDelegateProvider interactionDelegateProvider; + /// A convenience function to get platform-specific default scroll physics. /// /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a @@ -601,13 +620,15 @@ class PdfViewerParams { other.scaleEnabled != scaleEnabled || other.interactionEndFrictionCoefficient != interactionEndFrictionCoefficient || other.scrollByMouseWheel != scrollByMouseWheel || + other.scaleByPointerScale != scaleByPointerScale || other.scrollHorizontallyByMouseWheel != scrollHorizontallyByMouseWheel || other.enableKeyboardNavigation != enableKeyboardNavigation || other.scrollByArrowKey != scrollByArrowKey || other.horizontalCacheExtent != horizontalCacheExtent || other.verticalCacheExtent != verticalCacheExtent || other.linkHandlerParams != linkHandlerParams || - other.scrollPhysics != scrollPhysics; + other.scrollPhysics != scrollPhysics || + other.interactionDelegateProvider != interactionDelegateProvider; } @override @@ -648,6 +669,7 @@ class PdfViewerParams { other.onPageChanged == onPageChanged && other.getPageRenderingScale == getPageRenderingScale && other.scrollByMouseWheel == scrollByMouseWheel && + other.scaleByPointerScale == scaleByPointerScale && other.scrollHorizontallyByMouseWheel == scrollHorizontallyByMouseWheel && other.enableKeyboardNavigation == enableKeyboardNavigation && other.scrollByArrowKey == scrollByArrowKey && @@ -668,7 +690,8 @@ class PdfViewerParams { other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && other.forceReload == forceReload && - other.scrollPhysics == scrollPhysics; + other.scrollPhysics == scrollPhysics && + other.interactionDelegateProvider == interactionDelegateProvider; } @override @@ -707,6 +730,7 @@ class PdfViewerParams { onPageChanged.hashCode ^ getPageRenderingScale.hashCode ^ scrollByMouseWheel.hashCode ^ + scaleByPointerScale.hashCode ^ scrollHorizontallyByMouseWheel.hashCode ^ enableKeyboardNavigation.hashCode ^ scrollByArrowKey.hashCode ^ @@ -727,7 +751,8 @@ class PdfViewerParams { keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ forceReload.hashCode ^ - scrollPhysics.hashCode; + scrollPhysics.hashCode ^ + interactionDelegateProvider.hashCode; } } diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart new file mode 100644 index 00000000..b92dadc4 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart @@ -0,0 +1,77 @@ +import 'package:flutter/gestures.dart' show PointerScrollEvent; +import 'package:flutter/scheduler.dart' show Ticker; +import 'package:flutter/widgets.dart'; + +import '../../../pdfrx.dart' show PdfViewerParams; +import '../pdf_viewer.dart'; +import '../pdf_viewer_params.dart' show PdfViewerParams; + +/// Interface for a factory that creates [PdfViewerScrollInteractionDelegate] instances. +/// +/// ### Why use a Provider class instead of a simple closure? +/// [PdfViewerParams] relies on `operator ==` to determine if the viewer needs to be +/// reloaded or updated. If we allowed passing a simple closure (e.g. `() => MyDelegate()`) +/// in the params, it would likely be a different object instance on every `build` call, +/// forcing the [PdfViewer] to dispose and recreate the delegate constantly. +/// +/// By using a `const` Provider class with a proper `operator ==` implementation, we ensure +/// that the delegate lifecycle is stable across rebuilds. +abstract class PdfViewerScrollInteractionDelegateProvider { + const PdfViewerScrollInteractionDelegateProvider(); + + /// Creates the runtime delegate instance. + /// + /// This is called by [PdfViewerState] when the widget initializes or when the + /// provider configuration changes. + PdfViewerScrollInteractionDelegate create(); + + /// Subclasses must implement equality to prevent unnecessary delegate recreation. + @override + bool operator ==(Object other); + + @override + int get hashCode; +} + +/// The "Brain" for handling desktop-style pointer interactions (Mouse Wheel, Trackpad). +/// +/// This delegate decouples the **Intent** (e.g., "User wants to pan 50 pixels") from +/// the **Execution** (e.g., "Jump immediately" vs "Animate with friction"). +/// +/// Lifecycle: +/// 1. [create] is called by the Provider. +/// 2. [init] is called when the controller is attached. +/// 3. [pan] / [zoom] are called on user interaction. +/// 4. [stop] is called when the user interrupts (e.g., touches the screen). +/// 5. [dispose] is called when the viewer is destroyed. +abstract class PdfViewerScrollInteractionDelegate { + /// Called when the [PdfViewerState] is ready. + /// + /// [controller]: Used to read/write the transformation matrix. + /// [vsync]: Used to create [Ticker]s for physics-based animations. + void init(PdfViewerController controller, TickerProvider vsync); + + /// Called when the delegate is being destroyed. + /// Implementations should dispose of any Tickers or listeners here. + void dispose(); + + /// Called when the user initiates a competing interaction (e.g., Touch Pan, Pinch Zoom). + /// + /// Implementations **must** stop any ongoing animations immediately to prevent + /// "fighting" with the user's gesture (e.g., don't keep animating scroll down + /// if the user is dragging up). + void stop(); + + /// Request a pan (scroll) operation. + /// + /// [delta] is the requested move distance in **viewport logical pixels**. + /// (e.g., [PointerScrollEvent.scrollDelta]). + void pan(Offset delta); + + /// Request a zoom operation. + /// + /// [scale] is the relative scale factor (e.g., `1.1` means "increase zoom by 10%"). + /// [focalPoint] is the pixel position in the viewport that should remain stationary + /// during the zoom (the anchor point). + void zoom(double scale, Offset focalPoint); +} diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart new file mode 100644 index 00000000..76b8cdfc --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart @@ -0,0 +1,89 @@ +import 'package:flutter/widgets.dart'; + +import '../pdf_viewer.dart'; +import 'pdf_viewer_scroll_interaction_delegate.dart'; + +/// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Instant** behavior. +/// +/// This implementation applies scroll and zoom deltas immediately to the controller +/// without any animation or physics. It replicates the legacy behavior of `pdfrx`. +/// +/// Use this if you prefer a "raw" feel or need to minimize CPU usage. +class PdfViewerScrollInteractionDelegateProviderInstant extends PdfViewerScrollInteractionDelegateProvider { + const PdfViewerScrollInteractionDelegateProviderInstant(); + + @override + PdfViewerScrollInteractionDelegate create() => _PdfViewerScrollInteractionDelegateInstant(); + + @override + bool operator ==(Object other) => other is PdfViewerScrollInteractionDelegateProviderInstant; + + @override + int get hashCode => runtimeType.hashCode; +} + +/// Implementation of [PdfViewerScrollInteractionDelegate] that applies changes instantly. +/// +/// This implementation performs no animation/tweening. It directly calculates the +/// target matrix and sets it on the controller. +class _PdfViewerScrollInteractionDelegateInstant implements PdfViewerScrollInteractionDelegate { + PdfViewerController? _controller; + + @override + void init(PdfViewerController controller, TickerProvider vsync) { + _controller = controller; + } + + @override + void dispose() { + _controller = null; + } + + @override + void stop() { + // No animations to stop in the instant implementation. + } + + @override + void pan(Offset delta) { + final controller = _controller; + if (controller == null || !controller.isReady) { + return; + } + + // Clone the current matrix to apply translations. + final m = controller.value.clone(); + + // The [delta] is in viewport pixels. To translate the content within the matrix, + // we must divide by the current zoom level. + // e.g. If zoomed in 2x, moving 10 pixels on screen means moving 5 pixels in the document space. + // We *add* the delta because the matrix represents the content position. + m.translateByDouble(delta.dx, delta.dy, 0, 1); + + // Apply the new matrix, ensuring it stays within the configured boundaries. + controller.value = controller.makeMatrixInSafeRange(m, forceClamp: true); + } + + @override + void zoom(double scale, Offset focalPoint) { + final controller = _controller; + if (controller == null || !controller.isReady) { + return; + } + + final currentZoom = controller.currentZoom; + final params = controller.params; + + // Calculate the target zoom level, clamped to the min/max allowed by params. + final newZoom = (currentZoom * scale).clamp(params.minScale, params.maxScale); + + // Optimization: Ignore negligible changes to prevent unnecessary rebuilds. + if ((newZoom - currentZoom).abs() < 0.0001) { + return; + } + + // Apply the zoom instantly using the controller's helper, which handles + // the matrix math to keep [focalPoint] stationary. + controller.zoomOnLocalPosition(localPosition: focalPoint, newZoom: newZoom, duration: Duration.zero); + } +} diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart new file mode 100644 index 00000000..ff81af75 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart @@ -0,0 +1,255 @@ +import 'dart:math' as math; + +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:vector_math/vector_math_64.dart' as vec; + +import '../pdf_viewer.dart'; +import 'pdf_viewer_scroll_interaction_delegate.dart'; + +/// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Physics-based** behavior. +/// +/// This implementation provides smooth, additive animations for scroll (pan) and zoom interactions, +/// similar to browser or desktop OS behavior. +/// +/// It uses exponential decay to smoothly transition to the target state, allowing for +/// "catch-up" animations when rapid events (like continuous scroll wheel or trackpad gestures) occur. +class PdfViewerScrollInteractionDelegateProviderPhysics extends PdfViewerScrollInteractionDelegateProvider { + const PdfViewerScrollInteractionDelegateProviderPhysics({this.panFriction = 12.0, this.zoomFriction = 12.0}); + + /// Friction factor for panning. Higher means stops faster. Default 12.0. + /// + /// Controls the "weight" of the scroll physics. + final double panFriction; + + /// Friction factor for zooming. Higher means stops faster. Default 12.0. + /// + /// Controls the "weight" of the zoom physics. + final double zoomFriction; + + @override + PdfViewerScrollInteractionDelegate create() => + _PdfViewerScrollInteractionDelegatePhysics(panFriction: panFriction, zoomFriction: zoomFriction); + + @override + bool operator ==(Object other) => + other is PdfViewerScrollInteractionDelegateProviderPhysics && + other.panFriction == panFriction && + other.zoomFriction == zoomFriction; + + @override + int get hashCode => Object.hash(panFriction, zoomFriction); +} + +/// Implementation of [PdfViewerScrollInteractionDelegate] that uses physics simulations +/// to animate pan and zoom transitions. +/// +/// This delegate handles [Ticker] management to drive the animations frame-by-frame. +class _PdfViewerScrollInteractionDelegatePhysics implements PdfViewerScrollInteractionDelegate { + _PdfViewerScrollInteractionDelegatePhysics({required this.panFriction, required this.zoomFriction}); + + final double panFriction; + final double zoomFriction; + + PdfViewerController? _controller; + TickerProvider? _vsync; + + // --- Pan Physics State --- + Ticker? _panTicker; + + /// The target translation offset (tx, ty) the physics simulation is moving towards. + Offset? _panTarget; + Duration? _lastPanFrameTime; + + // --- Zoom Physics State --- + Ticker? _zoomTicker; + + /// The target zoom level the physics simulation is scaling towards. + double? _zoomTarget; + Duration? _lastZoomFrameTime; + Offset? _lastFocalPoint; + + /// Pixel distance threshold to stop animation + static const double _kEpsilon = 0.5; + + /// Scale threshold to stop animation + static const double _kScaleEpsilon = 0.0001; + + @override + void init(PdfViewerController controller, TickerProvider vsync) { + _controller = controller; + _vsync = vsync; + } + + @override + void dispose() { + stop(); + _controller = null; + _vsync = null; + } + + @override + void stop() { + _panTicker?.dispose(); + _panTicker = null; + _panTarget = null; + + _zoomTicker?.dispose(); + _zoomTicker = null; + _zoomTarget = null; + } + + @override + void pan(Offset delta) { + final controller = _controller; + final vsync = _vsync; + if (controller == null || !controller.isReady || vsync == null) { + return; + } + + // Stop zoom if panning starts. + // Explicit panning (e.g. scroll wheel) takes precedence over an ongoing zoom animation. + _zoomTicker?.dispose(); + _zoomTicker = null; + _zoomTarget = null; + + // Initialize target with current translation if not already animating + if (_panTarget == null) { + final currentTrans = controller.value.getTranslation(); + _panTarget = Offset(currentTrans.x, currentTrans.y); + } + + // Accumulate delta. + // [delta] is "viewport pixels to move". + // e.g. Scroll Down -> delta.y is negative -> visual content moves up -> matrix translation y decreases. + // So we add the delta to the target translation. + _panTarget = _panTarget! + delta; + + if (_panTicker == null) { + _lastPanFrameTime = null; + _panTicker = vsync.createTicker(_onPanTick)..start(); + } + } + + void _onPanTick(Duration elapsed) { + final controller = _controller; + if (controller == null || _panTarget == null) { + _panTicker?.dispose(); + _panTicker = null; + return; + } + + final dt = _lastPanFrameTime == null + ? (1.0 / 60.0) // assuming 60 FPS for the first frame + : (elapsed - _lastPanFrameTime!).inMicroseconds / 1000000.0; + _lastPanFrameTime = elapsed; + + final currentTransVec = controller.value.getTranslation(); + final currentTrans = Offset(currentTransVec.x, currentTransVec.y); + + final diff = _panTarget! - currentTrans; + + // Stop if close enough to target + if (diff.distance < _kEpsilon) { + _applyTranslation(_panTarget!); + _panTicker?.dispose(); + _panTicker = null; + _panTarget = null; + return; + } + + // Exponential Decay: Move a percentage of the remaining distance + final alpha = 1.0 - math.exp(-panFriction * dt); + final newTrans = currentTrans + diff * alpha; + + _applyTranslation(newTrans); + } + + void _applyTranslation(Offset translation) { + final controller = _controller; + if (controller == null) return; + + // Reconstruct matrix with new translation, preserving current rotation/scale + final currentMatrix = controller.value; + + final newMatrix = currentMatrix.clone(); + newMatrix.setTranslation(vec.Vector3(translation.dx, translation.dy, 0.0)); + + // Apply and clamp to boundaries + controller.value = controller.makeMatrixInSafeRange(newMatrix, forceClamp: true); + + // Update target if we hit a boundary to prevent "sticky" physics trying to push through. + // If the actual translation after clamping differs significantly from the requested translation, + // we adjust the target to match the clamped reality for that axis. + final actualTransVec = controller.value.getTranslation(); + final actualTrans = Offset(actualTransVec.x, actualTransVec.y); + + if (_panTarget != null) { + // If we clamped, adjust the target so we don't keep trying to move past edge + if ((actualTrans.dx - translation.dx).abs() > 1.0) { + _panTarget = Offset(actualTrans.dx, _panTarget!.dy); + } + if ((actualTrans.dy - translation.dy).abs() > 1.0) { + _panTarget = Offset(_panTarget!.dx, actualTrans.dy); + } + } + } + + @override + void zoom(double scaleFactor, Offset focalPoint) { + final controller = _controller; + final vsync = _vsync; + if (controller == null || !controller.isReady || vsync == null) return; + + // Stop pan if zoom starts + _panTicker?.dispose(); + _panTicker = null; + _panTarget = null; + + final currentZoom = controller.currentZoom; + _zoomTarget ??= currentZoom; + + // Apply accumulated scale to target + _zoomTarget = (_zoomTarget! * scaleFactor).clamp(controller.minScale, controller.params.maxScale); + + // Update last focal point for the animation tick + _lastFocalPoint = focalPoint; + + if (_zoomTicker == null) { + _lastZoomFrameTime = null; + _zoomTicker = vsync.createTicker(_onZoomTick)..start(); + } + } + + void _onZoomTick(Duration elapsed) { + final controller = _controller; + if (controller == null || _zoomTarget == null || _lastFocalPoint == null) { + _zoomTicker?.dispose(); + _zoomTicker = null; + return; + } + + final dt = _lastZoomFrameTime == null ? 1.0 / 60.0 : (elapsed - _lastZoomFrameTime!).inMicroseconds / 1000000.0; + _lastZoomFrameTime = elapsed; + + final currentZoom = controller.currentZoom; + final diff = _zoomTarget! - currentZoom; + + // Stop if close enough + if (diff.abs() < _kScaleEpsilon) { + // Snap to target + controller.zoomOnLocalPosition(localPosition: _lastFocalPoint!, newZoom: _zoomTarget!, duration: Duration.zero); + _zoomTicker?.dispose(); + _zoomTicker = null; + _zoomTarget = null; + return; + } + + // Exponential Decay + final alpha = 1.0 - math.exp(-zoomFriction * dt); + final newZoom = currentZoom + diff * alpha; + + // Use controller's helper which handles the matrix math to keep focal point stationary + controller.zoomOnLocalPosition(localPosition: _lastFocalPoint!, newZoom: newZoom, duration: Duration.zero); + } +} From 75efb8c63907bea63ae8cd006a7456764713621f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 2 Jan 2026 02:39:31 +0900 Subject: [PATCH 637/663] WIP: adding PdfDocument.useNativeDocumentHandle/reloadPages --- packages/pdfrx/assets/pdfium_worker.js | 58 ++++++++++++++ packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 29 +++++++ .../lib/pdfrx_coregraphics.dart | 16 ++++ .../lib/src/native/pdfrx_pdfium.dart | 76 +++++++++++++++++++ .../pdfrx_engine/lib/src/pdf_document.dart | 27 +++++++ 5 files changed, 206 insertions(+) diff --git a/packages/pdfrx/assets/pdfium_worker.js b/packages/pdfrx/assets/pdfium_worker.js index 48cac450..f22c6aa2 100644 --- a/packages/pdfrx/assets/pdfium_worker.js +++ b/packages/pdfrx/assets/pdfium_worker.js @@ -981,6 +981,63 @@ function loadPagesProgressively(params) { return { pages, missingFonts: missingFonts[docHandle] }; } +/** + * + * @param {{docHandle: number, pageIndices: number[]|undefined, currentPagesCount: number}} params + * @returns {{pages: PdfPage[], missingFonts: FontQueries}} + */ +function reloadPages(params) { + const { docHandle, pageIndices } = params; + /** @type {PdfPage[]} */ + const pages = []; + const pageCount = Pdfium.wasmExports.FPDF_GetPageCount(docHandle); + /** @type {number[]} */ + var indicesToLoad = []; + if (pageIndices) { + for (const pageIndex of pageIndices) { + if (pageIndex < 0 || pageIndex >= pageCount) { + throw new Error(`Invalid page index ${pageIndex} (page count: ${pageCount})`); + } + if (pageIndex < currentPagesCount) { + indicesToLoad.push(pageIndex); + } + } + for (let i = currentPagesCount; i < pageCount; i++) { + indicesToLoad.push(i); + } + } else { + for (let i = 0; i < pageCount; i++) { + indicesToLoad.push(i); + } + } + + _resetMissingFonts(); + for (const pageIndex of indicesToLoad) { + const pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex); + if (!pageHandle) { + const error = Pdfium.wasmExports.FPDF_GetLastError(); + throw new Error(`FPDF_LoadPage failed (${_getErrorMessage(error)})`); + } + const rectBuffer = Pdfium.wasmExports.malloc(4 * 4); // FS_RECTF: float[4] + Pdfium.wasmExports.FPDF_GetPageBoundingBox(pageHandle, rectBuffer); + const rect = new Float32Array(Pdfium.memory.buffer, rectBuffer, 4); + const bbLeft = rect[0]; + const bbBottom = rect[3]; + Pdfium.wasmExports.free(rectBuffer); + pages.push({ + pageIndex: pageIndex, + width: Pdfium.wasmExports.FPDF_GetPageWidthF(pageHandle), + height: Pdfium.wasmExports.FPDF_GetPageHeightF(pageHandle), + rotation: Pdfium.wasmExports.FPDFPage_GetRotation(pageHandle), + isLoaded: true, + bbLeft: bbLeft, + bbBottom: bbBottom, + }); + Pdfium.wasmExports.FPDF_ClosePage(pageHandle); + } + return { pages, missingFonts: missingFonts[docHandle] }; +} + /** * @param {{formHandle: number, formInfo: number, docHandle: number}} params */ @@ -2068,6 +2125,7 @@ const functions = { createNewDocument, createDocumentFromJpegData, loadPagesProgressively, + reloadPages, closeDocument, loadOutline, loadPage, diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 416dd45c..faf90b82 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -449,6 +449,30 @@ class _PdfDocumentWasm extends PdfDocument { }); } + @override + Future reloadPages({List? pageNumbersToReload}) async { + if (isDisposed) return; + await synchronized(() async { + final pageIndices = pageNumbersToReload?.map((n) => n - 1).toList(); + final result = await _sendCommand( + 'reloadPages', + parameters: { + 'docHandle': document['docHandle'], + if (pageIndices != null) 'pageIndices': pageIndices, + 'currentPagesCount': pages.length, + }, + ); + final reloadedPages = parsePages(this, result['pages'] as List); + final newPages = pages.toList(growable: false); + for (final page in reloadedPages) { + newPages[page.pageNumber - 1] = page; // Update the existing page + } + pages = newPages; + + updateMissingFonts(result['missingFonts']); + }); + } + /// Don't handle [_pages] directly unless you really understand what you're doing; use [pages] getter/setter instead. /// /// [pages] automatically keeps consistency and also notifies page changes. @@ -600,6 +624,11 @@ class _PdfDocumentWasm extends PdfDocument { final bb = result['data'] as ByteBuffer; return Uint8List.view(bb.asByteData().buffer, 0, bb.lengthInBytes); } + + @override + Future useNativeDocumentHandle(FutureOr Function(int nativeDocumentHandle) task) { + throw UnimplementedError('PdfDocument.useNativeDocumentHandle is not implemented for WASM backend.'); + } } class _PdfPageRenderCancellationTokenWasm extends PdfPageRenderCancellationToken { diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 8c944a2b..1744a541 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -501,6 +501,22 @@ class _CoreGraphicsPdfDocument extends PdfDocument { 'encodePdf() is not implemented for CoreGraphics backend.', ); } + + @override + Future useNativeDocumentHandle( + FutureOr Function(int nativeDocumentHandle) task, + ) { + throw UnimplementedError( + 'useNativeDocumentHandle() is not implemented for CoreGraphics backend.', + ); + } + + @override + Future reloadPages({List? pageNumbersToReload}) { + throw UnimplementedError( + 'reloadPages() is not implemented for CoreGraphics backend.', + ); + } } class _CoreGraphicsPdfPage extends PdfPage { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index a0ea3a3e..3fab2e78 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -1,5 +1,6 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; @@ -761,6 +762,71 @@ class _PdfDocumentPdfium extends PdfDocument { } } + @override + Future reloadPages({List? pageNumbersToReload}) async { + try { + final results = await (await BackgroundWorker.instance).computeWithArena((arena, params) { + final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); + final pageCount = pdfium.FPDF_GetPageCount(doc); + if (params.pageNumbersToReload != null) { + for (final pageNumber in params.pageNumbersToReload!) { + if (pageNumber < 1 || pageNumber > pageCount) { + throw ArgumentError('Invalid page number to reload: $pageNumber', 'pageNumbersToReload'); + } + } + } + + final pageNumbersToLoad = SplayTreeSet.from(params.pageNumbersToReload ?? []); + pageNumbersToLoad.addAll( + Iterable.generate(pageCount - params.currentPageCount, (index) => params.currentPageCount + index + 1), + ); + + final pages = <({int pageIndex, double width, double height, int rotation, double bbLeft, double bbBottom})>[]; + for (final pageNumber in pageNumbersToLoad) { + final page = pdfium.FPDF_LoadPage(doc, pageNumber - 1); + try { + final rect = arena(); + pdfium.FPDF_GetPageBoundingBox(page, rect); + pages.add(( + pageIndex: pageNumber - 1, + width: pdfium.FPDF_GetPageWidthF(page), + height: pdfium.FPDF_GetPageHeightF(page), + rotation: pdfium.FPDFPage_GetRotation(page), + bbLeft: rect.ref.left.toDouble(), + bbBottom: rect.ref.bottom.toDouble(), + )); + } finally { + pdfium.FPDF_ClosePage(page); + } + } + return (pages: pages); + }, (docAddress: document.address, pageNumbersToReload: pageNumbersToReload, currentPageCount: _pages.length)); + + final newPages = [..._pages]; + for (var i = 0; i < results.pages.length; i++) { + final pageData = results.pages[i]; + final newPage = _PdfPagePdfium._( + document: this, + pageNumber: i + 1, + width: pageData.width, + height: pageData.height, + rotation: PdfPageRotation.values[pageData.rotation], + bbLeft: pageData.bbLeft, + bbBottom: pageData.bbBottom, + isLoaded: true, + ); + if (i < newPages.length) { + newPages[i] = newPage; + } else { + newPages.add(newPage); + } + } + pages = newPages; + } catch (e) { + rethrow; + } + } + @override List get pages => _pages; @@ -887,6 +953,16 @@ class _PdfDocumentPdfium extends PdfDocument { } }, (document: document.address, incremental: incremental, removeSecurity: removeSecurity)); } + + @override + Future useNativeDocumentHandle(FutureOr Function(int nativeDocumentHandle) task) async { + if (isDisposed) { + throw StateError('Document is already disposed.'); + } + return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() { + return task(document.address); + }); + } } typedef _NativeFileWriteCallable = diff --git a/packages/pdfrx_engine/lib/src/pdf_document.dart b/packages/pdfrx_engine/lib/src/pdf_document.dart index a7cc9b9f..a43dd52f 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document.dart @@ -262,6 +262,33 @@ abstract class PdfDocument { /// /// This function internally calls [assemble] before encoding the PDF. Future encodePdf({bool incremental = false, bool removeSecurity = false}); + + /// Execute native document task with the native document handle. + /// + /// Only supported for native backends (e.g., PDFium). + /// + /// Please note that this function suspends pdfrx internal PDFium worker during the execution of [task]. + /// + /// If you modify the document inside [task], make sure to keep consistency of the document after the + /// modification; e.g., call [reloadPages] if necessary. + /// + /// `nativeDocumentHandle` is a pointer to the native PDF document handle (e.g., FPDF_DOCUMENT in PDFium) but + /// represented as an integer to avoid direct dependency to PDFium bindings. + /// + /// ```dart + /// final result = await pdfDocument.useNativeDocumentHandle((nativeDocumentHandle) { + /// // Convert nativeDocumentHandle to FPDF_DOCUMENT handle. + /// final pdfDocument = pdfium_bindings.FPDF_DOCUMENT.fromAddress(nativeDocumentHandle); + /// // <> + /// return someResult; + /// }); + /// ``` + Future useNativeDocumentHandle(FutureOr Function(int nativeDocumentHandle) task); + + /// Reload specified pages. + /// + /// [pageNumbersToReload] is the list of page numbers (1-based) to reload. If null, all pages are reloaded. + Future reloadPages({List? pageNumbersToReload}); } typedef PdfPageLoadingCallback = FutureOr Function(int currentPageNumber, int totalPageCount, T? data); From 7320b1e01e2d70b81fd97f5b67d23a610daba6a8 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 2 Jan 2026 02:52:45 +0900 Subject: [PATCH 638/663] Update Gradle wrapper to version 8.12 and Android plugin to version 8.9.1 --- .../viewer/android/gradle/wrapper/gradle-wrapper.properties | 2 +- packages/pdfrx/example/viewer/android/settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties b/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties index 6cb8454c..ac3b4792 100644 --- a/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/packages/pdfrx/example/viewer/android/settings.gradle.kts b/packages/pdfrx/example/viewer/android/settings.gradle.kts index ab39a10a..43394ed5 100644 --- a/packages/pdfrx/example/viewer/android/settings.gradle.kts +++ b/packages/pdfrx/example/viewer/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } From 2bd6aa2ae4e18f64861ff4a4618af1591b4a561d Mon Sep 17 00:00:00 2001 From: quaaantumdev Date: Fri, 2 Jan 2026 12:01:21 +0100 Subject: [PATCH 639/663] Improved positioning and scaling with a pluggable size delegate --- packages/pdfrx/lib/pdfrx.dart | 5 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 297 +++++------ .../lib/src/widgets/pdf_viewer_params.dart | 82 ++- .../sizing/pdf_viewer_size_delegate.dart | 229 +++++++++ .../pdf_viewer_size_delegate_legacy.dart | 343 +++++++++++++ .../pdf_viewer_size_delegate_smart.dart | 469 ++++++++++++++++++ 6 files changed, 1251 insertions(+), 174 deletions(-) create mode 100644 packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart create mode 100644 packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart create mode 100644 packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index af1d294e..54fe8a2b 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -3,9 +3,12 @@ export 'package:pdfrx_engine/pdfrx_engine.dart'; export 'src/pdf_document_ref.dart'; export 'src/pdfrx_flutter.dart'; +export 'src/utils/fixed_overscroll_physics.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; export 'src/widgets/pdf_viewer_params.dart'; export 'src/widgets/pdf_viewer_scroll_thumb.dart'; export 'src/widgets/pdf_widgets.dart'; -export 'src/utils/fixed_overscroll_physics.dart'; +export 'src/widgets/sizing/pdf_viewer_size_delegate.dart'; +export 'src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart'; +export 'src/widgets/sizing/pdf_viewer_size_delegate_smart.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 024a2627..7c30b390 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -221,10 +221,7 @@ class _PdfViewerState extends State PdfDocument? _document; PdfPageLayout? _layout; Size? _viewSize; - double? _coverScale; - double? _alternativeFitScale; - static const _defaultMinScale = 0.1; - double _minScale = _defaultMinScale; + late PdfViewerLayoutMetrics _layoutMetrics; int? _pageNumber; bool _initialized = false; bool _usingScrollPercentageMode = false; @@ -232,7 +229,7 @@ class _PdfViewerState extends State StreamSubscription? _documentSubscription; final _interactiveViewerKey = GlobalKey(); - final List _zoomStops = [1.0]; + List _zoomStops = const [1.0]; final _imageCache = _PdfPageImageCache(); final _magnifierImageCache = _PdfPageImageCache(); @@ -282,18 +279,36 @@ class _PdfViewerState extends State // the viewport EdgeInsets _adjustedBoundaryMargins = EdgeInsets.zero; + PdfViewerSizeDelegate? _sizeDelegate; + @override void initState() { super.initState(); pdfrxFlutterInitialize(); _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); _widgetUpdated(null); + _updateSizeDelegate(); + + // Initialize metrics with default/current state + // (Layout is null here, so it will return defaults) + _recalculateMetrics(); } @override void didUpdateWidget(covariant PdfViewer oldWidget) { super.didUpdateWidget(oldWidget); _widgetUpdated(oldWidget); + if (widget.params.sizeDelegateProvider != oldWidget.params.sizeDelegateProvider) { + _updateSizeDelegate(); + } + } + + void _updateSizeDelegate() { + _sizeDelegate?.dispose(); + _sizeDelegate = widget.params.getSizeDelegateProvider().create(); + if (_controller != null) { + _sizeDelegate!.init(_controller!); + } } Future _widgetUpdated(PdfViewer? oldWidget) async { @@ -352,6 +367,7 @@ class _PdfViewerState extends State _controller!._attach(this); _txController.addListener(_onMatrixChanged); _documentSubscription = document.events.listen(_onDocumentEvent); + _sizeDelegate?.init(_controller!); if (mounted) { setState(() {}); @@ -386,6 +402,7 @@ class _PdfViewerState extends State @override void dispose() { + _sizeDelegate?.dispose(); focusReportForPreventingContextMenuWeb(this, false); _documentSubscription?.cancel(); _textSelectionChangedDebounceTimer?.cancel(); @@ -473,7 +490,7 @@ class _PdfViewerState extends State boundaryMargin: widget.params.scrollPhysics == null ? const EdgeInsets.all(double.infinity) // NOTE: boundaryMargin is handled manually : _adjustedBoundaryMargins, - maxScale: widget.params.maxScale, + maxScale: _layoutMetrics.maxScale, minScale: minScale, panAxis: widget.params.panAxis, panEnabled: widget.params.panEnabled, @@ -593,17 +610,30 @@ class _PdfViewerState extends State void _updateLayout(Size viewSize) { if (viewSize.height <= 0) return; // For fix blank pdf when restore window from minimize on Windows final currentPageNumber = _guessCurrentPageNumber(); + final oldSnapshot = PdfViewerLayoutSnapshot( + viewSize: _viewSize ?? Size.zero, + layout: _layout, + minScale: _layoutMetrics.minScale, + coverScale: _layoutMetrics.coverScale, + alternativeFitScale: _layoutMetrics.alternativeFitScale, + ); final oldVisibleRect = _initialized ? _visibleRect : Rect.zero; - final oldLayout = _layout; - final oldMinScale = _minScale; final oldSize = _viewSize; final isViewSizeChanged = oldSize != viewSize; _viewSize = viewSize; final isLayoutChanged = _relayoutPages(); - _calcCoverFitScale(); + _recalculateMetrics(); _calcZoomStopTable(); - _adjustBoundaryMargins(viewSize, max(_minScale, _currentZoom)); + _adjustBoundaryMargins(viewSize, max(_layoutMetrics.minScale, _currentZoom)); + + final newSnapshot = PdfViewerLayoutSnapshot( + viewSize: viewSize, + layout: _layout, + minScale: _layoutMetrics.minScale, + coverScale: _layoutMetrics.coverScale, + alternativeFitScale: _layoutMetrics.alternativeFitScale, + ); void callOnViewerSizeChanged() { if (isViewSizeChanged) { @@ -613,83 +643,63 @@ class _PdfViewerState extends State } } - if (!_initialized && _layout != null && _coverScale != null) { + if (!_initialized && _layout != null) { _initialized = true; - Future.microtask(() async { - // forcibly calculate fit scale for the initial page + Future.microtask(() { + if (!mounted) { + return; + } + + // Calculate initial page (using params or default) + // We set it here so internal state is consistent before delegate runs _pageNumber = _gotoTargetPageNumber = _calcInitialPageNumber(); - _calcCoverFitScale(); + + // RECALCULATE for specific initial page + _recalculateMetrics(); _calcZoomStopTable(); - final zoom = - widget.params.calculateInitialZoom?.call( - _document!, - _controller!, - _alternativeFitScale ?? _coverScale!, - _coverScale!, - ) ?? - _coverScale!; - await _setZoom(Offset.zero, zoom, duration: Duration.zero); + + _sizeDelegate?.onLayoutInitialized( + state: newSnapshot, + initialPageNumber: _pageNumber!, + coverScale: _layoutMetrics.coverScale, + alternativeFitScale: _layoutMetrics.alternativeFitScale, + layout: _layout!, + document: _document!, + ); + if (_pageNumber! <= _layout!.pageLayouts.length) { - await _goToPage(pageNumber: _pageNumber!, duration: Duration.zero); + unawaited(_goToPage(pageNumber: _pageNumber!, duration: Duration.zero)); } - if (mounted && _document != null && _controller != null) { - widget.params.onViewerReady?.call(_document!, _controller!); + + final onViewerReady = widget.params.onViewerReady; + if (_document != null && _controller != null && onViewerReady != null) { + onViewerReady(_document!, _controller!); } + callOnViewerSizeChanged(); }); } else if (isLayoutChanged || isViewSizeChanged) { - Future.microtask(() async { - if (mounted) { - // preserve the current zoom whilst respecting the new minScale - final zoomTo = _currentZoom < _minScale || _currentZoom == oldMinScale ? _minScale : _currentZoom; - if (isLayoutChanged) { - // if the layout changed, calculate the top-left position in the document - // before the layout change and go to that position in the new layout - - if (oldLayout != null && currentPageNumber != null) { - // The top-left position of the screen (oldVisibleRect.topLeft) may be - // in the boundary margin, or a margin between pages, and it could be - // the current page or one of the neighboring pages - final hit = _getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); - final pageNumber = hit?.page.pageNumber ?? currentPageNumber; - - // Compute relative position within the old pageRect - final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; - final newPageRect = _layout!.pageLayouts[pageNumber - 1]; - final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; - final fracX = oldOffset.dx / oldPageRect.width; - final fracY = oldOffset.dy / oldPageRect.height; - - // Map into new layoutRect - final newOffset = Offset( - newPageRect.left + fracX * newPageRect.width, - newPageRect.top + fracY * newPageRect.height, - ); + Future.microtask(() { + if (!mounted) { + return; + } - // preserve the position after a layout change - await _goToPosition(documentOffset: newOffset, zoom: zoomTo); - } - } else { - if (zoomTo != _currentZoom) { - // layout hasn't changed, but size and zoom has - final zoomChange = zoomTo / _currentZoom; - final pivot = vec.Vector3(_txController.value.x, _txController.value.y, 0); - - final pivotScale = Matrix4.identity() - ..translateByVector3(pivot) - ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) - ..translateByVector3(-pivot / zoomChange); - - final Matrix4 zoomPivoted = pivotScale * _txController.value; - _adjustBoundaryMargins(viewSize, zoomTo); - _clampToNearestBoundary(zoomPivoted, viewSize: viewSize); - } else { - // size changes (e.g. rotation) can still cause out-of-bounds matrices - // so clamp here - _clampToNearestBoundary(_txController.value, viewSize: viewSize); - } - callOnViewerSizeChanged(); - } + _sizeDelegate?.onLayoutUpdate( + oldState: oldSnapshot, + newState: newSnapshot, + currentZoom: _currentZoom, + oldVisibleRect: oldVisibleRect, + anchorPageNumber: currentPageNumber, + isLayoutChanged: isLayoutChanged, + isViewSizeChanged: isViewSizeChanged, + ); + + if (!mounted) { + return; + } + + if (isViewSizeChanged) { + callOnViewerSizeChanged(); } }); } else if (currentPageNumber != null && _pageNumber != currentPageNumber) { @@ -697,16 +707,6 @@ class _PdfViewerState extends State } } - /// Stop InteractiveViewer animations and apply boundary clamping - void _clampToNearestBoundary(Matrix4 candidate, {required Size viewSize}) { - if (_isInteractionGoingOn) return; - - _stopInteractiveViewerAnimation(); - - // Apply the clamped matrix - _txController.value = _calcMatrixForClampedToNearestBoundary(candidate, viewSize: viewSize); - } - /// Get the state of the internal [iv.InteractiveViewer]. iv.InteractiveViewerState? get _interactiveViewerState => _interactiveViewerKey.currentState; @@ -718,7 +718,12 @@ class _PdfViewerState extends State } int _calcInitialPageNumber() { - return widget.params.calculateInitialPageNumber?.call(_document!, _controller!) ?? widget.initialPageNumber; + int? pageNumber; + final calculateInitialPageNumber = widget.params.calculateInitialPageNumber; + if (calculateInitialPageNumber != null) { + pageNumber = calculateInitialPageNumber(_document!, _controller!); + } + return pageNumber ?? widget.initialPageNumber; } PdfPageHitTestResult? _getClosestPageHit(int currentPageNumber, PdfPageLayout oldLayout, ui.Rect oldVisibleRect) { @@ -1023,78 +1028,20 @@ class _PdfViewerState extends State return true; } - void _calcCoverFitScale() { - final params = widget.params; - final bmh = params.boundaryMargin?.horizontal == double.infinity ? 0 : params.boundaryMargin?.horizontal ?? 0; - final bmv = params.boundaryMargin?.vertical == double.infinity ? 0 : params.boundaryMargin?.vertical ?? 0; - - if (_viewSize != null) { - final s1 = _viewSize!.width / (_layout!.documentSize.width + bmh); - final s2 = _viewSize!.height / (_layout!.documentSize.height + bmv); - _coverScale = max(s1, s2); - } + void _recalculateMetrics() { final pageNumber = _pageNumber ?? _gotoTargetPageNumber; - if (pageNumber != null && pageNumber >= 1 && pageNumber <= _layout!.pageLayouts.length) { - final rect = _layout!.pageLayouts[pageNumber - 1]; - final m2 = params.margin * 2; - _alternativeFitScale = min( - (_viewSize!.width) / (rect.width + bmh + m2), - (_viewSize!.height) / (rect.height + bmv + m2), - ); - if (_alternativeFitScale! <= 0) { - _alternativeFitScale = null; - } - } else { - _alternativeFitScale = null; - } - if (_coverScale == null) { - _minScale = _defaultMinScale; - return; - } - _minScale = !widget.params.useAlternativeFitScaleAsMinScale - ? widget.params.minScale - : _alternativeFitScale == null - ? _coverScale! - : min(_coverScale!, _alternativeFitScale!); + + _layoutMetrics = _sizeDelegate!.calculateMetrics( + viewSize: _viewSize ?? Size.zero, + layout: _layout, + pageNumber: pageNumber, + pageMargin: widget.params.margin, + boundaryMargin: widget.params.boundaryMargin, + ); } void _calcZoomStopTable() { - _zoomStops.clear(); - double z; - if (_alternativeFitScale != null && !_areZoomsAlmostIdentical(_alternativeFitScale!, _coverScale!)) { - if (_alternativeFitScale! < _coverScale!) { - _zoomStops.add(_alternativeFitScale!); - z = _coverScale!; - } else { - _zoomStops.add(_coverScale!); - z = _alternativeFitScale!; - } - } else { - z = _coverScale!; - } - // in some case, z may be 0 and it causes infinite loop. - if (z < 1 / 128) { - _zoomStops.add(1.0); - return; - } - while (z < widget.params.maxScale) { - _zoomStops.add(z); - z *= 2; - } - if (!_areZoomsAlmostIdentical(z, widget.params.maxScale)) { - _zoomStops.add(widget.params.maxScale); - } - - if (!widget.params.useAlternativeFitScaleAsMinScale) { - z = _zoomStops.first; - while (z > widget.params.minScale) { - z /= 2; - _zoomStops.insert(0, z); - } - if (!_areZoomsAlmostIdentical(z, widget.params.minScale)) { - _zoomStops.insert(0, widget.params.minScale); - } - } + _zoomStops = _sizeDelegate!.generateZoomStops(_layoutMetrics); } double _findNextZoomStop(double zoom, {required bool zoomUp, bool loop = true}) { @@ -1288,7 +1235,7 @@ class _PdfViewerState extends State context, page, _controller!, - widget.params.onePassRenderingScaleThreshold, + _sizeDelegate!.onePassRenderingScaleThreshold, ); if (dropShadowPaint != null) { @@ -1588,7 +1535,7 @@ class _PdfViewerState extends State // NOTE: I believe that either only dx or dy is set, but I don't know which one is guaranteed to be set. // So, I just add both values. var zoomFactor = -(event.scrollDelta.dx + event.scrollDelta.dy) / 120.0; - final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(widget.params.minScale, widget.params.maxScale); + final newZoom = (_currentZoom * (pow(1.2, zoomFactor))).clamp(_layoutMetrics.minScale, _layoutMetrics.maxScale); if (_areZoomsAlmostIdentical(newZoom, _currentZoom)) return; // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. _controller!.zoomOnLocalPosition( @@ -1638,7 +1585,7 @@ class _PdfViewerState extends State } /// The minimum zoom ratio allowed. - double get minScale => _minScale; + double get minScale => _layoutMetrics.minScale; Matrix4 _calcMatrixForRect(Rect rect, {double? zoomMax, double? margin}) { margin ??= 0; @@ -3681,10 +3628,10 @@ class PdfViewerController extends ValueListenable { Size get viewSize => _state._viewSize!; /// The zoom ratio that fits the page's smaller side (either horizontal or vertical) to the view port. - double get coverScale => _state._coverScale!; + double get coverScale => _state._layoutMetrics.coverScale; /// The zoom ratio that fits whole the page to the view port. - double? get alternativeFitScale => _state._alternativeFitScale; + double? get alternativeFitScale => _state._layoutMetrics.alternativeFitScale; /// The minimum zoom ratio allowed. double get minScale => _state.minScale; @@ -3801,6 +3748,14 @@ class PdfViewerController extends ValueListenable { Duration duration = const Duration(milliseconds: 200), }) => _state._goToRectInsidePage(pageNumber: pageNumber, rect: rect, anchor: anchor, duration: duration); + /// Scrolls/zooms so that the specified PDF document coordinate appears at + /// the top-left corner of the viewport. + Future goToPosition({ + required Offset documentOffset, + double? zoom, + Duration duration = const Duration(milliseconds: 0), + }) => _state._goToPosition(documentOffset: documentOffset, zoom: zoom, duration: duration); + /// Calculate the rectangle for the specified area inside the page. /// /// [pageNumber] specifies the page number. @@ -4075,6 +4030,26 @@ class PdfViewerController extends ValueListenable { /// Force redraw all the page images. void forceRepaintAllPageImages() => _state.forceRepaintAllPageImages(); + + /// Stop the inertia animation of the interactive viewer. + /// + /// This is useful when you want to manually update the matrix without + /// fighting against the active inertia animation. + void stopInteractiveViewerAnimation() => _state._stopInteractiveViewerAnimation(); + + /// Calculate the matrix that clamps the given [matrix] to the nearest boundary. + /// + /// This ensures that the content is visible within the view port. + /// This is used by the default resizing logic to prevent the content from + /// being hidden when the view size is reduced. + Matrix4 calcMatrixForClampedToNearestBoundary(Matrix4 matrix, {required Size viewSize}) => + _state._calcMatrixForClampedToNearestBoundary(matrix, viewSize: viewSize); + + /// Find the closest page hit for the given parameters. + /// + /// This is used to determine the anchor position when the layout changes. + PdfPageHitTestResult? getClosestPageHit(int currentPageNumber, PdfPageLayout oldLayout, Rect oldVisibleRect) => + _state._getClosestPageHit(currentPageNumber, oldLayout, oldVisibleRect); } /// [PdfViewerController.calcFitZoomMatrices] returns the list of this class. diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 77d07678..b1b5afc4 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -18,16 +18,16 @@ class PdfViewerParams { this.backgroundColor = Colors.grey, this.layoutPages, this.normalizeMatrix, - this.maxScale = 8.0, - this.minScale = 0.1, - this.useAlternativeFitScaleAsMinScale = true, + this.maxScale, + this.minScale, + this.useAlternativeFitScaleAsMinScale, this.panAxis = PanAxis.free, this.boundaryMargin, this.annotationRenderingMode = PdfAnnotationRenderingMode.annotationAndForms, this.limitRenderingCache = true, this.pageAnchor = PdfPageAnchor.top, this.pageAnchorEnd = PdfPageAnchor.bottom, - this.onePassRenderingScaleThreshold = 200 / 72, + this.onePassRenderingScaleThreshold, this.onePassRenderingSizeThreshold = 2000, this.textSelectionParams, this.matchTextColor, @@ -73,7 +73,16 @@ class PdfViewerParams { this.forceReload = false, this.scrollPhysics, this.scrollPhysicsScale, - }); + this.sizeDelegateProvider, + }) : assert( + (maxScale == null && + minScale == null && + useAlternativeFitScaleAsMinScale == null && + onePassRenderingScaleThreshold == null || + calculateInitialZoom == null) || + sizeDelegateProvider == null, + 'You cannot set both maxScale, minScale, useAlternativeFitScaleAsMinScale, onePassRenderingScaleThreshold and sizeDelegateProvider at the same time. Please set these in the sizeDelegateProvider instead.', + ); /// Margin around the page. final double margin; @@ -142,7 +151,8 @@ class PdfViewerParams { /// The maximum allowed scale. /// /// The default is 8.0. - final double maxScale; + @Deprecated('Use sizeDelegateProvider: PdfViewerSizeDelegateProviderLegacy(maxScale: ...) instead') + final double? maxScale; /// The minimum allowed scale. /// @@ -150,14 +160,18 @@ class PdfViewerParams { /// /// Please note that the value is not used if [useAlternativeFitScaleAsMinScale] is true. /// See [useAlternativeFitScaleAsMinScale] for the details. - final double minScale; + @Deprecated('Use sizeDelegateProvider: PdfViewerSizeDelegateProviderLegacy(minScale: ...) instead') + final double? minScale; /// If true, the minimum scale is set to the calculated [PdfViewerController.alternativeFitScale]. /// /// If the minimum scale is small value, it makes many pages visible inside the view and it finally /// renders many pages at once. It may make the viewer to be slow or even crash due to high memory consumption. /// So, it is recommended to set this to false if you want to show PDF documents with many pages. - final bool useAlternativeFitScaleAsMinScale; + @Deprecated( + 'Use sizeDelegateProvider: PdfViewerSizeDelegateProviderLegacy(useAlternativeFitScaleAsMinScale: ...) instead', + ) + final bool? useAlternativeFitScaleAsMinScale; /// See [InteractiveViewer.panAxis] for details. final PanAxis panAxis; @@ -191,7 +205,10 @@ class PdfViewerParams { /// /// The default is 200 / 72, which implies rendering at 200 dpi. /// If you want more granular control for each page, use [getPageRenderingScale]. - final double onePassRenderingScaleThreshold; + @Deprecated( + 'Use sizeDelegateProvider: PdfViewerSizeDelegateProviderLegacy(onePassRenderingScaleThreshold: ...) instead', + ) + final double? onePassRenderingScaleThreshold; /// If a page is too large, the page is rendered with the size which fits within the threshold size (in pixels). /// @@ -303,6 +320,7 @@ class PdfViewerParams { final PdfViewerCalculateInitialPageNumberFunction? calculateInitialPageNumber; /// Function to calculate the initial zoom level. + @Deprecated('Use sizeDelegateProvider: PdfViewerSizeDelegateProviderLegacy(calculateInitialZoom: ...) instead') final PdfViewerCalculateZoomFunction? calculateInitialZoom; /// Function to guess the current page number based on the visible rectangle and page layouts. @@ -563,6 +581,12 @@ class PdfViewerParams { /// Scroll physics for scaling within the viewer. If null, it uses the same value as [scrollPhysics]. final ScrollPhysics? scrollPhysicsScale; + /// Provider to create a delegate that handles layout/size change logic. + /// + /// Defaults to [PdfViewerSizeDelegateProviderLegacy] which maintains + /// relative positioning and boundary clamping. + final PdfViewerSizeDelegateProvider? sizeDelegateProvider; + /// A convenience function to get platform-specific default scroll physics. /// /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a @@ -575,6 +599,25 @@ class PdfViewerParams { } } + PdfViewerSizeDelegateProvider getSizeDelegateProvider() { + final sizeDelegateProvider = this.sizeDelegateProvider; + if (sizeDelegateProvider != null) { + return sizeDelegateProvider; + } + return PdfViewerSizeDelegateProviderLegacy( + // ignore: deprecated_member_use_from_same_package + maxScale: maxScale, + // ignore: deprecated_member_use_from_same_package + minScale: minScale, + // ignore: deprecated_member_use_from_same_package + onePassRenderingScaleThreshold: onePassRenderingScaleThreshold, + // ignore: deprecated_member_use_from_same_package + useAlternativeFitScaleAsMinScale: useAlternativeFitScaleAsMinScale, + // ignore: deprecated_member_use_from_same_package + calculateInitialZoom: calculateInitialZoom, + ); + } + /// Determine whether the viewer needs to be reloaded or not. /// bool doChangesRequireReload(PdfViewerParams? other) { @@ -582,8 +625,11 @@ class PdfViewerParams { forceReload || other.margin != margin || other.backgroundColor != backgroundColor || + // ignore: deprecated_member_use_from_same_package other.maxScale != maxScale || + // ignore: deprecated_member_use_from_same_package other.minScale != minScale || + // ignore: deprecated_member_use_from_same_package other.useAlternativeFitScaleAsMinScale != useAlternativeFitScaleAsMinScale || other.panAxis != panAxis || other.boundaryMargin != boundaryMargin || @@ -591,6 +637,7 @@ class PdfViewerParams { other.limitRenderingCache != limitRenderingCache || other.pageAnchor != pageAnchor || other.pageAnchorEnd != pageAnchorEnd || + // ignore: deprecated_member_use_from_same_package other.onePassRenderingScaleThreshold != onePassRenderingScaleThreshold || other.onePassRenderingSizeThreshold != onePassRenderingSizeThreshold || other.textSelectionParams != textSelectionParams || @@ -607,7 +654,8 @@ class PdfViewerParams { other.horizontalCacheExtent != horizontalCacheExtent || other.verticalCacheExtent != verticalCacheExtent || other.linkHandlerParams != linkHandlerParams || - other.scrollPhysics != scrollPhysics; + other.scrollPhysics != scrollPhysics || + other.sizeDelegateProvider != sizeDelegateProvider; } @override @@ -616,8 +664,11 @@ class PdfViewerParams { return other.margin == margin && other.backgroundColor == backgroundColor && + // ignore: deprecated_member_use_from_same_package other.maxScale == maxScale && + // ignore: deprecated_member_use_from_same_package other.minScale == minScale && + // ignore: deprecated_member_use_from_same_package other.useAlternativeFitScaleAsMinScale == useAlternativeFitScaleAsMinScale && other.panAxis == panAxis && other.boundaryMargin == boundaryMargin && @@ -625,6 +676,7 @@ class PdfViewerParams { other.limitRenderingCache == limitRenderingCache && other.pageAnchor == pageAnchor && other.pageAnchorEnd == pageAnchorEnd && + // ignore: deprecated_member_use_from_same_package other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && other.onePassRenderingSizeThreshold == onePassRenderingSizeThreshold && other.textSelectionParams == textSelectionParams && @@ -668,15 +720,19 @@ class PdfViewerParams { other.keyHandlerParams == keyHandlerParams && other.behaviorControlParams == behaviorControlParams && other.forceReload == forceReload && - other.scrollPhysics == scrollPhysics; + other.scrollPhysics == scrollPhysics && + other.sizeDelegateProvider == sizeDelegateProvider; } @override int get hashCode { return margin.hashCode ^ backgroundColor.hashCode ^ + // ignore: deprecated_member_use_from_same_package maxScale.hashCode ^ + // ignore: deprecated_member_use_from_same_package minScale.hashCode ^ + // ignore: deprecated_member_use_from_same_package useAlternativeFitScaleAsMinScale.hashCode ^ panAxis.hashCode ^ boundaryMargin.hashCode ^ @@ -684,6 +740,7 @@ class PdfViewerParams { limitRenderingCache.hashCode ^ pageAnchor.hashCode ^ pageAnchorEnd.hashCode ^ + // ignore: deprecated_member_use_from_same_package onePassRenderingScaleThreshold.hashCode ^ onePassRenderingSizeThreshold.hashCode ^ textSelectionParams.hashCode ^ @@ -727,7 +784,8 @@ class PdfViewerParams { keyHandlerParams.hashCode ^ behaviorControlParams.hashCode ^ forceReload.hashCode ^ - scrollPhysics.hashCode; + scrollPhysics.hashCode ^ + sizeDelegateProvider.hashCode; } } diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart new file mode 100644 index 00000000..22152956 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart @@ -0,0 +1,229 @@ +import 'package:flutter/rendering.dart'; + +import '../../../pdfrx.dart'; +import '../interactive_viewer.dart' show InteractiveViewer; + +/// Interface for a factory that creates [PdfViewerSizeDelegate] instances. +/// +/// ### Why use a Provider? +/// [PdfViewerParams] relies on `operator ==` to determine if the viewer needs to be +/// reloaded or updated. By using a `const` Provider class with a proper `operator ==` +/// implementation, we ensure that the delegate lifecycle is stable across widget rebuilds. +/// +/// If the configuration changes (e.g. `minScale` changes), the provider's equality check +/// should fail, triggering the creation of a new delegate via [create]. +abstract class PdfViewerSizeDelegateProvider { + const PdfViewerSizeDelegateProvider(); + + /// Creates the runtime delegate instance. + /// + /// This is called by [PdfViewerState] when the widget initializes or when the + /// provider configuration changes. + PdfViewerSizeDelegate create(); + + /// Subclasses must implement equality to prevent unnecessary delegate recreation. + @override + bool operator ==(Object other); + + @override + int get hashCode; +} + +/// The "Brain" for handling document sizing, zooming, and layout adaptation. +/// +/// This delegate decouples the **Sizing Strategy** (e.g., "Smart Scale" vs "Legacy Fit") +/// from the core viewer logic. It controls how the PDF fits into the viewport initially, +/// what the zoom limits are, and how it reacts when the viewport changes size. +/// +/// ### Lifecycle & Controller Access +/// 1. [create] is called by the Provider. +/// 2. **[calculateMetrics] and [generateZoomStops] may be called immediately.** +/// * **Warning:** At this stage, [init] has *not* been called yet. +/// * Implementations must not access the controller or internal state here. +/// * Calculations must rely solely on the arguments provided. +/// 3. [init] is called when the controller is attached. +/// 4. [onLayoutInitialized] is called **once** when the document is fully loaded and ready. +/// 5. [onLayoutUpdate] is called **repeatedly** whenever the view size changes or the document layout changes. +/// 6. [dispose] is called when the viewer is destroyed. +abstract class PdfViewerSizeDelegate { + /// Called when the [PdfViewerState] initializes or dependencies change. + /// + /// Implementations should store the [controller] to manipulate the view + /// during [onLayoutInitialized] and [onLayoutUpdate]. + void init(PdfViewerController controller); + + /// Called when the delegate is being destroyed. + void dispose(); + + /// Calculates the layout metrics (min/max scales) for the given environment. + /// + /// This is called synchronously by the State whenever layout or view size changes + /// to configure the [InteractiveViewer] constraints. + /// + /// **⚠️ Important:** This method is often called **before** [init]. + /// Do not access the [PdfViewerController] inside this method. Use only the + /// provided arguments to perform the calculation. + /// + /// [viewSize]: The current size of the widget. + /// [layout]: The current PDF layout (may be null during first build). + /// [pageNumber]: The page currently being viewed (used to calculate "fit page" logic). + /// [pageMargin]: The margin configuration from parameters. + /// [boundaryMargin]: The boundary margin configuration from parameters. + PdfViewerLayoutMetrics calculateMetrics({ + required Size viewSize, + required PdfPageLayout? layout, + required int? pageNumber, // Pivot page for calculation + required double pageMargin, + required EdgeInsets? boundaryMargin, + }); + + /// Generates the list of zoom stops (steps) for double-tap zooming. + /// + /// **⚠️ Important:** This method is often called **before** [init]. + /// Do not access the [PdfViewerController] inside this method. + /// + /// Typically this includes the "Fit Page" scale, 1.0 (100%), and powers of 2. + /// The result should be sorted in ascending order. + List generateZoomStops(PdfViewerLayoutMetrics metrics); + + /// The scale threshold for switching between one-pass rendering and progressive rendering. + /// + /// If the current zoom is below this threshold, the viewer may render the page + /// in a single pass. Above this, it may use tiled/progressive rendering to save memory. + double get onePassRenderingScaleThreshold; + + /// Called when the viewer is ready to display the document for the first time. + /// + /// The delegate is responsible for calculating the **Initial Zoom** and applying it + /// (usually via [PdfViewerController.setZoom]). + /// + /// [state]: The current snapshot of the viewer (size, layout, calculated limits). + /// [initialPageNumber]: The target page number requested by the user parameters. + /// [coverScale]: A calculated scale that covers the viewport with the document. + /// [alternativeFitScale]: A calculated scale that fits the whole page (if different from cover). + /// [layout]: The geometry of the PDF document. + /// [document]: The loaded PDF document instance. + void onLayoutInitialized({ + required PdfViewerLayoutSnapshot state, + required int initialPageNumber, + required double coverScale, + required double? alternativeFitScale, + required PdfPageLayout layout, + required PdfDocument document, + }); + + /// Called when the viewport dimensions or document layout have changed. + /// + /// This is the core hook for responsive behavior. The delegate must decide how to + /// adjust the transformation matrix (Zoom/Scroll) to accommodate the change. + /// + /// **Common Scenarios:** + /// * **Window Resize:** `isViewSizeChanged` is true. The delegate might center the view or adjust zoom to fit width. + /// * **Rotation:** Both `isViewSizeChanged` and `isLayoutChanged` might be true. + /// * **Page Modification:** `isLayoutChanged` is true. The delegate should try to keep the user looking at the same content. + /// + /// [oldState]: The structural state *before* the update. + /// [newState]: The structural state *after* the update. + /// [currentZoom]: The zoom level before the update started. + /// [oldVisibleRect]: The area of the document that was visible before the update. + /// [anchorPageNumber]: The page number determined to be the current "pivot" (mostly visible page). + /// [isLayoutChanged]: True if the document geometry changed (e.g. pages added/rotated). + /// [isViewSizeChanged]: True if the widget size changed (e.g. window resize). + void onLayoutUpdate({ + required PdfViewerLayoutSnapshot oldState, + required PdfViewerLayoutSnapshot newState, + required double currentZoom, + required Rect oldVisibleRect, + required int? anchorPageNumber, + required bool isLayoutChanged, + required bool isViewSizeChanged, + }); +} + +/// Immutable snapshot of the viewer's structural state. +/// +/// This bundles the "Container" properties (View Size) and "Content" properties (Layout) +/// to allow comparison between frames without race conditions. +/// +/// It does **not** include transient camera state (like current zoom or scroll position) +/// to avoid circular dependencies during calculation. +class PdfViewerLayoutSnapshot { + const PdfViewerLayoutSnapshot({ + required this.viewSize, + required this.layout, + required this.minScale, + required this.coverScale, + required this.alternativeFitScale, + }); + + /// The size of the viewport (the widget's build area). + final Size viewSize; + + /// The document layout (position and size of all pages). + final PdfPageLayout? layout; + + //// The calculated minimum scale for this layout/size combination. + /// + /// This is the "Effective Minimum" (often the "Fit Page" scale), derived from + /// the delegate's calculation in [PdfViewerSizeDelegate.calculateMetrics]. + final double minScale; + + /// The scale required to fit the document's bounding box within the viewport. + final double coverScale; + + /// The scale required to fit the content (usually the current page) entirely within the viewport. + /// + /// Conventionally, delegates calculate this as the "Fit Page" scale. + /// + /// It is often used as the effective minimum scale to ensure the user can always + /// see the full page content preventing zooming out too far (which could lead to + /// rendering performance issues if too many pages become visible). + /// + /// Null if the page cannot fit or if the layout is not ready. + final double? alternativeFitScale; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PdfViewerLayoutSnapshot && + viewSize == other.viewSize && + layout == other.layout && + minScale == other.minScale && + coverScale == other.coverScale && + alternativeFitScale == other.alternativeFitScale; + + @override + int get hashCode => Object.hash(viewSize, layout, minScale, coverScale, alternativeFitScale); +} + +/// A container for the calculated scaling limits of the viewer. +/// +/// Returned by [PdfViewerSizeDelegate.calculateMetrics]. +class PdfViewerLayoutMetrics { + const PdfViewerLayoutMetrics({ + required this.minScale, + required this.maxScale, + // We keep these because the Controller exposes them publicly as getters. + // The Delegate calculates them so the Controller can return them. + required this.coverScale, + this.alternativeFitScale, + }); + + /// The effective minimum scale allowed for the viewer. + final double minScale; + + /// The effective maximum scale allowed for the viewer. + final double maxScale; + + /// The scale required to fit the document's bounding box within the viewport. + final double coverScale; + + /// The scale required to fit the content (usually the current page) entirely within the viewport. + /// + /// Conventionally, delegates calculate this as the "Fit Page" scale. + /// + /// It is often used as the effective minimum scale to ensure the user can always + /// see the full page content preventing zooming out too far (which could lead to + /// rendering performance issues if too many pages become visible). + final double? alternativeFitScale; +} diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart new file mode 100644 index 00000000..d07f01a8 --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart @@ -0,0 +1,343 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/rendering.dart'; +import 'package:vector_math/vector_math_64.dart' as vec; + +import '../../../pdfrx.dart'; + +/// The default provider that creates the standard sizing behavior. +/// +/// This implementation replicates the legacy behavior of `pdfrx` prior to the +/// introduction of the [PdfViewerSizeDelegate] system. It is designed to preserve +/// exact behaviors for existing applications ensuring backwards compatibility. +/// +/// **Note:** For a more modern, adaptive experience (centering content, auto-fitting width), +/// consider using [PdfViewerSizeDelegateProviderSmart], especially for desktop or web apps. +/// +/// ### Behavior Scenarios +/// +/// * **Initialization:** Defaults to "Fit Page" (or "Cover" logic), ensuring the +/// entire page is visible initially. It prioritizes showing the whole document context +/// over legibility of text (which might be tiny on large screens). +/// +/// * **Resize (Window Change):** Strictly maintains the current absolute zoom level. +/// * If the window grows, the document stays anchored to the **top-left**, +/// adding whitespace to the right/bottom. +/// * If the window shrinks, the view is simply clipped (scrolled) from the +/// bottom/right, potentially hiding content without adjusting zoom. +/// +/// * **Layout Change (Rotation/Pages):** Attempts to preserve the user's reading position +/// by mapping the previous top-left visible point to the new layout structure. +class PdfViewerSizeDelegateProviderLegacy extends PdfViewerSizeDelegateProvider { + const PdfViewerSizeDelegateProviderLegacy({ + double? maxScale, + double? minScale, + bool? useAlternativeFitScaleAsMinScale, + double? onePassRenderingScaleThreshold, + this.calculateInitialZoom, + }) : maxScale = maxScale ?? 8.0, + minScale = minScale ?? 0.1, + useAlternativeFitScaleAsMinScale = useAlternativeFitScaleAsMinScale ?? true, + onePassRenderingScaleThreshold = onePassRenderingScaleThreshold ?? 200 / 72; + + /// The maximum allowed scale. + /// + /// The default is 8.0. + final double maxScale; + + /// The minimum allowed scale. + /// + /// The default is 0.1. + /// + /// Please note that the value is not used if [useAlternativeFitScaleAsMinScale] is true. + /// See [useAlternativeFitScaleAsMinScale] for the details. + final double minScale; + + /// If true, the minimum scale is set to the calculated [PdfViewerController.alternativeFitScale]. + /// + /// If the minimum scale is small value, it makes many pages visible inside the view and it finally + /// renders many pages at once. It may make the viewer to be slow or even crash due to high memory consumption. + /// So, it is recommended to set this to false if you want to show PDF documents with many pages. + final bool useAlternativeFitScaleAsMinScale; + + /// If a page is rendered over the scale threshold, the page is rendered with the threshold scale + /// and actual resolution image is rendered after some delay (progressive rendering). + /// + /// Basically, if the value is larger, the viewer renders each page in one-pass rendering; it is + /// faster and looks better to the user. However, larger value may consume more memory. + /// So you may want to set the smaller value to reduce memory consumption. + /// + /// The default is 200 / 72, which implies rendering at 200 dpi. + /// If you want more granular control for each page, use [getPageRenderingScale]. + final double onePassRenderingScaleThreshold; + + /// Optional callback to customize the initial zoom level calculation. + /// + /// If provided, this overrides the default "Cover/Fit" initialization logic. + final PdfViewerCalculateZoomFunction? calculateInitialZoom; + + @override + PdfViewerSizeDelegate create() => PdfViewerSizeDelegateLegacy( + maxScale: maxScale, + minScale: minScale, + useAlternativeFitScaleAsMinScale: useAlternativeFitScaleAsMinScale, + onePassRenderingScaleThreshold: onePassRenderingScaleThreshold, + calculateInitialZoom: calculateInitialZoom, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PdfViewerSizeDelegateProviderLegacy && + maxScale == other.maxScale && + minScale == other.minScale && + useAlternativeFitScaleAsMinScale == other.useAlternativeFitScaleAsMinScale && + onePassRenderingScaleThreshold == other.onePassRenderingScaleThreshold && + calculateInitialZoom == other.calculateInitialZoom; + + @override + int get hashCode => Object.hash( + maxScale, + minScale, + useAlternativeFitScaleAsMinScale, + onePassRenderingScaleThreshold, + calculateInitialZoom, + ); +} + +/// The legacy implementation of the sizing delegate. +/// +/// This class encapsulates the exact logic used in `_PdfViewerState` before +/// the resizing logic was abstracted. +class PdfViewerSizeDelegateLegacy implements PdfViewerSizeDelegate { + PdfViewerSizeDelegateLegacy({ + required double maxScale, + required double minScale, + required bool useAlternativeFitScaleAsMinScale, + required this.onePassRenderingScaleThreshold, + required PdfViewerCalculateZoomFunction? calculateInitialZoom, + }) : _minScale = minScale, + _maxScale = maxScale, + _useAlternativeFitScaleAsMinScale = useAlternativeFitScaleAsMinScale, + _calculateInitialZoom = calculateInitialZoom; + + PdfViewerController? _controller; + + @override + void init(PdfViewerController controller) { + _controller = controller; + } + + @override + void dispose() { + _controller = null; + } + + final double _maxScale; + final double _minScale; + final bool _useAlternativeFitScaleAsMinScale; + final PdfViewerCalculateZoomFunction? _calculateInitialZoom; + + @override + final double onePassRenderingScaleThreshold; + + @override + PdfViewerLayoutMetrics calculateMetrics({ + required Size viewSize, + required PdfPageLayout? layout, + required int? pageNumber, + required double pageMargin, + required EdgeInsets? boundaryMargin, + }) { + final bmh = boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; + final bmv = boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; + + var coverScale = 1.0; + double? alternativeFitScale; + + if (layout != null) { + final s1 = viewSize.width / (layout.documentSize.width + bmh); + final s2 = viewSize.height / (layout.documentSize.height + bmv); + coverScale = math.max(s1, s2); + } + if (pageNumber != null && pageNumber >= 1 && pageNumber <= layout!.pageLayouts.length) { + final rect = layout.pageLayouts[pageNumber - 1]; + final m2 = pageMargin * 2; + alternativeFitScale = math.min( + (viewSize.width) / (rect.width + bmh + m2), + (viewSize.height) / (rect.height + bmv + m2), + ); + if (alternativeFitScale <= 0) { + alternativeFitScale = null; + } + } else { + alternativeFitScale = null; + } + + // Determine effective minScale based on delegate rules + final effectiveMinScale = !_useAlternativeFitScaleAsMinScale + ? _minScale + : alternativeFitScale == null + ? coverScale + : math.min(coverScale, alternativeFitScale); + + return PdfViewerLayoutMetrics( + minScale: effectiveMinScale, + maxScale: _maxScale, + coverScale: coverScale, + alternativeFitScale: alternativeFitScale, + ); + } + + @override + void onLayoutInitialized({ + required PdfViewerLayoutSnapshot state, + required int initialPageNumber, + required double coverScale, + required double? alternativeFitScale, + required PdfPageLayout layout, + required PdfDocument document, + }) { + // 1. Determine Initial Zoom + double? zoom; + + // Check if user provided a custom calculator + final calculateInitialZoom = _calculateInitialZoom; + if (calculateInitialZoom != null) { + zoom = calculateInitialZoom(document, _controller!, alternativeFitScale ?? coverScale, coverScale); + } + + // Default: Use coverScale (fits the smaller dimension of the page to the viewport) + zoom ??= coverScale; + + // 2. Apply + unawaited(_controller!.setZoom(Offset.zero, zoom, duration: Duration.zero)); + } + + @override + void onLayoutUpdate({ + required PdfViewerLayoutSnapshot oldState, + required PdfViewerLayoutSnapshot newState, + required double currentZoom, + required Rect oldVisibleRect, + required int? anchorPageNumber, + required bool isLayoutChanged, + required bool isViewSizeChanged, + }) { + final controller = _controller; + if (controller == null) return; + + final oldLayout = oldState.layout; + + // preserve the current zoom whilst respecting the new minScale + final zoomTo = currentZoom < newState.minScale || currentZoom == oldState.minScale + ? newState.minScale + : currentZoom; + if (isLayoutChanged) { + // if the layout changed, calculate the top-left position in the document + // before the layout change and go to that position in the new layout + + if (oldLayout != null && anchorPageNumber != null) { + // The top-left position of the screen (oldVisibleRect.topLeft) may be + // in the boundary margin, or a margin between pages, and it could be + // the current page or one of the neighboring pages + final hit = controller.getClosestPageHit(anchorPageNumber, oldLayout, oldVisibleRect); + final pageNumber = hit?.page.pageNumber ?? anchorPageNumber; + + // Compute relative position within the old pageRect + final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; + final newPageRect = newState.layout!.pageLayouts[pageNumber - 1]; + final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; + final fracX = oldOffset.dx / oldPageRect.width; + final fracY = oldOffset.dy / oldPageRect.height; + + // Map into new layoutRect + final newOffset = Offset( + newPageRect.left + fracX * newPageRect.width, + newPageRect.top + fracY * newPageRect.height, + ); + + // preserve the position after a layout change + unawaited(controller.goToPosition(documentOffset: newOffset, zoom: zoomTo)); + } + return; + } + + assert(isViewSizeChanged); + + if (zoomTo != currentZoom) { + // layout hasn't changed, but size and zoom has + final zoomChange = zoomTo / currentZoom; + + final pivot = vec.Vector3(controller.value.x, controller.value.y, 0); + + final pivotScale = Matrix4.identity() + ..translateByVector3(pivot) + ..scaleByDouble(zoomChange, zoomChange, zoomChange, 1) + ..translateByVector3(-pivot / zoomChange); + + final Matrix4 zoomPivoted = pivotScale * controller.value; + + // Clamp using the new view size + final clamped = controller.calcMatrixForClampedToNearestBoundary(zoomPivoted, viewSize: newState.viewSize); + + controller.stopInteractiveViewerAnimation(); + controller.value = clamped; + } else { + // size changes (e.g. rotation or window resize without zoom change) + // can still cause out-of-bounds matrices so clamp here + final clamped = controller.calcMatrixForClampedToNearestBoundary(controller.value, viewSize: newState.viewSize); + controller.stopInteractiveViewerAnimation(); + controller.value = clamped; + } + } + + @override + List generateZoomStops(PdfViewerLayoutMetrics metrics) { + final zoomStops = []; + final alternativeFitScale = metrics.alternativeFitScale; + final coverScale = metrics.coverScale; + final minScale = metrics.minScale; + final maxScale = metrics.maxScale; + double z; + if (alternativeFitScale != null && !_areZoomsAlmostIdentical(alternativeFitScale, coverScale)) { + if (alternativeFitScale < coverScale) { + zoomStops.add(alternativeFitScale); + z = coverScale; + } else { + zoomStops.add(coverScale); + z = alternativeFitScale; + } + } else { + z = coverScale; + } + // in some case, z may be 0 and it causes infinite loop. + if (z < 1 / 128) { + zoomStops.add(1.0); + return zoomStops; + } + while (z < metrics.maxScale) { + zoomStops.add(z); + z *= 2; + } + if (!_areZoomsAlmostIdentical(z, maxScale)) { + zoomStops.add(maxScale); + } + + if (!_useAlternativeFitScaleAsMinScale) { + z = zoomStops.first; + while (z > minScale) { + z /= 2; + zoomStops.insert(0, z); + } + if (!_areZoomsAlmostIdentical(z, minScale)) { + zoomStops.insert(0, minScale); + } + } + + return zoomStops; + } + + static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; +} diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart new file mode 100644 index 00000000..07579f5e --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart @@ -0,0 +1,469 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/rendering.dart'; + +import '../../../pdfrx.dart'; + +/// A provider that creates a [PdfViewerSizeDelegateSmart] instance with smart scaling configuration. +/// +/// This provider configures a sizing strategy that offers a modern, natural +/// viewing experience. It prioritizes keeping content centered and adapts the +/// zoom level intelligently during viewport changes (e.g., window resize, +/// device rotation, or layout adjustments). +/// +/// ### Behavior Scenarios +/// +/// The delegate analyzes the state *before* the resize to determine the user's intent: +/// +/// * **Sticky Fit (Fit Width):** If the content fits the width exactly (within margin of error): +/// * **Shrinking:** The content shrinks with the viewport to maintain "Fit Width". +/// * **Growing:** The content grows with the viewport to maintain "Fit Width", +/// **up to** the [smartMaxScale]. Once this limit is reached, it stops growing +/// and introduces horizontal whitespace. +/// +/// * **Whitespace (Underflow):** If the content is smaller than the viewport: +/// * **General:** The absolute zoom level is preserved, and the content is re-centered. +/// * **Catch-on:** If the viewport shrinks so much that the content would start being +/// clipped, it switches to "Fit Width" mode and shrinks the content. +/// +/// * **Zoomed In (Overflow):** If the content is larger than the viewport (horizontal scrolling): +/// * The absolute zoom level is preserved. The viewport is re-centered on the +/// same point in the document. +/// +/// ### Comparison with Legacy Strategy +/// +/// * **Legacy (`PdfViewerSizeDelegateProviderLegacy`)**: Aligns content to the top-left. +/// Strictly preserves the exact zoom level during resizing unless limits are violated. +/// Defaults to "Fit Page" (full page visible) on initialization. +/// * **Smart (`PdfViewerSizeDelegateProviderSmart`)**: Aligns content to the center. +/// Adapts zoom level to keep content fitting the screen width. Defaults to "Fit Width" +/// on initialization. Offers more of a high-level API, deliberately not implementing a +/// calculateInitialZoom callback. +/// +class PdfViewerSizeDelegateProviderSmart extends PdfViewerSizeDelegateProvider { + const PdfViewerSizeDelegateProviderSmart({ + double? minScale, + double? maxScale, + double? smartMaxScale, + double? maxPagesVisible, + double? onePassRenderingScaleThreshold, + }) : maxScale = maxScale ?? 8.0, + minScale = minScale ?? 0.1, + smartMaxScale = smartMaxScale ?? 1.3, + maxPagesVisible = maxPagesVisible ?? 3.0, + onePassRenderingScaleThreshold = onePassRenderingScaleThreshold ?? 200 / 72; + + /// The maximum allowed scale. + /// + /// The default is 8.0. + final double maxScale; + + /// The minimum allowed scale. + /// + /// The default is 0.1. + /// + /// This is the hard ceiling for the viewer. Even if "Fit Width" logic would + /// prefer a larger scale, the viewer will not exceed this value. + final double minScale; + + /// The maximum number of pages (approximately) visible when zoomed out to the minimum. + /// + /// This factor divides the "Fit Page" scale to determine the effective minimum scale. + /// * `1.0`: Minimum scale fits exactly one page (Fit Page). + /// * `3.0`: (default) Minimum scale fits two pages. + /// * `double.infinity`: Ignore "Fit Page" limit entirely; use [minScale] as the only floor. + final double maxPagesVisible; + + /// If a page is rendered over the scale threshold, the page is rendered with the threshold scale + /// and actual resolution image is rendered after some delay (progressive rendering). + /// + /// Basically, if the value is larger, the viewer renders each page in one-pass rendering; it is + /// faster and looks better to the user. However, larger value may consume more memory. + /// So you may want to set the smaller value to reduce memory consumption. + /// + /// The default is 200 / 72, which implies rendering at 200 dpi. + /// If you want more granular control for each page, use [getPageRenderingScale]. + final double onePassRenderingScaleThreshold; + + /// The maximum zoom level for automatic "Fit Width" scaling. + /// + /// This prevents the document from becoming uncomfortably large on very wide screens. + /// For example, if set to 1.2, resizing the window to be very wide will stop + /// zooming the document once it reaches 120% scale, centering it with margins instead. + final double smartMaxScale; + + @override + PdfViewerSizeDelegate create() => PdfViewerSizeDelegateSmart( + smartMaxScale: smartMaxScale, + maxScale: maxScale, + minScale: minScale, + maxPagesVisible: maxPagesVisible, + onePassRenderingScaleThreshold: onePassRenderingScaleThreshold, + ); + + @override + bool operator ==(Object other) => + other is PdfViewerSizeDelegateProviderSmart && + other.smartMaxScale == smartMaxScale && + other.maxScale == maxScale && + other.minScale == minScale && + other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && + other.maxPagesVisible == maxPagesVisible; + + @override + int get hashCode => Object.hash(smartMaxScale, maxScale, minScale, onePassRenderingScaleThreshold, maxPagesVisible); +} + +/// A "Smart" resize delegate that adapts zoom to fit the page width and centers content. +/// +/// ### Core Behaviors +/// 1. **Smart Initialization**: Defaults to "Fit Width" (capped at [_smartMaxScale]) +/// instead of "Fit Page". +/// 2. **Adaptive Resizing**: +/// * **Shrinking**: If the window shrinks, the content scales down to stay +/// fully visible width-wise ("Catch on"). +/// * **Growing**: If the window grows, the content scales up to fit width, +/// but stops growing at [_smartMaxScale]. +/// 3. **Centering**: Unlike the default behavior (which clamps to top-left), +/// this delegate keeps the view centered on the same point in the document +/// during resizing. +class PdfViewerSizeDelegateSmart implements PdfViewerSizeDelegate { + PdfViewerSizeDelegateSmart({ + required double smartMaxScale, + required double maxScale, + required double minScale, + required double maxPagesVisible, + required this.onePassRenderingScaleThreshold, + }) : _minScale = minScale, + _maxScale = maxScale, + _smartMaxScale = smartMaxScale, + _maxPagesVisible = maxPagesVisible; + + final double _maxScale; + final double _minScale; + final double _maxPagesVisible; + final double _smartMaxScale; + + PdfViewerController? _controller; + + @override + final double onePassRenderingScaleThreshold; + + @override + void init(PdfViewerController controller) { + _controller = controller; + } + + @override + void dispose() { + _controller = null; + } + + @override + PdfViewerLayoutMetrics calculateMetrics({ + required Size viewSize, + required PdfPageLayout? layout, + required int? pageNumber, + required double pageMargin, + required EdgeInsets? boundaryMargin, + }) { + // Reuse the legacy math for geometric limits (coverScale/alternativeFitScale) + // We can delegate this calculation or duplicate the math (it's purely geometric). + // Duplicating for clarity/independence: + final bmh = boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; + final bmv = boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; + + var coverScale = 1.0; + double? alternativeFitScale; + + if (layout != null) { + final s1 = viewSize.width / (layout.documentSize.width + bmh); + final s2 = viewSize.height / (layout.documentSize.height + bmv); + coverScale = math.max(s1, s2); + + if (pageNumber != null && pageNumber >= 1 && pageNumber <= layout.pageLayouts.length) { + final rect = layout.pageLayouts[pageNumber - 1]; + final m2 = pageMargin * 2; + alternativeFitScale = math.min( + (viewSize.width) / (rect.width + bmh + m2), + (viewSize.height) / (rect.height + bmv + m2), + ); + } + } + + // Smart Policy for Min Scale: + // 1. Calculate "Fit Page" scale (fallback to coverScale if page not found) + final fitPageScale = alternativeFitScale ?? coverScale; + + // 2. Adjust for multi-page visibility + // If maxPagesVisible is 1.0, this is Fit Page. + // If maxPagesVisible is 2.0, we allow zooming out 2x further. + // If maxPagesVisible is infinity, this becomes 0. + final allowedMinScale = fitPageScale / _maxPagesVisible; + + // 3. The minimum scale is whichever is larger: the hard configuration or the physical fit. + // This prevents zooming out further than the page size. + final effectiveMinScale = math.max(_minScale, allowedMinScale); + + return PdfViewerLayoutMetrics( + minScale: effectiveMinScale, + maxScale: _maxScale, + coverScale: coverScale, + alternativeFitScale: alternativeFitScale, + ); + } + + @override + void onLayoutInitialized({ + required PdfViewerLayoutSnapshot state, + required int initialPageNumber, + required double coverScale, + required double? alternativeFitScale, + required PdfPageLayout layout, + required PdfDocument document, + }) { + final controller = _controller; + if (controller == null) return; + + // --- Smart Initialization --- + // Instead of defaulting to "Fit Page" (which might be tiny on a large monitor), + // we default to "Fit Width" (clamped by smartMaxScale). + + // 1. Calculate raw Fit Width Scale + final docWidth = state.layout?.documentSize.width ?? 1.0; + // Avoid division by zero + final rawFitWidthScale = docWidth > 0 ? state.viewSize.width / docWidth : 1.0; + + // 2. Limit the Fit Width Scale + // This is the "Smart" part: We prevent the default zoom from being too large on wide screens. + // e.g. On a 4k monitor, Fit Width might be 300%. We cap this default to 120% (smartMaxScale). + final effectiveFitWidthScale = (rawFitWidthScale > _smartMaxScale) ? _smartMaxScale : rawFitWidthScale; + + // 3. Determine Initial Zoom + var zoom = effectiveFitWidthScale; + + // 4. Hard Constraints + // Ensure we are within the hard configuration limits + if (zoom < _minScale) { + zoom = _minScale; + } + if (zoom > _maxScale) { + zoom = _maxScale; + } + + // Ensure we don't go below the effective minimum calculated by the Viewer State + // (e.g. if Fit Page is required to avoid rendering issues) + if (zoom < state.minScale) { + zoom = state.minScale; + } + + // 5. Apply + unawaited(controller.setZoom(Offset.zero, zoom, duration: Duration.zero)); + } + + @override + void onLayoutUpdate({ + required PdfViewerLayoutSnapshot oldState, + required PdfViewerLayoutSnapshot newState, + required double currentZoom, + required Rect oldVisibleRect, + required int? anchorPageNumber, + required bool isLayoutChanged, + required bool isViewSizeChanged, + }) { + final controller = _controller; + if (controller == null) return; + + // Calculate the target zoom based on the legacy "preserve zoom" rule first. + // This is often just currentZoom, but handles minScale clamping logic. + final zoomTo = currentZoom < newState.minScale || currentZoom == oldState.minScale + ? newState.minScale + : currentZoom; + + if (isLayoutChanged) { + // --- 1. Handle Layout Changes --- + // If the document layout changed (e.g. pages rotated, added, or margins changed), + // we defer to the Default/Legacy logic. Mapping visual positions across layout + // changes is complex, and the default implementation handles it robustly + // (mapping the user's previous look-at point to the new layout). + + final oldLayout = oldState.layout; + + if (oldLayout != null && anchorPageNumber != null) { + // Use the controller's helper to find where the user was looking + final hit = controller.getClosestPageHit(anchorPageNumber, oldLayout, oldVisibleRect); + final pageNumber = hit?.page.pageNumber ?? anchorPageNumber; + + // Compute relative position within the old pageRect + final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; + final newPageRect = newState.layout!.pageLayouts[pageNumber - 1]; + final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; + final fracX = oldOffset.dx / oldPageRect.width; + final fracY = oldOffset.dy / oldPageRect.height; + + // Map into new layoutRect + final newOffset = Offset( + newPageRect.left + fracX * newPageRect.width, + newPageRect.top + fracY * newPageRect.height, + ); + + // Preserve the position after a layout change + unawaited(controller.goToPosition(documentOffset: newOffset, zoom: zoomTo)); + } + + return; + } + + // From here on, we assume only the View Size (window size) changed. + assert(isViewSizeChanged); + + final oldLayout = oldState.layout; + final newLayout = newState.layout; + if (oldLayout == null || newLayout == null) return; + + final oldSize = oldState.viewSize; + final newSize = newState.viewSize; + + // --- 2. Calculate Fit Metrics --- + // We compare the viewport width to the document width to determine the "Fit Width" ratio. + final oldDocWidth = oldLayout.documentSize.width; + final newDocWidth = newLayout.documentSize.width; + + final oldFitScale = oldSize.width / oldDocWidth; + final newFitScale = newSize.width / newDocWidth; + + // --- 3. Determine User Intent (State Machine) --- + // We analyze the state BEFORE the resize to guess what the user wants. + const epsilon = 0.01; + + // State B: Sticky Fit + // The user was effectively viewing "Fit Width" before the resize. + final wasFittingExact = (currentZoom - oldFitScale).abs() < epsilon; + + // State A: Overflow (Zoomed In) + // The user was zoomed in closer than "Fit Width" (horizontal scrolling possible). + final wasOverflowing = !wasFittingExact && currentZoom > oldFitScale; + + var targetZoom = currentZoom; + + if (wasOverflowing) { + // Scenario A: User Manual Zoom (Overflow) + // If the user manually zoomed in, we preserve the absolute zoom level. + // We don't snap them back to "Fit Width" just because they resized the window. + // Position is maintained by the centering logic below. + targetZoom = currentZoom; + } else if (wasFittingExact) { + // Scenario B: Sticky Fit + // The content was fitting the width. We want it to *continue* fitting the width + // of the new window size. + targetZoom = newFitScale; + + // Smart Limit Logic (Growing): + // If the window is growing, we follow the width up to [smartMaxScale]. + if (newFitScale > oldFitScale) { + if (targetZoom > _smartMaxScale) { + targetZoom = _smartMaxScale; + // Exception: If the user was somehow already above the limit (e.g. manual zoom + // followed by a resize that landed in sticky range), don't snap DOWN to the limit. + if (targetZoom < currentZoom) { + targetZoom = currentZoom; + } + } + } + // Smart Limit Logic (Shrinking): + // If shrinking, we always accept `newFitScale` to ensure the content stays + // inside the window (avoiding horizontal scrolling). + } else { + // Scenario C: Underflow (Whitespace) + // The content was smaller than the viewport (zoomed out). + // Generally, keep the absolute zoom and just recenter. + targetZoom = currentZoom; + + // "Catch On" Logic: + // If the window shrinks so much that the old zoom would now cause overflow, + // we switch strategy to "Fit Width" to prevent content from being clipped. + if (targetZoom > newFitScale) { + targetZoom = newFitScale; + } + } + + // --- 4. Apply Hard Constraints --- + final minS = newState.minScale; + // Ensure we don't violate the viewer's absolute minimum limit (calculated by State). + if (targetZoom < minS) targetZoom = minS; + + // --- 5. Apply Transformation (Centering) --- + // Instead of clamping to the top-left (default behavior), we pivot around the center. + + // Find the point in the document currently at the center of the viewport + final oldCenterInDoc = controller.value.calcPosition(oldSize); + + // Create a new matrix that puts that same document point at the center of the NEW viewport + final newMatrix = controller.calcMatrixFor(oldCenterInDoc, zoom: targetZoom, viewSize: newSize); + + // Stop any physics animations (inertia) to prevent fighting with the layout update + controller.stopInteractiveViewerAnimation(); + controller.value = newMatrix; + } + + @override + List generateZoomStops(PdfViewerLayoutMetrics metrics) { + final alternativeFitScale = metrics.alternativeFitScale; + final coverScale = metrics.coverScale; + final minScale = metrics.minScale; + final maxScale = metrics.maxScale; + + // Standard logic: Fit Page, Fit Width (implicitly handled by coverScale logic usually), + // and Powers of 2. + double z; + + // Set to simplify deduplication + final stops = {}; + + // Add Fit Page / Cover stops + if (alternativeFitScale != null && !_areZoomsAlmostIdentical(alternativeFitScale, coverScale)) { + stops.add(math.min(alternativeFitScale, coverScale)); + stops.add(math.max(alternativeFitScale, coverScale)); + z = math.max(alternativeFitScale, coverScale); + } else { + z = coverScale; + stops.add(coverScale); + } + + // Add 100% stop if reasonable + if (z < 1.0 && 1.0 < maxScale) { + stops.add(1.0); + } + + // Generate stops up to maxScale + if (z < 1 / 128) { + // prevent infinite loop + z = 1 / 128; + } + while (z < maxScale) { + stops.add(z); + z *= 2; + } + if (!_areZoomsAlmostIdentical(z, maxScale)) { + stops.add(maxScale); + } + + // Generate stops down to minScale + z = stops.first; + while (z > minScale) { + z /= 2; + stops.add(z); + } + if ((z - minScale).abs() > 0.01) { + stops.add(minScale); + } + + // Convert to list and sort + final stopsList = stops.toList(); + stopsList.sort(); + return stopsList; + } + + static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; +} From 5fd2ff8e70d0593b7eef824becbc94f0366c3c1e Mon Sep 17 00:00:00 2001 From: quaaantumdev Date: Fri, 2 Jan 2026 21:33:14 +0100 Subject: [PATCH 640/663] Extracted zoom steps into it's own provider --- packages/pdfrx/lib/pdfrx.dart | 4 +- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 23 +- .../widgets/pdf_viewer_layout_metrics.dart | 33 ++ .../lib/src/widgets/pdf_viewer_params.dart | 14 +- .../sizing/pdf_viewer_size_delegate.dart | 43 +- .../pdf_viewer_size_delegate_legacy.dart | 46 -- .../pdf_viewer_size_delegate_smart.dart | 469 ------------------ .../pdf_viewer_zoom_steps_delegate.dart | 49 ++ ...df_viewer_zoom_steps_delegate_default.dart | 92 ++++ .../pdf_viewer_zoom_steps_delegate_smart.dart | 126 +++++ 10 files changed, 332 insertions(+), 567 deletions(-) create mode 100644 packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart delete mode 100644 packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart create mode 100644 packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart create mode 100644 packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart create mode 100644 packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index 54fe8a2b..3f973cd4 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -6,9 +6,11 @@ export 'src/pdfrx_flutter.dart'; export 'src/utils/fixed_overscroll_physics.dart'; export 'src/widgets/pdf_text_searcher.dart'; export 'src/widgets/pdf_viewer.dart'; +export 'src/widgets/pdf_viewer_layout_metrics.dart'; export 'src/widgets/pdf_viewer_params.dart'; export 'src/widgets/pdf_viewer_scroll_thumb.dart'; export 'src/widgets/pdf_widgets.dart'; export 'src/widgets/sizing/pdf_viewer_size_delegate.dart'; export 'src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart'; -export 'src/widgets/sizing/pdf_viewer_size_delegate_smart.dart'; +export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart'; +export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 7c30b390..bc7ac5af 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -280,6 +280,7 @@ class _PdfViewerState extends State EdgeInsets _adjustedBoundaryMargins = EdgeInsets.zero; PdfViewerSizeDelegate? _sizeDelegate; + PdfViewerZoomStepsDelegate? _zoomStepsDelegate; @override void initState() { @@ -288,6 +289,7 @@ class _PdfViewerState extends State _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); _widgetUpdated(null); _updateSizeDelegate(); + _updateZoomStepsDelegate(); // Initialize metrics with default/current state // (Layout is null here, so it will return defaults) @@ -301,6 +303,9 @@ class _PdfViewerState extends State if (widget.params.sizeDelegateProvider != oldWidget.params.sizeDelegateProvider) { _updateSizeDelegate(); } + if (widget.params.zoomStepsDelegateProvider != oldWidget.params.zoomStepsDelegateProvider) { + _updateZoomStepsDelegate(); + } } void _updateSizeDelegate() { @@ -311,6 +316,11 @@ class _PdfViewerState extends State } } + void _updateZoomStepsDelegate() { + _zoomStepsDelegate?.dispose(); + _zoomStepsDelegate = widget.params.zoomStepsDelegateProvider.create(); + } + Future _widgetUpdated(PdfViewer? oldWidget) async { if (widget == oldWidget) { return; @@ -403,6 +413,7 @@ class _PdfViewerState extends State @override void dispose() { _sizeDelegate?.dispose(); + _zoomStepsDelegate?.dispose(); focusReportForPreventingContextMenuWeb(this, false); _documentSubscription?.cancel(); _textSelectionChangedDebounceTimer?.cancel(); @@ -491,7 +502,7 @@ class _PdfViewerState extends State ? const EdgeInsets.all(double.infinity) // NOTE: boundaryMargin is handled manually : _adjustedBoundaryMargins, maxScale: _layoutMetrics.maxScale, - minScale: minScale, + minScale: _layoutMetrics.minScale, panAxis: widget.params.panAxis, panEnabled: widget.params.panEnabled, scaleEnabled: widget.params.scaleEnabled, @@ -1041,7 +1052,7 @@ class _PdfViewerState extends State } void _calcZoomStopTable() { - _zoomStops = _sizeDelegate!.generateZoomStops(_layoutMetrics); + _zoomStops = _zoomStepsDelegate!.generateZoomStops(_layoutMetrics); } double _findNextZoomStop(double zoom, {required bool zoomUp, bool loop = true}) { @@ -1584,9 +1595,6 @@ class _PdfViewerState extends State ); } - /// The minimum zoom ratio allowed. - double get minScale => _layoutMetrics.minScale; - Matrix4 _calcMatrixForRect(Rect rect, {double? zoomMax, double? margin}) { margin ??= 0; var zoom = min((_viewSize!.width - margin * 2) / rect.width, (_viewSize!.height - margin * 2) / rect.height); @@ -3634,7 +3642,10 @@ class PdfViewerController extends ValueListenable { double? get alternativeFitScale => _state._layoutMetrics.alternativeFitScale; /// The minimum zoom ratio allowed. - double get minScale => _state.minScale; + double get minScale => _state._layoutMetrics.minScale; + + /// The maximum zoom ratio allowed. + double get maxScale => _state._layoutMetrics.maxScale; /// The area of the document layout which is visible on the view port. Rect get visibleRect => _state._visibleRect; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart new file mode 100644 index 00000000..e665d3ce --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart @@ -0,0 +1,33 @@ +import '../../pdfrx.dart'; + +/// A container for the calculated scaling limits of the viewer. +/// +/// Returned by [PdfViewerSizeDelegate.calculateMetrics]. +class PdfViewerLayoutMetrics { + const PdfViewerLayoutMetrics({ + required this.minScale, + required this.maxScale, + // We keep these because the Controller exposes them publicly as getters. + // The Delegate calculates them so the Controller can return them. + required this.coverScale, + this.alternativeFitScale, + }); + + /// The effective minimum scale allowed for the viewer. + final double minScale; + + /// The effective maximum scale allowed for the viewer. + final double maxScale; + + /// The scale required to fit the document's bounding box within the viewport. + final double coverScale; + + /// The scale required to fit the content (usually the current page) entirely within the viewport. + /// + /// Conventionally, delegates calculate this as the "Fit Page" scale. + /// + /// It is often used as the effective minimum scale to ensure the user can always + /// see the full page content preventing zooming out too far (which could lead to + /// rendering performance issues if too many pages become visible). + final double? alternativeFitScale; +} diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index b1b5afc4..174acfc5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -74,6 +74,7 @@ class PdfViewerParams { this.scrollPhysics, this.scrollPhysicsScale, this.sizeDelegateProvider, + this.zoomStepsDelegateProvider = const PdfViewerZoomStepsDelegateProviderDefault(), }) : assert( (maxScale == null && minScale == null && @@ -587,6 +588,9 @@ class PdfViewerParams { /// relative positioning and boundary clamping. final PdfViewerSizeDelegateProvider? sizeDelegateProvider; + /// Provider to create a delegate that generates zoom stops (snap points). + final PdfViewerZoomStepsDelegateProvider zoomStepsDelegateProvider; + /// A convenience function to get platform-specific default scroll physics. /// /// On iOS/MacOS this is [BouncingScrollPhysics], and on Android this is [FixedOverscrollPhysics], a @@ -655,7 +659,8 @@ class PdfViewerParams { other.verticalCacheExtent != verticalCacheExtent || other.linkHandlerParams != linkHandlerParams || other.scrollPhysics != scrollPhysics || - other.sizeDelegateProvider != sizeDelegateProvider; + other.sizeDelegateProvider != sizeDelegateProvider || + other.zoomStepsDelegateProvider != zoomStepsDelegateProvider; } @override @@ -721,7 +726,8 @@ class PdfViewerParams { other.behaviorControlParams == behaviorControlParams && other.forceReload == forceReload && other.scrollPhysics == scrollPhysics && - other.sizeDelegateProvider == sizeDelegateProvider; + other.sizeDelegateProvider == sizeDelegateProvider && + other.zoomStepsDelegateProvider == zoomStepsDelegateProvider; } @override @@ -757,6 +763,7 @@ class PdfViewerParams { onLongPressStart.hashCode ^ onDocumentChanged.hashCode ^ calculateInitialPageNumber.hashCode ^ + // ignore: deprecated_member_use_from_same_package calculateInitialZoom.hashCode ^ calculateCurrentPageNumber.hashCode ^ onViewerReady.hashCode ^ @@ -785,7 +792,8 @@ class PdfViewerParams { behaviorControlParams.hashCode ^ forceReload.hashCode ^ scrollPhysics.hashCode ^ - sizeDelegateProvider.hashCode; + sizeDelegateProvider.hashCode ^ + zoomStepsDelegateProvider.hashCode; } } diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart index 22152956..b597404c 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart @@ -37,7 +37,7 @@ abstract class PdfViewerSizeDelegateProvider { /// /// ### Lifecycle & Controller Access /// 1. [create] is called by the Provider. -/// 2. **[calculateMetrics] and [generateZoomStops] may be called immediately.** +/// 2. **[calculateMetrics] may be called immediately.** /// * **Warning:** At this stage, [init] has *not* been called yet. /// * Implementations must not access the controller or internal state here. /// * Calculations must rely solely on the arguments provided. @@ -77,15 +77,6 @@ abstract class PdfViewerSizeDelegate { required EdgeInsets? boundaryMargin, }); - /// Generates the list of zoom stops (steps) for double-tap zooming. - /// - /// **⚠️ Important:** This method is often called **before** [init]. - /// Do not access the [PdfViewerController] inside this method. - /// - /// Typically this includes the "Fit Page" scale, 1.0 (100%), and powers of 2. - /// The result should be sorted in ascending order. - List generateZoomStops(PdfViewerLayoutMetrics metrics); - /// The scale threshold for switching between one-pass rendering and progressive rendering. /// /// If the current zoom is below this threshold, the viewer may render the page @@ -195,35 +186,3 @@ class PdfViewerLayoutSnapshot { @override int get hashCode => Object.hash(viewSize, layout, minScale, coverScale, alternativeFitScale); } - -/// A container for the calculated scaling limits of the viewer. -/// -/// Returned by [PdfViewerSizeDelegate.calculateMetrics]. -class PdfViewerLayoutMetrics { - const PdfViewerLayoutMetrics({ - required this.minScale, - required this.maxScale, - // We keep these because the Controller exposes them publicly as getters. - // The Delegate calculates them so the Controller can return them. - required this.coverScale, - this.alternativeFitScale, - }); - - /// The effective minimum scale allowed for the viewer. - final double minScale; - - /// The effective maximum scale allowed for the viewer. - final double maxScale; - - /// The scale required to fit the document's bounding box within the viewport. - final double coverScale; - - /// The scale required to fit the content (usually the current page) entirely within the viewport. - /// - /// Conventionally, delegates calculate this as the "Fit Page" scale. - /// - /// It is often used as the effective minimum scale to ensure the user can always - /// see the full page content preventing zooming out too far (which could lead to - /// rendering performance issues if too many pages become visible). - final double? alternativeFitScale; -} diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart index d07f01a8..362f4271 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart @@ -293,51 +293,5 @@ class PdfViewerSizeDelegateLegacy implements PdfViewerSizeDelegate { } } - @override - List generateZoomStops(PdfViewerLayoutMetrics metrics) { - final zoomStops = []; - final alternativeFitScale = metrics.alternativeFitScale; - final coverScale = metrics.coverScale; - final minScale = metrics.minScale; - final maxScale = metrics.maxScale; - double z; - if (alternativeFitScale != null && !_areZoomsAlmostIdentical(alternativeFitScale, coverScale)) { - if (alternativeFitScale < coverScale) { - zoomStops.add(alternativeFitScale); - z = coverScale; - } else { - zoomStops.add(coverScale); - z = alternativeFitScale; - } - } else { - z = coverScale; - } - // in some case, z may be 0 and it causes infinite loop. - if (z < 1 / 128) { - zoomStops.add(1.0); - return zoomStops; - } - while (z < metrics.maxScale) { - zoomStops.add(z); - z *= 2; - } - if (!_areZoomsAlmostIdentical(z, maxScale)) { - zoomStops.add(maxScale); - } - - if (!_useAlternativeFitScaleAsMinScale) { - z = zoomStops.first; - while (z > minScale) { - z /= 2; - zoomStops.insert(0, z); - } - if (!_areZoomsAlmostIdentical(z, minScale)) { - zoomStops.insert(0, minScale); - } - } - - return zoomStops; - } - static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; } diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart deleted file mode 100644 index 07579f5e..00000000 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart +++ /dev/null @@ -1,469 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:flutter/rendering.dart'; - -import '../../../pdfrx.dart'; - -/// A provider that creates a [PdfViewerSizeDelegateSmart] instance with smart scaling configuration. -/// -/// This provider configures a sizing strategy that offers a modern, natural -/// viewing experience. It prioritizes keeping content centered and adapts the -/// zoom level intelligently during viewport changes (e.g., window resize, -/// device rotation, or layout adjustments). -/// -/// ### Behavior Scenarios -/// -/// The delegate analyzes the state *before* the resize to determine the user's intent: -/// -/// * **Sticky Fit (Fit Width):** If the content fits the width exactly (within margin of error): -/// * **Shrinking:** The content shrinks with the viewport to maintain "Fit Width". -/// * **Growing:** The content grows with the viewport to maintain "Fit Width", -/// **up to** the [smartMaxScale]. Once this limit is reached, it stops growing -/// and introduces horizontal whitespace. -/// -/// * **Whitespace (Underflow):** If the content is smaller than the viewport: -/// * **General:** The absolute zoom level is preserved, and the content is re-centered. -/// * **Catch-on:** If the viewport shrinks so much that the content would start being -/// clipped, it switches to "Fit Width" mode and shrinks the content. -/// -/// * **Zoomed In (Overflow):** If the content is larger than the viewport (horizontal scrolling): -/// * The absolute zoom level is preserved. The viewport is re-centered on the -/// same point in the document. -/// -/// ### Comparison with Legacy Strategy -/// -/// * **Legacy (`PdfViewerSizeDelegateProviderLegacy`)**: Aligns content to the top-left. -/// Strictly preserves the exact zoom level during resizing unless limits are violated. -/// Defaults to "Fit Page" (full page visible) on initialization. -/// * **Smart (`PdfViewerSizeDelegateProviderSmart`)**: Aligns content to the center. -/// Adapts zoom level to keep content fitting the screen width. Defaults to "Fit Width" -/// on initialization. Offers more of a high-level API, deliberately not implementing a -/// calculateInitialZoom callback. -/// -class PdfViewerSizeDelegateProviderSmart extends PdfViewerSizeDelegateProvider { - const PdfViewerSizeDelegateProviderSmart({ - double? minScale, - double? maxScale, - double? smartMaxScale, - double? maxPagesVisible, - double? onePassRenderingScaleThreshold, - }) : maxScale = maxScale ?? 8.0, - minScale = minScale ?? 0.1, - smartMaxScale = smartMaxScale ?? 1.3, - maxPagesVisible = maxPagesVisible ?? 3.0, - onePassRenderingScaleThreshold = onePassRenderingScaleThreshold ?? 200 / 72; - - /// The maximum allowed scale. - /// - /// The default is 8.0. - final double maxScale; - - /// The minimum allowed scale. - /// - /// The default is 0.1. - /// - /// This is the hard ceiling for the viewer. Even if "Fit Width" logic would - /// prefer a larger scale, the viewer will not exceed this value. - final double minScale; - - /// The maximum number of pages (approximately) visible when zoomed out to the minimum. - /// - /// This factor divides the "Fit Page" scale to determine the effective minimum scale. - /// * `1.0`: Minimum scale fits exactly one page (Fit Page). - /// * `3.0`: (default) Minimum scale fits two pages. - /// * `double.infinity`: Ignore "Fit Page" limit entirely; use [minScale] as the only floor. - final double maxPagesVisible; - - /// If a page is rendered over the scale threshold, the page is rendered with the threshold scale - /// and actual resolution image is rendered after some delay (progressive rendering). - /// - /// Basically, if the value is larger, the viewer renders each page in one-pass rendering; it is - /// faster and looks better to the user. However, larger value may consume more memory. - /// So you may want to set the smaller value to reduce memory consumption. - /// - /// The default is 200 / 72, which implies rendering at 200 dpi. - /// If you want more granular control for each page, use [getPageRenderingScale]. - final double onePassRenderingScaleThreshold; - - /// The maximum zoom level for automatic "Fit Width" scaling. - /// - /// This prevents the document from becoming uncomfortably large on very wide screens. - /// For example, if set to 1.2, resizing the window to be very wide will stop - /// zooming the document once it reaches 120% scale, centering it with margins instead. - final double smartMaxScale; - - @override - PdfViewerSizeDelegate create() => PdfViewerSizeDelegateSmart( - smartMaxScale: smartMaxScale, - maxScale: maxScale, - minScale: minScale, - maxPagesVisible: maxPagesVisible, - onePassRenderingScaleThreshold: onePassRenderingScaleThreshold, - ); - - @override - bool operator ==(Object other) => - other is PdfViewerSizeDelegateProviderSmart && - other.smartMaxScale == smartMaxScale && - other.maxScale == maxScale && - other.minScale == minScale && - other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && - other.maxPagesVisible == maxPagesVisible; - - @override - int get hashCode => Object.hash(smartMaxScale, maxScale, minScale, onePassRenderingScaleThreshold, maxPagesVisible); -} - -/// A "Smart" resize delegate that adapts zoom to fit the page width and centers content. -/// -/// ### Core Behaviors -/// 1. **Smart Initialization**: Defaults to "Fit Width" (capped at [_smartMaxScale]) -/// instead of "Fit Page". -/// 2. **Adaptive Resizing**: -/// * **Shrinking**: If the window shrinks, the content scales down to stay -/// fully visible width-wise ("Catch on"). -/// * **Growing**: If the window grows, the content scales up to fit width, -/// but stops growing at [_smartMaxScale]. -/// 3. **Centering**: Unlike the default behavior (which clamps to top-left), -/// this delegate keeps the view centered on the same point in the document -/// during resizing. -class PdfViewerSizeDelegateSmart implements PdfViewerSizeDelegate { - PdfViewerSizeDelegateSmart({ - required double smartMaxScale, - required double maxScale, - required double minScale, - required double maxPagesVisible, - required this.onePassRenderingScaleThreshold, - }) : _minScale = minScale, - _maxScale = maxScale, - _smartMaxScale = smartMaxScale, - _maxPagesVisible = maxPagesVisible; - - final double _maxScale; - final double _minScale; - final double _maxPagesVisible; - final double _smartMaxScale; - - PdfViewerController? _controller; - - @override - final double onePassRenderingScaleThreshold; - - @override - void init(PdfViewerController controller) { - _controller = controller; - } - - @override - void dispose() { - _controller = null; - } - - @override - PdfViewerLayoutMetrics calculateMetrics({ - required Size viewSize, - required PdfPageLayout? layout, - required int? pageNumber, - required double pageMargin, - required EdgeInsets? boundaryMargin, - }) { - // Reuse the legacy math for geometric limits (coverScale/alternativeFitScale) - // We can delegate this calculation or duplicate the math (it's purely geometric). - // Duplicating for clarity/independence: - final bmh = boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; - final bmv = boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; - - var coverScale = 1.0; - double? alternativeFitScale; - - if (layout != null) { - final s1 = viewSize.width / (layout.documentSize.width + bmh); - final s2 = viewSize.height / (layout.documentSize.height + bmv); - coverScale = math.max(s1, s2); - - if (pageNumber != null && pageNumber >= 1 && pageNumber <= layout.pageLayouts.length) { - final rect = layout.pageLayouts[pageNumber - 1]; - final m2 = pageMargin * 2; - alternativeFitScale = math.min( - (viewSize.width) / (rect.width + bmh + m2), - (viewSize.height) / (rect.height + bmv + m2), - ); - } - } - - // Smart Policy for Min Scale: - // 1. Calculate "Fit Page" scale (fallback to coverScale if page not found) - final fitPageScale = alternativeFitScale ?? coverScale; - - // 2. Adjust for multi-page visibility - // If maxPagesVisible is 1.0, this is Fit Page. - // If maxPagesVisible is 2.0, we allow zooming out 2x further. - // If maxPagesVisible is infinity, this becomes 0. - final allowedMinScale = fitPageScale / _maxPagesVisible; - - // 3. The minimum scale is whichever is larger: the hard configuration or the physical fit. - // This prevents zooming out further than the page size. - final effectiveMinScale = math.max(_minScale, allowedMinScale); - - return PdfViewerLayoutMetrics( - minScale: effectiveMinScale, - maxScale: _maxScale, - coverScale: coverScale, - alternativeFitScale: alternativeFitScale, - ); - } - - @override - void onLayoutInitialized({ - required PdfViewerLayoutSnapshot state, - required int initialPageNumber, - required double coverScale, - required double? alternativeFitScale, - required PdfPageLayout layout, - required PdfDocument document, - }) { - final controller = _controller; - if (controller == null) return; - - // --- Smart Initialization --- - // Instead of defaulting to "Fit Page" (which might be tiny on a large monitor), - // we default to "Fit Width" (clamped by smartMaxScale). - - // 1. Calculate raw Fit Width Scale - final docWidth = state.layout?.documentSize.width ?? 1.0; - // Avoid division by zero - final rawFitWidthScale = docWidth > 0 ? state.viewSize.width / docWidth : 1.0; - - // 2. Limit the Fit Width Scale - // This is the "Smart" part: We prevent the default zoom from being too large on wide screens. - // e.g. On a 4k monitor, Fit Width might be 300%. We cap this default to 120% (smartMaxScale). - final effectiveFitWidthScale = (rawFitWidthScale > _smartMaxScale) ? _smartMaxScale : rawFitWidthScale; - - // 3. Determine Initial Zoom - var zoom = effectiveFitWidthScale; - - // 4. Hard Constraints - // Ensure we are within the hard configuration limits - if (zoom < _minScale) { - zoom = _minScale; - } - if (zoom > _maxScale) { - zoom = _maxScale; - } - - // Ensure we don't go below the effective minimum calculated by the Viewer State - // (e.g. if Fit Page is required to avoid rendering issues) - if (zoom < state.minScale) { - zoom = state.minScale; - } - - // 5. Apply - unawaited(controller.setZoom(Offset.zero, zoom, duration: Duration.zero)); - } - - @override - void onLayoutUpdate({ - required PdfViewerLayoutSnapshot oldState, - required PdfViewerLayoutSnapshot newState, - required double currentZoom, - required Rect oldVisibleRect, - required int? anchorPageNumber, - required bool isLayoutChanged, - required bool isViewSizeChanged, - }) { - final controller = _controller; - if (controller == null) return; - - // Calculate the target zoom based on the legacy "preserve zoom" rule first. - // This is often just currentZoom, but handles minScale clamping logic. - final zoomTo = currentZoom < newState.minScale || currentZoom == oldState.minScale - ? newState.minScale - : currentZoom; - - if (isLayoutChanged) { - // --- 1. Handle Layout Changes --- - // If the document layout changed (e.g. pages rotated, added, or margins changed), - // we defer to the Default/Legacy logic. Mapping visual positions across layout - // changes is complex, and the default implementation handles it robustly - // (mapping the user's previous look-at point to the new layout). - - final oldLayout = oldState.layout; - - if (oldLayout != null && anchorPageNumber != null) { - // Use the controller's helper to find where the user was looking - final hit = controller.getClosestPageHit(anchorPageNumber, oldLayout, oldVisibleRect); - final pageNumber = hit?.page.pageNumber ?? anchorPageNumber; - - // Compute relative position within the old pageRect - final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; - final newPageRect = newState.layout!.pageLayouts[pageNumber - 1]; - final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; - final fracX = oldOffset.dx / oldPageRect.width; - final fracY = oldOffset.dy / oldPageRect.height; - - // Map into new layoutRect - final newOffset = Offset( - newPageRect.left + fracX * newPageRect.width, - newPageRect.top + fracY * newPageRect.height, - ); - - // Preserve the position after a layout change - unawaited(controller.goToPosition(documentOffset: newOffset, zoom: zoomTo)); - } - - return; - } - - // From here on, we assume only the View Size (window size) changed. - assert(isViewSizeChanged); - - final oldLayout = oldState.layout; - final newLayout = newState.layout; - if (oldLayout == null || newLayout == null) return; - - final oldSize = oldState.viewSize; - final newSize = newState.viewSize; - - // --- 2. Calculate Fit Metrics --- - // We compare the viewport width to the document width to determine the "Fit Width" ratio. - final oldDocWidth = oldLayout.documentSize.width; - final newDocWidth = newLayout.documentSize.width; - - final oldFitScale = oldSize.width / oldDocWidth; - final newFitScale = newSize.width / newDocWidth; - - // --- 3. Determine User Intent (State Machine) --- - // We analyze the state BEFORE the resize to guess what the user wants. - const epsilon = 0.01; - - // State B: Sticky Fit - // The user was effectively viewing "Fit Width" before the resize. - final wasFittingExact = (currentZoom - oldFitScale).abs() < epsilon; - - // State A: Overflow (Zoomed In) - // The user was zoomed in closer than "Fit Width" (horizontal scrolling possible). - final wasOverflowing = !wasFittingExact && currentZoom > oldFitScale; - - var targetZoom = currentZoom; - - if (wasOverflowing) { - // Scenario A: User Manual Zoom (Overflow) - // If the user manually zoomed in, we preserve the absolute zoom level. - // We don't snap them back to "Fit Width" just because they resized the window. - // Position is maintained by the centering logic below. - targetZoom = currentZoom; - } else if (wasFittingExact) { - // Scenario B: Sticky Fit - // The content was fitting the width. We want it to *continue* fitting the width - // of the new window size. - targetZoom = newFitScale; - - // Smart Limit Logic (Growing): - // If the window is growing, we follow the width up to [smartMaxScale]. - if (newFitScale > oldFitScale) { - if (targetZoom > _smartMaxScale) { - targetZoom = _smartMaxScale; - // Exception: If the user was somehow already above the limit (e.g. manual zoom - // followed by a resize that landed in sticky range), don't snap DOWN to the limit. - if (targetZoom < currentZoom) { - targetZoom = currentZoom; - } - } - } - // Smart Limit Logic (Shrinking): - // If shrinking, we always accept `newFitScale` to ensure the content stays - // inside the window (avoiding horizontal scrolling). - } else { - // Scenario C: Underflow (Whitespace) - // The content was smaller than the viewport (zoomed out). - // Generally, keep the absolute zoom and just recenter. - targetZoom = currentZoom; - - // "Catch On" Logic: - // If the window shrinks so much that the old zoom would now cause overflow, - // we switch strategy to "Fit Width" to prevent content from being clipped. - if (targetZoom > newFitScale) { - targetZoom = newFitScale; - } - } - - // --- 4. Apply Hard Constraints --- - final minS = newState.minScale; - // Ensure we don't violate the viewer's absolute minimum limit (calculated by State). - if (targetZoom < minS) targetZoom = minS; - - // --- 5. Apply Transformation (Centering) --- - // Instead of clamping to the top-left (default behavior), we pivot around the center. - - // Find the point in the document currently at the center of the viewport - final oldCenterInDoc = controller.value.calcPosition(oldSize); - - // Create a new matrix that puts that same document point at the center of the NEW viewport - final newMatrix = controller.calcMatrixFor(oldCenterInDoc, zoom: targetZoom, viewSize: newSize); - - // Stop any physics animations (inertia) to prevent fighting with the layout update - controller.stopInteractiveViewerAnimation(); - controller.value = newMatrix; - } - - @override - List generateZoomStops(PdfViewerLayoutMetrics metrics) { - final alternativeFitScale = metrics.alternativeFitScale; - final coverScale = metrics.coverScale; - final minScale = metrics.minScale; - final maxScale = metrics.maxScale; - - // Standard logic: Fit Page, Fit Width (implicitly handled by coverScale logic usually), - // and Powers of 2. - double z; - - // Set to simplify deduplication - final stops = {}; - - // Add Fit Page / Cover stops - if (alternativeFitScale != null && !_areZoomsAlmostIdentical(alternativeFitScale, coverScale)) { - stops.add(math.min(alternativeFitScale, coverScale)); - stops.add(math.max(alternativeFitScale, coverScale)); - z = math.max(alternativeFitScale, coverScale); - } else { - z = coverScale; - stops.add(coverScale); - } - - // Add 100% stop if reasonable - if (z < 1.0 && 1.0 < maxScale) { - stops.add(1.0); - } - - // Generate stops up to maxScale - if (z < 1 / 128) { - // prevent infinite loop - z = 1 / 128; - } - while (z < maxScale) { - stops.add(z); - z *= 2; - } - if (!_areZoomsAlmostIdentical(z, maxScale)) { - stops.add(maxScale); - } - - // Generate stops down to minScale - z = stops.first; - while (z > minScale) { - z /= 2; - stops.add(z); - } - if ((z - minScale).abs() > 0.01) { - stops.add(minScale); - } - - // Convert to list and sort - final stopsList = stops.toList(); - stopsList.sort(); - return stopsList; - } - - static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; -} diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart new file mode 100644 index 00000000..ec9f481d --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart @@ -0,0 +1,49 @@ +import '../../../pdfrx.dart'; + +/// Interface for a factory that creates [PdfViewerZoomStepsDelegate] instances. +/// +/// ### Why use a Provider? +/// [PdfViewerParams] relies on `operator ==` to determine if the viewer needs to be +/// reloaded or updated. By using a `const` Provider class with a proper `operator ==` +/// implementation, we ensure that the delegate lifecycle is stable across widget rebuilds. +/// +/// ### Why not just a function? +/// While the current logic for calculating zoom stops is pure and could be represented +/// by a simple function callback, this Provider/Delegate pattern is used to: +/// 1. **Maintain Consistency:** Match the architecture of [PdfViewerSizeDelegate] and +/// [PdfViewerScrollInteractionDelegate]. +/// 2. **Future Proofing:** Allow for potential stateful logic (e.g., caching calculations) +/// or resource management (via [dispose]) in the future without breaking the API. +abstract class PdfViewerZoomStepsDelegateProvider { + const PdfViewerZoomStepsDelegateProvider(); + + /// Creates the runtime delegate instance. + PdfViewerZoomStepsDelegate create(); + + @override + bool operator ==(Object other); + + @override + int get hashCode; +} + +/// Delegate to determine the "Zoom Stops" (snap points) for the viewer. +/// +/// Zoom stops are primarily used for: +/// 1. **Double-tap zooming:** Cycling through specific levels (e.g. Fit Page -> Fit Width -> 100%). +/// 2. **Accessibility/Keyboard:** Incrementing zoom levels via shortcuts. +/// +/// This delegate decouples the **Interaction Policy** (which zoom levels are "interesting"?) +/// from the **Sizing Policy** (what are the hard limits?). +abstract class PdfViewerZoomStepsDelegate { + /// Called when the delegate is being destroyed. + void dispose(); + + /// Generates the list of zoom stops (steps) for user interaction. + /// + /// The list should be sorted in ascending order. + /// + /// [metrics]: The current layout metrics calculated by the [PdfViewerSizeDelegate], + /// containing the effective min/max scales and fit-page scales. + List generateZoomStops(PdfViewerLayoutMetrics metrics); +} diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart new file mode 100644 index 00000000..60de9f1e --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart @@ -0,0 +1,92 @@ +import '../../../pdfrx.dart'; + +/// The default provider that creates the standard zoom stepping behavior. +/// +/// **Strategy:** +/// * Always includes "Fit Page" (and "Fit Width" if different). +/// * Generates powers-of-2 steps (0.25, 0.5, 1.0, 2.0, 4.0, ...). +/// * Respects the `minScale` and `maxScale` defined in the metrics. +class PdfViewerZoomStepsDelegateProviderDefault extends PdfViewerZoomStepsDelegateProvider { + const PdfViewerZoomStepsDelegateProviderDefault(); + + @override + PdfViewerZoomStepsDelegate create() => PdfViewerZoomStepsDelegateDefault(); + + @override + bool operator ==(Object other) => other is PdfViewerZoomStepsDelegateProviderDefault; + + @override + int get hashCode => runtimeType.hashCode; +} + +/// Default implementation for `PdfViewerZoomStepsDelegate`. +class PdfViewerZoomStepsDelegateDefault implements PdfViewerZoomStepsDelegate { + @override + void dispose() {} + + @override + List generateZoomStops(PdfViewerLayoutMetrics metrics) { + final zoomStops = []; + + // 1. Identify key structural scales + final alternativeFitScale = metrics.alternativeFitScale; + final coverScale = metrics.coverScale; + final minScale = metrics.minScale; + final maxScale = metrics.maxScale; + + // Logic to include both Fit-Page and Fit-Width if they differ significantly + double z; + if (alternativeFitScale != null && !_areZoomsAlmostIdentical(alternativeFitScale, coverScale)) { + if (alternativeFitScale < coverScale) { + zoomStops.add(alternativeFitScale); + z = coverScale; + } else { + zoomStops.add(coverScale); + z = alternativeFitScale; + } + } else { + z = coverScale; + } + + // Safety check to prevent infinite loops if scale is extremely small + if (z < 1 / 128) { + zoomStops.add(1.0); + return zoomStops; + } + + // 2. Generate steps upwards (Powers of 2) + while (z < metrics.maxScale) { + zoomStops.add(z); + z *= 2; + } + + // Ensure maxScale is included + if (!_areZoomsAlmostIdentical(z, maxScale)) { + zoomStops.add(maxScale); + } + + // 3. Generate steps downwards + // We start from the smallest structural scale (Fit Page or Cover) + z = zoomStops.first; + + // We rely on metrics.minScale being the effective minimum. + // If sizing policy set minScale = Fit Page, this loop effectively won't run (z > z is false). + // If sizing policy set minScale = 0.1, this loop fills the gap with powers of 2. + while (z > minScale) { + z /= 2; + zoomStops.insert(0, z); + } + + // Ensure minScale is included + // Note: Legacy behavior inserted this at 0, potentially making the list [minScale, smaller_val, ...] + // if the loop overshot. We keep `insert(0)` to match legacy behavior strictly, + // assuming _findNextZoomStop handles unsorted lists or the overshoot is negligible/handled elsewhere. + if (!_areZoomsAlmostIdentical(z, minScale)) { + zoomStops.insert(0, minScale); + } + + return zoomStops; + } + + static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; +} diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart new file mode 100644 index 00000000..211d1d3e --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart @@ -0,0 +1,126 @@ +import 'dart:math' as math; + +import '../../../pdfrx.dart'; + +/// The smart provider that creates the standard zoom stepping behavior. +class PdfViewerZoomStepsDelegateProviderSmart extends PdfViewerZoomStepsDelegateProvider { + const PdfViewerZoomStepsDelegateProviderSmart({double? zoomStep, double? minZoomStep}) + : assert(zoomStep == null || zoomStep > 1.01, 'zoomStep must be greater than 1.01'), + assert(minZoomStep == null || minZoomStep > 1.01, 'minZoomStep must be greater than 1.01'), + zoomStep = zoomStep ?? 1.5, + minZoomStep = minZoomStep ?? 1.2; + + /// The target geometric factor between zoom stops. + /// + /// Used by [PdfViewerSizeDelegate.generateZoomStops] to fill gaps between semantic + /// zoom levels (like Fit Page and Fit Width). + final double zoomStep; + + /// The minimum ratio gap required to insert an intermediate zoom step. + final double minZoomStep; + + @override + PdfViewerZoomStepsDelegate create() => PdfViewerZoomStepsDelegateSmart(zoomStep: zoomStep, minZoomStep: minZoomStep); + + @override + bool operator ==(Object other) => other is PdfViewerZoomStepsDelegateProviderSmart; + + @override + int get hashCode => runtimeType.hashCode; +} + +class PdfViewerZoomStepsDelegateSmart implements PdfViewerZoomStepsDelegate { + PdfViewerZoomStepsDelegateSmart({required double zoomStep, required double minZoomStep}) + : _zoomStep = zoomStep, + _minZoomStep = minZoomStep; + + final double _zoomStep; + final double _minZoomStep; + @override + void dispose() {} + + @override + List generateZoomStops(PdfViewerLayoutMetrics metrics) { + // 1. Define "Reasonable" bounds for zoom stops. + // Even if the technical minScale allows zooming out to 1% (0.01), + // we don't want to generate a specific stop there because it's usually illegible. + // The user can still pinch/scroll there manually, but double-tap/buttons won't force it. + const reasonableMin = 0.125; // 12.5% + const reasonableMax = 8.0; // 800% + + // Calculate the effective range for zoom stops. + // We clamp the hard limits to the reasonable bounds. + final effectiveMin = math.max(metrics.minScale, reasonableMin); + final effectiveMax = math.min(metrics.maxScale, reasonableMax); + + // If the configuration forces min > reasonableMax or max < reasonableMin, + // we fallback to the hard limits to ensure at least one stop exists. + if (effectiveMin > effectiveMax) { + return [metrics.minScale, metrics.maxScale]; + } + + // 2. Identify Semantic Anchors + final anchors = { + effectiveMin, + effectiveMax, + // We include Fit Page/Width/100% only if they fall within the reasonable/effective range. + // This prevents snapping to a "Fit Page" that is microscopic. + if (metrics.alternativeFitScale != null) metrics.alternativeFitScale!, + metrics.coverScale, + 1.0, + }; + + final sortedAnchors = anchors.where((s) => s >= effectiveMin && s <= effectiveMax).toList()..sort(); + + // 3. Logarithmic Gap Filling + // We want steps to increase by roughly 50% (factor 1.5) each tap. + // Safety: Prevent infinite loops if configuration is broken + final safeZoomStep = _zoomStep <= 1.01 ? 1.5 : _zoomStep; + final safeMinZoomStep = _minZoomStep <= 1.01 ? 1.1 : _minZoomStep; + + final result = [sortedAnchors.first]; + + for (var i = 0; i < sortedAnchors.length - 1; i++) { + final start = sortedAnchors[i]; + final end = sortedAnchors[i + 1]; + + if (start <= 0 || end <= 0) continue; + + final gap = end / start; + + // If the gap is too small, don't add intermediate steps. + if (gap < safeMinZoomStep) { + result.add(end); + continue; + } + + // Calculate how many intervals fit in this gap. + // formula: base^intervals = gap -> intervals = log(gap) / log(base) + final intervals = (math.log(gap) / math.log(safeZoomStep)).round(); + + if (intervals <= 1) { + result.add(end); + } else { + // Calculate the specific geometric ratio to land exactly on 'end' + final specificRatio = math.pow(gap, 1 / intervals); + + var current = start; + for (var k = 0; k < intervals - 1; k++) { + current *= specificRatio; + result.add(current); + } + result.add(end); + } + } + + // Deduplicate logic (handles float precision issues) + final deduped = [result.first]; + for (var i = 1; i < result.length; i++) { + if ((result[i] - deduped.last).abs() > 0.001) { + deduped.add(result[i]); + } + } + + return deduped; + } +} From 1ff49bfa3114c84d71e883fef9dd1b68afba9f0c Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 Jan 2026 19:58:33 +0900 Subject: [PATCH 641/663] FIXED: #550 round10BitFrac should not process Infinity or NaN --- packages/pdfrx/lib/src/utils/double_extensions.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/pdfrx/lib/src/utils/double_extensions.dart b/packages/pdfrx/lib/src/utils/double_extensions.dart index 5a86e0b4..6fb1ef98 100644 --- a/packages/pdfrx/lib/src/utils/double_extensions.dart +++ b/packages/pdfrx/lib/src/utils/double_extensions.dart @@ -14,5 +14,10 @@ extension DoubleExtensions on double { /// Round the double to keep 10-bits of precision under the binary point. /// /// It's almost 3 decimal places (i.e. 1.23456789 => 1.234) but cleaner in binary representation. - double round10BitFrac() => (this * 1024).round() / 1024; + double round10BitFrac() { + if (isInfinite || isNaN) { + return this; + } + return (this * 1024).round() / 1024; + } } From 232f808d20cacf9164268e9febd7fef26bf33574 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 Jan 2026 20:18:58 +0900 Subject: [PATCH 642/663] Update Gradle configuration and dependencies to improve performance and compatibility --- .../pdf_combine/android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../pdf_combine/android/settings.gradle.kts | 4 ++-- .../pdfrx/example/viewer/android/.gitignore | 3 ++- .../example/viewer/android/build.gradle.kts | 5 ++++- .../example/viewer/android/gradle.properties | 3 +-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../viewer/android/settings.gradle.kts | 19 ++++++++++--------- 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/pdfrx/example/pdf_combine/android/gradle.properties b/packages/pdfrx/example/pdf_combine/android/gradle.properties index f018a618..fbee1d8c 100644 --- a/packages/pdfrx/example/pdf_combine/android/gradle.properties +++ b/packages/pdfrx/example/pdf_combine/android/gradle.properties @@ -1,3 +1,2 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties b/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties index ac3b4792..e4ef43fb 100644 --- a/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/pdfrx/example/pdf_combine/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts b/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts index fb605bc8..ca7fe065 100644 --- a/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts +++ b/packages/pdfrx/example/pdf_combine/android/settings.gradle.kts @@ -19,8 +19,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.9.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") diff --git a/packages/pdfrx/example/viewer/android/.gitignore b/packages/pdfrx/example/viewer/android/.gitignore index 6f568019..be3943c9 100644 --- a/packages/pdfrx/example/viewer/android/.gitignore +++ b/packages/pdfrx/example/viewer/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/pdfrx/example/viewer/android/build.gradle.kts b/packages/pdfrx/example/viewer/android/build.gradle.kts index 89176ef4..dbee657b 100644 --- a/packages/pdfrx/example/viewer/android/build.gradle.kts +++ b/packages/pdfrx/example/viewer/android/build.gradle.kts @@ -5,7 +5,10 @@ allprojects { } } -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { diff --git a/packages/pdfrx/example/viewer/android/gradle.properties b/packages/pdfrx/example/viewer/android/gradle.properties index 25971708..fbee1d8c 100644 --- a/packages/pdfrx/example/viewer/android/gradle.properties +++ b/packages/pdfrx/example/viewer/android/gradle.properties @@ -1,3 +1,2 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties b/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties index ac3b4792..e4ef43fb 100644 --- a/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/pdfrx/example/viewer/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/packages/pdfrx/example/viewer/android/settings.gradle.kts b/packages/pdfrx/example/viewer/android/settings.gradle.kts index 43394ed5..ca7fe065 100644 --- a/packages/pdfrx/example/viewer/android/settings.gradle.kts +++ b/packages/pdfrx/example/viewer/android/settings.gradle.kts @@ -1,11 +1,12 @@ pluginManagement { - val flutterSdkPath = run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") @@ -18,8 +19,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.9.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") From eb8a0d7be3e602e330eb17a0880c0b8634c5c62d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 6 Jan 2026 21:38:58 +0900 Subject: [PATCH 643/663] #567: Add isDirty flag to page image cache to not remove cache before re-rendering page --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 34 ++++++++++++++----- .../lib/src/pdf_document_event.dart | 2 ++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 024a2627..61f7205a 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -242,6 +242,7 @@ class _PdfViewerState extends State // Changes to the stream rebuilds the viewer final _updateStream = BehaviorSubject(); + /// page-number keyed cache of extracted text final _textCache = {}; Timer? _textSelectionChangedDebounceTimer; final double _hitTestMargin = 3.0; @@ -409,12 +410,11 @@ class _PdfViewerState extends State if (event is PdfDocumentPageStatusChangedEvent) { // FIXME: We can handle the event more efficiently by only updating the affected pages. for (final change in event.changes.entries) { - _imageCache.removeCacheImagesForPage(change.key); - _magnifierImageCache.removeCacheImagesForPage(change.key); + _imageCache.makeCacheImageForPageDirty(change.key); + _magnifierImageCache.makeCacheImageForPageDirty(change.key); + _canvasLinkPainter.releaseLinksForPage(change.key); + _textCache.remove(change.key); } - // very conservative approach: just clear all caches; we can optimize this later - _canvasLinkPainter.resetAll(); - _textCache.clear(); _clearTextSelections(invalidate: false); _invalidate(); } @@ -1320,7 +1320,8 @@ class _PdfViewerState extends State ); } - if (enableLowResolutionPagePreview && (previewImage == null || previewImage.scale != previewScaleLimit)) { + if (enableLowResolutionPagePreview && + (previewImage == null || previewImage.isDirty || previewImage.scale != previewScaleLimit)) { _requestPagePreviewImageCached(cache, page, previewScaleLimit); } @@ -1476,13 +1477,15 @@ class _PdfViewerState extends State double scale, ) async { if (!mounted) return; - if (cache.pageImages[page.pageNumber]?.scale == scale) return; + final prev = cache.pageImages[page.pageNumber]; + if (prev != null && !prev.isDirty && prev.scale == scale) return; final cancellationToken = page.createCancellationToken(); cache.addCancellationToken(page.pageNumber, cancellationToken); await cache.synchronized(() async { if (!mounted || cancellationToken.isCanceled) return; - if (cache.pageImages[page.pageNumber]?.scale == scale) return; + final prev = cache.pageImages[page.pageNumber]; + if (prev != null && !prev.isDirty && prev.scale == scale) return; PdfImage? img; try { img = await page.render( @@ -1516,7 +1519,7 @@ class _PdfViewerState extends State final pageRect = _layout!.pageLayouts[page.pageNumber - 1]; final rect = pageRect.intersect(targetRect); final prev = cache.pageImagesPartial[page.pageNumber]; - if (prev?.rect == rect && prev?.scale == scale) return; + if (prev != null && !prev.isDirty && prev.rect == rect && prev.scale == scale) return; if (rect.width < 1 || rect.height < 1) return; cache.pageImagePartialRenderingRequests[page.pageNumber]?.cancel(); @@ -3339,6 +3342,17 @@ class _PdfPageImageCache { cancellationTokens.clear(); } + void makeCacheImageForPageDirty(int pageNumber) { + final image = pageImages[pageNumber]; + if (image != null) { + image.isDirty = true; + } + final imagePartial = pageImagesPartial[pageNumber]; + if (imagePartial != null) { + imagePartial.isDirty = true; + } + } + void removeCacheImagesForPage(int pageNumber) { final removed = pageImages.remove(pageNumber); if (removed != null) { @@ -3395,6 +3409,8 @@ class _PdfImageWithScale { int get width => image.width; int get height => image.height; + bool isDirty = false; + void dispose() { image.dispose(); } diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart index 08885d90..2a914e95 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document_event.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -31,6 +31,8 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { /// The pages that have changed. /// /// The map is from page number (1-based) to it's status change. + /// + /// You can assume that the keys in this map are sorted in ascending order. final Map changes; } From 4ab73d4f60746b1af9e599b8ea8900e7d4f4f594 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 7 Jan 2026 01:06:14 +0900 Subject: [PATCH 644/663] pdfium_flutter 0.1.8/pdfrx_engine 0.3.7/pdfrx_coregraphics 0.1.11/pdfrx 2.2.19 --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx/CHANGELOG.md | 8 ++++++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 6 +++--- packages/pdfrx_coregraphics/CHANGELOG.md | 6 ++++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 6 ++++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 10 files changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index e6c1b19b..a414763a 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.8 + +- Dependency configuration updates. + ## 0.1.7 - Documentation updates. diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 800f0909..9d4f404b 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.7 +version: 0.1.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index cd9e6a5e..bf7c91d1 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.2.19 + +- Updated to pdfrx_engine 0.3.7 and pdfium_flutter 0.1.8 +- IMPROVED: Add `isDirty` flag to page image cache to prevent cache removal before re-rendering page ([#567](https://github.com/espresso3389/pdfrx/issues/567)) +- FIXED: `round10BitFrac` should not process `Infinity` or `NaN` ([#550](https://github.com/espresso3389/pdfrx/issues/550)) +- Updated Gradle wrapper to version 8.12 and Android plugin to version 8.9.1 +- WIP: Adding `PdfDocument.useNativeDocumentHandle`/`reloadPages` + # 2.2.18 - FIXED: Dependency conflicts with `dart_pubspec_licenses` causing version resolution failures ([#563](https://github.com/espresso3389/pdfrx/issues/563), [#570](https://github.com/espresso3389/pdfrx/issues/570)) diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 64d04823..627dad13 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.17 + pdfrx: ^2.2.19 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 56a2fbf5..93b4dc4d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.18 +version: 2.2.19 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,8 +14,8 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.6 - pdfium_flutter: ^0.1.1 + pdfrx_engine: ^0.3.7 + pdfium_flutter: ^0.1.8 collection: crypto: ^3.0.6 ffi: diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index bfdd59ef..d2674cea 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.11 + +- Updated to pdfrx_engine 0.3.7 +- Dependency configuration updates +- WIP: Adding `PdfDocument.useNativeDocumentHandle`/`reloadPages` + ## 0.1.10 - Updated to pdfrx_engine 0.3.6 diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index d2086cdb..4a8be14c 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.17 - pdfrx_coregraphics: ^0.1.10 + pdfrx: ^2.2.19 + pdfrx_coregraphics: ^0.1.11 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 6107079c..8bb61086 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.10 +version: 0.1.11 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.3.6 + pdfrx_engine: ^0.3.7 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 570c779a..5a93a193 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.3.7 + +- IMPROVED: Add `isDirty` flag to page image cache to prevent cache removal before re-rendering page ([#567](https://github.com/espresso3389/pdfrx/issues/567)) +- FIXED: `round10BitFrac` should not process `Infinity` or `NaN` ([#550](https://github.com/espresso3389/pdfrx/issues/550)) +- WIP: Adding `PdfDocument.useNativeDocumentHandle`/`reloadPages` + ## 0.3.6 - NEW: [`PdfDateTime`](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDateTime.html) extension type for PDF date string parsing ([PDF 32000-1:2008, 7.9.4 Dates](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#page=95)) diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 2f1d6f4c..167409ef 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.6 +version: 0.3.7 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From f90c8d095ebe1ab579ba117a4ac1b1373e691402 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 7 Jan 2026 01:12:27 +0900 Subject: [PATCH 645/663] Re-organize AGENTS.md --- AGENTS.md | 225 ++------------------------------ doc/agents/CODE-STYLE.md | 50 +++++++ doc/agents/COMMANDS.md | 135 +++++++++++++++++++ doc/agents/PROJECT-STRUCTURE.md | 92 +++++++++++++ doc/agents/RELEASING.md | 69 ++++++++++ 5 files changed, 360 insertions(+), 211 deletions(-) create mode 100644 doc/agents/CODE-STYLE.md create mode 100644 doc/agents/COMMANDS.md create mode 100644 doc/agents/PROJECT-STRUCTURE.md create mode 100644 doc/agents/RELEASING.md diff --git a/AGENTS.md b/AGENTS.md index c5c14f54..3880e83d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,220 +9,23 @@ This file provides guidance to AI agents and developers when working with code i - Leave release artifacts (`CHANGELOG.md`, version numbers, tags) untouched unless the task is explicitly about publishing. - Default to ASCII output and add only brief clarifying comments when the code is non-obvious. -## Project Overview +## Documentation Index -pdfrx is a monorepo containing five packages: - -1. **pdfium_dart** (`packages/pdfium_dart/`) - Low-level Dart FFI bindings for PDFium - - Pure Dart package with auto-generated FFI bindings using `ffigen` - - Provides direct access to PDFium's C API - - Includes `getPdfium()` function for on-demand PDFium binary downloads - - Used as a foundation by higher-level packages - -2. **pdfium_flutter** (`packages/pdfium_flutter/`) - Flutter FFI plugin for loading PDFium native libraries - - Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux) - - Provides utilities for loading PDFium at runtime - - Re-exports `pdfium_dart` FFI bindings - -3. **pdfrx_engine** (`packages/pdfrx_engine/`) - A platform-agnostic PDF rendering API built on top of PDFium - - Pure Dart package with no Flutter dependencies - - Depends on `pdfium_dart` for PDFium bindings - - Provides core PDF document API - - Can be used independently for non-Flutter Dart applications - -4. **pdfrx** (`packages/pdfrx/`) - A cross-platform PDF viewer plugin for Flutter - - Depends on pdfrx_engine for PDF rendering functionality - - Depends on pdfium_flutter for bundled PDFium binaries - - Provides Flutter widgets and UI components - - Supports iOS, Android, Windows, macOS, Linux, and Web - - Uses PDFium for native platforms and PDFium WASM for web platforms - -5. **pdfrx_coregraphics** (`packages/pdfrx_coregraphics/`) - CoreGraphics-backed renderer for iOS/macOS - - Experimental package using PDFKit/CoreGraphics instead of PDFium - - Drop-in replacement for Apple platforms - - iOS and macOS only - -## Command and Tooling Expectations - -- Run commands directly in the repository environment with the correct `workdir`; coordinate with the user before escalating privileges or leaving the workspace. -- Prefer `rg`/`rg --files` for search and discovery tasks; they are significantly faster than alternatives. -- Use Flutter/Dart tooling for formatting (`dart format`, `flutter format`) and keep the 120 character width. -- On Windows, use `pwsh.exe -Command ...` if a command fails due to script launching quirks. - -### Pub Workspace Basics - -This project uses a pub workspace. Running `dart pub get` in any directory inside the repository fetches dependencies for all packages. - -### Common Commands - -```bash -# Flutter plugin (packages/pdfrx) -cd packages/pdfrx -flutter pub get -flutter analyze -flutter test -flutter format . - -# Core engine (packages/pdfrx_engine) -cd packages/pdfrx_engine -dart pub get -dart analyze -dart test -dart format . -``` - -### Platform Builds - -```bash -cd packages/pdfrx/example/viewer -flutter run -flutter build appbundle -flutter build ios -flutter build web --wasm -flutter build linux -flutter build windows -flutter build macos -``` - -### FFI Bindings - -FFI bindings for PDFium are maintained in the `pdfium_dart` package and generated using `ffigen`. - -#### Prerequisites - -The `ffigen` process requires the following prerequisites to be installed: - -- **LLVM/Clang**: Required for parsing C headers - - macOS: Install via Homebrew: `brew install llvm` - - Linux: Install via package manager: `apt-get install libclang-dev` (Ubuntu/Debian) or `dnf install clang-devel` (Fedora) - - Windows: Install LLVM from [llvm.org](https://releases.llvm.org/) - -#### Generating Bindings - -```bash -# For pdfium_dart package -cd packages/pdfium_dart -dart test # Downloads PDFium headers automatically -dart run ffigen - -# For pdfrx_engine (if needed) -cd packages/pdfrx_engine -dart test -dart run ffigen -``` - -#### On-Demand PDFium Downloads - -The `pdfium_dart` package provides a `getPdfium()` function that downloads PDFium binaries on demand. This is useful for testing or CLI applications that don't want to bundle PDFium binaries. - -## Release Process - -See `RELEASING.md` for the full checklist. Agents should avoid editing release metadata unless the task explicitly covers publishing. - -- Never bump versions or changelog entries preemptively. -- Surface blockers or uncertainties to the user before continuing a release flow. - -## Architecture Overview - -For architectural details and API surface breakdowns, refer to: +Detailed guidance is split into focused files in `doc/agents/`: -- `README.md` for a high-level overview of both packages. -- `packages/pdfrx_engine/README.md` for engine internals and FFI notes. -- `packages/pdfrx/README.md` for Flutter plugin structure, widgets, and overlays. +- [doc/agents/PROJECT-STRUCTURE.md](doc/agents/PROJECT-STRUCTURE.md) - Package overview, dependencies, and architecture +- [doc/agents/COMMANDS.md](doc/agents/COMMANDS.md) - Common commands, testing, and platform builds +- [doc/agents/RELEASING.md](doc/agents/RELEASING.md) - Release process and publishing checklist +- [doc/agents/CODE-STYLE.md](doc/agents/CODE-STYLE.md) - Code style, documentation, and dependency policies -These documents live alongside the code and stay in sync with implementation changes. +## Project Overview (Summary) -## Testing - -Tests download PDFium binaries automatically for supported platforms. Run tests with: - -```bash -# Test pdfrx_engine -cd packages/pdfrx_engine -dart test - -# Test pdfrx Flutter plugin -cd packages/pdfrx -flutter test -``` - -## Platform-Specific Notes - -### iOS/macOS - -- Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) -- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` -- Binaries downloaded during pod install (Or you can use Swift Package Manager if you like) - -### Android - -- Uses CMake for native build -- Requires Android NDK -- Downloads PDFium binaries during build - -### Web - -- `packages/pdfrx/assets/pdfium.wasm` is prebuilt PDFium WASM binary -- `packages/pdfrx/assets/pdfium_worker.js` is the worker script that contains Pdfium WASM's shim -- `packages/pdfrx/assets/pdfium_client.js` is the code that launches the worker and provides the API, which is used by pdfrx_engine's web implementation - -### Windows/Linux - -- CMake-based build -- Downloads PDFium binaries during build - -## Code Style - -- Single quotes for strings -- 120 character line width -- Relative imports within lib/ -- Follow flutter_lints with custom rules in analysis_options.yaml - -## Dependency Version Policy - -### pdfrx_engine - -This package follows standard Dart package versioning practices. - -### pdfrx - -This package intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This design decision allows: - -- Flutter SDK to manage these dependencies based on the user's Flutter version -- Broader compatibility across different Flutter stable versions -- Avoiding version conflicts for users on older Flutter stable releases - -When running `flutter pub publish`, warnings about missing version constraints for these packages can be safely ignored. Only packages that are not managed by Flutter SDK should have explicit version constraints. - -## Documentation Guidelines - -The following guidelines should be followed when writing documentation including comments, `README.md`, and other markdown files: - -- Use proper grammar and spelling -- Use clear and concise language -- Use consistent terminology -- Use proper headings for sections -- Use code blocks for code snippets -- Use bullet points for lists -- Use link to relevant issues/PRs when applicable -- Use backticks (`` ` ``) for code references and file/directory/path names in documentation - -### Commenting Guidelines - -- Use reference links for classes, enums, and functions in documentation -- Use `///` (dartdoc comments) for public API comments (and even for important private APIs) - -### Markdown Documentation Guidelines - -- Include links to issues/PRs when relevant; `#NNN` -> `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` -- Use link to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs if possible -- `README.md` should provide an overview of the project, how to use it, and any important notes -- `CHANGELOG.md` should follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles - - Be careful not to include implementation details in the changelog - - Focus on user-facing changes, new features, bug fixes, and breaking changes - - Use sections for different versions - - Use bullet points for changes +pdfrx is a monorepo containing five packages: -## Special Notes +1. **pdfium_dart** - Low-level Dart FFI bindings for PDFium +2. **pdfium_flutter** - Flutter FFI plugin bundling PDFium binaries +3. **pdfrx_engine** - Platform-agnostic PDF rendering API (pure Dart) +4. **pdfrx** - Cross-platform PDF viewer Flutter plugin +5. **pdfrx_coregraphics** - CoreGraphics-backed renderer for iOS/macOS (experimental) -- `CHANGELOG.md` is not an implementation node. So it should be updated only on releasing a new version +See [doc/agents/PROJECT-STRUCTURE.md](doc/agents/PROJECT-STRUCTURE.md) for detailed package descriptions. diff --git a/doc/agents/CODE-STYLE.md b/doc/agents/CODE-STYLE.md new file mode 100644 index 00000000..ef380c0f --- /dev/null +++ b/doc/agents/CODE-STYLE.md @@ -0,0 +1,50 @@ +# Code Style and Documentation + +## Code Style + +- Single quotes for strings +- 120 character line width +- Relative imports within `lib/` +- Follow flutter_lints with custom rules in `analysis_options.yaml` + +## Formatting + +```bash +dart format . +# or +flutter format . +``` + +## Documentation Guidelines + +### General Principles + +- Use proper grammar and spelling +- Use clear and concise language +- Use consistent terminology +- Use proper headings for sections +- Use code blocks for code snippets +- Use bullet points for lists +- Use backticks (`` ` ``) for code references and file/directory/path names + +### Dart Comments + +- Use `///` (dartdoc comments) for public API comments +- Use reference links for classes, enums, and functions in documentation +- Even important private APIs should have dartdoc comments + +### Markdown Files + +- Include links to issues/PRs when relevant + - Format: `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` +- Use links to [API reference](https://pub.dev/documentation/pdfrx/latest/pdfrx/) for public APIs +- `README.md` should provide an overview of the project, how to use it, and important notes + +### Changelog + +- Follow [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles +- Focus on user-facing changes, new features, bug fixes, and breaking changes +- Do NOT include implementation details +- Use sections for different versions +- Use bullet points for changes +- Update only when releasing a new version diff --git a/doc/agents/COMMANDS.md b/doc/agents/COMMANDS.md new file mode 100644 index 00000000..4521fb82 --- /dev/null +++ b/doc/agents/COMMANDS.md @@ -0,0 +1,135 @@ +# Commands Reference + +## Environment Notes + +- This project uses a **pub workspace**. Running `dart pub get` in any directory fetches dependencies for all packages. +- Prefer `rg`/`rg --files` for search and discovery tasks; they are significantly faster than alternatives. + +## Windows-Specific Notes (Claude Code) + +When running on Windows, Claude Code's Bash tool runs in a POSIX-like shell environment. Be aware of these issues: + +### Path Handling + +- **Use forward slashes** or properly escaped backslashes in paths +- **Windows paths like `d:\pdfrx`** may not work directly; wrap commands with `pwsh.exe -Command "..."` +- When using `cd`, the path may fail silently; prefer running commands with full paths or use PowerShell + +### Command Execution + +```bash +# WRONG - may fail with path issues +cd d:\pdfrx\packages\pdfrx && flutter pub get + +# CORRECT - use PowerShell wrapper +pwsh.exe -Command "cd 'd:\pdfrx\packages\pdfrx'; flutter pub get" +``` + +### Git Commands + +```bash +# WRONG - cd may not work as expected +cd d:\pdfrx && git status + +# CORRECT - use -C flag for git +git -C "d:\pdfrx" status +git -C "d:\pdfrx" log --oneline -10 + +# Or use PowerShell +pwsh.exe -Command "cd 'd:\pdfrx'; git status" +``` + +### Publishing Packages + +```bash +# Use PowerShell for pub publish +pwsh.exe -Command "cd 'd:\pdfrx\packages\pdfrx'; flutter pub publish --force" +pwsh.exe -Command "cd 'd:\pdfrx\packages\pdfrx_engine'; dart pub publish --force" +``` + +### GitHub CLI (gh) + +```bash +# gh commands work directly but use proper quoting +gh issue comment 123 --repo espresso3389/pdfrx --body "Comment text here" +``` + +## Common Commands + +### Flutter Plugin (packages/pdfrx) + +```bash +cd packages/pdfrx +flutter pub get +flutter analyze +flutter test +flutter format . +``` + +### Core Engine (packages/pdfrx_engine) + +```bash +cd packages/pdfrx_engine +dart pub get +dart analyze +dart test +dart format . +``` + +## Platform Builds + +```bash +cd packages/pdfrx/example/viewer +flutter run +flutter build appbundle # Android +flutter build ios # iOS +flutter build web --wasm # Web +flutter build linux # Linux +flutter build windows # Windows +flutter build macos # macOS +``` + +## FFI Bindings + +FFI bindings for PDFium are maintained in the `pdfium_dart` package and generated using `ffigen`. + +### Prerequisites + +The `ffigen` process requires LLVM/Clang: + +- **macOS**: `brew install llvm` +- **Linux (Ubuntu/Debian)**: `apt-get install libclang-dev` +- **Linux (Fedora)**: `dnf install clang-devel` +- **Windows**: Install LLVM from [llvm.org](https://releases.llvm.org/) + +### Generating Bindings + +```bash +# For pdfium_dart package +cd packages/pdfium_dart +dart test # Downloads PDFium headers automatically +dart run ffigen + +# For pdfrx_engine (if needed) +cd packages/pdfrx_engine +dart test +dart run ffigen +``` + +### On-Demand PDFium Downloads + +The `pdfium_dart` package provides a `getPdfium()` function that downloads PDFium binaries on demand. Useful for testing or CLI applications. + +## Testing + +Tests download PDFium binaries automatically for supported platforms. + +```bash +# Test pdfrx_engine +cd packages/pdfrx_engine +dart test + +# Test pdfrx Flutter plugin +cd packages/pdfrx +flutter test +``` diff --git a/doc/agents/PROJECT-STRUCTURE.md b/doc/agents/PROJECT-STRUCTURE.md new file mode 100644 index 00000000..00b7202f --- /dev/null +++ b/doc/agents/PROJECT-STRUCTURE.md @@ -0,0 +1,92 @@ +# Project Structure + +pdfrx is a monorepo containing five packages with the following dependency hierarchy: + +``` +pdfium_dart (FFI bindings) + ↓ +pdfium_flutter (bundles PDFium binaries) + ↓ +pdfrx_engine (PDF API, pure Dart) + ↓ +pdfrx (Flutter widgets) + ↑ +pdfrx_coregraphics (alternative backend for Apple platforms) +``` + +## Packages + +### pdfium_dart (`packages/pdfium_dart/`) + +Low-level Dart FFI bindings for PDFium. + +- Pure Dart package with auto-generated FFI bindings using `ffigen` +- Provides direct access to PDFium's C API +- Includes `getPdfium()` function for on-demand PDFium binary downloads +- Used as a foundation by higher-level packages + +### pdfium_flutter (`packages/pdfium_flutter/`) + +Flutter FFI plugin for loading PDFium native libraries. + +- Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux) +- Provides utilities for loading PDFium at runtime +- Re-exports `pdfium_dart` FFI bindings + +### pdfrx_engine (`packages/pdfrx_engine/`) + +Platform-agnostic PDF rendering API built on top of PDFium. + +- Pure Dart package with no Flutter dependencies +- Depends on `pdfium_dart` for PDFium bindings +- Provides core PDF document API +- Can be used independently for non-Flutter Dart applications + +### pdfrx (`packages/pdfrx/`) + +Cross-platform PDF viewer plugin for Flutter. + +- Depends on pdfrx_engine for PDF rendering functionality +- Depends on pdfium_flutter for bundled PDFium binaries +- Provides Flutter widgets and UI components +- Supports iOS, Android, Windows, macOS, Linux, and Web +- Uses PDFium for native platforms and PDFium WASM for web platforms + +### pdfrx_coregraphics (`packages/pdfrx_coregraphics/`) + +CoreGraphics-backed renderer for iOS/macOS. + +- Experimental package using PDFKit/CoreGraphics instead of PDFium +- Drop-in replacement for Apple platforms +- iOS and macOS only + +## Platform-Specific Notes + +### iOS/macOS + +- Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) +- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` +- Binaries downloaded during pod install (or use Swift Package Manager) + +### Android + +- Uses CMake for native build +- Requires Android NDK +- Downloads PDFium binaries during build + +### Web + +- `packages/pdfrx/assets/pdfium.wasm` - prebuilt PDFium WASM binary +- `packages/pdfrx/assets/pdfium_worker.js` - worker script with PDFium WASM shim +- `packages/pdfrx/assets/pdfium_client.js` - API for pdfrx_engine's web implementation + +### Windows/Linux + +- CMake-based build +- Downloads PDFium binaries during build + +## Architecture Resources + +- `README.md` - High-level overview +- `packages/pdfrx_engine/README.md` - Engine internals and FFI notes +- `packages/pdfrx/README.md` - Flutter plugin structure, widgets, and overlays diff --git a/doc/agents/RELEASING.md b/doc/agents/RELEASING.md new file mode 100644 index 00000000..3648499d --- /dev/null +++ b/doc/agents/RELEASING.md @@ -0,0 +1,69 @@ +# Release Process + +## Important Rules for Agents + +- **Never bump versions or changelog entries preemptively** - only when explicitly releasing +- Surface blockers or uncertainties to the user before continuing a release flow +- `CHANGELOG.md` should be updated only when releasing a new version + +## Release Order + +Packages must be published in dependency order: + +1. **pdfium_dart** (if changed) +2. **pdfium_flutter** (depends on pdfium_dart) +3. **pdfrx_engine** (depends on pdfium_dart) +4. **pdfrx_coregraphics** (depends on pdfrx_engine) +5. **pdfrx** (depends on pdfrx_engine, pdfium_flutter) + +## Pre-Release Checklist + +For each package being released: + +1. Update `CHANGELOG.md` with user-facing changes +2. Update version in `pubspec.yaml` +3. Update dependency versions in dependent packages +4. Update version references in `README.md` examples +5. Run dry-run: `dart pub publish --dry-run` or `flutter pub publish --dry-run` + +## Publishing + +```bash +# For Dart packages +cd packages/ +dart pub publish --force + +# For Flutter packages +cd packages/ +flutter pub publish --force +``` + +## Post-Release + +- Notify relevant GitHub issues about the fix/feature being released +- Comment format: "This has been addressed in . " + +## Changelog Guidelines + +Follow [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) principles: + +- Focus on user-facing changes, new features, bug fixes, and breaking changes +- Do NOT include implementation details +- Link to issues/PRs: `[#NNN](https://github.com/espresso3389/pdfrx/issues/NNN)` +- Use bullet points for changes + +## Dependency Version Policy + +### pdfrx_engine + +Follows standard Dart package versioning practices. + +### pdfrx + +Intentionally does NOT specify version constraints for core Flutter-managed packages (collection, ffi, http, path, rxdart). This allows: + +- Flutter SDK to manage dependencies based on user's Flutter version +- Broader compatibility across different Flutter stable versions +- Avoiding version conflicts for users on older Flutter stable releases + +Warnings about missing version constraints during `flutter pub publish` can be safely ignored. From a6ba8371fd78edd3aabe07dbc3ef878f07b8b27e Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Wed, 7 Jan 2026 01:16:29 +0900 Subject: [PATCH 646/663] Add note for tagging commits --- doc/agents/RELEASING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/agents/RELEASING.md b/doc/agents/RELEASING.md index 3648499d..e0143460 100644 --- a/doc/agents/RELEASING.md +++ b/doc/agents/RELEASING.md @@ -40,6 +40,34 @@ flutter pub publish --force ## Post-Release +### Git Tagging + +After publishing, create git tags for each released package and push them: + +```bash +# Tag format: -v +git tag pdfium_dart-v0.1.3 +git tag pdfium_flutter-v0.1.8 +git tag pdfrx_engine-v0.3.7 +git tag pdfrx-v2.2.19 +git tag pdfrx_coregraphics-v0.1.11 + +# Push all tags +git push --tags +``` + +### Commit Changes + +Before tagging, commit all release changes: + +```bash +git add -A +git commit -m "Release pdfrx 2.2.19, pdfrx_engine 0.3.7, etc." +git push +``` + +### Notify Issues + - Notify relevant GitHub issues about the fix/feature being released - Comment format: "This has been addressed in . " From 7a7e2996b07d9aef7e7dc0cdfb456e9a80071e0f Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 8 Jan 2026 06:49:35 +0900 Subject: [PATCH 647/663] FIXED: #553 --- packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx/example/pdf_combine/pubspec.yaml | 1 - packages/pdfrx/example/viewer/pubspec.yaml | 3 +-- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index 9d4f404b..ebad238c 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -7,7 +7,7 @@ issue_tracker: https://github.com/espresso3389/pdfrx/issues environment: sdk: '>=3.9.0 <4.0.0' - flutter: '>=3.29.0' + flutter: '>=3.35.1' resolution: workspace dependencies: diff --git a/packages/pdfrx/example/pdf_combine/pubspec.yaml b/packages/pdfrx/example/pdf_combine/pubspec.yaml index c602999e..3a12b8a9 100644 --- a/packages/pdfrx/example/pdf_combine/pubspec.yaml +++ b/packages/pdfrx/example/pdf_combine/pubspec.yaml @@ -5,7 +5,6 @@ version: 1.0.0+1 environment: sdk: ^3.9.2 - resolution: workspace dependencies: diff --git a/packages/pdfrx/example/viewer/pubspec.yaml b/packages/pdfrx/example/viewer/pubspec.yaml index 5614e00c..0b63a154 100644 --- a/packages/pdfrx/example/viewer/pubspec.yaml +++ b/packages/pdfrx/example/viewer/pubspec.yaml @@ -3,8 +3,7 @@ description: "Demonstrates how to use the pdfrx plugin." publish_to: 'none' environment: - sdk: '>=3.9.0 <4.0.0' - flutter: ">=3.29.0" + sdk: ^3.9.2 resolution: workspace dependencies: diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 93b4dc4d..89671d70 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -10,7 +10,7 @@ screenshots: environment: sdk: '>=3.9.0 <4.0.0' - flutter: ">=3.29.0" + flutter: '>=3.35.1' resolution: workspace dependencies: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 8bb61086..4d6e787a 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -7,7 +7,7 @@ issue_tracker: https://github.com/espresso3389/pdfrx/issues environment: sdk: '>=3.9.0 <4.0.0' - flutter: '>=3.29.0' + flutter: '>=3.35.1' resolution: workspace dependencies: From eb9b1d787cf645fc3b58dffc1d1c60fe92fa31e4 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Thu, 8 Jan 2026 07:05:50 +0900 Subject: [PATCH 648/663] pdfium_flutter 0.1.9/pdfrx 2.2.20/pdfrx_coregraphics 0.1.12 Co-Authored-By: Claude Opus 4.5 --- packages/pdfium_flutter/CHANGELOG.md | 4 ++++ packages/pdfium_flutter/pubspec.yaml | 2 +- packages/pdfrx/CHANGELOG.md | 4 ++++ packages/pdfrx/README.md | 2 +- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/CHANGELOG.md | 4 ++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- 8 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/pdfium_flutter/CHANGELOG.md b/packages/pdfium_flutter/CHANGELOG.md index a414763a..ca0a8cf1 100644 --- a/packages/pdfium_flutter/CHANGELOG.md +++ b/packages/pdfium_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.9 + +- FIXED: Inconsistent environment constraints - Flutter version now correctly requires 3.35.1+ to match Dart 3.9.0 requirement ([#553](https://github.com/espresso3389/pdfrx/issues/553)) + ## 0.1.8 - Dependency configuration updates. diff --git a/packages/pdfium_flutter/pubspec.yaml b/packages/pdfium_flutter/pubspec.yaml index ebad238c..80edad41 100644 --- a/packages/pdfium_flutter/pubspec.yaml +++ b/packages/pdfium_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfium_flutter description: Flutter FFI plugin for loading PDFium native libraries. Bundles PDFium binaries for Android, iOS, Windows, macOS, and Linux. -version: 0.1.8 +version: 0.1.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfium_flutter issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index bf7c91d1..991dccf6 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.20 + +- FIXED: Inconsistent environment constraints - Flutter version now correctly requires 3.35.1+ to match Dart 3.9.0 requirement ([#553](https://github.com/espresso3389/pdfrx/issues/553)) + # 2.2.19 - Updated to pdfrx_engine 0.3.7 and pdfium_flutter 0.1.8 diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 627dad13..625ca05f 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.19 + pdfrx: ^2.2.20 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 89671d70..45491a9d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.19 +version: 2.2.20 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index d2674cea..c02199da 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.12 + +- FIXED: Inconsistent environment constraints - Flutter version now correctly requires 3.35.1+ to match Dart 3.9.0 requirement ([#553](https://github.com/espresso3389/pdfrx/issues/553)) + ## 0.1.11 - Updated to pdfrx_engine 0.3.7 diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 4a8be14c..caab4110 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.19 - pdfrx_coregraphics: ^0.1.11 + pdfrx: ^2.2.20 + pdfrx_coregraphics: ^0.1.12 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 4d6e787a..dbbd6022 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.11 +version: 0.1.12 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues From 1322d1ee9f0822d9b8404760156f927cdd7a9a3b Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 9 Jan 2026 01:31:50 +0900 Subject: [PATCH 649/663] Introduces experimental PdfrxEntryFunctions.compute for #551 --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 5 +++++ .../pdfrx_coregraphics/lib/pdfrx_coregraphics.dart | 10 ++++++++++ packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 8 ++++++++ packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart | 5 +++++ .../pdfrx_engine/lib/src/pdfrx_entry_functions.dart | 9 +++++++++ 5 files changed, 37 insertions(+) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index faf90b82..18fe14b6 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -112,6 +112,11 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { return await action(); } + @override + Future compute(FutureOr Function(M message) callback, M message) async { + throw UnimplementedError('compute() is not implemented for WASM backend.'); + } + static String? _pdfiumWasmModulesUrlFromMetaTag() { final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; return meta?.content; diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index 1744a541..ef016e88 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -37,6 +37,16 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { return await Future.sync(action); } + @override + Future compute( + FutureOr Function(M message) callback, + M message, + ) async { + throw UnimplementedError( + 'compute() is not implemented for CoreGraphics backend.', + ); + } + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 68e8558d..4604213a 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -26,6 +26,14 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { return await action(); } + @override + Future compute(FutureOr Function(M message) callback, M message) async { + throw UnimplementedError( + 'compute() is not implemented because PdfrxEntryFunctions.instance is not initialized. ' + 'Please call pdfrxInitialize()/pdfrxFlutterInitialize() or explicitly set PdfrxEntryFunctions.instance.', + ); + } + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 3fab2e78..d346eda9 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -199,6 +199,11 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { return await (await BackgroundWorker.instance).suspendDuringAction(action); } + @override + Future compute(FutureOr Function(M message) callback, M message) async { + return await (await BackgroundWorker.instance).compute(callback, message); + } + @override Future openAsset( String name, { diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 5f69138e..9a004d11 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -29,6 +29,15 @@ abstract class PdfrxEntryFunctions { /// To avoid such problems, you can wrap the code that calls those libraries with this function. Future suspendPdfiumWorkerDuringAction(FutureOr Function() action); + /// Perform a computation in the background worker isolate. + /// + /// The [callback] function is executed in the background isolate with [message] as its argument. + /// The result of the [callback] function is returned as a [Future]. + /// + /// The background worker isolate is same to the one used by pdfrx internally to call PDFium + /// functions. + Future compute(FutureOr Function(M message) callback, M message); + /// See [PdfDocument.openAsset]. Future openAsset( String name, { From ccf33f1198f97d92f0843101bb630935488c9337 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 05:22:58 +0900 Subject: [PATCH 650/663] Introduces PdfViewerParams.onDocumentLoadFinished (Related: #586) --- packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 34 ++++++++++++++++--- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 25 +++++++++++++- .../lib/src/widgets/pdf_viewer_params.dart | 30 ++++++++++++++++ .../lib/pdfrx_coregraphics.dart | 12 +++++-- .../lib/src/native/pdfrx_pdfium.dart | 13 ++++++- .../lib/src/pdf_document_event.dart | 18 +++++++++- 6 files changed, 121 insertions(+), 11 deletions(-) diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 18fe14b6..120ff07c 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -198,6 +198,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { sourceName: sourceName ?? _sourceNameFromData(data), passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, onDispose: onDispose, ); @@ -222,6 +223,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { sourceName: 'file%$filePath', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, onDispose: null, ); @@ -264,6 +266,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { sourceName: 'uri%$uri', passwordProvider: passwordProvider, firstAttemptByEmptyPassword: firstAttemptByEmptyPassword, + useProgressiveLoading: useProgressiveLoading, onDispose: cleanupCallbacks, ); } catch (e) { @@ -277,6 +280,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { required String sourceName, required PdfPasswordProvider? passwordProvider, required bool firstAttemptByEmptyPassword, + required bool useProgressiveLoading, required void Function()? onDispose, }) async { await init(); @@ -303,7 +307,12 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { throw StateError('Failed to open document: ${result['errorCodeStr']} ($errorCode)'); } - return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: onDispose); + return _PdfDocumentWasm._( + result, + sourceName: sourceName, + disposeCallback: onDispose, + useProgressiveLoading: useProgressiveLoading, + ); } } @@ -315,7 +324,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { if (errorCode != null) { throw StateError('Failed to create new document: ${result['errorCodeStr']} ($errorCode)'); } - return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null, useProgressiveLoading: false); } @override @@ -336,7 +345,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { if (errorCode != null) { throw StateError('Failed to create document from JPEG data: ${result['errorCodeStr']} ($errorCode)'); } - return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null); + return _PdfDocumentWasm._(result, sourceName: sourceName, disposeCallback: null, useProgressiveLoading: false); } @override @@ -363,10 +372,21 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { } class _PdfDocumentWasm extends PdfDocument { - _PdfDocumentWasm._(this.document, {required super.sourceName, this.disposeCallback}) - : permissions = parsePermissions(document) { + _PdfDocumentWasm._( + this.document, { + required super.sourceName, + required bool useProgressiveLoading, + this.disposeCallback, + }) : permissions = parsePermissions(document) { _pages = parsePages(this, document['pages'] as List); updateMissingFonts(document['missingFonts']); + if (!useProgressiveLoading) { + _notifyDocumentLoadComplete(); + } + } + + void _notifyDocumentLoadComplete() { + subject.add(PdfDocumentLoadCompleteEvent(this)); } final Map document; @@ -451,6 +471,10 @@ class _PdfDocumentWasm extends PdfDocument { } } } + // All pages loaded + if (firstPageIndex >= pages.length) { + _notifyDocumentLoadComplete(); + } }); } diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 61f7205a..41188355 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -337,13 +337,17 @@ class _PdfViewerState extends State _txController.removeListener(_onMatrixChanged); _controller?._attach(null); - final document = widget.documentRef.resolveListenable().document; + final listenable = widget.documentRef.resolveListenable(); + final document = listenable.document; if (document == null) { _document = null; if (mounted) { setState(() {}); } _notifyOnDocumentChanged(); + if (listenable.error != null) { + _notifyDocumentLoadFinished(succeeded: false); + } return; } @@ -417,9 +421,28 @@ class _PdfViewerState extends State } _clearTextSelections(invalidate: false); _invalidate(); + } else if (event is PdfDocumentLoadCompleteEvent) { + _notifyDocumentLoadFinished(succeeded: true); } } + Future _notifyDocumentLoadFinished({required bool succeeded}) async { + final listenable = widget.documentRef.resolveListenable(); + if (succeeded) { + // FIXME: This is a temporary workaround to wait until the initial page is loaded. + while (mounted) { + if (_imageCache.pageImages.containsKey(widget.initialPageNumber)) break; + await Future.delayed(const Duration(milliseconds: 100)); + } + } + + Future.microtask(() async { + if (mounted) { + widget.params.onDocumentLoadFinished?.call(widget.documentRef, succeeded); + } + }); + } + @override Widget build(BuildContext context) { final listenable = widget.documentRef.resolveListenable(); diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 77d07678..a74014b5 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -42,6 +42,7 @@ class PdfViewerParams { this.onSecondaryTapUp, this.onLongPressStart, this.onDocumentChanged, + this.onDocumentLoadFinished, this.calculateInitialPageNumber, this.calculateInitialZoom, this.calculateCurrentPageNumber, @@ -264,6 +265,11 @@ class PdfViewerParams { /// If you want to be notified when the viewer is ready to interact, use [onViewerReady] instead. final PdfViewerDocumentChangedCallback? onDocumentChanged; + /// Function to notify that the document loading is finished regardless of success or failure. + /// + /// For the function usage, see [PdfDocumentLoadFinished]. + final PdfDocumentLoadFinished? onDocumentLoadFinished; + /// Function called when the viewer is ready. /// /// Unlike [PdfViewerDocumentChangedCallback], this function is called after the viewer is ready to interact. @@ -640,6 +646,7 @@ class PdfViewerParams { other.onSecondaryTapUp == onSecondaryTapUp && other.onLongPressStart == onLongPressStart && other.onDocumentChanged == onDocumentChanged && + other.onDocumentLoadFinished == onDocumentLoadFinished && other.calculateInitialPageNumber == calculateInitialPageNumber && other.calculateInitialZoom == calculateInitialZoom && other.calculateCurrentPageNumber == calculateCurrentPageNumber && @@ -699,6 +706,7 @@ class PdfViewerParams { onSecondaryTapUp.hashCode ^ onLongPressStart.hashCode ^ onDocumentChanged.hashCode ^ + onDocumentLoadFinished.hashCode ^ calculateInitialPageNumber.hashCode ^ calculateInitialZoom.hashCode ^ calculateCurrentPageNumber.hashCode ^ @@ -1337,6 +1345,28 @@ typedef PdfViewerCalculateCurrentPageNumberFunction = /// typedef PdfViewerReadyCallback = void Function(PdfDocument document, PdfViewerController controller); +/// Function to called when the document loading is finished regardless of success or failure. +/// +/// [documentRef] is the reference to the loaded document. +/// [loadSucceeded] indicates whether the document was loaded successfully. +/// +/// The following fragment illustrates how to use the function: +/// ```dart +/// onDocumentLoadFinished: (documentRef, succeeded) { +/// if (succeeded) { +/// // all the pages are loaded successfully +/// debugPrint('Document loaded successfully.'); +/// } else { +/// // there was an error loading the document +/// final listenable = widget.documentRef.resolveListenable(); +/// final error = listenable.error; +/// final stackTrace = listenable.stackTrace; +/// debugPrint('Document load failed: $error\n$stackTrace'); +/// } +/// } +/// ``` +typedef PdfDocumentLoadFinished = void Function(PdfDocumentRef documentRef, bool loadSucceeded); + /// Function to be called when the viewer view size is changed. /// /// [viewSize] is the new view size. diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index ef016e88..b333c323 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -358,9 +358,17 @@ class _CoreGraphicsPdfDocument extends PdfDocument { rotation: _rotationFromDegrees(pageInfos[i]['rotation'] as int? ?? 0), ), ]); + // CoreGraphics loads all pages immediately, so notify load complete + if (!useProgressiveLoading) { + doc._notifyDocumentLoadComplete(); + } return doc; } + void _notifyDocumentLoadComplete() { + subject.add(PdfDocumentLoadCompleteEvent(this)); + } + static PdfPageRotation _rotationFromDegrees(int degrees) { switch (degrees % 360) { case 90: @@ -416,9 +424,7 @@ class _CoreGraphicsPdfDocument extends PdfDocument { T? data, Duration loadUnitDuration = const Duration(milliseconds: 250), }) async { - if (onPageLoadProgress != null) { - await onPageLoadProgress(_pages.length, _pages.length, data); - } + // CoreGraphics loads all pages immediately; nothing to do. } @override diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index d346eda9..7946bf4f 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -631,6 +631,9 @@ class _PdfDocumentPdfium extends PdfDocument { maxPageCountToLoadAdditionally: useProgressiveLoading ? 1 : null, ); pdfDoc._pages = List.unmodifiable(pages.pages); + if (!useProgressiveLoading) { + pdfDoc._notifyDocumentLoadComplete(); + } pdfDoc._notifyMissingFonts(); return pdfDoc; } catch (e) { @@ -676,12 +679,20 @@ class _PdfDocumentPdfium extends PdfDocument { return; } } - if (loaded.pageCountLoadedTotal == loaded.pages.length || isDisposed) { + if (loaded.pageCountLoadedTotal == loaded.pages.length) { + _notifyDocumentLoadComplete(); + return; + } + if (isDisposed) { return; } } } + void _notifyDocumentLoadComplete() { + subject.add(PdfDocumentLoadCompleteEvent(this)); + } + /// Loads pages in the document in a time-limited manner. Future<({List pages, int pageCountLoadedTotal})> _loadPagesInLimitedTime({ List pagesLoadedSoFar = const [], diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart index 2a914e95..92f47a37 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document_event.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -4,9 +4,14 @@ import 'pdf_page_status_change.dart'; /// PDF document event types. enum PdfDocumentEventType { + /// [PdfDocumentLoadCompleteEvent]: Document's loading is complete; i.e., all pages are loaded. + documentLoadComplete, + /// [PdfDocumentPageStatusChangedEvent]: Page status changed. pageStatusChanged, - missingFonts, // [PdfDocumentMissingFontsEvent]: Missing fonts changed. + + /// [PdfDocumentMissingFontsEvent]: Missing fonts changed. + missingFonts, } /// Base class for PDF document events. @@ -18,6 +23,17 @@ abstract class PdfDocumentEvent { PdfDocument get document; } +/// Event that is triggered when the PDF document has finished loading. +class PdfDocumentLoadCompleteEvent implements PdfDocumentEvent { + PdfDocumentLoadCompleteEvent(this.document); + + @override + PdfDocumentEventType get type => PdfDocumentEventType.documentLoadComplete; + + @override + final PdfDocument document; +} + /// Event that is triggered when the status of PDF document pages has changed. class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); From d744135027d761557ae509b0aa0b6fdf1eef78d2 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 06:04:52 +0900 Subject: [PATCH 651/663] pdfrx_engine 0.3.8/pdfrx 2.2.22/pdfrx_coregraphics 0.1.14 Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx/CHANGELOG.md | 9 +++++++++ packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/CHANGELOG.md | 8 ++++++++ packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 2 +- packages/pdfrx_engine/CHANGELOG.md | 4 ++++ packages/pdfrx_engine/lib/src/pdf_document_event.dart | 6 +++--- packages/pdfrx_engine/pubspec.yaml | 2 +- 8 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 991dccf6..96bd5f4c 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,12 @@ +# 2.2.22 + +- Updated to pdfrx_engine 0.3.8 + +# 2.2.21 (Invalid release - use 2.2.22) + +- NEW: `onDocumentLoadFinished` callback in `PdfViewerParams` to notify when document loading completes (or fails) +- Implemented `PdfDocumentLoadCompleteEvent` for WASM backend + # 2.2.20 - FIXED: Inconsistent environment constraints - Flutter version now correctly requires 3.35.1+ to match Dart 3.9.0 requirement ([#553](https://github.com/espresso3389/pdfrx/issues/553)) diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 45491a9d..c87b4a5d 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.20 +version: 2.2.22 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index c02199da..17ec060d 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.1.14 + +- Updated to pdfrx_engine 0.3.8 + +## 0.1.13 (Invalid release - use 0.1.14) + +- Implemented `PdfDocumentLoadCompleteEvent` for CoreGraphics backend + ## 0.1.12 - FIXED: Inconsistent environment constraints - Flutter version now correctly requires 3.35.1+ to match Dart 3.9.0 requirement ([#553](https://github.com/espresso3389/pdfrx/issues/553)) diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index caab4110..85de013b 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.20 - pdfrx_coregraphics: ^0.1.12 + pdfrx: ^2.2.22 + pdfrx_coregraphics: ^0.1.14 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index dbbd6022..08e7e218 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.12 +version: 0.1.14 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index 5a93a193..f3fb1053 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.8 + +- NEW: `PdfDocumentLoadCompleteEvent` for document load completion notification + ## 0.3.7 - IMPROVED: Add `isDirty` flag to page image cache to prevent cache removal before re-rendering page ([#567](https://github.com/espresso3389/pdfrx/issues/567)) diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart index 92f47a37..c8494942 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document_event.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -25,7 +25,7 @@ abstract class PdfDocumentEvent { /// Event that is triggered when the PDF document has finished loading. class PdfDocumentLoadCompleteEvent implements PdfDocumentEvent { - PdfDocumentLoadCompleteEvent(this.document); + const PdfDocumentLoadCompleteEvent(this.document); @override PdfDocumentEventType get type => PdfDocumentEventType.documentLoadComplete; @@ -36,7 +36,7 @@ class PdfDocumentLoadCompleteEvent implements PdfDocumentEvent { /// Event that is triggered when the status of PDF document pages has changed. class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { - PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); + const PdfDocumentPageStatusChangedEvent(this.document, {required this.changes}); @override PdfDocumentEventType get type => PdfDocumentEventType.pageStatusChanged; @@ -55,7 +55,7 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { /// Event that is triggered when the list of missing fonts in the PDF document has changed. class PdfDocumentMissingFontsEvent implements PdfDocumentEvent { /// Create a [PdfDocumentMissingFontsEvent]. - PdfDocumentMissingFontsEvent(this.document, this.missingFonts); + const PdfDocumentMissingFontsEvent(this.document, this.missingFonts); @override PdfDocumentEventType get type => PdfDocumentEventType.missingFonts; diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 167409ef..76ebf664 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.7 +version: 0.3.8 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From dde4da94c9985dad3e4a4d1826f09dfceb0db913 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 06:22:14 +0900 Subject: [PATCH 652/663] minor fixes --- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 1 - packages/pdfrx/pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 41188355..9bafcba4 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -427,7 +427,6 @@ class _PdfViewerState extends State } Future _notifyDocumentLoadFinished({required bool succeeded}) async { - final listenable = widget.documentRef.resolveListenable(); if (succeeded) { // FIXME: This is a temporary workaround to wait until the initial page is loaded. while (mounted) { diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index c87b4a5d..a07ea83f 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.7 + pdfrx_engine: ^0.3.8 pdfium_flutter: ^0.1.8 collection: crypto: ^3.0.6 From 1ef3375cfa2b0ec75a098c0cfdcd009555ce6f01 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 06:27:32 +0900 Subject: [PATCH 653/663] pdfrx 2.2.23/pdfrx_coregraphics 0.1.15 Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx/CHANGELOG.md | 10 +++++++--- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/CHANGELOG.md | 8 ++++++-- packages/pdfrx_coregraphics/README.md | 4 ++-- packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 96bd5f4c..ce6385a0 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,8 +1,12 @@ -# 2.2.22 +# 2.2.23 -- Updated to pdfrx_engine 0.3.8 +- Minor fixes + +# 2.2.22 (Invalid release - use 2.2.23) + +- pdfrx_engine dependency was not updated correctly -# 2.2.21 (Invalid release - use 2.2.22) +# 2.2.21 (Invalid release - use 2.2.23) - NEW: `onDocumentLoadFinished` callback in `PdfViewerParams` to notify when document loading completes (or fails) - Implemented `PdfDocumentLoadCompleteEvent` for WASM backend diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index a07ea83f..a7bff84f 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.22 +version: 2.2.23 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 17ec060d..ac671c50 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,8 +1,12 @@ -## 0.1.14 +## 0.1.15 - Updated to pdfrx_engine 0.3.8 -## 0.1.13 (Invalid release - use 0.1.14) +## 0.1.14 (Invalid release - use 0.1.15) + +- pdfrx_engine dependency was not updated correctly + +## 0.1.13 (Invalid release - use 0.1.15) - Implemented `PdfDocumentLoadCompleteEvent` for CoreGraphics backend diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 85de013b..3cf22f6b 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,8 +14,8 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.22 - pdfrx_coregraphics: ^0.1.14 + pdfrx: ^2.2.23 + pdfrx_coregraphics: ^0.1.15 ``` Set the CoreGraphics entry functions before initializing pdfrx: diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index 08e7e218..e52dfa15 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.14 +version: 0.1.15 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.3.7 + pdfrx_engine: ^0.3.8 http: dev_dependencies: From 4952c802894723ded94b612476188e2c1b5323ee Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 06:29:21 +0900 Subject: [PATCH 654/663] Remove invalid versions from CHANGELOG --- packages/pdfrx/CHANGELOG.md | 9 +-------- packages/pdfrx_coregraphics/CHANGELOG.md | 7 ------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index ce6385a0..184b7968 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,13 +1,6 @@ # 2.2.23 -- Minor fixes - -# 2.2.22 (Invalid release - use 2.2.23) - -- pdfrx_engine dependency was not updated correctly - -# 2.2.21 (Invalid release - use 2.2.23) - +- pdfrx_engine 0.3.8 - NEW: `onDocumentLoadFinished` callback in `PdfViewerParams` to notify when document loading completes (or fails) - Implemented `PdfDocumentLoadCompleteEvent` for WASM backend diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index ac671c50..44fb6112 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,13 +1,6 @@ ## 0.1.15 - Updated to pdfrx_engine 0.3.8 - -## 0.1.14 (Invalid release - use 0.1.15) - -- pdfrx_engine dependency was not updated correctly - -## 0.1.13 (Invalid release - use 0.1.15) - - Implemented `PdfDocumentLoadCompleteEvent` for CoreGraphics backend ## 0.1.12 From d7c5f064c8f53b1dbaa14b6613142cc4cfb65b26 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 17:59:37 +0900 Subject: [PATCH 655/663] Update documentation: Revise CLAUDE.md, enhance COMMANDS.md with shell syntax notes, restructure PROJECT-STRUCTURE.md, and expand RELEASING.md with versioning and publishing details. --- CLAUDE.md | 4 +- RELEASING.md | 126 -------------------------------- doc/agents/COMMANDS.md | 2 + doc/agents/PROJECT-STRUCTURE.md | 15 ++-- doc/agents/RELEASING.md | 72 +++++++++++++++--- 5 files changed, 72 insertions(+), 147 deletions(-) delete mode 100644 RELEASING.md diff --git a/CLAUDE.md b/CLAUDE.md index 43c994c2..b0c347bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,3 @@ -@AGENTS.md +# NOTE for Claude + +Read and follow the instructions in AGENTS.md. diff --git a/RELEASING.md b/RELEASING.md deleted file mode 100644 index c6292457..00000000 --- a/RELEASING.md +++ /dev/null @@ -1,126 +0,0 @@ -# Releasing Packages - -This guide covers the full release checklist for all packages in the monorepo. Follow the steps that apply to the package you are releasing. - -## Package Overview - -The monorepo contains five packages: -- **pdfium_dart** - Low-level Dart FFI bindings for PDFium -- **pdfium_flutter** - Flutter plugin for loading PDFium native libraries -- **pdfrx_engine** - Platform-agnostic PDF rendering API -- **pdfrx** - Cross-platform PDF viewer plugin for Flutter -- **pdfrx_coregraphics** - CoreGraphics-backed renderer for iOS/macOS (experimental) - -## pdfrx_engine Releases - -1. Update the version in `packages/pdfrx_engine/pubspec.yaml`. - - For non-breaking or small breaking changes, bump the patch version (`X.Y.Z -> X.Y.Z+1`). - - For breaking changes, bump the minor version (`X.Y.Z -> X.Y+1.0`). - - For major changes, bump the major version (`X.Y.Z -> X+1.0.0`). -2. Update `packages/pdfrx_engine/CHANGELOG.md` with user-facing changes. - - Skip CI/CD updates and meta-doc changes (`CLAUDE.md`, `AGENTS.md`) unless significant. -3. Update `packages/pdfrx_engine/README.md` (at least, the versions hard-coded on it). -4. Update the root `README.md` if necessary. -5. Run `pana` inside `packages/pdfrx_engine` to validate the package. -6. Publish with `dart pub publish` inside `packages/pdfrx_engine/`. - -## pdfrx Releases - -1. Update the version in `packages/pdfrx/pubspec.yaml`. - - If `pdfrx_engine` was updated, update the dependency version here as well. -2. Update `packages/pdfrx/CHANGELOG.md` with user-facing changes. -3. Update `packages/pdfrx/README.md` with the new version. - - Update version numbers in sample snippets. - - Note new features or breaking changes when relevant. - - Report any issues found in the example app or documentation to the owner. -4. Update the root `README.md` (at least, the versions hard-coded on it). -5. Run `dart pub get` to refresh dependencies. -6. Run tests: - - `dart test` inside `packages/pdfrx_engine/`. - - `flutter test` inside `packages/pdfrx/`. -7. Validate the example app builds: `flutter build web --wasm` in `packages/pdfrx/example/viewer`. -8. Run `pana` in `packages/pdfrx` (and other packages being released) to validate code integrity. - - Flag any WASM compatibility warnings emitted by `pana`. -9. Commit changes with `Release pdfrx vX.Y.Z` or `Release pdfrx_engine vX.Y.Z`. -10. Tag the commit with `git tag pdfrx-vX.Y.Z` or `git tag pdfrx_engine-vX.Y.Z`. -11. Push commits and tags. -12. Publish with `flutter pub publish` inside `packages/pdfrx/`. -13. Comment on related GitHub issues/PRs once the release is live. - - Use `gh issue comment` or `gh pr comment` as appropriate. - - If a PR references issues, comment on those issues as well. - - Template: - - ```md - The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). - - ...Fix/update summary... - - Written by [AGENT SIGNATURE] - ``` - - - Focus on release notes and what changed; link to the version-specific changelog entry. - -## pdfium_dart Releases - -1. Update the version in `packages/pdfium_dart/pubspec.yaml`. - - Follow semantic versioning based on the scope of changes. -2. Update `packages/pdfium_dart/CHANGELOG.md` with user-facing changes. - - Include PDFium version updates if applicable. - - Document any changes to the FFI bindings or `getPdfium()` functionality. -3. Update `packages/pdfium_dart/README.md` if necessary. -4. Run tests: `dart test` inside `packages/pdfium_dart/`. -5. Run `pana` inside `packages/pdfium_dart/` to validate the package. -6. Commit changes with `Release pdfium_dart vX.Y.Z`. -7. Tag the commit with `git tag pdfium_dart-vX.Y.Z`. -8. Push commits and tags. -9. Publish with `dart pub publish` inside `packages/pdfium_dart/`. - -## pdfium_flutter Releases - -1. Update the version in `packages/pdfium_flutter/pubspec.yaml`. - - If `pdfium_dart` was updated, update the dependency version here as well. -2. Update `packages/pdfium_flutter/CHANGELOG.md` with user-facing changes. - - Include PDFium binary version updates if applicable. - - Document platform-specific changes (iOS, Android, Windows, macOS, Linux). -3. Update `packages/pdfium_flutter/README.md` if necessary. -4. Update platform-specific build configurations if PDFium binaries changed: - - `darwin/pdfium_flutter.podspec` for iOS/macOS (CocoaPods) - - `darwin/pdfium_flutter/Package.swift` for Swift Package Manager - - `android/CMakeLists.txt` for Android - - `windows/CMakeLists.txt` for Windows - - `linux/CMakeLists.txt` for Linux -5. Run tests: `flutter test` inside `packages/pdfium_flutter/`. -6. Run `pana` inside `packages/pdfium_flutter/` to validate the package. -7. Commit changes with `Release pdfium_flutter vX.Y.Z`. -8. Tag the commit with `git tag pdfium_flutter-vX.Y.Z`. -9. Push commits and tags. -10. Publish with `flutter pub publish` inside `packages/pdfium_flutter/`. - -## pdfrx_coregraphics Releases - -1. Update the version in `packages/pdfrx_coregraphics/pubspec.yaml`. -2. Update `packages/pdfrx_coregraphics/CHANGELOG.md` with user-facing changes. -3. Update `packages/pdfrx_coregraphics/README.md` if necessary. -4. Run tests on macOS/iOS devices if possible. -5. Run `pana` inside `packages/pdfrx_coregraphics/` to validate the package. -6. Commit changes with `Release pdfrx_coregraphics vX.Y.Z`. -7. Tag the commit with `git tag pdfrx_coregraphics-vX.Y.Z`. -8. Push commits and tags. -9. Publish with `flutter pub publish` inside `packages/pdfrx_coregraphics/`. - -## Dependency Order - -When releasing multiple packages, follow this order to respect dependencies: - -1. **pdfium_dart** (no dependencies on other packages in monorepo) -2. **pdfium_flutter** (depends on pdfium_dart) -3. **pdfrx_engine** (depends on pdfium_dart) -4. **pdfrx** (depends on pdfrx_engine and pdfium_flutter) -5. **pdfrx_coregraphics** (depends on pdfrx_engine, independent of pdfium_flutter) - -## General Notes - -- Keep `CHANGELOG.md` entries user-focused and concise. -- Coordinate with the repository owner if any release blockers appear. -- When releasing multiple packages together, create a single commit with all version changes. -- Tag format: `-vX.Y.Z` (e.g., `pdfium_dart-v0.1.3`, `pdfrx-v2.2.11`). diff --git a/doc/agents/COMMANDS.md b/doc/agents/COMMANDS.md index 4521fb82..1969b11c 100644 --- a/doc/agents/COMMANDS.md +++ b/doc/agents/COMMANDS.md @@ -56,6 +56,8 @@ gh issue comment 123 --repo espresso3389/pdfrx --body "Comment text here" ## Common Commands +Commands below use standard shell syntax. On Windows with Claude Code, wrap with `pwsh.exe -Command "..."` as shown in the Windows-Specific Notes section above. + ### Flutter Plugin (packages/pdfrx) ```bash diff --git a/doc/agents/PROJECT-STRUCTURE.md b/doc/agents/PROJECT-STRUCTURE.md index 00b7202f..0457cb39 100644 --- a/doc/agents/PROJECT-STRUCTURE.md +++ b/doc/agents/PROJECT-STRUCTURE.md @@ -4,14 +4,11 @@ pdfrx is a monorepo containing five packages with the following dependency hiera ``` pdfium_dart (FFI bindings) - ↓ -pdfium_flutter (bundles PDFium binaries) - ↓ -pdfrx_engine (PDF API, pure Dart) - ↓ -pdfrx (Flutter widgets) - ↑ -pdfrx_coregraphics (alternative backend for Apple platforms) + ├──→ pdfium_flutter (bundles PDFium binaries) + │ ↓ + └──→ pdfrx_engine (PDF API, pure Dart) + ├──→ pdfrx (Flutter widgets) ←── pdfium_flutter + └──→ pdfrx_coregraphics (alternative backend for Apple platforms) ``` ## Packages @@ -65,7 +62,7 @@ CoreGraphics-backed renderer for iOS/macOS. ### iOS/macOS - Uses pre-built PDFium binaries from [GitHub releases](https://github.com/espresso3389/pdfrx/releases) -- CocoaPods integration via `packages/pdfrx/darwin/pdfrx.podspec` +- CocoaPods integration via `packages/pdfium_flutter/darwin/pdfium_flutter.podspec` - Binaries downloaded during pod install (or use Swift Package Manager) ### Android diff --git a/doc/agents/RELEASING.md b/doc/agents/RELEASING.md index e0143460..d705d8ec 100644 --- a/doc/agents/RELEASING.md +++ b/doc/agents/RELEASING.md @@ -5,6 +5,7 @@ - **Never bump versions or changelog entries preemptively** - only when explicitly releasing - Surface blockers or uncertainties to the user before continuing a release flow - `CHANGELOG.md` should be updated only when releasing a new version +- Skip CI/CD updates and meta-doc changes (`CLAUDE.md`, `AGENTS.md`, and `doc/agents/*md`) in changelogs unless significant ## Release Order @@ -16,6 +17,14 @@ Packages must be published in dependency order: 4. **pdfrx_coregraphics** (depends on pdfrx_engine) 5. **pdfrx** (depends on pdfrx_engine, pdfium_flutter) +## Versioning + +Follow semantic versioning: + +- **Patch** (`X.Y.Z -> X.Y.Z+1`): Bug fixes, non-breaking changes +- **Minor** (`X.Y.Z -> X.Y+1.0`): New features, breaking changes +- **Major** (`X.Y.Z -> X+1.0.0`): Major breaking changes + ## Pre-Release Checklist For each package being released: @@ -24,10 +33,42 @@ For each package being released: 2. Update version in `pubspec.yaml` 3. Update dependency versions in dependent packages 4. Update version references in `README.md` examples -5. Run dry-run: `dart pub publish --dry-run` or `flutter pub publish --dry-run` +5. Update the root `README.md` if necessary +6. Run dry-run: `dart pub publish --dry-run` or `flutter pub publish --dry-run` +7. Run `pana` to validate package quality + +### pdfrx-Specific Steps + +- Run tests: `dart test` in `packages/pdfrx_engine/` and `flutter test` in `packages/pdfrx/` +- Validate example app: `flutter build web --wasm` in `packages/pdfrx/example/viewer` +- Flag any WASM compatibility warnings from `pana` + +### pdfium_flutter-Specific Steps + +Update platform-specific build configurations if PDFium binaries changed: + +- `darwin/pdfium_flutter.podspec` for iOS/macOS (CocoaPods) +- `darwin/pdfium_flutter/Package.swift` for Swift Package Manager +- `android/CMakeLists.txt` for Android +- `windows/CMakeLists.txt` for Windows +- `linux/CMakeLists.txt` for Linux ## Publishing +### Windows (Claude Code) + +On Windows, use PowerShell wrapper for reliable path handling: + +```bash +# For Dart packages +pwsh.exe -Command "cd 'd:\pdfrx\packages\'; dart pub publish --force" + +# For Flutter packages +pwsh.exe -Command "cd 'd:\pdfrx\packages\'; flutter pub publish --force" +``` + +### Other Platforms + ```bash # For Dart packages cd packages/ @@ -40,6 +81,16 @@ flutter pub publish --force ## Post-Release +### Commit Changes + +Before tagging, commit all release changes: + +```bash +git add -A +git commit -m "Release pdfrx 2.2.19, pdfrx_engine 0.3.7, etc." +git push +``` + ### Git Tagging After publishing, create git tags for each released package and push them: @@ -56,20 +107,19 @@ git tag pdfrx_coregraphics-v0.1.11 git push --tags ``` -### Commit Changes +### Notify Issues -Before tagging, commit all release changes: +Comment on related GitHub issues/PRs once the release is live: -```bash -git add -A -git commit -m "Release pdfrx 2.2.19, pdfrx_engine 0.3.7, etc." -git push -``` +```md +The FIX|UPDATE|SOMETHING for this issue has been released in v[x.y.z](https://pub.dev/packages/pdfrx/versions/x.y.z). -### Notify Issues +...Fix/update summary... + +Written by [AGENT SIGNATURE] +``` -- Notify relevant GitHub issues about the fix/feature being released -- Comment format: "This has been addressed in . " +Use `gh issue comment` or `gh pr comment` as appropriate. ## Changelog Guidelines From 4f488730e49499b5782fcc6860bb8f2a16675cd5 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 18:25:43 +0900 Subject: [PATCH 656/663] Refactor pdfium.dart: Remove unused native function lookup and clean up imports --- .../pdfrx_engine/lib/src/native/pdfium.dart | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/pdfrx_engine/lib/src/native/pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfium.dart index fde9f866..a1cf803c 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfium.dart @@ -1,5 +1,4 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:ffi' as ffi; import 'dart:ffi'; import 'dart:io'; @@ -40,21 +39,3 @@ pdfium_bindings.PDFium get pdfium { set pdfium(pdfium_bindings.PDFium value) { _pdfium = value; } - -typedef PdfrxNativeFunctionLookup = ffi.Pointer Function(String symbolName); - -PdfrxNativeFunctionLookup? createPdfrxNativeFunctionLookup() { - if (Pdfrx.pdfiumNativeBindings != null) { - final bindings = Pdfrx.pdfiumNativeBindings!; - ffi.Pointer lookup(String symbolName) { - final ptr = bindings[symbolName]; - if (ptr == null) { - throw Exception('Failed to find binding for $symbolName'); - } - return ffi.Pointer.fromAddress(ptr); - } - - return lookup; - } - return null; -} From 8fa7ea968ffd415b995c5ff7f0cdd3edf58212e6 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 21:04:57 +0900 Subject: [PATCH 657/663] Introduces PdfrxEntryFunctions.stopBackgroundWorker #184/#430 --- .vscode/settings.json | 1 + packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart | 7 +- .../lib/pdfrx_coregraphics.dart | 9 +- .../pdfrx_engine/lib/src/mock/pdfrx_mock.dart | 10 +- .../lib/src/native/pdfrx_pdfium.dart | 58 +++++--- .../lib/src/native/pthread/file_access.dart | 2 +- .../lib/src/native/pthread/file_write.dart | 2 +- .../lib/src/native/win32/file_access.dart | 2 +- .../lib/src/native/win32/file_write.dart | 2 +- .../pdfrx_engine/lib/src/native/worker.dart | 136 ++++++++++++------ .../lib/src/pdfrx_entry_functions.dart | 20 ++- 11 files changed, 176 insertions(+), 73 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f27314b..827beeee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "d_reclen", "dartdoc", "dartify", + "deinit", "Dests", "DEVICECMYK", "DEVICEGRAY", diff --git a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart index 120ff07c..326c1616 100644 --- a/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart +++ b/packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart @@ -117,6 +117,11 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { throw UnimplementedError('compute() is not implemented for WASM backend.'); } + @override + Future stopBackgroundWorker() async { + throw UnimplementedError('stopBackgroundWorker() is not implemented for WASM backend.'); + } + static String? _pdfiumWasmModulesUrlFromMetaTag() { final meta = web.document.querySelector('meta[name="pdfium-wasm-module-url"]') as web.HTMLMetaElement?; return meta?.content; @@ -368,7 +373,7 @@ class PdfrxEntryFunctionsWasmImpl extends PdfrxEntryFunctions { } @override - PdfrxBackend get backend => PdfrxBackend.pdfiumWasm; + PdfrxBackendType get backendType => PdfrxBackendType.pdfiumWasm; } class _PdfDocumentWasm extends PdfDocument { diff --git a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart index b333c323..ffe78c3e 100644 --- a/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart +++ b/packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart @@ -47,6 +47,13 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { ); } + @override + Future stopBackgroundWorker() async { + throw UnimplementedError( + 'stopBackgroundWorker() is not implemented for CoreGraphics backend.', + ); + } + @override Future openAsset( String name, { @@ -259,7 +266,7 @@ class PdfrxCoreGraphicsEntryFunctions implements PdfrxEntryFunctions { } @override - PdfrxBackend get backend => PdfrxBackend.pdfKit; + PdfrxBackendType get backendType => PdfrxBackendType.pdfKit; Future _openWithPassword({ required PdfPasswordProvider? passwordProvider, diff --git a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart index 4604213a..9af2db25 100644 --- a/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart +++ b/packages/pdfrx_engine/lib/src/mock/pdfrx_mock.dart @@ -34,6 +34,14 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { ); } + @override + Future stopBackgroundWorker() async { + throw UnimplementedError( + 'stopBackgroundWorker() is not implemented because PdfrxEntryFunctions.instance is not initialized. ' + 'Please call pdfrxInitialize()/pdfrxFlutterInitialize() or explicitly set PdfrxEntryFunctions.instance.', + ); + } + @override Future openAsset( String name, { @@ -107,5 +115,5 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { Future clearAllFontData() => unimplemented(); @override - PdfrxBackend get backend => PdfrxBackend.mock; + PdfrxBackendType get backendType => PdfrxBackendType.mock; } diff --git a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart index 7946bf4f..af1a420b 100644 --- a/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart +++ b/packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart @@ -51,7 +51,7 @@ Future _init() async { _appLocalFontPath = await getCacheDirectory('pdfrx.fonts'); - (await BackgroundWorker.instance).computeWithArena((arena, params) { + BackgroundWorker.computeWithArena((arena, params) { final config = arena(); config.ref.version = 2; @@ -80,6 +80,17 @@ Future _init() async { await _initializeFontEnvironment(); } +Future _deinit() async { + await BackgroundWorker.compute((params) { + pdfium.FPDF_DestroyLibrary(); + }, {}); + await BackgroundWorker.stop(); + _mapFont?.close(); + _mapFont = null; + _lastMissingFonts.clear(); + _initialized = false; +} + /// Stores the fonts that were not found during mapping. /// NOTE: This is used by [BackgroundWorker] and should not be used directly; use [_getAndClearMissingFonts] instead. final _lastMissingFonts = {}; @@ -101,7 +112,7 @@ _mapFont; /// Setup the system font info in PDFium. Future _initializeFontEnvironment() async { - await (await BackgroundWorker.instance).computeWithArena((arena, params) { + await BackgroundWorker.computeWithArena((arena, params) { // kBase14FontNames const fontNamesToIgnore = { 'Courier': true, @@ -181,7 +192,7 @@ Future _initializeFontEnvironment() async { /// Retrieve and clear the last missing fonts from [_lastMissingFonts] in a thread-safe manner. Future> _getAndClearMissingFonts() async { - return await (await BackgroundWorker.instance).compute((params) { + return await BackgroundWorker.compute((params) { final fonts = _lastMissingFonts.values.toList(); _lastMissingFonts.clear(); return fonts; @@ -196,14 +207,17 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future suspendPdfiumWorkerDuringAction(FutureOr Function() action) async { - return await (await BackgroundWorker.instance).suspendDuringAction(action); + return await BackgroundWorker.suspendDuringAction(action); } @override Future compute(FutureOr Function(M message) callback, M message) async { - return await (await BackgroundWorker.instance).compute(callback, message); + return await BackgroundWorker.compute(callback, message); } + @override + Future stopBackgroundWorker() => _deinit(); + @override Future openAsset( String name, { @@ -261,7 +275,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { }) async { await _init(); return _openByFunc( - (password) async => (await BackgroundWorker.instance).computeWithArena((arena, params) { + (password) async => BackgroundWorker.computeWithArena((arena, params) { final doc = pdfium.FPDF_LoadDocument(params.filePath.toUtf8(arena), params.password?.toUtf8(arena) ?? nullptr); return doc.address; }, (filePath: filePath, password: password)), @@ -323,7 +337,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { try { await read(buffer.asTypedList(fileSize), 0, fileSize); return _openByFunc( - (password) async => (await BackgroundWorker.instance).computeWithArena( + (password) async => BackgroundWorker.computeWithArena( (arena, params) => pdfium.FPDF_LoadMemDocument( Pointer.fromAddress(params.buffer), params.fileSize, @@ -353,7 +367,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { final fa = await PdfiumFileAccess.create(fileSize, read); try { return _openByFunc( - (password) async => (await BackgroundWorker.instance).computeWithArena( + (password) async => BackgroundWorker.computeWithArena( (arena, params) => pdfium.FPDF_LoadCustomDocument( Pointer.fromAddress(params.fileAccess), params.password?.toUtf8(arena) ?? nullptr, @@ -440,7 +454,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { @override Future createNew({required String sourceName}) async { await _init(); - final doc = await (await BackgroundWorker.instance).compute((params) { + final doc = await BackgroundWorker.compute((params) { return pdfium.FPDF_CreateNewDocument().address; }, null); return _PdfDocumentPdfium.fromPdfDocument( @@ -462,7 +476,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { final dataBuffer = malloc(jpegData.length); try { dataBuffer.asTypedList(jpegData.length).setAll(0, jpegData); - final doc = await (await BackgroundWorker.instance).computeWithArena( + final doc = await BackgroundWorker.computeWithArena( (arena, params) { final document = pdfium.FPDF_CreateNewDocument(); final newPage = pdfium.FPDFPage_New(document, 0, params.width, params.height); @@ -546,7 +560,7 @@ class PdfrxEntryFunctionsImpl implements PdfrxEntryFunctions { } @override - PdfrxBackend get backend => PdfrxBackend.pdfium; + PdfrxBackendType get backendType => PdfrxBackendType.pdfium; } extension _FpdfUtf8StringExt on String { @@ -591,7 +605,7 @@ class _PdfDocumentPdfium extends PdfDocument { } _PdfDocumentPdfium? pdfDoc; try { - final result = await (await BackgroundWorker.instance).computeWithArena((arena, docAddress) { + final result = await BackgroundWorker.computeWithArena((arena, docAddress) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(docAddress); Pointer formInfo = nullptr; pdfium_bindings.FPDF_FORMHANDLE formHandle = nullptr; @@ -700,7 +714,7 @@ class _PdfDocumentPdfium extends PdfDocument { Duration? timeout, }) async { try { - final results = await (await BackgroundWorker.instance).computeWithArena( + final results = await BackgroundWorker.computeWithArena( (arena, params) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); final pageCount = pdfium.FPDF_GetPageCount(doc); @@ -781,7 +795,7 @@ class _PdfDocumentPdfium extends PdfDocument { @override Future reloadPages({List? pageNumbersToReload}) async { try { - final results = await (await BackgroundWorker.instance).computeWithArena((arena, params) { + final results = await BackgroundWorker.computeWithArena((arena, params) { final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docAddress); final pageCount = pdfium.FPDF_GetPageCount(doc); if (params.pageNumbersToReload != null) { @@ -893,7 +907,7 @@ class _PdfDocumentPdfium extends PdfDocument { if (!isDisposed) { isDisposed = true; subject.close(); - await (await BackgroundWorker.instance).compute((params) { + await BackgroundWorker.compute((params) { final formHandle = pdfium_bindings.FPDF_FORMHANDLE.fromAddress(params.formHandle); final formInfo = Pointer.fromAddress(params.formInfo); pdfium.FPDFDOC_ExitFormFillEnvironment(formHandle); @@ -910,7 +924,7 @@ class _PdfDocumentPdfium extends PdfDocument { @override Future> loadOutline() async => isDisposed ? [] - : await (await BackgroundWorker.instance).computeWithArena((arena, params) { + : await BackgroundWorker.computeWithArena((arena, params) { final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); return _getOutlineNodeSiblings(pdfium.FPDFBookmark_GetFirstChild(document, nullptr), document, arena); }, (document: document.address)); @@ -944,7 +958,7 @@ class _PdfDocumentPdfium extends PdfDocument { Future encodePdf({bool incremental = false, bool removeSecurity = false}) async { await assemble(); final byteBuffer = BytesBuilder(); - return await (await BackgroundWorker.instance).computeWithArena((arena, params) { + return await BackgroundWorker.computeWithArena((arena, params) { int write(Pointer pThis, Pointer pData, int size) { byteBuffer.add(Pointer.fromAddress(pData.address).asTypedList(size)); return size; @@ -1020,7 +1034,7 @@ class _DocumentPageArranger with ShuffleItemsInPlaceMixin { return false; } - await (await BackgroundWorker.instance).computeWithArena( + await BackgroundWorker.computeWithArena( (arena, params) { final arranger = _DocumentPageArranger._( pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document), @@ -1164,7 +1178,7 @@ class _PdfPagePdfium extends PdfPage { ct?.attach(cancelFlag); if (cancelFlag.value || document.isDisposed) return false; - return await (await BackgroundWorker.instance).compute( + return await BackgroundWorker.compute( (params) { final cancelFlag = Pointer.fromAddress(params.cancelFlag); if (cancelFlag.value) return false; @@ -1279,7 +1293,7 @@ class _PdfPagePdfium extends PdfPage { @override Future loadText() async { if (document.isDisposed || !isLoaded) return null; - return await (await BackgroundWorker.instance).computeWithArena((arena, params) { + return await BackgroundWorker.computeWithArena((arena, params) { final doubleSize = sizeOf(); final rectBuffer = arena(4); final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle); @@ -1326,7 +1340,7 @@ class _PdfPagePdfium extends PdfPage { Future> _loadWebLinks() async => document.isDisposed ? [] - : await (await BackgroundWorker.instance).computeWithArena((arena, params) { + : await BackgroundWorker.computeWithArena((arena, params) { pdfium_bindings.FPDF_PAGE page = nullptr; pdfium_bindings.FPDF_TEXTPAGE textPage = nullptr; pdfium_bindings.FPDF_PAGELINK linkPage = nullptr; @@ -1405,7 +1419,7 @@ class _PdfPagePdfium extends PdfPage { Future> _loadAnnotLinks() async => document.isDisposed ? [] - : await (await BackgroundWorker.instance).computeWithArena((arena, params) { + : await BackgroundWorker.computeWithArena((arena, params) { final document = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.document); final page = pdfium.FPDF_LoadPage(document, params.pageNumber - 1); try { diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart index 1e22345b..8d03b2e7 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_access.dart @@ -54,7 +54,7 @@ final _readFuncPtr = _NativeFileReadCallable.isolateLocal(_read, exceptionalRetu /// Gets the read function pointer address on the background worker isolate. Future _getReadFuncOnBackgroundWorker() async { - return await (await BackgroundWorker.instance).compute((m) => _readFuncPtr.address, {}); + return await BackgroundWorker.compute((m) => _readFuncPtr.address, {}); } int _read(Pointer param, int position, Pointer buffer, int size) { diff --git a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart index 62d3fb54..76f2ca83 100644 --- a/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/pthread/file_write.dart @@ -52,7 +52,7 @@ final _writeFuncPtr = _NativeFileWriteCallable.isolateLocal(_write, exceptionalR /// Gets the write function pointer address on the background worker isolate. Future _getWriteFuncOnBackgroundWorker() async { - return await (await BackgroundWorker.instance).compute((m) => _writeFuncPtr.address, {}); + return await BackgroundWorker.compute((m) => _writeFuncPtr.address, {}); } int _write(Pointer pThis, Pointer pData, int size) { diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart index 6a9b1ff5..cc54ad3b 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_access.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_access.dart @@ -56,7 +56,7 @@ final _readFuncPtr = _NativeFileReadCallable.isolateLocal(_read, exceptionalRetu /// Gets the read function pointer address on the background worker isolate. Future _getReadFuncOnBackgroundWorker() async { - return await (await BackgroundWorker.instance).compute((m) => _readFuncPtr.address, {}); + return await BackgroundWorker.compute((m) => _readFuncPtr.address, {}); } int _read(Pointer param, int position, Pointer buffer, int size) { diff --git a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart index 2e50f49d..375da15b 100644 --- a/packages/pdfrx_engine/lib/src/native/win32/file_write.dart +++ b/packages/pdfrx_engine/lib/src/native/win32/file_write.dart @@ -51,7 +51,7 @@ final _writeFuncPtr = _NativeFileWriteCallable.isolateLocal(_write, exceptionalR /// Gets the write function pointer address on the background worker isolate. Future _getWriteFuncOnBackgroundWorker() async { - return await (await BackgroundWorker.instance).compute((m) => _writeFuncPtr.address, {}); + return await BackgroundWorker.compute((m) => _writeFuncPtr.address, {}); } int _write(Pointer pThis, Pointer pData, int size) { diff --git a/packages/pdfrx_engine/lib/src/native/worker.dart b/packages/pdfrx_engine/lib/src/native/worker.dart index 62468605..7ac5ed0d 100644 --- a/packages/pdfrx_engine/lib/src/native/worker.dart +++ b/packages/pdfrx_engine/lib/src/native/worker.dart @@ -4,6 +4,7 @@ import 'dart:developer' as developer; import 'dart:isolate'; import 'package:ffi/ffi.dart'; +import 'package:synchronized/extension.dart'; import '../pdfrx.dart'; @@ -11,31 +12,53 @@ typedef PdfrxComputeCallback = FutureOr Function(M message); /// Background worker based on Dart [Isolate]. class BackgroundWorker { - BackgroundWorker._(this._sendPort); - - static final instance = create(debugName: 'PdfrxEngineWorker'); - - final SendPort _sendPort; - bool _isDisposed = false; - - static Future create({String? debugName}) async { - final receivePort = ReceivePort(); - await Isolate.spawn(_workerEntry, receivePort.sendPort, debugName: debugName); - final worker = BackgroundWorker._(await receivePort.first as SendPort); - - // propagate the pdfium module path to the worker - worker.compute((params) { - Pdfrx.pdfiumModulePath = params.modulePath; - Pdfrx.pdfiumNativeBindings = params.bindings; - }, (modulePath: Pdfrx.pdfiumModulePath, bindings: Pdfrx.pdfiumNativeBindings)); + BackgroundWorker._(this.debugName); + + static final _instance = BackgroundWorker._('PdfrxEngineWorker'); + + final String debugName; + SendPort? _sendPort; + Isolate? _isolate; + + /// Ensures that the worker isolate is initialized, and returns its [SendPort]. + Future _ensureInit() async { + if (_sendPort != null) return _sendPort!; + await synchronized(() async { + if (_sendPort != null) return; + final receivePort = ReceivePort(); + _isolate = await Isolate.spawn(_workerEntry, receivePort.sendPort, debugName: debugName); + _sendPort = await receivePort.first as SendPort; + + // propagate the pdfium module path to the worker + _compute((params) { + Pdfrx.pdfiumModulePath = params.modulePath; + Pdfrx.pdfiumNativeBindings = params.bindings; + }, (modulePath: Pdfrx.pdfiumModulePath, bindings: Pdfrx.pdfiumNativeBindings)); + }); + return _sendPort!; + } - return worker; + /// Stops the worker isolate. + Future _stop() async { + if (_sendPort == null) return; + await synchronized(() async { + try { + if (_sendPort == null) return; + await _sendComputeParamsNoInit(_sendPort!, (sendPort) => _StopRequest._(sendPort)); + _sendPort = null; + } catch (e) { + developer.log('Failed to dispose worker (possible double-dispose?): $e'); + } + }); + _isolate?.kill(priority: Isolate.immediate); + _isolate = null; } + /// Entry point for the worker isolate. static void _workerEntry(SendPort sendPort) { final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); - late final StreamSubscription sub; + late StreamSubscription? sub; final suspendingQueue = Queue<_ComputeParams>(); var suspendingLevel = 0; sub = receivePort.listen((message) { @@ -56,50 +79,75 @@ class BackgroundWorker { } else { message.execute(); } - } else { - sub.cancel(); + } else if (message is _StopRequest) { + developer.log('Stopping worker isolate.'); + message.execute(); + sub?.cancel(); + sub = null; receivePort.close(); } }); } - Future _sendComputeParams(T Function(SendPort) createParams) async { - if (_isDisposed) { - throw StateError('Worker is already disposed'); - } + static Future _sendComputeParamsNoInit( + SendPort sendPort, + T Function(SendPort) createParams, + ) async { final receivePort = ReceivePort(); - _sendPort.send(createParams(receivePort.sendPort)); + sendPort.send(createParams(receivePort.sendPort)); return await receivePort.first; } - Future compute(PdfrxComputeCallback callback, M message) async { + Future _sendComputeParams(T Function(SendPort) createParams) async { + return _sendComputeParamsNoInit(await _ensureInit(), createParams); + } + + /// Runs [callback] in the worker isolate with [message]. + /// + /// [callback] can be any function that takes a single argument of type [M] and returns a value of type [R] or + /// a [Future]. + /// Inside [callback], you can only use passed message and create new objects. + /// You cannot access any variables from the outer scope, otherwise, it will throw an error. + Future _compute(PdfrxComputeCallback callback, M message) async { return await _sendComputeParams((sendPort) => _ExecuteParams(sendPort, callback, message)) as R; } - Future suspendDuringAction(FutureOr Function() action) async { - if (_isDisposed) { - throw StateError('Worker is already disposed'); - } - await _sendComputeParams((sendPort) => _SuspendRequest._(sendPort)); + /// Runs [callback] in the worker isolate with a new [Arena]. + /// + /// [callback] can be any function that takes a single argument of type [M] and returns a value of type [R] or + /// a [Future]. + /// Inside [callback], you can only use passed message and create new objects. + /// You cannot access any variables from the outer scope, otherwise, it will throw an error. + static Future compute(PdfrxComputeCallback callback, M message) async => + await _instance._compute(callback, message); + + /// Suspends the worker isolate during the execution of [action]. + static Future suspendDuringAction(FutureOr Function() action) async { + await _instance._sendComputeParams((sendPort) => _SuspendRequest._(sendPort)); try { return await action(); } finally { - await _sendComputeParams((sendPort) => _ResumeRequest._(sendPort)); + await _instance._sendComputeParams((sendPort) => _ResumeRequest._(sendPort)); } } /// [compute] wrapper that also provides [Arena] for temporary memory allocation. - Future computeWithArena(R Function(Arena arena, M message) callback, M message) => + /// + /// [callback] can be any function that takes a single argument of type [M] and returns a value of type [R] or + /// a [Future]. + /// Inside [callback], you can only use passed message and create new objects. + /// You cannot access any variables from the outer scope, otherwise, it will throw an error. + /// + /// [Arena] is provided as the first argument to [callback] for temporary memory allocation; the memory block + /// allocated using the [Arena] within the [callback] will be automatically released after the [callback] execution. + static Future computeWithArena(R Function(Arena arena, M message) callback, M message) => compute((message) => using((arena) => callback(arena, message)), message); - void dispose() { - try { - _isDisposed = true; - _sendPort.send(null); - } catch (e) { - developer.log('Failed to dispose worker (possible double-dispose?): $e'); - } - } + /// Stop the background worker isolate. + /// + /// This will release all resources associated with the worker. But you can still call [compute], [computeWithArena], + /// and [suspendDuringAction] afterwards, which will recreate the worker isolate. + static Future stop() => _instance._stop(); } class _ComputeParams { @@ -125,3 +173,7 @@ class _SuspendRequest extends _ComputeParams { class _ResumeRequest extends _ComputeParams { _ResumeRequest._(super.sendPort); } + +class _StopRequest extends _ComputeParams { + _StopRequest._(super.sendPort); +} diff --git a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart index 9a004d11..8924fa84 100644 --- a/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart +++ b/packages/pdfrx_engine/lib/src/pdfrx_entry_functions.dart @@ -36,8 +36,24 @@ abstract class PdfrxEntryFunctions { /// /// The background worker isolate is same to the one used by pdfrx internally to call PDFium /// functions. + /// + /// This function is only available for native PDFium backend; for other backends, calling this function + /// will throw an [UnimplementedError]. Future compute(FutureOr Function(M message) callback, M message); + /// **Experimental** + /// Stop the background worker isolate. + /// + /// This function can be called anytime to stop the background worker isolate. + /// If you call [compute] after calling this function, the background worker isolate will be recreated automatically. + /// + /// The function internally calls `FPDF_DestroyLibrary` and then stops the isolate. + /// You should ensure any PDFium-related resources are properly released before calling this function. + /// + /// This function is only available for native PDFium backend; for other backends, calling this function + /// will throw an [UnimplementedError]. + Future stopBackgroundWorker(); + /// See [PdfDocument.openAsset]. Future openAsset( String name, { @@ -117,11 +133,11 @@ abstract class PdfrxEntryFunctions { Future clearAllFontData(); /// Backend in use. - PdfrxBackend get backend; + PdfrxBackendType get backendType; } /// Pdfrx backend types. -enum PdfrxBackend { +enum PdfrxBackendType { /// PDFium backend. pdfium, From 2c3364aed4dfd9b4c55fc6b6a854f8a21e47d68d Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 10 Jan 2026 22:34:09 +0900 Subject: [PATCH 658/663] pdfrx_engine 0.3.9/pdfrx 2.2.24/pdfrx_coregraphics 0.1.16 Co-Authored-By: Claude Opus 4.5 --- packages/pdfrx/CHANGELOG.md | 5 +++++ packages/pdfrx/pubspec.yaml | 4 ++-- packages/pdfrx_coregraphics/CHANGELOG.md | 5 +++++ packages/pdfrx_coregraphics/pubspec.yaml | 4 ++-- packages/pdfrx_engine/CHANGELOG.md | 5 +++++ packages/pdfrx_engine/pubspec.yaml | 2 +- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 184b7968..943c56a4 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.2.24 + +- Updated to pdfrx_engine 0.3.9 +- NEW: `PdfrxEntryFunctions.stopBackgroundWorker()` to stop the background worker thread ([#184](https://github.com/espresso3389/pdfrx/issues/184), [#430](https://github.com/espresso3389/pdfrx/issues/430)) + # 2.2.23 - pdfrx_engine 0.3.8 diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index a7bff84f..6d1a0e82 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.23 +version: 2.2.24 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -14,7 +14,7 @@ environment: resolution: workspace dependencies: - pdfrx_engine: ^0.3.8 + pdfrx_engine: ^0.3.9 pdfium_flutter: ^0.1.8 collection: crypto: ^3.0.6 diff --git a/packages/pdfrx_coregraphics/CHANGELOG.md b/packages/pdfrx_coregraphics/CHANGELOG.md index 44fb6112..36b72ffe 100644 --- a/packages/pdfrx_coregraphics/CHANGELOG.md +++ b/packages/pdfrx_coregraphics/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.16 + +- Updated to pdfrx_engine 0.3.9 +- NEW: `PdfrxEntryFunctions.stopBackgroundWorker()` support ([#184](https://github.com/espresso3389/pdfrx/issues/184), [#430](https://github.com/espresso3389/pdfrx/issues/430)) + ## 0.1.15 - Updated to pdfrx_engine 0.3.8 diff --git a/packages/pdfrx_coregraphics/pubspec.yaml b/packages/pdfrx_coregraphics/pubspec.yaml index e52dfa15..75b006d1 100644 --- a/packages/pdfrx_coregraphics/pubspec.yaml +++ b/packages/pdfrx_coregraphics/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_coregraphics description: CoreGraphics-backed Pdfrx engine layer for Apple platforms. -version: 0.1.15 +version: 0.1.16 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues @@ -13,7 +13,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - pdfrx_engine: ^0.3.8 + pdfrx_engine: ^0.3.9 http: dev_dependencies: diff --git a/packages/pdfrx_engine/CHANGELOG.md b/packages/pdfrx_engine/CHANGELOG.md index f3fb1053..40ad6106 100644 --- a/packages/pdfrx_engine/CHANGELOG.md +++ b/packages/pdfrx_engine/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.9 + +- NEW: `PdfrxEntryFunctions.stopBackgroundWorker()` to stop the background worker thread ([#184](https://github.com/espresso3389/pdfrx/issues/184), [#430](https://github.com/espresso3389/pdfrx/issues/430)) +- Code cleanup: removed unused native function lookup + ## 0.3.8 - NEW: `PdfDocumentLoadCompleteEvent` for document load completion notification diff --git a/packages/pdfrx_engine/pubspec.yaml b/packages/pdfrx_engine/pubspec.yaml index 76ebf664..ca00097a 100644 --- a/packages/pdfrx_engine/pubspec.yaml +++ b/packages/pdfrx_engine/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx_engine description: pdfrx_engine is a PDF rendering and manipulation API built on top of PDFium, designed to be used with the pdfrx plugin. Supports viewing, editing, and combining PDF documents. -version: 0.3.8 +version: 0.3.9 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx_engine issue_tracker: https://github.com/espresso3389/pdfrx/issues From a4d138e1f9d73734dc8d4100e9d172ce57cd0a32 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 16 Jan 2026 06:02:55 +0900 Subject: [PATCH 659/663] Doc updates/remove unused function --- packages/pdfrx/lib/pdfrx.dart | 1 + .../lib/src/widgets/pdf_viewer_params.dart | 24 +++++++++++++------ ...df_viewer_scroll_interaction_delegate.dart | 6 ++--- .../sizing/pdf_viewer_size_delegate.dart | 6 ++--- .../pdf_viewer_size_delegate_legacy.dart | 4 +--- .../pdf_viewer_zoom_steps_delegate.dart | 2 +- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index ae892e1e..adc2d6b2 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -17,3 +17,4 @@ export 'src/widgets/sizing/pdf_viewer_size_delegate.dart'; export 'src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart'; export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart'; export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart'; +export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index b2c3e961..3f0f084d 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -79,13 +79,15 @@ class PdfViewerParams { this.sizeDelegateProvider, this.zoomStepsDelegateProvider = const PdfViewerZoomStepsDelegateProviderDefault(), }) : assert( - (maxScale == null && - minScale == null && - useAlternativeFitScaleAsMinScale == null && - onePassRenderingScaleThreshold == null || - calculateInitialZoom == null) || - sizeDelegateProvider == null, - 'You cannot set both maxScale, minScale, useAlternativeFitScaleAsMinScale, onePassRenderingScaleThreshold and sizeDelegateProvider at the same time. Please set these in the sizeDelegateProvider instead.', + sizeDelegateProvider == null || + (maxScale == null && + minScale == null && + useAlternativeFitScaleAsMinScale == null && + onePassRenderingScaleThreshold == null && + calculateInitialZoom == null), + 'sizeDelegateProvider cannot be used together with the deprecated parameters: ' + 'maxScale, minScale, useAlternativeFitScaleAsMinScale, onePassRenderingScaleThreshold, or calculateInitialZoom. ' + 'Please configure these values in the sizeDelegateProvider instead.', ); /// Margin around the page. @@ -342,6 +344,7 @@ class PdfViewerParams { /// Function to customize the rendering scale of the page. /// + // ignore: deprecated_member_use_from_same_package /// In some cases, if [maxScale]/[onePassRenderingScaleThreshold] is too large, /// certain pages may not be rendered correctly due to memory limitation, /// or anyway they may take too long to render. @@ -611,6 +614,8 @@ class PdfViewerParams { /// /// Defaults to [PdfViewerSizeDelegateProviderLegacy] which maintains /// relative positioning and boundary clamping. + /// + /// To get the actual delegate set, use [getSizeDelegateProvider]. final PdfViewerSizeDelegateProvider? sizeDelegateProvider; /// Provider to create a delegate that generates zoom stops (snap points). @@ -628,6 +633,10 @@ class PdfViewerParams { } } + /// Get the size delegate provider. + /// + /// If [sizeDelegateProvider] is non-null, it is returned; otherwise, a + /// [PdfViewerSizeDelegateProviderLegacy] is created with the deprecated parameters. PdfViewerSizeDelegateProvider getSizeDelegateProvider() { final sizeDelegateProvider = this.sizeDelegateProvider; if (sizeDelegateProvider != null) { @@ -726,6 +735,7 @@ class PdfViewerParams { other.onDocumentChanged == onDocumentChanged && other.onDocumentLoadFinished == onDocumentLoadFinished && other.calculateInitialPageNumber == calculateInitialPageNumber && + // ignore: deprecated_member_use_from_same_package other.calculateInitialZoom == calculateInitialZoom && other.calculateCurrentPageNumber == calculateCurrentPageNumber && other.onViewerReady == onViewerReady && diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart index b92dadc4..1d9e0898 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart @@ -21,7 +21,7 @@ abstract class PdfViewerScrollInteractionDelegateProvider { /// Creates the runtime delegate instance. /// - /// This is called by [PdfViewerState] when the widget initializes or when the + /// This is called by [PdfViewer] when the widget initializes or when the /// provider configuration changes. PdfViewerScrollInteractionDelegate create(); @@ -39,13 +39,13 @@ abstract class PdfViewerScrollInteractionDelegateProvider { /// the **Execution** (e.g., "Jump immediately" vs "Animate with friction"). /// /// Lifecycle: -/// 1. [create] is called by the Provider. +/// 1. Created by [PdfViewerScrollInteractionDelegateProvider]'s instance. /// 2. [init] is called when the controller is attached. /// 3. [pan] / [zoom] are called on user interaction. /// 4. [stop] is called when the user interrupts (e.g., touches the screen). /// 5. [dispose] is called when the viewer is destroyed. abstract class PdfViewerScrollInteractionDelegate { - /// Called when the [PdfViewerState] is ready. + /// Called when the [PdfViewer] is ready. /// /// [controller]: Used to read/write the transformation matrix. /// [vsync]: Used to create [Ticker]s for physics-based animations. diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart index b597404c..d84a63f8 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart @@ -17,7 +17,7 @@ abstract class PdfViewerSizeDelegateProvider { /// Creates the runtime delegate instance. /// - /// This is called by [PdfViewerState] when the widget initializes or when the + /// This is called by [PdfViewer] when the widget initializes or when the /// provider configuration changes. PdfViewerSizeDelegate create(); @@ -36,7 +36,7 @@ abstract class PdfViewerSizeDelegateProvider { /// what the zoom limits are, and how it reacts when the viewport changes size. /// /// ### Lifecycle & Controller Access -/// 1. [create] is called by the Provider. +/// 1. Created by [PdfViewerSizeDelegateProvider]'s instance. /// 2. **[calculateMetrics] may be called immediately.** /// * **Warning:** At this stage, [init] has *not* been called yet. /// * Implementations must not access the controller or internal state here. @@ -46,7 +46,7 @@ abstract class PdfViewerSizeDelegateProvider { /// 5. [onLayoutUpdate] is called **repeatedly** whenever the view size changes or the document layout changes. /// 6. [dispose] is called when the viewer is destroyed. abstract class PdfViewerSizeDelegate { - /// Called when the [PdfViewerState] initializes or dependencies change. + /// Called when the [PdfViewer] initializes or dependencies change. /// /// Implementations should store the [controller] to manipulate the view /// during [onLayoutInitialized] and [onLayoutUpdate]. diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart index 362f4271..6798bcbb 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart @@ -69,7 +69,7 @@ class PdfViewerSizeDelegateProviderLegacy extends PdfViewerSizeDelegateProvider /// So you may want to set the smaller value to reduce memory consumption. /// /// The default is 200 / 72, which implies rendering at 200 dpi. - /// If you want more granular control for each page, use [getPageRenderingScale]. + /// If you want more granular control for each page, use [PdfViewerParams.getPageRenderingScale]. final double onePassRenderingScaleThreshold; /// Optional callback to customize the initial zoom level calculation. @@ -292,6 +292,4 @@ class PdfViewerSizeDelegateLegacy implements PdfViewerSizeDelegate { controller.value = clamped; } } - - static bool _areZoomsAlmostIdentical(double z1, double z2) => (z1 - z2).abs() < 0.01; } diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart index ec9f481d..9f3fe7bf 100644 --- a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart @@ -13,7 +13,7 @@ import '../../../pdfrx.dart'; /// 1. **Maintain Consistency:** Match the architecture of [PdfViewerSizeDelegate] and /// [PdfViewerScrollInteractionDelegate]. /// 2. **Future Proofing:** Allow for potential stateful logic (e.g., caching calculations) -/// or resource management (via [dispose]) in the future without breaking the API. +/// or resource management (via `dispose`) in the future without breaking the API. abstract class PdfViewerZoomStepsDelegateProvider { const PdfViewerZoomStepsDelegateProvider(); From bc32d8c4ffb543689aa7248625380f9120d1f269 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Fri, 16 Jan 2026 06:14:19 +0900 Subject: [PATCH 660/663] WIP: updating docs for 2.3.0 --- README.md | 2 +- packages/pdfrx/CHANGELOG.md | 15 +++++++++++++++ packages/pdfrx/README.md | 2 +- .../pdf_viewer_zoom_steps_delegate_smart.dart | 2 +- packages/pdfrx/pubspec.yaml | 2 +- packages/pdfrx_coregraphics/README.md | 2 +- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 72c08900..7cb3a112 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Add [pdfrx](https://pub.dev/packages/pdfrx) to your `pubspec.yaml`: ```yaml dependencies: - pdfrx: ^2.2.15 + pdfrx: ^2.3.0 ``` ### For Pure Dart Applications diff --git a/packages/pdfrx/CHANGELOG.md b/packages/pdfrx/CHANGELOG.md index 943c56a4..f57b0d4b 100644 --- a/packages/pdfrx/CHANGELOG.md +++ b/packages/pdfrx/CHANGELOG.md @@ -1,3 +1,18 @@ +# 2.3.0 + +- NEW: Pluggable scroll/zoom interaction architecture ([#581](https://github.com/espresso3389/pdfrx/pull/581)) + - `PdfViewerScrollInteractionDelegateProviderInstant` (default) - instant updates (legacy behavior) + - `PdfViewerScrollInteractionDelegateProviderPhysics` - smooth, physics-based animations for mouse wheel and trackpad + - New parameter `scaleByPointerScale` to control trackpad pinch/Ctrl+scroll sensitivity + - Shift+scroll now triggers horizontal scrolling (standard desktop behavior) +- NEW: Pluggable sizing/layout architecture ([#582](https://github.com/espresso3389/pdfrx/pull/582)) + - `PdfViewerSizeDelegateProviderLegacy` (default) - maintains existing behavior + - `PdfViewerSizeDelegateProviderSmart` - responsive resizing with content centering and adaptive scaling + - `PdfViewerZoomStepsDelegate` for customizable double-tap zoom snap points +- DEPRECATED: `maxScale`, `minScale`, `useAlternativeFitScaleAsMinScale`, `onePassRenderingScaleThreshold`, and `calculateInitialZoom` parameters in `PdfViewerParams` - use `sizeDelegateProvider` instead +- NEW: `PdfViewerController.maxScale` getter +- NEW: `PdfViewerController.goToPosition()` method + # 2.2.24 - Updated to pdfrx_engine 0.3.9 diff --git a/packages/pdfrx/README.md b/packages/pdfrx/README.md index 625ca05f..19d0b10c 100644 --- a/packages/pdfrx/README.md +++ b/packages/pdfrx/README.md @@ -60,7 +60,7 @@ Add this to your package's `pubspec.yaml` file and execute `flutter pub get`: ```yaml dependencies: - pdfrx: ^2.2.20 + pdfrx: ^2.3.0 ``` **Note:** You only need to add `pdfrx` to your dependencies. The `pdfrx_engine` package is automatically included as a dependency of `pdfrx`. diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart index 211d1d3e..de082bab 100644 --- a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart @@ -12,7 +12,7 @@ class PdfViewerZoomStepsDelegateProviderSmart extends PdfViewerZoomStepsDelegate /// The target geometric factor between zoom stops. /// - /// Used by [PdfViewerSizeDelegate.generateZoomStops] to fill gaps between semantic + /// Used by [PdfViewerZoomStepsDelegateSmart.generateZoomStops] to fill gaps between semantic /// zoom levels (like Fit Page and Fit Width). final double zoomStep; diff --git a/packages/pdfrx/pubspec.yaml b/packages/pdfrx/pubspec.yaml index 6d1a0e82..910fcd60 100644 --- a/packages/pdfrx/pubspec.yaml +++ b/packages/pdfrx/pubspec.yaml @@ -1,6 +1,6 @@ name: pdfrx description: pdfrx is a rich and fast PDF viewer and manipulation plugin built on the top of PDFium. Supports viewing, editing, combining PDFs on Android, iOS, Windows, macOS, Linux, and Web. -version: 2.2.24 +version: 2.3.0 homepage: https://github.com/espresso3389/pdfrx repository: https://github.com/espresso3389/pdfrx/tree/master/packages/pdfrx issue_tracker: https://github.com/espresso3389/pdfrx/issues diff --git a/packages/pdfrx_coregraphics/README.md b/packages/pdfrx_coregraphics/README.md index 3cf22f6b..ee4f14cc 100644 --- a/packages/pdfrx_coregraphics/README.md +++ b/packages/pdfrx_coregraphics/README.md @@ -14,7 +14,7 @@ Add the package to your Flutter app: ```yaml dependencies: - pdfrx: ^2.2.23 + pdfrx: ^2.3.0 pdfrx_coregraphics: ^0.1.15 ``` From 368eba1cfb527f435c44c51762b7c6dc2b8d34e4 Mon Sep 17 00:00:00 2001 From: quaaantumdev Date: Fri, 16 Jan 2026 16:01:24 +0100 Subject: [PATCH 661/663] Added a smarter sizing strategy --- packages/pdfrx/lib/pdfrx.dart | 1 + .../pdfrx/lib/src/widgets/pdf_viewer.dart | 8 +- ...df_viewer_scroll_interaction_delegate.dart | 7 +- ...r_scroll_interaction_delegate_instant.dart | 11 +- ...r_scroll_interaction_delegate_physics.dart | 9 +- .../pdf_viewer_size_delegate_smart.dart | 409 ++++++++++++++++++ 6 files changed, 425 insertions(+), 20 deletions(-) create mode 100644 packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart diff --git a/packages/pdfrx/lib/pdfrx.dart b/packages/pdfrx/lib/pdfrx.dart index adc2d6b2..d3842ea3 100644 --- a/packages/pdfrx/lib/pdfrx.dart +++ b/packages/pdfrx/lib/pdfrx.dart @@ -15,6 +15,7 @@ export 'src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_in export 'src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart'; export 'src/widgets/sizing/pdf_viewer_size_delegate.dart'; export 'src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart'; +export 'src/widgets/sizing/pdf_viewer_size_delegate_smart.dart'; export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart'; export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart'; export 'src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart'; diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 8cdc9a9f..ab856b90 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -356,8 +356,6 @@ class _PdfViewerState extends State ..load(); } - // we dont check/update `_interactionDelegate` here because we dont assume the user - _onDocumentChanged(); } @@ -1597,7 +1595,7 @@ class _PdfViewerState extends State final scaleFactor = (rawScaleFactor - 1.0) * dampening + 1.0; // NOTE: _onWheelDelta may be called from other widget's context and localPosition may be incorrect. - _interactionDelegate?.zoom(scaleFactor, _controller!.globalToLocal(event.position)!); + _interactionDelegate?.zoom(scaleFactor, _controller!.globalToLocal(event.position)!, _layoutMetrics); return; } @@ -1627,7 +1625,7 @@ class _PdfViewerState extends State delta = Offset(dx, dy); } - _interactionDelegate?.pan(delta); + _interactionDelegate?.pan(delta, _layoutMetrics); } finally { _stopInteraction(); } @@ -1638,7 +1636,7 @@ class _PdfViewerState extends State try { final dampening = widget.params.scaleByPointerScale; final scaleFactor = (event.scale - 1.0) * dampening + 1.0; - _interactionDelegate?.zoom(scaleFactor, event.localPosition); + _interactionDelegate?.zoom(scaleFactor, event.localPosition, _layoutMetrics); } finally { _stopInteraction(); } diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart index 1d9e0898..22d1d0bd 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart @@ -2,9 +2,8 @@ import 'package:flutter/gestures.dart' show PointerScrollEvent; import 'package:flutter/scheduler.dart' show Ticker; import 'package:flutter/widgets.dart'; -import '../../../pdfrx.dart' show PdfViewerParams; +import '../../../pdfrx.dart' show PdfViewerParams, PdfViewerLayoutMetrics; import '../pdf_viewer.dart'; -import '../pdf_viewer_params.dart' show PdfViewerParams; /// Interface for a factory that creates [PdfViewerScrollInteractionDelegate] instances. /// @@ -66,12 +65,12 @@ abstract class PdfViewerScrollInteractionDelegate { /// /// [delta] is the requested move distance in **viewport logical pixels**. /// (e.g., [PointerScrollEvent.scrollDelta]). - void pan(Offset delta); + void pan(Offset delta, PdfViewerLayoutMetrics layoutMetrics); /// Request a zoom operation. /// /// [scale] is the relative scale factor (e.g., `1.1` means "increase zoom by 10%"). /// [focalPoint] is the pixel position in the viewport that should remain stationary /// during the zoom (the anchor point). - void zoom(double scale, Offset focalPoint); + void zoom(double scale, Offset focalPoint, PdfViewerLayoutMetrics layoutMetrics); } diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart index 234776e6..840553d5 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart @@ -1,7 +1,6 @@ import 'package:flutter/widgets.dart'; -import '../pdf_viewer.dart'; -import 'pdf_viewer_scroll_interaction_delegate.dart'; +import '../../../pdfrx.dart'; /// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Instant** behavior. /// @@ -45,7 +44,7 @@ class _PdfViewerScrollInteractionDelegateInstant implements PdfViewerScrollInter } @override - void pan(Offset delta) { + void pan(Offset delta, PdfViewerLayoutMetrics layoutMetrics) { final controller = _controller; if (controller == null || !controller.isReady) { return; @@ -65,7 +64,7 @@ class _PdfViewerScrollInteractionDelegateInstant implements PdfViewerScrollInter } @override - void zoom(double scale, Offset focalPoint) { + void zoom(double scale, Offset focalPoint, PdfViewerLayoutMetrics layoutMetrics) { final controller = _controller; if (controller == null || !controller.isReady) { return; @@ -73,8 +72,8 @@ class _PdfViewerScrollInteractionDelegateInstant implements PdfViewerScrollInter final currentZoom = controller.currentZoom; - // Calculate the target zoom level, clamped to the min/max allowed by params. - final newZoom = (currentZoom * scale).clamp(controller.minScale, controller.maxScale); + // Calculate the target zoom level, clamped to the min/max allowed by layoutMetrics. + final newZoom = (currentZoom * scale).clamp(layoutMetrics.minScale, layoutMetrics.maxScale); // Optimization: Ignore negligible changes to prevent unnecessary rebuilds. if ((newZoom - currentZoom).abs() < 0.0001) { diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart index 96e4d24c..77cf25b2 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart @@ -4,8 +4,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:vector_math/vector_math_64.dart' as vec; -import '../pdf_viewer.dart'; -import 'pdf_viewer_scroll_interaction_delegate.dart'; +import '../../../pdfrx.dart'; /// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Physics-based** behavior. /// @@ -100,7 +99,7 @@ class _PdfViewerScrollInteractionDelegatePhysics implements PdfViewerScrollInter } @override - void pan(Offset delta) { + void pan(Offset delta, PdfViewerLayoutMetrics layoutMetrics) { final controller = _controller; final vsync = _vsync; if (controller == null || !controller.isReady || vsync == null) { @@ -196,7 +195,7 @@ class _PdfViewerScrollInteractionDelegatePhysics implements PdfViewerScrollInter } @override - void zoom(double scaleFactor, Offset focalPoint) { + void zoom(double scaleFactor, Offset focalPoint, PdfViewerLayoutMetrics layoutMetrics) { final controller = _controller; final vsync = _vsync; if (controller == null || !controller.isReady || vsync == null) return; @@ -210,7 +209,7 @@ class _PdfViewerScrollInteractionDelegatePhysics implements PdfViewerScrollInter _zoomTarget ??= currentZoom; // Apply accumulated scale to target - _zoomTarget = (_zoomTarget! * scaleFactor).clamp(controller.minScale, controller.maxScale); + _zoomTarget = (_zoomTarget! * scaleFactor).clamp(layoutMetrics.minScale, layoutMetrics.maxScale); // Update last focal point for the animation tick _lastFocalPoint = focalPoint; diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart new file mode 100644 index 00000000..ce927aec --- /dev/null +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart @@ -0,0 +1,409 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/rendering.dart'; + +import '../../../pdfrx.dart'; + +/// A provider that creates a [PdfViewerSizeDelegateSmart] instance with smart scaling configuration. +/// +/// This provider configures a sizing strategy that offers a modern, natural +/// viewing experience. It prioritizes keeping content centered and adapts the +/// zoom level intelligently during viewport changes (e.g., window resize, +/// device rotation, or layout adjustments). +/// +/// ### Behavior Scenarios +/// +/// The delegate analyzes the state *before* the resize to determine the user's intent: +/// +/// * **Sticky Fit (Fit Width):** If the content fits the width exactly (within margin of error): +/// * **Shrinking:** The content shrinks with the viewport to maintain "Fit Width". +/// * **Growing:** The content grows with the viewport to maintain "Fit Width", +/// **up to** the [smartMaxScale]. Once this limit is reached, it stops growing +/// and introduces horizontal whitespace. +/// +/// * **Whitespace (Underflow):** If the content is smaller than the viewport: +/// * **General:** The absolute zoom level is preserved, and the content is re-centered. +/// * **Catch-on:** If the viewport shrinks so much that the content would start being +/// clipped, it switches to "Fit Width" mode and shrinks the content. +/// +/// * **Zoomed In (Overflow):** If the content is larger than the viewport (horizontal scrolling): +/// * The absolute zoom level is preserved. The viewport is re-centered on the +/// same point in the document. +/// +/// ### Comparison with Legacy Strategy +/// +/// * **Legacy (`PdfViewerSizeDelegateProviderLegacy`)**: Aligns content to the top-left. +/// Strictly preserves the exact zoom level during resizing unless limits are violated. +/// Defaults to "Fit Page" (full page visible) on initialization. +/// * **Smart (`PdfViewerSizeDelegateProviderSmart`)**: Aligns content to the center. +/// Adapts zoom level to keep content fitting the screen width. Defaults to "Fit Width" +/// on initialization. Offers more of a high-level API, deliberately not implementing a +/// calculateInitialZoom callback. +/// +class PdfViewerSizeDelegateProviderSmart extends PdfViewerSizeDelegateProvider { + const PdfViewerSizeDelegateProviderSmart({ + double? minScale, + double? maxScale, + double? smartMaxScale, + double? maxPagesVisible, + double? onePassRenderingScaleThreshold, + }) : maxScale = maxScale ?? 8.0, + minScale = minScale ?? 0.1, + smartMaxScale = smartMaxScale ?? 1.3, + maxPagesVisible = maxPagesVisible ?? 3.0, + onePassRenderingScaleThreshold = onePassRenderingScaleThreshold ?? 200 / 72; + + /// The maximum allowed scale. + /// + /// The default is 8.0. + final double maxScale; + + /// The minimum allowed scale. + /// + /// The default is 0.1. + /// + /// This is the hard ceiling for the viewer. Even if "Fit Width" logic would + /// prefer a larger scale, the viewer will not exceed this value. + final double minScale; + + /// The maximum number of pages (approximately) visible when zoomed out to the minimum. + /// + /// This factor divides the "Fit Page" scale to determine the effective minimum scale. + /// * `1.0`: Minimum scale fits exactly one page (Fit Page). + /// * `3.0`: (default) Minimum scale fits two pages. + /// * `double.infinity`: Ignore "Fit Page" limit entirely; use [minScale] as the only floor. + final double maxPagesVisible; + + /// If a page is rendered over the scale threshold, the page is rendered with the threshold scale + /// and actual resolution image is rendered after some delay (progressive rendering). + /// + /// Basically, if the value is larger, the viewer renders each page in one-pass rendering; it is + /// faster and looks better to the user. However, larger value may consume more memory. + /// So you may want to set the smaller value to reduce memory consumption. + /// + /// The default is 200 / 72, which implies rendering at 200 dpi. + /// If you want more granular control for each page, use [PdfViewerParams.getPageRenderingScale]. + final double onePassRenderingScaleThreshold; + + /// The maximum zoom level for automatic "Fit Width" scaling. + /// + /// This prevents the document from becoming uncomfortably large on very wide screens. + /// For example, if set to 1.2, resizing the window to be very wide will stop + /// zooming the document once it reaches 120% scale, centering it with margins instead. + final double smartMaxScale; + + @override + PdfViewerSizeDelegate create() => PdfViewerSizeDelegateSmart( + smartMaxScale: smartMaxScale, + maxScale: maxScale, + minScale: minScale, + maxPagesVisible: maxPagesVisible, + onePassRenderingScaleThreshold: onePassRenderingScaleThreshold, + ); + + @override + bool operator ==(Object other) => + other is PdfViewerSizeDelegateProviderSmart && + other.smartMaxScale == smartMaxScale && + other.maxScale == maxScale && + other.minScale == minScale && + other.onePassRenderingScaleThreshold == onePassRenderingScaleThreshold && + other.maxPagesVisible == maxPagesVisible; + + @override + int get hashCode => Object.hash(smartMaxScale, maxScale, minScale, onePassRenderingScaleThreshold, maxPagesVisible); +} + +/// A "Smart" resize delegate that adapts zoom to fit the page width and centers content. +/// +/// ### Core Behaviors +/// 1. **Smart Initialization**: Defaults to "Fit Width" (capped at [_smartMaxScale]) +/// instead of "Fit Page". +/// 2. **Adaptive Resizing**: +/// * **Shrinking**: If the window shrinks, the content scales down to stay +/// fully visible width-wise ("Catch on"). +/// * **Growing**: If the window grows, the content scales up to fit width, +/// but stops growing at [_smartMaxScale]. +/// 3. **Centering**: Unlike the default behavior (which clamps to top-left), +/// this delegate keeps the view centered on the same point in the document +/// during resizing. +class PdfViewerSizeDelegateSmart implements PdfViewerSizeDelegate { + PdfViewerSizeDelegateSmart({ + required double smartMaxScale, + required double maxScale, + required double minScale, + required double maxPagesVisible, + required this.onePassRenderingScaleThreshold, + }) : _minScale = minScale, + _maxScale = maxScale, + _smartMaxScale = smartMaxScale, + _maxPagesVisible = maxPagesVisible; + + final double _maxScale; + final double _minScale; + final double _maxPagesVisible; + final double _smartMaxScale; + + PdfViewerController? _controller; + + @override + final double onePassRenderingScaleThreshold; + + @override + void init(PdfViewerController controller) { + _controller = controller; + } + + @override + void dispose() { + _controller = null; + } + + @override + PdfViewerLayoutMetrics calculateMetrics({ + required Size viewSize, + required PdfPageLayout? layout, + required int? pageNumber, + required double pageMargin, + required EdgeInsets? boundaryMargin, + }) { + // Reuse the legacy math for geometric limits (coverScale/alternativeFitScale) + // We can delegate this calculation or duplicate the math (it's purely geometric). + // Duplicating for clarity/independence: + final bmh = boundaryMargin?.horizontal == double.infinity ? 0 : boundaryMargin?.horizontal ?? 0; + final bmv = boundaryMargin?.vertical == double.infinity ? 0 : boundaryMargin?.vertical ?? 0; + + var coverScale = 1.0; + double? alternativeFitScale; + + if (layout != null) { + final s1 = viewSize.width / (layout.documentSize.width + bmh); + final s2 = viewSize.height / (layout.documentSize.height + bmv); + coverScale = math.max(s1, s2); + + if (pageNumber != null && pageNumber >= 1 && pageNumber <= layout.pageLayouts.length) { + final rect = layout.pageLayouts[pageNumber - 1]; + final m2 = pageMargin * 2; + alternativeFitScale = math.min( + (viewSize.width) / (rect.width + bmh + m2), + (viewSize.height) / (rect.height + bmv + m2), + ); + } + } + + // Smart Policy for Min Scale: + // 1. Calculate "Fit Page" scale (fallback to coverScale if page not found) + final fitPageScale = alternativeFitScale ?? coverScale; + + // 2. Adjust for multi-page visibility + // If maxPagesVisible is 1.0, this is Fit Page. + // If maxPagesVisible is 2.0, we allow zooming out 2x further. + // If maxPagesVisible is infinity, this becomes 0. + final allowedMinScale = fitPageScale / _maxPagesVisible; + + // 3. The minimum scale is whichever is larger: the hard configuration or the physical fit. + // This prevents zooming out further than the page size. + final effectiveMinScale = math.max(_minScale, allowedMinScale); + + return PdfViewerLayoutMetrics( + minScale: effectiveMinScale, + maxScale: _maxScale, + coverScale: coverScale, + alternativeFitScale: alternativeFitScale, + ); + } + + @override + void onLayoutInitialized({ + required PdfViewerLayoutSnapshot state, + required int initialPageNumber, + required double coverScale, + required double? alternativeFitScale, + required PdfPageLayout layout, + required PdfDocument document, + }) { + final controller = _controller; + if (controller == null) return; + + // --- Smart Initialization --- + // Instead of defaulting to "Fit Page" (which might be tiny on a large monitor), + // we default to "Fit Width" (clamped by smartMaxScale). + + // 1. Calculate raw Fit Width Scale + final docWidth = state.layout?.documentSize.width ?? 1.0; + // Avoid division by zero + final rawFitWidthScale = docWidth > 0 ? state.viewSize.width / docWidth : 1.0; + + // 2. Limit the Fit Width Scale + // This is the "Smart" part: We prevent the default zoom from being too large on wide screens. + // e.g. On a 4k monitor, Fit Width might be 300%. We cap this default to 120% (smartMaxScale). + final effectiveFitWidthScale = (rawFitWidthScale > _smartMaxScale) ? _smartMaxScale : rawFitWidthScale; + + // 3. Determine Initial Zoom + var zoom = effectiveFitWidthScale; + + // 4. Hard Constraints + // Ensure we are within the hard configuration limits + if (zoom < _minScale) { + zoom = _minScale; + } + if (zoom > _maxScale) { + zoom = _maxScale; + } + + // Ensure we don't go below the effective minimum calculated by the Viewer State + // (e.g. if Fit Page is required to avoid rendering issues) + if (zoom < state.minScale) { + zoom = state.minScale; + } + + // 5. Apply + unawaited(controller.setZoom(Offset.zero, zoom, duration: Duration.zero)); + } + + @override + void onLayoutUpdate({ + required PdfViewerLayoutSnapshot oldState, + required PdfViewerLayoutSnapshot newState, + required double currentZoom, + required Rect oldVisibleRect, + required int? anchorPageNumber, + required bool isLayoutChanged, + required bool isViewSizeChanged, + }) { + final controller = _controller; + if (controller == null) return; + + // Calculate the target zoom based on the legacy "preserve zoom" rule first. + // This is often just currentZoom, but handles minScale clamping logic. + final zoomTo = currentZoom < newState.minScale || currentZoom == oldState.minScale + ? newState.minScale + : currentZoom; + + if (isLayoutChanged) { + // --- 1. Handle Layout Changes --- + // If the document layout changed (e.g. pages rotated, added, or margins changed), + // we defer to the Default/Legacy logic. Mapping visual positions across layout + // changes is complex, and the default implementation handles it robustly + // (mapping the user's previous look-at point to the new layout). + + final oldLayout = oldState.layout; + + if (oldLayout != null && anchorPageNumber != null) { + // Use the controller's helper to find where the user was looking + final hit = controller.getClosestPageHit(anchorPageNumber, oldLayout, oldVisibleRect); + final pageNumber = hit?.page.pageNumber ?? anchorPageNumber; + + // Compute relative position within the old pageRect + final oldPageRect = oldLayout.pageLayouts[pageNumber - 1]; + final newPageRect = newState.layout!.pageLayouts[pageNumber - 1]; + final oldOffset = oldVisibleRect.topLeft - oldPageRect.topLeft; + final fracX = oldOffset.dx / oldPageRect.width; + final fracY = oldOffset.dy / oldPageRect.height; + + // Map into new layoutRect + final newOffset = Offset( + newPageRect.left + fracX * newPageRect.width, + newPageRect.top + fracY * newPageRect.height, + ); + + // Preserve the position after a layout change + unawaited(controller.goToPosition(documentOffset: newOffset, zoom: zoomTo)); + } + + return; + } + + // From here on, we assume only the View Size (window size) changed. + assert(isViewSizeChanged); + + final oldLayout = oldState.layout; + final newLayout = newState.layout; + if (oldLayout == null || newLayout == null) return; + + final oldSize = oldState.viewSize; + final newSize = newState.viewSize; + + // --- 2. Calculate Fit Metrics --- + // We compare the viewport width to the document width to determine the "Fit Width" ratio. + final oldDocWidth = oldLayout.documentSize.width; + final newDocWidth = newLayout.documentSize.width; + + final oldFitScale = oldSize.width / oldDocWidth; + final newFitScale = newSize.width / newDocWidth; + + // --- 3. Determine User Intent (State Machine) --- + // We analyze the state BEFORE the resize to guess what the user wants. + const epsilon = 0.01; + + // State B: Sticky Fit + // The user was effectively viewing "Fit Width" before the resize. + final wasFittingExact = (currentZoom - oldFitScale).abs() < epsilon; + + // State A: Overflow (Zoomed In) + // The user was zoomed in closer than "Fit Width" (horizontal scrolling possible). + final wasOverflowing = !wasFittingExact && currentZoom > oldFitScale; + + var targetZoom = currentZoom; + + if (wasOverflowing) { + // Scenario A: User Manual Zoom (Overflow) + // If the user manually zoomed in, we preserve the absolute zoom level. + // We don't snap them back to "Fit Width" just because they resized the window. + // Position is maintained by the centering logic below. + targetZoom = currentZoom; + } else if (wasFittingExact) { + // Scenario B: Sticky Fit + // The content was fitting the width. We want it to *continue* fitting the width + // of the new window size. + targetZoom = newFitScale; + + // Smart Limit Logic (Growing): + // If the window is growing, we follow the width up to [smartMaxScale]. + if (newFitScale > oldFitScale) { + if (targetZoom > _smartMaxScale) { + targetZoom = _smartMaxScale; + // Exception: If the user was somehow already above the limit (e.g. manual zoom + // followed by a resize that landed in sticky range), don't snap DOWN to the limit. + if (targetZoom < currentZoom) { + targetZoom = currentZoom; + } + } + } + // Smart Limit Logic (Shrinking): + // If shrinking, we always accept `newFitScale` to ensure the content stays + // inside the window (avoiding horizontal scrolling). + } else { + // Scenario C: Underflow (Whitespace) + // The content was smaller than the viewport (zoomed out). + // Generally, keep the absolute zoom and just recenter. + targetZoom = currentZoom; + + // "Catch On" Logic: + // If the window shrinks so much that the old zoom would now cause overflow, + // we switch strategy to "Fit Width" to prevent content from being clipped. + if (targetZoom > newFitScale) { + targetZoom = newFitScale; + } + } + + // --- 4. Apply Hard Constraints --- + final minS = newState.minScale; + // Ensure we don't violate the viewer's absolute minimum limit (calculated by State). + if (targetZoom < minS) targetZoom = minS; + + // --- 5. Apply Transformation (Centering) --- + // Instead of clamping to the top-left (default behavior), we pivot around the center. + + // Find the point in the document currently at the center of the viewport + final oldCenterInDoc = controller.value.calcPosition(oldSize); + + // Create a new matrix that puts that same document point at the center of the NEW viewport + final newMatrix = controller.calcMatrixFor(oldCenterInDoc, zoom: targetZoom, viewSize: newSize); + + // Stop any physics animations (inertia) to prevent fighting with the layout update + controller.stopInteractiveViewerAnimation(); + controller.value = newMatrix; + } +} From b6cd5963c7d79bc53c88176b47161d4b8fc70cb0 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 17 Jan 2026 18:16:24 +0900 Subject: [PATCH 662/663] Fixes internal imports not to use `pdfrx.dart` as much as possible; even some imports import `pdfrx.dart` with `show` --- packages/pdfrx/lib/src/pdf_document_ref.dart | 3 ++- packages/pdfrx/lib/src/pdfrx_flutter.dart | 2 +- packages/pdfrx/lib/src/utils/native/native.dart | 3 +-- packages/pdfrx/lib/src/utils/web/web.dart | 2 +- .../lib/src/widgets/internals/pdf_error_widget.dart | 2 +- .../widgets/internals/pdf_viewer_key_handler.dart | 2 +- .../lib/src/widgets/pdf_page_links_overlay.dart | 4 +++- .../pdfrx/lib/src/widgets/pdf_text_searcher.dart | 5 ++++- packages/pdfrx/lib/src/widgets/pdf_viewer.dart | 9 ++++++++- .../lib/src/widgets/pdf_viewer_layout_metrics.dart | 2 +- .../pdfrx/lib/src/widgets/pdf_viewer_params.dart | 13 ++++++++++++- packages/pdfrx/lib/src/widgets/pdf_widgets.dart | 4 +++- .../pdf_viewer_scroll_interaction_delegate.dart | 3 ++- ..._viewer_scroll_interaction_delegate_instant.dart | 4 +++- ..._viewer_scroll_interaction_delegate_physics.dart | 4 +++- .../widgets/sizing/pdf_viewer_size_delegate.dart | 7 +++++-- .../sizing/pdf_viewer_size_delegate_legacy.dart | 7 ++++++- .../sizing/pdf_viewer_size_delegate_smart.dart | 6 +++++- .../zoom_steps/pdf_viewer_zoom_steps_delegate.dart | 5 ++++- .../pdf_viewer_zoom_steps_delegate_default.dart | 3 ++- .../pdf_viewer_zoom_steps_delegate_smart.dart | 3 ++- 21 files changed, 70 insertions(+), 23 deletions(-) diff --git a/packages/pdfrx/lib/src/pdf_document_ref.dart b/packages/pdfrx/lib/src/pdf_document_ref.dart index 148cc791..1e857fc1 100644 --- a/packages/pdfrx/lib/src/pdf_document_ref.dart +++ b/packages/pdfrx/lib/src/pdf_document_ref.dart @@ -2,9 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:synchronized/extension.dart'; -import '../pdfrx.dart'; +import 'pdfrx_flutter.dart'; /// Callback function to notify download progress. /// diff --git a/packages/pdfrx/lib/src/pdfrx_flutter.dart b/packages/pdfrx/lib/src/pdfrx_flutter.dart index b00928b1..27c5d64b 100644 --- a/packages/pdfrx/lib/src/pdfrx_flutter.dart +++ b/packages/pdfrx/lib/src/pdfrx_flutter.dart @@ -4,8 +4,8 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show WidgetsFlutterBinding; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../pdfrx.dart'; import 'utils/platform.dart'; bool _isInitialized = false; diff --git a/packages/pdfrx/lib/src/utils/native/native.dart b/packages/pdfrx/lib/src/utils/native/native.dart index d20b1092..a93d2765 100644 --- a/packages/pdfrx/lib/src/utils/native/native.dart +++ b/packages/pdfrx/lib/src/utils/native/native.dart @@ -2,8 +2,7 @@ import 'dart:io'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; - -import '../../../pdfrx.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; final isApple = Platform.isMacOS || Platform.isIOS; final isAndroid = Platform.isAndroid; diff --git a/packages/pdfrx/lib/src/utils/web/web.dart b/packages/pdfrx/lib/src/utils/web/web.dart index 66b96fb3..8b9d94d0 100644 --- a/packages/pdfrx/lib/src/utils/web/web.dart +++ b/packages/pdfrx/lib/src/utils/web/web.dart @@ -2,9 +2,9 @@ import 'dart:js_interop'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:web/web.dart' as web; -import '../../../pdfrx.dart'; import '../../wasm/pdfrx_wasm.dart'; final isApple = false; diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart index ae8b09f2..f346ab42 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_error_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import '../../../pdfrx.dart'; import '../../utils/platform.dart'; /// Show error widget when pdf viewer failed to load pdf. diff --git a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart index c6994530..1cfcfedc 100644 --- a/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart +++ b/packages/pdfrx/lib/src/widgets/internals/pdf_viewer_key_handler.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import '../../../pdfrx.dart'; +import '../pdf_viewer_params.dart'; /// A widget that handles key events for the PDF viewer. class PdfViewerKeyHandler extends StatelessWidget { diff --git a/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart index 0f207bf0..246f1f56 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_page_links_overlay.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../pdfrx.dart'; +import '../pdfrx_flutter.dart'; +import 'pdf_viewer_params.dart'; typedef PdfPageLinkWrapperWidgetBuilder = Widget Function(Widget child); diff --git a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart index 15c587bf..b5484930 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_text_searcher.dart @@ -2,8 +2,11 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../pdfrx.dart'; +import '../pdfrx_flutter.dart'; +import 'pdf_viewer.dart'; +import 'pdf_viewer_params.dart' show PdfViewerParams; /// Helper class to interactively search text in a PDF document. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index ab856b90..b1f95e06 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -8,11 +8,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:rxdart/rxdart.dart'; import 'package:synchronized/extension.dart'; import 'package:vector_math/vector_math_64.dart' as vec; -import '../../pdfrx.dart'; +import '../pdf_document_ref.dart'; +import '../pdfrx_flutter.dart'; import '../utils/edge_insets_extensions.dart'; import '../utils/platform.dart'; import 'interactive_viewer.dart' as iv; @@ -20,6 +22,11 @@ import 'internals/pdf_error_widget.dart'; import 'internals/pdf_viewer_key_handler.dart'; import 'internals/widget_size_sniffer.dart'; import 'pdf_page_links_overlay.dart'; +import 'pdf_viewer_layout_metrics.dart'; +import 'pdf_viewer_params.dart'; +import 'scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart'; +import 'sizing/pdf_viewer_size_delegate.dart'; +import 'zoom_steps/pdf_viewer_zoom_steps_delegate.dart'; /// A widget to display PDF document. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart index e665d3ce..ee03eb18 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_layout_metrics.dart @@ -1,4 +1,4 @@ -import '../../pdfrx.dart'; +import 'sizing/pdf_viewer_size_delegate.dart'; /// A container for the calculated scaling limits of the viewer. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart index 3f0f084d..1fa13a5d 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer_params.dart @@ -3,9 +3,20 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../pdfrx.dart'; +import '../pdf_document_ref.dart'; +import '../utils/fixed_overscroll_physics.dart'; import '../utils/platform.dart'; +import 'pdf_viewer.dart'; +import 'pdf_viewer_scroll_thumb.dart'; +import 'scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart'; +import 'scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart'; +import 'scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart'; +import 'sizing/pdf_viewer_size_delegate.dart'; +import 'sizing/pdf_viewer_size_delegate_legacy.dart'; +import 'zoom_steps/pdf_viewer_zoom_steps_delegate.dart'; +import 'zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart'; /// Viewer customization parameters. /// diff --git a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart index 8021a8fe..a3b7db0a 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_widgets.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_widgets.dart @@ -4,8 +4,10 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../pdfrx.dart'; +import '../pdf_document_ref.dart'; +import '../pdfrx_flutter.dart'; /// A widget that loads PDF document. /// diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart index 22d1d0bd..84f160ba 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart @@ -2,8 +2,9 @@ import 'package:flutter/gestures.dart' show PointerScrollEvent; import 'package:flutter/scheduler.dart' show Ticker; import 'package:flutter/widgets.dart'; -import '../../../pdfrx.dart' show PdfViewerParams, PdfViewerLayoutMetrics; import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import '../pdf_viewer_params.dart'; /// Interface for a factory that creates [PdfViewerScrollInteractionDelegate] instances. /// diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart index 840553d5..32ebe094 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_instant.dart @@ -1,6 +1,8 @@ import 'package:flutter/widgets.dart'; -import '../../../pdfrx.dart'; +import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import 'pdf_viewer_scroll_interaction_delegate.dart'; /// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Instant** behavior. /// diff --git a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart index 77cf25b2..1cd96fd8 100644 --- a/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart +++ b/packages/pdfrx/lib/src/widgets/scroll_interaction/pdf_viewer_scroll_interaction_delegate_physics.dart @@ -4,7 +4,9 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:vector_math/vector_math_64.dart' as vec; -import '../../../pdfrx.dart'; +import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import 'pdf_viewer_scroll_interaction_delegate.dart'; /// A provider that creates a [PdfViewerScrollInteractionDelegate] with **Physics-based** behavior. /// diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart index d84a63f8..c15509ee 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart @@ -1,7 +1,10 @@ import 'package:flutter/rendering.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../../pdfrx.dart'; -import '../interactive_viewer.dart' show InteractiveViewer; +import '../interactive_viewer.dart'; +import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import '../pdf_viewer_params.dart'; /// Interface for a factory that creates [PdfViewerSizeDelegate] instances. /// diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart index 6798bcbb..a4300ecb 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_legacy.dart @@ -2,9 +2,14 @@ import 'dart:async'; import 'dart:math' as math; import 'package:flutter/rendering.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; import 'package:vector_math/vector_math_64.dart' as vec; -import '../../../pdfrx.dart'; +import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import '../pdf_viewer_params.dart'; +import 'pdf_viewer_size_delegate.dart'; +import 'pdf_viewer_size_delegate_smart.dart'; /// The default provider that creates the standard sizing behavior. /// diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart index ce927aec..0a38e012 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate_smart.dart @@ -2,8 +2,12 @@ import 'dart:async'; import 'dart:math' as math; import 'package:flutter/rendering.dart'; +import 'package:pdfrx_engine/pdfrx_engine.dart'; -import '../../../pdfrx.dart'; +import '../pdf_viewer.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import '../pdf_viewer_params.dart'; +import 'pdf_viewer_size_delegate.dart'; /// A provider that creates a [PdfViewerSizeDelegateSmart] instance with smart scaling configuration. /// diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart index 9f3fe7bf..fa522240 100644 --- a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate.dart @@ -1,4 +1,7 @@ -import '../../../pdfrx.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import '../pdf_viewer_params.dart'; +import '../scroll_interaction/pdf_viewer_scroll_interaction_delegate.dart'; +import '../sizing/pdf_viewer_size_delegate.dart'; /// Interface for a factory that creates [PdfViewerZoomStepsDelegate] instances. /// diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart index 60de9f1e..7b320f5a 100644 --- a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_default.dart @@ -1,4 +1,5 @@ -import '../../../pdfrx.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import 'pdf_viewer_zoom_steps_delegate.dart'; /// The default provider that creates the standard zoom stepping behavior. /// diff --git a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart index de082bab..8c55bba7 100644 --- a/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart +++ b/packages/pdfrx/lib/src/widgets/zoom_steps/pdf_viewer_zoom_steps_delegate_smart.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; -import '../../../pdfrx.dart'; +import '../pdf_viewer_layout_metrics.dart'; +import 'pdf_viewer_zoom_steps_delegate.dart'; /// The smart provider that creates the standard zoom stepping behavior. class PdfViewerZoomStepsDelegateProviderSmart extends PdfViewerZoomStepsDelegateProvider { From a1a6da1f6fa9d6728e2199540dcdf10f37d88433 Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Sat, 17 Jan 2026 18:29:50 +0900 Subject: [PATCH 663/663] Fix minor typos --- packages/pdfium_dart/lib/src/pdfium_downloader.dart | 4 ++-- .../lib/src/widgets/sizing/pdf_viewer_size_delegate.dart | 2 +- packages/pdfrx_engine/lib/src/pdf_document_event.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pdfium_dart/lib/src/pdfium_downloader.dart b/packages/pdfium_dart/lib/src/pdfium_downloader.dart index 8095dbf0..9f2800f0 100644 --- a/packages/pdfium_dart/lib/src/pdfium_downloader.dart +++ b/packages/pdfium_dart/lib/src/pdfium_downloader.dart @@ -51,11 +51,11 @@ Future getPdfium({ } } -/// PdfiumDownloader is a utility class to download the PDFium module for various platforms. +/// [PDFiumDownloader] is a utility class to download the PDFium module for various platforms. class PDFiumDownloader { PDFiumDownloader._(); - /// Downloads the pdfium module for the current platform and architecture. + /// Downloads the PDFium module for the current platform and architecture. /// /// Currently, the following platforms are supported: /// - Windows x64 diff --git a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart index c15509ee..756c336e 100644 --- a/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart +++ b/packages/pdfrx/lib/src/widgets/sizing/pdf_viewer_size_delegate.dart @@ -156,7 +156,7 @@ class PdfViewerLayoutSnapshot { /// The document layout (position and size of all pages). final PdfPageLayout? layout; - //// The calculated minimum scale for this layout/size combination. + /// The calculated minimum scale for this layout/size combination. /// /// This is the "Effective Minimum" (often the "Fit Page" scale), derived from /// the delegate's calculation in [PdfViewerSizeDelegate.calculateMetrics]. diff --git a/packages/pdfrx_engine/lib/src/pdf_document_event.dart b/packages/pdfrx_engine/lib/src/pdf_document_event.dart index c8494942..ad926a61 100644 --- a/packages/pdfrx_engine/lib/src/pdf_document_event.dart +++ b/packages/pdfrx_engine/lib/src/pdf_document_event.dart @@ -46,7 +46,7 @@ class PdfDocumentPageStatusChangedEvent implements PdfDocumentEvent { /// The pages that have changed. /// - /// The map is from page number (1-based) to it's status change. + /// The map is from page number (1-based) to its status change. /// /// You can assume that the keys in this map are sorted in ascending order. final Map changes;

G`m1UyKa<48N z+(<0#1BGP~?FStZbO4k@q>R9EsW^zTLww$^(TLQ2!~=VIu2%(Q{gy;yuIR)5zPM zf#Dsqn61=Sz}4>f&(OFte1bchV_Q^+TwQ_uM~q6$s43(T?(C4%@g*NRo2`<{i+JE{ z<}2lW#ebg7ZoZ!KHO*KSNk_u+NRLQ7PLPpSfJ%vEPmxi+60U`u)he@F9k~WJyqN4- zSg*)+k+0!=J#2b0cb>~!rPgBZIhVOiY5ta0{wIOA}Iyc zl-ECwrtg6*<@}ZT>=`NUBHuQj{?GIxP5&85>qTyu&z#2TD`3=hrro=aB%O>@4=NDU z0K)GhD(AEDqkG7xjOe}<*$%J&k!IhH?1;P**%^5k=ID=n^aAD;5?V>CsP(v`63PAb z`2B|d37lHNCH+L{08l28Y^@)tw?T$C`-an8rqmXcAZRcspUBcqKLp%LthU!Xh#8JB zp;a)BL-kI=;Cml`uz(GeQ%OrB&`?mt9Tzh9nMbP0JO^`D&;(E;k?MId)dh7A)#{0; zT`?xXBwwLfZlKI7JaHi#>D5HWa;l!e)z(lt4P>|qQoe#&ucAt)%4)iPhFF;{+)UUi zvhpmwJM3(XIdE;{q`9EbTJE%nIl3g&!q_g+FBNl_;dHqm5A>X+OuYiNC&o%zvzBjO zgd;uX8jr(lDeD@~S;UsO?XN?p^=>iqE&bc#{9ou#B(Ho2^}kVNP-Xe9{ylJU9iOt8 zjg{K#_{PO-j#PM^r_lM~dTw0ICP*dq+<6I}_jT6u1xuKhRNcUnm*9C}Xd^FKf&(q1 zk#{U%ZlhZpv8Ggm6+?faeYlEk)nZ%|`%niT_X}UNl(|T?zwq~$veDB1CZ4nuE95ot z@}+E~T-!t|Ncf=lt|s2Q6qhRb26tSB7YMc9;IA)Z!|h_4(IMMM;r-q*@K)#O&1~j} zm$C8kxmKDz2BxZ&mo8&dq`2R><#HCl3~fkkmt%$p#)>G#PVxfbWzm_-*~lp^qH9CcMpK_Rvanm!wF#sbC=e8X8%_=~?@2IR#cdwy!JI>M9klMd$lilZ64m>tLQqvv zC53`I1?>Tq-XRwTswPtIMQ=E&7OGVHQ0(KxYAN$Be|rU98Pj+dT1rKO@h(qU!PZDA_qgQA++}??C1ptBySaxa8!wf2^H5Lb zA*bA@q!mg2eO~KHKTh1|!&l-kI`M!{U&)5Z)eopcX~2#Dfcvau<3logVbnGT+ND7T zTO=i9q`{y|L{@glh8XNchXZUoS*4?4sKE(U_#dcL!$dk9bt2W$lc01W*%`?Q!$_Qt zf-5Gc9Bmk57>la%k9uykijC}f zF=IJu4^-_`S*e+`K+AHy_~Q5`jW zf;tIxkElOIg_jzj)@vJh;~F+%d5#v1a=PKTi168j2+xzVK4CJM;`}6hH+f~2A={9H z%Ali}xrRKEpEBgbrRf^@sk$KFWltQ4I;87|@cvY`r{NA=voi@C~8dgM|)Ojs0u)eFD%KWl)oxhZDQrE3BG zYG_5r{zKIJ8_gzCx0!l=hbbasX*Apdmy5o3P_3ZbAfuoTP|RUkJW}bayA1FS;G!A zB{qi!Au)VF^KC(SSww?DRo%IV*0Iz5o~;UN!aS?E*AZI_ci6FZe%r5zbwBIU}!OP+~y1R zPDskf=(4cJrU{;jMABuUP~5}NgQGAE81%_OeV-n52=!rXpp|CWM5Btqpsx>beuOrC z6v-xzDi-5E7;zXLet|oK1AP1f(Yb6aoC;6a`=_(%atHNiTYQM7c)mKFWlC8|x&4o` zLHIz}WbT~7-22CVhL+_Yp(BCs%wS8Tj?eg)8O%dEpTY;7V0+eAevXFW|3yP0#>W_4 z*k>BFXGc+gf<5b@E~_L|pJF6qdt8zzy=z#LS$7( z%$l(YHyN?Yc*#M5+7El2j@r)pvV$ThaJNqgj3jeUm6y3=7IPYqkVI`LHbJ zAjh2Olkru28?|)_6M>0}pB)qv`3|%VsL7-@lD?3xLWUHbgmfZV0*Mb)55O_3kTB@e z)#)?T>1T54If$O|)uLz1qdgJXeLaSUJ)0iwX{8=#TBid&vphTlEcnW7HrTB^hXM_P zHCIF+HqZ|{u{$8x$2-7#7kykiz7Oa2_2K2o3EB8>n%F!Tdz$H7gR6AAw-0TW#t#1= zUwo%Kh1@nA*_HtBpuQ~VZNDHq7>KMHnXmU&pT|w}TqkV~#PgASnz!hxgJMU()<4$- z;|4xWSq8G5%LBHX!<}F}A{J0+TZ^6fOGb=?QDkpZw)4+<53{N>nCDbe%QZRpK`yTl zmAw`hYx*_p0xnA5u0On3iCLCeyOZN8poq@ATr(>3aL-9h2MBY1n?rs;sdz5Z*;W03{zdgK4O zk-J3RBl4iP`qmc*)AQnJswu_-wj1kl2yXXzvu|-?+`+%09V|xX;uo-s@5*9!Hi_rp z#9=$G0mri0hkx7Wb?~lzUT!vf{0XI39xirS2|DfR`tfXZQ2f&`a97_}iE&tchuk&~ z*;XGv^FiXg#UBSkY$;_<6yM_R88nZIv<`PwAL-Kvr@mlj;`nXc}AS4xoIE?g;wpD2q#Ht~_HLu6gZ zTtglSt1i*R*M7wXes4{aoKOU#sYi-`!?tk=!aV<;|gV2$lmhv-`$rH<1SZ2nSGms;?2LzBgXer zk-dv7Xp2vXc_Io-thq`P>kxVe{dt@P`S@dlMYWW<|BjvU|MO-i#smH}%2JT+@Ch_O z^!NmC$4wDYN11GNQ0(&g^A16*n6aT%gu4(11_gNk??Y|{TQB&As>TtBhJU- zybI?~J%%6a>d~QI=7(oHyMltfx5G5Tj2Mp|x!plw!CMQM{dgP>P0)6v?Qc7X$FCLM zLBV){_uq*Jk@)7mNuxV>WC0sCAf*N7S?YJWfA znO=9gwy zBOjpBm-ms9NzUwv7=}Sh<$24L)sRx^`33$l-yskBxzL7cBK#M2kWS+9cz!{@e8>L$ zGy3^E^fU7v`^zuT;0bF(fl>nWAP)|g{H^d;NfM{s=3MR!K}aG&49l_-4lXQ%@5#P4 zNTU7+u?fy3XLR&8)f+ba*5#~@LRbkx2qy z<+ZpJ#a|1%$>Uhm6^~IYL{eN|85hRH`MM6se7E3Kwdc zppdqIXHWnu2?&*t8OguT3Z)@ANcXcmS}jpa5MUtz!bB4ivEl!~^Fn*0UkVM{Vg$52 z6ZQ_9jj&R;k8RfupVk?<_NYC6ruz!D!tKNBq0ez%_zE*(WGDb}9N9ra14OQ5S{4Oz z2!E$HSQ#z=V6>WJI}Ady02WCRCnZype;x27eHZ*v{{(%39~tJN&v3r+BQCmHG(Kaj z58GliIVEyAlH=B<2=pcHVTxoscEmZR2=$jW5es`PCA9zQs}XpO>1#9+heo`Ia`2}S zA4h!6aEN7q30O`NBViE}B#}x-p|279m%rrVnGyXW59*{cgeJ4u*=&G{^&nTplcglSQn{5OK@DI9WBLeBK7Yt z6DOdZJGr`aX`bG_y*r~<_Y|fuUPnd}A>vQ~Y?+$rW>(<*NPX><@!?esU@dLFPfO|S zUCb&GJF#;mj48t9k^1VZn(#Dc|!HsQ4vOIWc;MR8*2(hg8!HscCQs9{UIwrX0K;ER+D zcWUR9OlB)KTTR@y_;75ohJMpF+1-uV;Xe$tZJC;$!tC^46pWqJE2B$F2D8h*8rpiR zcgOQp(=|)ZY?0EJ*@G8b^&gh_6As1!f~Tf;{UW(XpA=@VKMV%*hwU3O`G2C)L!W= zlfO^t!JLgZRL#&lQv|h7=>hwe(uFyPE26?%p7&=`f?Kb2FMC|@hY|czLJ*I~`^;9k*~;&$yOVqL>e>kw&@wr_YadWYOflBPgfx4ZK-G{dD>=1Cr$#B6pbYvluM?i0cB zxR^q$i4AYMB97wg-%4bGm2q^{m4GIx>0Ovradg#{m1KcH5N0**9jn)^i4(P0XTr+_ zo6r09PGQ#KX|Y=4x;Swal`ygoRI#kbrLo~?1HKV!(r%2S8iI^T`hSs{@*}ee8{#57 z#c_0(h}04YDP5w@V>ZV{5P9(jecguWAzyl>Gd*b-o?6nFCHDL=WHz2av0I*58=pGXoi0vMW3ty6w%-c!71*5I6Y6Sp=t z66eP21DmJ!Aj*0@-WRVoZiuBD;cH6xJt@`QGr2dj5u*e>7!6~B6&2%t3A&(0AQO#J zI``@V%(fXPCq$wxP#?u@jWy!>3EFC~hi*{!+hQZgBR-uV=eEb1>HUj@YG?iX~}Z#E%Y1p>%C#rf64jL$?0ld(yE(NUoA z=lDQ%1Nsf$tZwFB01XdT55eXda_(iUmEM0`L(RR4wUB#F+vJSop3T$Uq7i%@8%0|z ztfAqu<7jISZIRTY*Y{$RexkhlrhR)={oeo+bja4j1auf?K%Ch}dO+F-7HzHAVql!< zjTTMC5eNcegF(NaL0r2C9xO7w4?mz8RS)Cw+II}5A#d_B!7P(VMOF$k6j!`sM8mMM zX1H;wR3HLL`=@mA!pGwDS~{?z#D5!y z+t!LeXt*NF0Q%Xf_tQ~$7?DQfIUP_;}Hxu~4n-P)&gr3UX%f>+`J zL`3}aUd&;?DzX-+IwBfZIzUp&-%?XDpz>$})eN+fdh*OMyeLtpKQ0^%#*h*RIQgkKV6kW5A0r3Hw_UPNY8`&37%1hplLKXP%`_Z79pT2L=uXBbrY2T;?C2MNW zlqP-BMPs`j*Q#Rz!{542n0BL+8h$F1;AgwtDd`YX^{o@h_D{f*>R8zU33x*tD;kK; z*Ri!7l)yJ>)2>ygHtDT;^~p>j+{FwAM)IrsvRewc(nAuI-pVH6hh;KD6N0_ZEtC4V z-M!Kq_4+ZL8HPXmAjC5~LH{f$DOQ33&>)xP-YSD1iU@+M7M5}9NsV@+L?R|!smdmHGZ+I21Zsowr= z(p`TA$AL%_*>TWCJ?rW}ECBf>3vSfQ=QMxX-s>4n_wG##bbT4oQ-c(nwo9QJ* zqh4f4mlrk*->n<2pY5Huc_z&i_{ho3!IqDrk~bt!N0~$>Kh@(O^%WBrzu^lXg&1Fm z(TcZ0YqE;W&wI52$L%Fn*E58@5~DE@iH=^4{2JGN&ZkP9x5) zrw#1X-kqG`W|rZSdd8;9#qa{W5y+KfP&pHoP@$8m=}zsFAu*EKuvhQC%w8ZD*-ldR zWvaUyqy;jGf!v>9!@V18)lD*b^(2fYV!s)G_Oac#MT}A?x2ASYPj>goND%{xS$Oov z25yeig!xZGl?_4tffmVR<~k!tMNm>-cS<`5xKfyTVvVAGpYOen<$SFAB!XMu3@0rE zKL_Q(EObVb2mI|P8ng)a{v-mu#G^h5N3ZalPb}y)-t~!%%Z`u0Ke$51)K_r*;w|E> zMA_NhCs~w_{_(h0eFHZj-aue-*e4S4F$1wCNjs*#MB@gR;rl*rFaCq#9o`}Eg8Do+ zINnYk@xl5b+z{`5X_7|Th#4x5r-1RO(HSP9p@L`FEm}1^-bgwtNt0AJXxb4Xerif8 z8n=ND%8bN$4eVS_d_0}0q(L}0D&9oygPB48#7kU39H#3r;!`dqH&(n4^3SP^12V*L@mquh?e{Oo2w&$q+UNUWiizM@wIQ+3$mj=*abgg0oq=N z`C^Rz4eUtkHW{g1!8^T#(;G)xac^WSX#c}8kaQoR6-q2DO33OXi!GN2Vy$Fd+H6Drhd&VrjFqO^M;+oBMwboUQ*GWtZR5n-- zei<=f&C^pMX8JxO8MH^gSo~9SlfJ(gx%;AeA9O%0UesJycVH}KMamyOCxjUkD~<*j zlVwpwK19q^(1snGxFG6!)m+C7jSU;~X{hA=+Tc>w{L~OW+&dZR3F$7M9UXzc|1<&4 zQ$~HNhx3%VpGH7ny5mz@Os;o!Xig|8GIL&R5M4{9avP1+Ewt6gyxCR^EOFI*WPoPW z(Qm?x#hXf#>Z-j#~@(@$QWhU(;K`>V1hS8Q>8;E;?fq{ zgh^GCw@l7-i|X%JZ)KyDEI32>oVsD)3GyvNt4|R@VxGynn@qll>>HJhe`2QM&syrj zr@a9L;`N|nR)DhsIQz0W#e}38aE*$H|o>72b@PO#;CQv^AZuCPINbkf$l;QN}0^ks_m$(P3lW6 z__8YUR)U!2VpfTa4-D5f8FKgDlchrZkJjO}S5>Xd4^Everl^W9KstdFGa8dz#%jE~ zbxYGAg|1Ud&rIs_rlfZQi`+XS#oZ}0#SLq()y9NI;MQ#nz^YQBy;E9h8nex*^gX7g!t?ejFnQeJRQZ8P%}%HK&6+f3my>^^Dm5*Q*ZZetwcp;v;(;d*tRR!RcsNvl3mTNVb`+j*!AoN zb|bq9m$o%}4zWkrqwF#EG<%$#f=;j}*=tZ%{kX~PdG-uj#-3wOu@6~$7W~^O$(Ht) z4w4R$4wH_M=16m;qor6nPAW(zN`IAZk`zmJNRCL3N{&g6OHN2mN=hZCB&Q{3Bt?=k z$py(J$rZ^}$rN;5a>FCJC%G?qBzY`(B6%ixA$cHqC^^QR=AKHPOI}LciHW-;yCr)h zC6c|8eUkl>12ED-$sx&MN#a?_S;;xcbTl2Ehil?ZNjW?cWN%4sOWuCGBf0B^RuE{_ zqlY}Z6SKK~Tz}4;IDi|-4dxPuaG0CO{mM;&pQ&5{H;bFY&Epc6ax1vi+!}5z{H){F zbDOwgZZns-lPlp4a)-FX+!5|1cZy3q$6eqqb62>l+%@h7cN50F%T;jqxcl5g?lJd_ zdo39#9V#6wogf|Qk>*JW_yzZpd&Ol-`b!2$`bh^%hfBvxCrL+1$4IA0CrbxQ$aR<` zafD=qWF-B`fwhj3jQYc>TuH9CRi3ZaA5@K&Opqjcrb?zu66Z=5OA?nz3MGjvBr7H7 z5frPSSRq|0Et0O5u92>lZj)}8o<}>SyQF)hgvuq-ebW8XQt3hXJu5vgJtr-bjtdwc zAOxHS{<#9bz&XnTCInQla06Ue9I!cHOTgBEZ2>z2b_JXcI1_LVN>5q9<$x;zR|BpE zTo1Sra5JDh;8ws`**Mu!*)rKNZmq0d=Va$)WwHyhi?U0y%d#u7tFmjd>$3BxU*H4TL)jzQY4)Ye z{Yv&k_E?r3*e}pCJa9zdxWMs&(*tJ&&J3ItI6H7|;G97BJX&#EQs}E#5m*$sCU9-w zY1Vxva9yA~Ti#zjKt4#GE1xWP3-Sr_iSm*1N%AT3eEA~ySu9^7-y@%bwz0eA+vPju zJLJ3M?!@V6zx;r_RDMwIJ_L_P=sum`Ip;)2gZJ(Cnu6#0s&ifM`h#SFzv z#Vo~a#e7B90>vW5V#N~0QpGazxLmP9u~M;0QKVR{Sfg00Sg+Wi$l9pb1P@yjTNT?B zS=$vm6uT9B6eWtiihYXxiUW#L#X+b!tT>`LsyL=N4mBqfCly(z6sHwu73UP^6=jMG z3S7H`xzjyG)_uhT#UsUI#S_I-#WTfo#czriikFI4ir0#4DEtmn4u|Uq}x(!cv;OVZiLU~VlU->}! zQ29vtSouWxRQXK#T=|>wrSg?>j^ed)t|B`qE9*Quj|K$|4C*-r4GS6`G$LrECnzUq zchH_7cVcPK!JxxIM}m$9oeVk^bUJ95VpqWBphrRXg6;=B30kdMr7BXbQLR<2Q>|C6 zRP9pjR_#%hsP?L!sSc}7s!pj+tInv-s?Mp-tIAXtR2Nm3RF_p(R998kRM%BEROPA) z)jidH)dST-)g#qo)f3frRn`vGPLFDzYQO4$>Y(b7>X_=d>V)dH>W=EJDqB5VJx%>w zmGztIg{r@LfO?>Mkb1Cshe^pObPf_Qqr>bA83e?lpGt@KHv(&TIbJg?I^VJK~%ha3ITht4q)r-`N)l1Y% z)yvg|>J?1jp|J>W3l>}YD`Ci=b-wq`iT0d`lR}l`n39t`mFk#`nkIZCA9&vDH*%_+@L%`nYy z%?Qm%O^#-iCRdZE8Lb(k8K)666EqVwlQh3-CTpf>@-6#gunVMOe*_yeU z`I-fAw@|ZKvsAN8vs_cC>A6C)QnN}^q*<+5qgkg}ui2nkuHK|6*6h%n)|}Ct)tu9u z*OX~)Qan8`*o&I`{3Xq0jfm$J%~j2H%?(Y3=AP!h=7Hv+=8@*H=85L1=9y+l@X+94 z!NY^c29FCKA3Py=T5v(|bnkUx@Z#X5!7GDT1s4Uc4t~L&N8l2z30@n#E_iQnskhO& z;Id#GGB#vF$oPF8|8xsV4T4?|vsybkFX`hx8rIwEvr=%~;FHaB#A=!Vc; zp}Rv*hMq^KLQjXDf!~V&sq&=WynF2l&DP!s9jbj5+E05a^k!&z=&jJJq1Qrhhu#UD zj@<6Mp%tO`Lm$%nN1;zcpM^dS&GG@ChyE7&5=QT@9iRmV2&@~{j@6FSj@J^!(JxTY zPS8%&{;Hh}P`5i@yF|NCyGXlOJ6k(dTcDk%ou!@LQM*yQ3BWty*N2*$dV9VnQl8PC z)n3qE)Lzzt7ihSmymsynB3*f(=Pj(sG?{Jsn_!Zj-K9w^_GEw^g?d zCfcssq1y@NF5Pb39w?x=YahvhE7> z?~HX0anw>9WK7h4&915I!(` zQ25~RA>l*AhlLLh9}zw}d`vhF9~(X{e0;bNJ|TQ!_@wZW;W^=>!gIs(@HgK?%6Eou z58oBOJA4No^v(9q7wVVjm+6=53-v4YtMse&>-6jO8}u9X+w|M@JM^3NTl8D?YxJA+ z#rnni{rUs?qx$3eQvDJAG5tOLef=Z-L;VB&V|@kx4DO%n&!ej-+ko@Gt=B%^Fw-#0 zFxxN(e&!nH8Ri=n7#13q8kQMW8Hx<64QmbS4I2y_4Vw(bhRud8h8>3eh69FD!$HF_ zLjijSey`4x5gcj+%~{PMA)bPMOY_vd)^$na-O$ zWu}X!OQy@Fo2K)q9DbL`@0lK(hDYQ?fF$NdFWF_v5SS&vRzenjW@kD(;cOxny^26^%+>bbq zMw_u&Fi$XhrkM-O^UU+j%goEoDJ@b9@1M@@k zBlBzXh{%zVIgz6xb0hO2M@No{#F2;02h9h}TRrB(=3?_E^JeolbI%g<3G*@YUh^LF zF7wdHVUa^3UzwkoUz!I-4v!ohIVkdl`8V@`$o`T2BC{hWMoxE0fhxFd2`hp%&>rxvs|%MSngTwTOL>*T5_zp*3s6n zR)5>G(Ynd%PTXldk4RKZ|NRyy{_L`np9+4rb&oZx#Jbly1?_`&Kkc_3wH~)#uwJxY zvU)CCuUM~I%dNMpx2<=qcdhrV_pOhu&#cd_(`_?sGi|eMFRU-EFW49CE9*Sld9=W` zz*Yf9zJgz2TWMQoE3$2}ZL|@tCwiZh;?D-#LK|@+*V=xwW@Wv$_OlJO4YSR)4Y!T3 zjkM+1M%i+018qY*wgI+0TehvgZM03~tBJPf*4ef>wrRG_wk@{l!D6ldb*pW=ZMW^Y zey44hZJTX}ZI5lQZJ+I=?X>Nd?WV2VcH4HxcFuOycFlI(cENVYcHVZ^cF}gpcG-5t zcF%U-cGh;rcEVO_J7PO#OS@q^U^{F(Xe;wh|HSsx_RRL&_R^L(AnKJ^42y!Nkx?T^ zF)AvD6r-bZqsBzxs0mSHX*o9P*QnD`XW-{-)cGh7o#N=3(RaCXhHcSXqaQ>)ih3L+ zyx#m?m}N%vrKpEdPokbh{TB5y>UC7V=mF7#qK8Beiyjf36P+84?{p6HjEc^So)bMU zdTI3PXrh9$gn%h%WAvuzgVBegdmfJV5t)=C*Cux68lp70{cSyEc+b0INEmmPWxW_e)|D?sr{h+ zvYjx=A^UOr3Hveo5&L0#iG8lp1AX_vC z)iKT?IK-)@J7zi-ITkyXIF>q=IhH%fPOoyTb?kQRajbW2a%^_&bnJ5+a2#}Oa2#k~_#PQVe%mFEu7mins>FBj1J7z%4z?i$- zpqRliLt}=;OhLnAM#kjCjEb2Vm>ZK9vp8mc3}~>`^gSyJHgf#FW6z-k7H`&!GB* z2l5avV_wDF1gms3_Hb`vH|P?B&q;aoz`OK`cI za4q3_!i|JG33s8*VCN90J8`IUm~%LkB(X8hIo>HaCpfWlqH~h-SLbBs6lcD3s&ksN zz&YJH!#UFlBQJ4Q%RM`qMa%KuyIR<76?jTld(&+d*O*wAvA{*?V%=#j=6#lN`Dzg# z&^QPw=re^VER2g!i)LIa?q6eE>+fBo&2Qbq@@^y8vV1(g+k1RjIe==CVLWPut1Q3p zbhkwud$}AB?@=#uhb>DbU{n|P-LV()_6 zBu^b^T1@V;r)9%G33(>XE!AAgG*|B*TqouqoiEKKBbGw7xNp<$;;aly`^iv2DGLLS z_1<$9X_`%L;aUaBEyyBo9wb+=4P4UzTAg8Bk{=pN8nE6UilVRoOlSe07~$`-bYn6j z+2Xs{nps`tn}MM-;Qz@?X1CqNn=%t+WJDMiKg;~&V=}i70ZRaEJHd7WRKnmEhR}vQ zSV%QNhES$zTI_bmdLDYKV0{)KVk;w{=^~YMdlfD~%{7;d5kQ6}xQhEe5SYv5A$3E^ zotMt!c`3A^#NrwNXK`+umlI+CMBXIZ3J((qdulE82~Gn5d6%VW5Tk(Y#5MaESlGdq zeXO!MYU~}Lh?OH;QYlJOXv(;4 z0kj=RQM;cu+kMOrg4ZgDtQ_!w``@vDf>Z+Pp&WPX`xzx2VTu4E+aeta+CI|pf798B zXb@?uY(_wz-W1H01!y=`sz#<7kbOCU|&Huj0O96== z1<11AqJZJ&K3)n)-=F~ShDG8>f#p9Rh!E%H$h=ZmkOg!YVJe2si}U}n_Ya6JfpHKe zI`R>H)GvLEBGDyeN{bwt0I@kAvCWh<`}S|?r6@~QMl?3?D%k)F z@}uar{$nb)Q&PE|Z%9JLfN&Sl*>p3T1_e+xEov}QN$jCapdsUyv{~+ss z8Cu&x1&6Mf3WN-B{GANIwyYdFP~2TFad6{dpH|Xk{IJ!8McNM6%ZT*@L<)Nk{|AEE z3-6AAZ$n|XALK+=HaCx&Om=$xDBn&~9%e)yCI;2Q`t5Xl?tnkoX{zLXf?JYns9#C! ze|?|n7`}Zj$<^bJ^J-Qi>CQV!*3@Ej<(hE!(F1E%PUGEE!kDn9$h)VYmwkK6(mlnI zW5S5TP-l$$PgNURwSk5E$IL=?luQ&jK;4NvD-}|P2g4P}08=2|NpCEOub~Kl8s4r@ zf*#7h#_;d(HSvtGpK5C}fQ=#qfY7SC4S{OqYh!ktLkbYMt3J+xY8SR1HxDU4v;n7q z1`6uVCkdPT8A5QFV5haLh4ZWZT1Z57@hG7gT#pOA;o59M5?q%~3db`he6RM6!*_SF zS$s2bsF;aP3Lrzh0r*<5;_(x;(0}2?4sc~BeFN9;CbdCz3ztp`Kx};l)=#(U>sdJq zIBkp-q+c|Uti)qs`TF?&ufJ5QXA_}(1AKbA4b{i5rbpnllN-U^cQZ`bIHkI@6>Z#V zN=H?FB@09$J|@tl9@folfN1I_d}eZxOpU;F95RBPy4Uz5u9CDXOFly;!&S~laYbv2~xY7Fc802&H2 z-$xzr{+a&+R85!#AJKQf^=BW1t7c9|Aa~lFsi=8jwYi^>CI8Pnx2ircaUl4rRwawjuSvd*;c-pGpP#-+GC<^_Ei;LQ!9}7cP&tRi}1i~ONK^*@# zg`yUfd5B$raDD)MYHcg@3yxjaEy}N!*dYkUtc1>dnoa5*<{!9logMuHpImn_qN;U2 zqz2C!R!?aU+rG18~a?qv11&a~qn%)v>V-T)*A;5nLy2OolmMZ2T0iO*g%7 ze0wbaktS?9`%mF{#XTY>u_(YJb_%petCk0-z+X2A-&_Z9X}ftKT#s%}GJN2p#P6Hd z+!ARt{wY|2Pwl%YB(rE{1dT6Ez>i5F^F)6zR`8F&otGB#If9KtFVP6WB0=AQl@t0) z(1)blAVCIFo|K@5+(`K5H+4%J_tvLqqgNLFloR+2*Zyp+^}sdvW*8*rT)RD@FJ2|4<@u#>+XHbZU9qd?sz9^ zJjAXotkqnMRbi!%j|;c&kRjC=uA3=}~=7yld^7C=U;S~6S;EN;O0KVyn1uZHpJJJfF z#f3GFWgz&A5Cz9~z_r_n>1b)8;$%M-EyFpdJ!m)%I@1MGDH&&y&@8<2%*Sy5;EWUQ zEoXITG5+AJ6>7dd`!oDGiQ{KyG+GXex9BW9zZh;@H7zoW+@b)zC&}aq2x@|1Wek0= z1c3&n?JT}_#SFB=8_y9Q#;49Dp!tOX=Lc|!(28?qv)yeRxH&C|C4gz9pmi3*5~Tnk z>RnBx3fzfMmzH0M*I%4ijiM7`(eR6Lr%R3y?_@wLNM2-%lTEl}2U2dl)F=R!%-#IS zRMkgBm?iO)cL`XySqqx&zS$PmZYVdJ$o#OzRuEnQi?<9yAfNL0K-mZK%kXFAwV~PI z@~@yvS$Xq!Vfp~DX4U|U!V;807y{z<958Tn84wj(0xbcsMgYTaGb3RAms<%RP=r)K zI8}FONO)gi4FCcFF#}xkG?$#xlT=uM4Jfi+I^G(MmKCO_)8PyNj4V+D&bdivTh_?IsR?_|C^@9@gCb1r_4FyGPL~{MU*&4!9VP zsTe@Z@O#fxRE_D0ym%CfSfQdJ8!8i-vJlt3??el6*Zc1RQ%<`7vrhuRCV-?g19}oU z;MYJzOh!$7&=UUCNA`mV;J*9^miJ~M8|z}40qHr&6#;IDpAXCqp&p@yrz!QesLZ)6 z8GwK|%7X{?pw&TF(E6`fJ;dy=Qb^-x<@ozPO zzfGio$$-Nne(Q_YVd1yWqoA!s(g2tzz;%X6v&dW$ejOqjSgZgR*@9IsB4O?tFY@7i z@qI78M(c6xODDe`S*0N83YW>Mq zHwALCu|)H@EQIh9U>*7dOr)|PVE(y8D-i(P7VQs0C8YNx9(@VLeI8}0p?Nj%%AgfU z4nJ4;QG*14se{oH3L~9ekihE>{r3fPY041>S9vtJkyhT5Th!nrW?5fla;Y7E79 zI`knFlW0+NLRcD^Qw6k^~0UB8}sQvj0e8 z_z}Y9N^q$^_&O57F98gLSdDb74~@v7rDNqmV=hTO7zTqOVH~#+)fPe7C@l#X6Z(sw zq76oL0>L2TBhaT%T!=tZs}qV5f`nSMu;&#_nn+QgFK5vZ{2JImxZ}8HHU$j6$c!4Q z$h-uaB&8F+GNWVYfN(StrHAh$AZX2lM+`+cC>05)%VWY{ET~Sx0Z_qIwk*}4znnin z{99y|5ZwY)0R{wg30?So!UhZa1PJ>A`ax0KiuysZ%8Ig~sAEGTpxA0dGh`)5VPnBD z=@|uUDiKykp(IpN^jj2gBJ4n*9nFQ>Wp?yA6afzO4HR7*=ojLwTy~%qVmh6;I}jql z#CwGLG4MCEN`%Z9)C7vPF(_44f~+jyNYp>#ON3gns6K#xh(!}x?j_^{V(BefK%AtK zv$Km|*5?T!h;9QS)FhV=$i-sQlOooprEqc&Sw$d|CCn1e?-x46p?7t(XM^sfzAd?Q z^aAtaC_-1_P)D>!_#_^Mp<}pdmf0pJy{9t1<_`{^(QfR0Liwp}Wru?&ug-tbAN8uir5ikqLF|wI&PpQtn1|6bY1**PumQ z4Rq6=D*}KktnZ)?fb+h72k{8FqkB#CG3?B|ny9O|7nH}qrv@>jd=^~`_M%v5QVZ4b z02fIJ7X=XFNeJIa2;acv19GvDODv%dlst?8ML5y4ybEP5vV?*M?2om?RR*FY=jo`W za0|`9BbRPTo}}0G-=^2ELCDBlz-bZ>VKsnv{5>4xU!1QQnNQ=oh`KMdgm!p)LZXGd z6N1(uW93m~CxA@(q{2HP5p#fNQ}|E{my2RU#D3trC|g8)DH$i0%>$YUp^3->)PFA# z*+d}W5g)KC01b34u(2*Mija+QyOzFY$?+F3eF5owOxTi$K2nM#fzD)9d2M7N;wGWo zN|FGs>#uyh8UPUOD@v}7YH>!oD-}|zCA|x}GO1`(F~N9I9cZ{;*k1?580hfDVmq?P zQt*o6xD!%-Uy=F)6oDi}j)a;Yg6tj=ntq64B1FNGxTZlT9dI(C4DB>j${!a-e27d2 zU~Sk@Ng8mhp1><4^n) zhLej4NH7}eU|sOU^J5_4tb|9%<;Ak0JQkKEp-?ndsA!791PHr+vH=+z&?KG}a0F@^ zU@E!*g&gnzs*e}48X`N;Wpcx}=@LNba-kt?bh+>w6z}7K%!(I_A;6T zk9;RE#w5jfM`+#%wS@7#FMRkgb7Tw!vg{pS^ppBC8{NuY$cNbp$a*H5d=3;GZGIhmaE! zQx_~a@GytavoZRlu0T$1tYkvc0aAEc(#1zc3E3^cTVaIWE@UMb`#4p&(zuerf<*@F z$NCtoKRe!q!nFgyOQH-0ips}h1BAg%P+~g*f>nwLU$9i6L?r zMPKAF32<2ZW^Z$tmoUI#{e_LqfWrm|Cz>J4$9_bR@gfoa&m4vngiZK!!h6k;qmsqI z-=kY4a+Ann--4&{pRpK8CIgizmx*)+4f8$=QC6iA%4UT-%|XXK7e4tEMS9L6aGV+b zIkKVdaC}XK0l19oZUL?YYr{XFj7zYwfudz0VtxqRB=Ryb=K_Ae=B5RWuC%gg% zO-WxE=mIFmTL@B&A~y#TuC;Q)89<`ZKo({ObpfJd;lKfgIVB`ZjCi^Oe18a)Ad_l< zHc-NgBSf6(y^?4((({f`zXj?P1)NnmHx+J?&p<#5;g8&Upkc1Cxdj>+a|H5SAVS`( zl_rE}17I8RFsBo64HcnpgdQzXEi_KZZ;9fPE)zpQ7D_1!6G2Q)@Z`t(W~IyolVFz0 zFp!Dqo~BU1QVGTQae}-RGNW7}wiWu=@cK`O6FjYu)-#EXtKc(oMA26gUawpl&DB)Ra6cq82+F2kmEL7YYL|>gCy!T3oYJ==vJgq=0 zm`7C5tVqxnL1b2-+@GP0{upJT6^H|_yQrfEgIgS4?PKUt-rjQUxEEwF4X!8g$2MMkOCA^zk=q4!mzKv z1z02;`wCJ(%LLok$N}w|e+`alq42}k$Of>nUxSU9RhG zU%DU@%<^j&6iIMj-vvGj-XL^JLDyy9kj4#(Q33?Xwwb7W61$t22PW}`&XblK$3wU`5Xi(Y{(XoG)I0n6eoq` z38YT;c7s5FkKjy2@6mJ$G!(j}qS_G6PJudBai3&A{;=Qs)kwOJU1$r1AzSo--6#># zd!Ui*2_)oaqOhXio@hBD$n8i+@6yqwF#79sn#*X?3$hm_LT)d}@stQVd!ZVD#^YWn zNxBC?Kr4LQ8xn6N!i3(`?Z4R@O@gOEKcI9oAa0Yo4nm9v;%Za0f&*K|5}L$VLSS(t z1Wg9&4Nd!Jpfo6MXQ1}M(8JfBB=X7d?cgBK1Z$Hhz!B0}Lit_j19%C9^F7WJ?q$NK z=_SHXZs4|i!ZbJ91y8AckOztzeITn?Qsl}4-b2HMu0NvBmDfScoBjZzkp1AAu=7VG zn9J_a>=x^))gp*>RlpPI7I^mw6r5}6J>ln{&}1$%W%SxbX)Z^M0o-H)1s)KQB9UzdPnH+3*_IP3QiAlqt!xz2V$(% z!b=axQ@)Upjhyx;tWu_MEyxbE2EhRU7^5dFfV9~q*=mFX1qWnRy2D0Jb z4neU_01<-@-5F37RBBaO0!U~pv8edb!kH1EbB_!1kq{U@7d{+`KC(X|n%}Bq zA=?j1L>HDKtJJ z~g05?)SRAAwl2sSW1 z56s^pVQn7DV9SOHHAaKAfU?bK)GnS_ZXmi;{c7NZ|8a$Q^?w)%>sAm`TFE z)&KIw6pTd)l_Vv{WuUYCNGc`q1tlraj*#^6SdpZa7GQbbG#j+vP@%(A z@J^o#i>5+!^;{^QiaygUf|onG+Z3+IPa(Jl2Y8s!Y8t8&m}y~YKq!n_j~sECHi*UH z8T=F=5yNK?i$r2#$Sy(^L}sKy28%tr9=U}!1<3sUpV%KUOCljz5(_cY@LdhVpMvyC z90yf#Tp;+{V3Nhm5KAYKZ0vlZuf^_+&QXMS+2~K-i(sL-eD9N77h?7hNgR^PVthHcFuB3*rPRDIZab zEi#}y8oVK4|7?)|Q^MWZ#D6LmI?qAxl2Vw1zWsD63v5V5l{)m~&L?@#he-iKrUB`r zfT4bFsK45vN<*kW7sXZT9q>T^Q@!Kwf!^DU>|g%g`y}E?y2w_3-!)wCGdFTVr zOmMql01#vk$R%h3@_w|71~f!pvLyF4^ciafy#Y}Hxpjr}X9=$i0= zP6J&bS3#2GE)qdQikak+_?h&R2=7F##F+L-Saw|*ZkF8k-<;&K`;S_YFS=k0Kj#)!pYjM#4> zq|yE`M!fyI81c3kBi?x#Z2E@ZXOS^B1EAAJbrg$O{pml^m5a zUr4tNM0%B0_|Fy9@a{%R5V8Zc`34)Nfbw{^)1m_6Pd@ZEA``|qW&e@OOEDV$n_-ubUx z=G&pw|GO)FBSiXtc$xlD&OcqIaSdu5v4JQ!;@AJ7pY@M5;Ai<1-1`6ASy)f~tXovU zIX8lzwNX@Xq{d&t?FB?=kn%|Y%qHPy3i)e&X%T1(s9yJJIc@MQe?N=#yeHgRi>g8s zud)+E6Hs>E(1dEYZPZZ^LlZwe=M7ETta~doX)e@QkK|#X?jSyaQ?5R51}W>&hyR-( zW#Rv|yCA2#K!OLMV689v@~>=!b`xqUu8J7eKjf{xoBVid?|;r)d&K~l^49B3RlN1$ zE#5jqc?*2_PXb^z3A|Yij=a+w^itk>TE$y0{CSJ?yeGUYMu~sui2E5;(T@OL`_~-t z$d2pj#H-1~Rc^%p&LxK-!Hpm;`A1$ig4B4W>|b`th0nGiBclF! zv>qUE30VzstSi$c5ZMF*H92@2`M@@GAspOtl0+a84Sii~6#{%D;Fs^(j{3#HnGAfa zMaB&viSz()s(?16g3o7Rc@Xr_!R8AYJK(6XP?*01UKXc?K~V2RGVgtu+{LC@;ZKan z0oii!LgAPFAoMn?`6x;Ia=!i!Z_8GI>Y z2_SSV6!z|fF9GfeVY}db08SitA!qOzf+2nTrW{hFH0H|Ng(8&Y;5O2aM=r9$7`sLubd zJ5pnhZ@~WzTG#iWy1_>|pa4moOe5L0$q>H^{26%vaLQ?<5IU5g5O`fQwFCm&eZq(m zWYpzypx%b7smShf+lG!WU6h+zOfhJz1H z;6gPM5eIT;*tY1_UijZ=!z&K=_QC)EdzE0@kK#gsW8vc|@jV$!fR!9hEfG5HN2cJn zxHpL3A>{0b^WZ|Ea6f7tRY>9{Duj$p20=G5sK7NO4F_K?I~fFA7JUFE%E*zAjDIYA zdjM7=KKvkjdk*O|ULk<5yy#gTKVMjU0Oyy8l2K*weI`S*QGYp%%=Pzy9)O7&bcgqT2{Ih{(w=BQuRPN%6cG=i#X&!ky9V&K zzF09|SbZ2y>Pv-7hfzb{8(;KYJ$S*DSOWU`6#A1lzUaGpkjtrj@eJP8gFbK+2wx7v zyM+HAZQlVHRh9KW@4ovolT6ABDWs9i455S?dM7-jixg3@v8$`DF4z#)-Oo0lAdx00 za6np66c7|FIH;fj0clEfEr=+g2&kYzK|1_@=e{?SNr<}N{`aHIyYIex&+Vt(bI%3t zV~a2F4QvKmlf{>=U&mYk!kSX&Qhenl$X;k9~je0^< zrBUn@f-x@BC^n)3nOg9f*40Ledq30aW-HWsHVUo|b&Bb<|GMpf9v6I{!KFgdN7*i{&+~-s;CyB|`P;VPlqe<0ESo z^rr@jwDpUqm0Fb|og~jrt$%#w+Vs9o{>@sT7PH-M%hiuHSQvLV%-5&y)SB0tEUHnf zC+M@Cn(q>7<@Dy7UE$s=*J>hEH0Dd#FR0#((5S@yJYle`?PWtNKhx^NIun{GX(~?Q zzcoa`U0Mfw(EpQaYWu0vSJ5=9lx+pph$xzs3p%ytD~-w3H(ynfDb6_U zQ|Kz1QURciiNF_vVleK9Z!hLZB9!Ze-4V)#MAa$RO^1Jva;d(Krd$~!z|z|U4y{$EcbLdA4g;gtneO-GgQvqu6q- zZ6$$1*R&N!cz{8m_%tGDQvn`tmuor9ih#UWNW=TwFzInv5zwQy7~A~4T7AWZM3W-G zno57#tGEz{^i10uAw3YeFCjfaHJHCidemq}lO7zri6%WO9nw>Vv&M?_C?eyy8?jcZ zElT`aso+eY^Tv55na`ymlJjZoeyvXmxK~=Fr614&+K<%X z0GRh5Y2*Q|XYg}F?HPcW^FBljZ*T|;hFP59a|6b>Y(>q-8t?-t2M|#YBv2M23pEz@ewM<8bzOHKxE-cLYRRr_!k;<5KWv!#d!R7Ca?%m znNDz~eDWRw_cV#}4r%S`*I1mvqC!-@ll1T*7}%boxrekr{gt~s%VIm$QWvJdX9lo7 z_mPXu`(w->b-$)}!%Hj$KN!Wy_dVZV-fM5jSO+lfX4ufa= zlpZ*&bxuGn-P|J{Jc{YP!#MZ#3tjOYWR??j+jm-{BqbxM9~Lc-(){mWDSL|E`3|Re zk5cCMTGQO4;1uHHQ<$-4_mDn!xHEB@&pbu9eh=yE6ut62Oo&g>+3z9souXbpz|ip& z&G%s5v^}>@dTrx z%heX_lG_?H!6si1wQ9?yc}k~S&u9ai#$$`lu5x&MJ(y-_>5sZq z5BvFiqU#IhI(ZbS{ESwI;(yXwMph;kdexQ5dKAn8EDmrX=2OMfOV)m(f(kAB&p%zd5&se9Nv5jnb&RCJ2erEPf(3Vp1>2y;tkoCnbx~NMWFm9(?3v4L&5F!ZV2g>|+N^A_$aZRlB%-S!sQ1CxL&G;}!JG*-E@p;oy~{j8|^Z##86BkaDKdU1y;x`;Jze)pApDM9Vg|Mm6BEL>)_| zy=SpPr_=ef+I4xwKyWOSLJ(yfFb9Z-F+7MTzn-SQpVLMG^mpg9eAwV8p4aL%gQ^fl zMSzQRyblcF`2v_^5U~}^7qdRX$46U&Fbg&Z(aq;^6hYOh3y%e8@b~BcxMrC&_=1+3 zxB!Bt+LPC;rsi&X^Nx^IENWzW|ovUaO%BB_Nc85Ih}< z*1_2_pqtN1UmTy>l5Jf)vH8rGIY;8)0Ef-vm@fQe3DWsEQD^Tf)jy^nn{A_(jF4^G z>|}s-GrMED1X@s)H9^OjgiF5a=3%AQa6ruczo=;+cs%(xtNQyjrI&^1fzkM%TB29TIBF&!#pQdZE%s z=MNVTI8>4%vQtqTG|~pkj)pi$J5ogJWVHifH^s}G#&nu3g)hiE5%HL6DsGGND#@*n z!z#47cy3*s$$Jn$mh5@W2E++`UdR-1c?^$VXOvG__`)!4fD8_d8myvDX?{+ynfDGj zvp=~m*8hk=DlmX%W-egjE=@eZd*fik0zMFdY<+34AyR`}n8&Fe>Ij~FIv;I-WXhG{ zN~nDmbR}2K-d5qV?X-4#P)}d05pzaowv7lj;GdohdyNun+2s$7v+=k4N=R z1msMQNTv7~k(A01MG(`al*bzEx&|jZ&0|C^ZA}wNbYqOD4SaupjA*0Hq=(Z*7IjG% z9>}F1Mxy+b?-t3ipBg-FfI(f|qCq2O?@_@FR{v^fTxAnDS)@Gh$~4#`(kQdG@Z)sc zCbzh$;0#6&F3B#*6=}J^c(THZN^6T8ROaj2qJU<2M2Z{Mu_)CfUxvuaW89LUFifKQ zjaR?5)Nc|Fpj?+B&_#TNT)ATSic(^M{D}I+ireeYlUaOFnU%&6NfE-?6AvC3hNpC# zxSbD*2N++(iiV6-H&>p1W>Zy{Nt7BVZvA(`%#Ra|wL!EsPFQJPlCJVmAb8Q@EDz*W+y4N6s-Ty=A}juN z9asZRw+f+6FY}6ig?xwufWF3D2Sna7dsYP8ymZtilk)=s3L-3YNaZuLM4p?^kK+s7 z8!u9|gETH)1cF=#Cd0$w0s||~t}S=bMX4B2sjc}+tHmMxyUZ_G$-Sbt5se1-O^fodE7yU8Kd;uNLD+L;K z6~M4pQupYOHg)H{D4S$*$RW-DE_IIvSB<*A_$=YNZ$N0TOpqTBm6OwmCtt5S4S-xf}>ka@=@ilT$a;wz(n*8Tpy z8d0}lGDMrGPXxIEQQuPoQP0Ct`3<6OJv`E(>nTjvnZsp}_`zhKPN!2v_RU<9jEb&G z!j%LF6m6)ARv8V|T&GcC@{I~!0QHUFKG)K^+9HE)atosok23l)wK@T}b2tQCO~D8O&vws*oW+{fPPN5NflJAF zRB24YpQN?5MV?l{^t-b*i?TqE^B83$D8!Mde&f||6{6iJ_Gd^BJQWtxqzuuqMv8fa zzRD17t}gj4im73-vG$42@Hj{C5dd2`nuWtCBFfwo6H&}X=aWN3Pa0U9HW58Z6Ej8k zRt^z8TS-Kpmo*Vl7L74u!gSPE3^0nqDmOZ;B4tX+%4m>GluAWe!tdqM9m+~0vP9;M zINa=PpZ)EjGPOiZafOQbg%tz@N|>FBv=I{F0pvO!0WQip7j7c7p@ z(NcRKA?#RWf^$B(80P}a$-4I`XI}x_G&3~Oo_ru=D_>E%^1;I#0!Rrw{eVpYiMYfW z2$ZUPj9U$5FUWo2u@v1nd9MJQGQyP;Pfx(m5X~`gWPpeZuth?8<$6Sk;iFacs7{U; zj5F-_kipDF>`OBni1aR~T|Dpsik^!Z;d8^U2E3DE9m46#2%J#^2a7k*3QoYB91y6T z0P$2_2N+{2J={R}sD7?!FnAfWPuNjyDDwHo$7&w4j7@Fg*+9Xo7-^5gPBXKun#82)7w9`)fXYxru&Mxg994l62UP(uI^$KS3e*!NaDAQc z%WKF39^=wH?0`l%Dg?G33)(sa2MoB3m`jQYmjM-mZJ-rh2Y|3u2*RBEpR@$p3GTB0 zMpOQymLyhoCpU#~%|ly=ToB4JAZZviTReuYh$UbR;xFPh14{x~!hy?jpZFVWiqice zFK)ho5?;ysROA-{-+bXu^3`TGJqiDXl9rWF8_qn&FPg%K;tId`6JR6;L~lH<35dJ| z?BxcnAgChN@d zRP2yc#c|D`K)j|Jj$7_ls12A<-2WSB?i`C>w(b%^7y{U6Zn?V>&0)8RnS^onxhfLMc+>i#Mp^fJH1z)WR!|jj4aLkQ(6bF)|IuGNFYE;8; zB_5>=W(?=@uF#=SutnK8kTjkME{s2pp_1zY>K<75#5C<$v80FM5O4 z7l@7#+?N`K`%-P(H<1;VdmP;N8r5qA+;@_mY9#U!Be+i;b)%_`L}op(aq%z}27y#a z&>f8gE1{fe1SI$qHEb;I1F$)b#Q;3MZ!G-PkYGj=(Gwi}pPGo;8Ol7%y2y7tSwVlY zjS5qM3MV%amUe`8G!e~f#DTLE4)g#ALMDPMnQl$7(4(;D1i0E>l#BqE+d)QvOAuVC5a89-5a1HMDh6D_mYWL2U5Ww$#aXan9B8A) zB|25)i<}k_H1ls`#LdmH-d|N1u``x-1S9g&j&Vc`n|a|rz|sbLr?^(b3l~K}n{_qJ zd1N&gDG_&c*yM5C(HT+h=tedd^@6rrAx8E<%eFf@L%E}4Z%wu$(WvhG5X>n6+P)7} z*m2~CEq1Ux5C%YzKx@FF+Z;b8fbEXX@owY?A8Rd{&KE1yVj| z2~n>?G2Fd~w{amlg$gkbm(aI`qN60}OsQ@Q5zsz>D`_Z+o~K7z2;2=W{oq(_Z5eCo zTo8jGWx`XBu$>iR_6-k!kw#R&00#lg=NrK{v4F!v`$q&|{Tc9>FY=}sd#aT@51F9z z40ucOME01A4AegXOU{q`+3*hVZrov5*F_mEMRLOPMgZsqd^SEUrM4}F?^@;C)DLMh z#;zL!;tqi{$Jza0Bf|a_?Yj|J%Z@&TeD|E8h^I*ye;wY2V))fCmw|yWU!Yf7idNck z+SgM2C2koy%8h!XuLiUdts8O2xmn!jb|63I5^g$Buh94M>2It9^@*0Z64%1Bq|sUo zYWp&`9Y$VCcl;MPgK-&n%G(tVE=MeYtg*XeA~oH zb-&3k=uikQ%b|wGQx(cU(R4~|1Ce?eHESd4_?Gb&J8HxS5I8kLVA9nucmGQ?qK(KO z^tQx&!1pBvpU41TVmn_#cSpkJb3koz&~jp&>`3L}&z5DFsPKMK3b)FMnvR5}25he@e!z5}wYTA`Ex zm_5*LasvM0T^MsIMv8@%m#~8*>>1)x3)y^6;?^tnr|ab4dTZu-2$kye^&Qq`$bM?& zn9mE_<8xFLy1`5FnkQ&goCqH|x2tR9Llavq&YpREZbpG%bwj+lntkc*oJft^iTXSj zp@DlCtgF%n5Z&BPWVC-C=Mnlss$~DgYTp6O1ES0qK*nu6fm;UIB91Q`&y)cWB@;wZ(<2Vfo`!l{l!{G%K9i8vCpRP({tE1XvPDa%7H^ zL`5yBOtK`Bn~QCyhI9r^49?03E;# z0LpKbOgV6~`>m`pTlZJ^S_q>ZI#^hvXQ+aBdOJB)N_DJ%jSTXT9tmf)R)>dFMXjtH z(&LdNE1z?kucB60COH>LGIo)ZL`9WWCK)>>GH@^0NrIM&+gzDuQY6i%FFRmVl-g0T zn>sy`G_B*~H2oFpo0R%MbkktEaKaFVE~lH-ShVK5saY1XWA(x|8tNXqHM_R@tjNtLNQr=M4&L!%`>naXSFC!-;oRW3zQ%m)+yfSe`4T(Qp?j%+p4_78W7YwJi4!r54Imb~eE1Ny`vGAnX9deSWsMV1q zVd`!u{0Y2b9RRYhkM4ndD}<{YZ-h@oSx4|)C2C`?lUWK&I|*7C*o3B0BKX*Hz@}8;(g@(p5B3 zfiDn4&vq4Fz3jXb%>hK0h$9o}e~Ltd-GVi7#sJcF3B`VRdXm7NX{sn1<6~>nw4VwusWZi^t#xcXoH|EAFGux{Dj|XmOeN zIvJKs%!_K~A`RqQ;JjE~5Aif|zScvG*Dg@Up5jT^tZ(QkCIY~qUZRh7fM)j+cjMvd zElSb=Kq+0B4>gYoP+LASxwbcU7WUCky~Q&rT!iJqUv=*jBhbYuXAS~eFJ3r1xH4eKlVYllKF^%bKM zIyg8}nVTxk-R_ZN+^^{%?P1i&9cD}Ic7k8%nxP`M<2-v?7scQ+UT{VPF2J_B%(+gu zWCkqIq<5+ELJc4z0-)~bzfSbZP*KO>@6kr+o55}{3+wop9>d)5C2m81*DW?UdY!o^ zTtb1x$EtYd1uk(P3f5Jrr%~6S%bN3HWo6&6D|@Bm-PH7{e#JgME6~-31WI6r+jjG= zxUU1KIQSz0zwKOm753>Pz-)k>z$j?EN$&ktgFYAJtEmfIm$exo3|o>n2%kJ*-bV%P z1!k3>#HxACYhD1|;ow^;c#4Chf@4o1xIhJ`a&U?Y&gEdK3NGQ`QWboIgB67gtdxUW zT61ta2ZLL-L1izko&2(*T!jyCxHyTUD>(S+^j z9e$UNUb5mxV9vm>kK2Q-Qm?lH{qSj|AIgH^j<8mriSz>poqm{#-m#XiHq6HjYc7X3 z+uAe*_yC8ezOB*+EBK|vemTeCm3Fw8>riTk zr*pW>4wrDa+zv12aD^S_HrZWL#v!gZcL_haOSnmuT>?Ks_NU%K?vqEt)2)-5ZfAdY zx?R+49$8acJW)5K00XjEhMA~lzOs@Zjp?W6`ftq4b3sha&rr|Zks;X`8IkgHkrCmb zGa{wj=y|bf8gOvB3hw0Kp^f(Z;qa41YW{HW12qZHaqyiTDn;>Ggx}|kL2EjPx2oAz z!oe>abH?Qy+^mADIasEG8#uUM1f6jHB0Z@Qd0U+|0q#i5&ctgYVb{a>v=@&K+lQ z@9?9ZUde;c@!rsg;OYB6Sa(J~J zF6Z!OJA8!0JMHi(4j;C|W4VJ)*uAg<69ZL3~q#aTCx( zWGD_thGKQ)P;k&0iV_Z9Jg#QTat?oJhZ%xh0fu04GOhqW6%xR+vI1%{1o>OI0>>j2 zsEAZxC%!v#frCy3N;z2kp6b@^9G-568G>B_hG20rH5>8d3NVDq3UChuRT@=+Ixg#k zT>+kW9CQjhJRiBe=y5!m1Mn7LE&-oA)jGu~w=2S_qMckE&AIr=ia}hRZH=qU#X+PD zx?EoCxKjW>!v##QOj^uIE9?R|+9`mexd48u0#L0*2s_o9#VJd03&dQZqMckE&AIrA zvVy8LZ&YSm!r7+JWS+(1XeS#-b2fge!Wv@DA?y^knp1w)jLYI!ClkkVCVqz7^(n$m zrp*ZUwGP{5alDg_<2f5YRatzqlR3`-Lzc((mA2Xt?1Q70RC=59ByNV?M+p_(EC$qJ zwS*!Cu)Sdof(wigv_H&Q;cAtaZpPm9A}YIC)XlQ_VK8KhOh9qNH!5*5qJ+PICmlyc ze-RDZJ!fQjo2bH}lJg6_8?!#PW``Ad$}u&YH!%Ohwme>Sy!%C($1ujzj=x}YaTz7t zA~O3Mp}e7mUU z3B#akZ-;vP1sZd^__T1|vG7QvdxC96f7YH&M$ak`7awTc9ij{S zzua(#SUPA8YcYA;(26zCB_3zv4xge4WwuXNEK(E=4aiyQ0tCP2@1NTqtI z3uBZZ+(&4O?TcI9z}#RZpf$HtNuIo)s#O2V;M3fI`QgIcZB#wU1{!lGX5bb>`K#$3 zY^P(QcE@Qkm)DK7<`#PQPBEQ`J@`}Sx=!}>=DUIfmKX4bn_nBYob)xXTV4%SzV4x| z_h4`N6Z+{MafP-Qw`z#oz?V334pfZF^OAM|LD9HVDPFNA_t7KwioW6#gI3%t(&?Lf zq09Y}w2>kw32E#KDMiv`YBCZw(7Wl{k;0#WO?kx)!gH|?NA~t1?$ zq-fI|iLi=?oBO%1Rj3$G^wt5k#jjHPfMI@1x%Y|s+ETiBpGb>~s^G2nVVdowZlgrq z{{&=aVn%W-Xco(FsLZACb4UH>@Sh$f8q`qbFGm4K?I7>{qOKdmYXxy?sLlQ8+P!q` z{i1Q;2lPxYwrz?YL1$PLM%_m*+%NJ2O7|L`W54no`_-Ofj8b`y9it!a7rE}A)f{U; zgYOa9)cEhB`0m|&LJ$jxiM99Nv*Y4B|Na+tT+ZEz_Qi>NvqfsF-!?Kj}u z7&8;Sa>oOrLCi5bJmUe;*H^k8j<7fI#tY`tX|*+@d<3|xhuM;A5M6ixmh?zrm2Tuz zn8T-44&J!(8z3VFgcX3Gj{gw#MCtm_&HoUq4Q!ZYJpyWWh=x8Q?u13fhDSs*tqc9~ zi0B31Jslr~0r%xJ^ik~Tyh&sDtv}6wRNR3i<`g|cD{E{8N}4GLYbq*l^RlG?sIBL{*0o5i5XiX_1H1c=~(pP7_mX? zKud<+XZQjem?MPbu^eD4siYSU*9wrMyPxP(~_K)OulGYSK) zA53!}7v0c;uOAn!Q;NLaWaQ*zPW~$?e=Mlel~nSS?#sRs*H1E4w!zuSY&cia=&_=M z_IG-FEH=LT&KxIhRlj*OcAU6P>r3B{6aQn}hrPP{=#D2q9C2cZ^v1ZJxpbDEPKD#K zv)zUIj~9Q%m)FM&zB)VlOY>sUM7x4+EJmN*M~@bZMrq$0b~~BV;0l@F8{R_tc&ki{ z0S&h{Z={{Y=t(@yC&lB&=6tW{M!Op3=TNuabxbO3l;4RiJ_&d;$UgxJGj+>TI=whS z6g2y+K5!9?rf}R7?jJm`r8kGcp%4}fuFA1*d%_LAe92P`7j%Atn2!t2=S~#Q;_{ky zlf>n?zUlEv;xPdI{vXDx6q+o085lM=h7wamxe)sVRcs9uQtC7@EO8&J2JtJm7Q&%!ww4l$Q!4Mt7PRvF$pWZd)jd zahKJP3&p!yY3SwWL=PdBZ=zooi3jiujd%eXPwhQA|03qwd(?HYXpYC-i^Zy#(oKdn zbxWvViI|F`7+VZ~EVD%$g+IAMPlhc1XOh z!!6O(Kd9G|AH>3BRIB@W@fjW&7sOZg!yih!$XI*qWw=EtGhDLM`3u*;A&oIr+4DI^tbe!(9rg}Tj0duO}+FiWP7lq{%Pggyq@~{DsQ9dY%e`I^krxL zHlckP0`xQukgx8huV%SDN_y&#g#O+GC#R|;uA$6{ zAZz1$>&FtnazGluw1@oAp2qdjZwFmJ+DC7wIowEAKYh8TbqsxQx!y?^B_%XqpkA0& zhO;CPBOzyAu9Ps$>f~cOIN#xMFgQ@pOKS&fn6XeT;tF;tbZcc|XYVS(yjtG}rINFQFSPqH7G zH0nD2uL$qD4iy_l6Ry{D0buF%dJ8xR=ugf`**kM!$jFf)aN$+pJL}nq4l@v z4{01xbUPLzBHp=OAE+YY|E5325mW!B*TLwX2(7qNPv-z3@EpDTcm3hexA*GLYtFX} zE%e+d%#r96Z^9$ftqU%ia$I(-QsQoaC8?86Pes+h^`an_Sr5{$LX3?V2 zSiwb9K3dOnI$e&@n~2h1LoLVXS%#?iH8kJ}-K~j56KLpoy{&id1UCxJ^;#>Rq*>$j z`-5*h$r0IJYqbhwd95`nkmmd9ZFn;>9r21051AHtOAK%>qtymGfPc+`agVSlTrdTzEb=ycie8j?*>8%%$EN7* zwVu@IDZQ=Mn{Iwe&(=GcH&PJ7rgo!}Iq_Kc6d-;;8B_JY<1uBbJ_OSEzNz~C5D14( z)33r*Ts}>o0l~5R(^!DxY3b9tKkK8C7`_;mIRSVd!`ZHwI|JGS_m!IC>A=$<0plTB z=?`a|f#Sr6Td6Zx9mtKv57Y)n>G@|Mo?cEno&i$tPft(RZ?3g^g4;X|)X1l{tWtg< zV}{-+>jp$kN7T;ncbqb@%GLM2GxUb)`&dLB4}Zsb6YHG%K5>TLKz%=ssHqbj`0(`y zUqB3h`wYFl`hEaWZ-l?^#>*P@J$@!o+YQueCKk~dS}{{+!TIz|^lA@kN%~!gtdz4k z(wL33%9e|X{ah1t4C17PF?>Zfx)joOe>@6#NVY3i zyhCW(T)iOSR1DkN*-=-~2XplYFiX15)33*4_B>GUA)z+&x#X*;>jJ$GUdJ!c!M0sR zb4v6;aOXrf&y0>P3_KR3d;lh$PK?WIxXhaBAAS&mn*FxmLes%lQeN|#5iu?v*)J-M-RDqw3%plznk3-wr0ynuQy z1bJUY6$|xYcno+BbfRr&^mBTm0Qr7uk^WM~D%)9e0FF_>Lis>qB`!lV3u*8RdJgj3 z{enIt2Z!ziy&fL-F4Obzm|?%YvrKOQ zS$n@7=~=Gd?H|U1EiCK(%!e~y$*vEv3N2=BSI3xZh?eWkM8${n!E(I|N;6*8d*gA% z%X&l9>w%Z`s{w!e%L?%EmodXPQT8k9(ft)9*+e5=!7N@yU%sLb1?_0D0!y_W4O;;k z-Hs-#(0gfnLLaZthiiCtepO#9@I3Rn{%5??^{dd&t7y_H{ZC+E42OX+90tY!19Nee z-T}4g@Fu9}4oX_B=hEUg^%Ky~&I{?;`0`c=6;xkZBeKV8{Sm-jzgoX3X_eyPy|7G2 zW6}LB-qMetu-v!x{{c%h>usR9H)+S)`dmcb`3^?mEqdx5{U3noUZXDp^Rax5ek;Bu zufjy+*EefOcmyP^dTa&!*t|beV}(H7#^h;^|1~5Mp~{PH})nlmgWsrcX-s8Fa?O4^rp#;N9ob(Dj%*MWGez^#QEpy193LdMI@RrW{gs-l%6d9LIGV z^*a%>X`{X?c^YiC!Q--vctsY+(W{#jA>6V_zdd>CC10%%P*M@y_yPKS2tD+HzOB{} zUYZV>QJ&_9(6yWO*5dF)nzC8%n*8)7Ku|;qRcwZ|QbZ>U;Ck+ll=@1pr8yNe9;RekG$Yq zIX(3ux_*3U@rU|U;pvENG#EB6rlK;v0CeqdW$2oYp`~T|J)V9g)7;!0!Ybv%0TAzX z%ZY+SgDJLHeTG7PIKKi#1srIYgE$4_7n}t<2PMJCu*#Bw!=;d$p&i}VXgnI4K?+~JV8Lix_*AA9D zFyoycr*Y^fQK*=j(uotBlq zPV4sTsqMp6%tg{_+4GRJT5cc8REXepN;shV>pHm+TQx6ZgBZJZH28p?BFbM6{q=zE z(Szk5+ICTOIL}hobwId>X2j#0&1O_;e&{iB^hYB`2;zjO&i>nT6*^?3p(N zdl^yi5*YQP?Zv{`;cF#7X42|GxVINvdPQEeQjxj-?r3oQ-B}T*Uy5U}Th| zA)>2sde71P7Z%Y3x3dZ___86y{_}jrt?4OYr(<3b3|#p;*aWAh&SDooas*k zgEaD}o+q{MY1%Q!Y2#?!F+Cle$j)PWtGeTqc;dmA?y;Iivp5IJ&C1IHPhrs z4S7q1d>D^9y0joJ{aKfvX=R}{Qcl&x;m<=K7_x(onW)9e+QEmhrdUsnvGFy=nl;9zBGwyFswC)URgz#$WqYr$lBT94msT2+9YCL{v0(T@LaX?j zw;n*NMWt-kLHj zPnaJ;b%-mIhoP3de(jg1J)@jKkMNhl`lM|8DU-i#`Syt2{Fkt&*J9cw|9#Ry~3m*@r9Us_W+z%iX6zv6-8 zcrNozHby(L(@C9XCuQX}g%t6zo7GFu;o%oXVPj&z z{a*@0Wtki=Gu>=)C~*Gl<#^dEwvVPhUCpaJs7$e~0$W(cI8bHioyBGU(iogkOM&k>{XEl#G zZv-@$;+`8BQ@~pZuP!mtw9Rx&8dOrFchQtI*#J&A-b|Bqu^@{@OAZ9znX*mOY`q zd!n|C!`{v=dOAaPPdLK&LGalxxR)uUy%{nuzB0SHisCb6U7X%4%#?Qm#Jo&71twm5 z1F}kh*teA0s}gy4EZEn7uxmBZy&^C?gU^B|q7BZq45(4bto3`qD~ zu51atRP#K!!;Q{X4TkB)bzJyOKAD5UAMweTQLn~+*;N116CnQsiA|~4FU<^SYM_Q> z8!Dw)cZqJ8jl%qrR{G^aT)gr7O+Cd;9f!h)=F6jw9;R4XdFCx5app`o|~p0MB zt)Z+ZDu9F<%FZC)fq3&PTG~(^z^?R^0(l$Uz8o*GQ)D-i9np|$8p$HW&TJ%uTHD3x zjipFf@)4Uva>wiD&QRT^n6&ohz6E>^_GmjzX(sd1XHA8^thhF~i?JL!myS@yQ}li_ z*+l!8Dw@fOEf+0_VRcelQ4YQgpG(5;yY2Vq!tZ7F`-=-gFE^J>wd|5`6|6^@Aix8M z$ySGMYxzfXqEI%*rn$d`{6z3;3)vmdP=}WCS}l1yYElR$`WOm@2N82u=($$%e>81Z zD5s4)rNLk!t)2WNXDcUT>qm%!K3s~$3ON*#1I6UO=t6t>1U=baUiXK|&H&!&4)V4n z2VNLN=&p`Z+C^SpD_p6{%G2mB@*(XjI@d+E z#t63PDmP=#{_7&iRs(58@`K8^Q$_OqDsQ7{sD*|2WoU&ZpV#VEOji3?(24kJ54XmG zs6hjau>tMv1chHV_7+2bzf8K7Q9w^sz(YOdXxi0F#?gWzpp0%kLFi$#5c$aERlcdD zd1A?e(Ai!R#&n<1;Xd*PbbOD#s3+POf4S_T?WDn%%R)#Ak6jL&RY)&hE{E`ZJ$t#F z%k$O1e4W={exjYHd#}JG8w3=OsqNt_II-)fm;#?c_XGmz^v)GPlG|w067{hEMz1(XaBdanhqPx*K#+~R{U>MamH<{RhU>l)gpsbCN@C=d%7~dx;e4h+_e`*jA_codu2-Ks_gC%4g zx^gg(!)|(Tu&fVD#)X6BU_5>qEEjQ0XJ09&0ea&hl9}F|tK`+tFy42SJi{i1cMX+K zYmd#mRz9IU78-x8d{>Kgbdpbas9PCkE1tv4^!haadN`8o7+QY4bo=^M?7BoX=|q9M z+vPT&@bD@E_888uKpRRmX&@fBRfbiQSiZaH{Dbly?TN*AJtVziP`ej!3R&tK)neuj8Aln_e1BE zGR}s}Lgm+S8S*9`k%XDD9Yp?~GqC{h`ol~) z!FdK~HpwD$P&_Uk#Mz0q;wmiO&3mcNESXLEESVwfmr2Zkl?;(-Fy@xEIP%k3@9=7OKq-wIqtB%Ehe%PNV~3nz9ppGplZt3zEu#@N8T3=cDIeqIvV>ljc%LJ|NPNvox#NW3~&mT_ABo-|KX! zMAnag9cx^11@O5T%3LVBz&4A9J}2AbG~3kYV|v?!$E@9ySE|gw8LLEx-pge*rjl82!%+vII5EdQrB* z+khA4*aXgSSp)>k<`s6Y2H$qk@}QJ z@o-AG6?`~K!1Ml=r7|b|CpT~1q04aC3bg~br@ozCD#wBWNvbo-8?VJg&i1&p7AFx&V%6-k6{8sjQwDRP{{qY2djy_cB_uN)E*F$oMxw zbs_q_DR0Ev)Hl&pZ9^Y(q`3&4qg%KOA>Re04N=OyS~eDk_fqTC5K=DC?W<*zxG&V! zK?b1G#j~mDP|LCag3Yqw=C~MI_)%TfTYj-xHi|pEmqj>?iXiLF+-$1<7I?WY=&miY z5zt-mEiep4L~qF<1q1<9CR9X?uK05;R(b0u7rfgRU9gpQyNGU5>n}eIRroWAaJ(jk-EqmeEC)|MD z43_4;cd(kCr#0^Y1;AC?JF;`2ls(x=vxq0B4tT6lW3a7(%+zVIQtGlseu{nKT5Ewh zf2F(E%C304yjG6FPOkr5`Lrn8N2}hIo%#98yO8kk9Pl1^pUbJ_JqXQs-S!@4-ZRv! z6d0*L-B~KTV;Qd~1uB?BdrQGP71GI4$RqDlyLGZpV&9%P1&Y(7SU;@$n7mF7gWc;7 z>trYIJ{)x8>uoe>bz7{L!;xj~df7e!S-|Poy;($i*MryEL!;i8t#zpPsN0dWRC@P) zncoa=Omr%Bf6yF2ZfC@dMQbLr&;s9PK%-E;K212Go8c+Kbw?=hCo0?^Z$ke~+kgr8 z9PQlzZdoNsC3BP3`2b4IBb=WH%@t!1q}kW`)Q*bn8$^n@07+E-73tl70$6Hop<48yH{!0CfWEd zGz$JJF(oj&>_*{LH44U5upd&N8CgPpX3}V*Nbs$xHB^bComy44lSiB@T@ov2XUI#Z%#CnE@l6 zzMEzLzu?TNjzP7Y)4AAu^>NcNZ*r0ayaB1Ne3TgDtqxGLE`jfg$9pN9r}K2KqEl1T z&a@1i+Qpe!!`w$FHv_-yJP?gvvZ&`4h!xfG%gQY>J7XATh2kZ6%JL~$^cDu_HP&jR z(2sx3ilK8`Anun^yREW$)^de<8j5^SHo=3HAEsNai#2fyZ9 z`eiG4Zag!$$<>`!1Lb1y)MP|{OhF8`FmX5znCn5HDS@%)`KK0?$;mk+5uz3w%qy`* z{=8jI%J?lfq<;uizO8%m)p&}|&GqKX4%d7e`t(DoYdAQ!^CLM9m*)Lx2gYhL{bL8Z z3%a}=@(!r4JZ19kR4h4l=^sp=OmnS=skevbCHQheFO~rpA@z43%RWfm@)LQBcWtEB zYpLWDd6hPiuGk|x$4`o}T>T&=a1YR{dt_4@{x5lj^A%UQPS_*!Y1>z_J{5f`ue9Uq z(27q%f+o@0oidYt`V=~qi8SCdaAcF{&My&F@)?lv1$zH8NSmr0J4dcvn3^xC0S6Tx z1@cpi>l-Tm9QZdXJ-{{HCF{US@=ssL>+ogu7pjb7U&s_312lGCk{lKKyIq$i^ye?( z%1Tw};b@?FHC5sW{BYQqc^6&23p8OOE#9TdD&HmlVQ23QV5&FX`bv&Rl(kzWcyPD8 zBNeQcwD>%vut!p0BM1n&r%-$1wU1zF!fsNPBR4SJx6j(wvl$3tgVrydNxR_*~U*-nY&GS@^+ zbgl^&O)9Q%iJ@s7>Za15a_QE$0ci{x6_Y~`l*?Lx^(NjR%hweUN zf4OX*`Z8^wYWw{CvR=Yr!Df~iR3PhT>D)fd(24Y03FcDSerSOvQvU-Wo9NI3@^xrl zx_&FqV#0PjC^u?FGY`p(WT;o!)dXK(0fd9y5h^$&M?moPj>#Gj*;Nhwa0uGy+I_+4 zxtOSce*ywzhfY>Jl+lIX%T>^<9{e7&;uPKZgKVP>3qAdV{0TYthrnnWoTXxsPnaD}Yn$LiC;%3zn*$2tJv zU8M7&jmn;@j%S!p@GjZsRPi(P11o9W&$59XDQk-K)D#(y$c0a4rN+9j z(orHv-Nj?_I)Jf<`v0WBOX_RZRJGI^t2UUX9G7i?fbEq<9>0e*ziD*fxa?p{>CS__ILh>}mO$fM)oWbFyv*_U1vQS#hTGGR;ssDBu%uPpa&Z zTne_vhc8W-r-fWMj=G+gwOLpnb5?If6V78ei)iV2AmSqW_&hY8`>5>&h_;96rVBD3 z#;D^i$g3_}1~BbGiP(~nH>yx57IphDb#h#eD7g!zfO79=y%qKZAY)@xF|HMqb5UOH z8)or}s>`Ah*Ou>T#shkEa45hR`)S%maK3%9tBkd{e@3d&Q7fD=&={`moAI{MH1{yG zOu|~uTXvB$mTRgI6k%vaznh&jFx`(zGZ$$fC9sT85re*h$CN7Hr{lYcSkPpAXUj>o zEf^_d627BJ%tuwwO3fG)bc%t6e^fCKx6`?s&_hlsu=bDoe1AK=5T6Spg~0wl>hqoL z^oID{P<=)Zv%V+l^Q|nOqc?dRD<~+8Yqjq}JB3jyU^6h=Fq+0~0bd3Ap3O0Ce$Ag6 z3r>TD8CXReHH_w%a&=;i;bM=6ZrW8hlUClClR{^n%&kYeVvIZ(f1ZgkTE*n549F1U>XKxybPT6&|PuvRaF z9`P8R8v}@K!mIIwv`uSa?8!%n9QMc;7P#nu$7l$k39&{;S>pD)XyH0v3wk)#NI{8H zV+}uwdM(yyjP>+QtT7}Vas_ZDoFFn!1o@vQ3RXeyIOCQc>pW`6bB0^rZ^qR^pe9(O zVC~VFNPQHO8#^iJSS3O7%+BxwJ=@DkB?FgCKgJo?=l5lG9x{Wo;7OE$;~&2;L^f+N zPsAyLORrq+FQA*oW~Bxgp#poekfc^8&_3$LJa=T7Zu1(qXzQqXyfF(6+Z=E7m0LXU zlJrrE(L3c6kJ9U~glB7I&2qXY!RTWrc_+bWo3hbP!qtx?k%>lsJIPgv#x@N0ooqDAt>HtApE)@Nl?rNMV$Y$+ zY8gWTdsnS+YgLD=uc1Rmr-X}FNj|C}NyAj5ad6f_aO+r}jF;L29bg!pLH~r&3Uj|| z*r5pERjQIVv6DAZtDk8$V*o~~tyO)Yz&ZeF2Q#L1c*Z~;#~k4?R`L&zK9?feUMqNU zE2#N4)O_F^rxJ{Ju<&@&qEb9(!ZPmf6=csPI`(*C7WGIo2DRgw<*0J8@U6vD1g55P zeZ{L*RTZtwL4}kok`oaswxz)d{+xrPr5jnjRB!MEhx!BBOIvd~hda-Q9VrvxHj&e=|(pk5(@Wzi?8iNyoYqbgYsG@S^2N@~H0q?{(k_MoRiZBzW-f5*4w<7xrjZeo z+TC63OQnICM%@9PhAvlI7upYvqf2k=f_w_YPgbl!M70OYZ`uNO96vJrSmP{#C0khk zz9d*d1lhqEDgh8wnrY;8Ho+vr#LeSNPL{4fEj8|e_>qAG)EI3s18!f!@IVS~NVb9l zP^|=vNJ@dr6-Y&`N*fuebyEi^LwBinW19&~&Hxm7!+n6^8XldnYw93oAbqJ1F1B>I zPT(YbGea#a^8o|u<_Ptjn64S$%rA*eBNX?x{Diz*#q%hbsu6VA=~f=nOwon zNZ|SbGzYhiaA6Dq$DM#j*w!ajX&SKOsG}6vkld--8-ZM<7*@rGzuB)h@0wPk|UJ%P}&8xClxx zNMX-yUfHOd0*NT497hB|U4$x$AuF<@VMmAI0Jl_wug)=HvD>|4ueF%yGQbNWBRW*) zQW8;5%Z>Wm<05%Wc=Nb2wa2cE@5l3DEUh0->wCTbZtXjD6E}Jh8 zO&T#W5C>~<^G7MtOhDzqZKvoY%MIGW5@S?bft(e3GR%pg>ySm$rqH8pjAqF^rW!;7 zUKHLB-6Db3wlS^{iw;tJTcc;(l7m3s%-Eiz8`>Hz*o@9+yGqAF2o?%E^HHKef>m90b)}*Qi9XY&T|jw&ki=({0`Vx$m%s$V?5;iHGwt+>(k1(nsq{)CHE2Ts z&S%~Tk7zK=_#0>zp9)9+S=?KEFzmcitb=-EN}(K3JiARGY8Iv?Z~g&ULrFaxuH^+R zC2;YMFNwdpyd7Pz`0~ZGbrUdj+gQ}lP~$VRy>T_h{hIcWU1I5BHDOMP^{bs^8Nk#4-35<=Y3V}6_^>;d;-<6?#ti^DvIBhF)I5%~ zq=Ru6ic9Zk6!pM)P}r?OL&uZ1&}Z;A5~euXsHwS|k1tr5245_0oBp#NTBXj9u#B@i z8an{+Z=H<#wJ?2|bp-}zvJDh7HMFpkQI{R#Z0v0O8IO8hj7Ok|dcKRX9dGw`HR{zt zCMe%qbE7fm@i4F-jJ1fvJh&9{wXQ}pq}4= zcGA1uz-BI_i`|SSUZvy)BpnKvQoU{l4X})X05;z;>gG-7JDynhmsTd0PLO~2JdB_>|yjq-HLh|4U^How)z)6flGm)5{ox{p6j!jJ%{73TxZ_#m$ZB zZQM}zg&03K7LD$Y$K0&8q7SAqwiEj(%S_CM?KHNx(W>TtvHBQU_0=xDz)c(IV%Wk$ zu88y@>sOqSqA`7pOh(U~#Ga{9-K_ePs(|b7fQw37+0Ww?IP}?^KE`ZxiNE^Q`7nxZ{lrxKO@7&~?1~keppw!EafS+q>Z|c~^coc=xnA#X3s@*)fE`0zV!JOMmt=bnK-~mPwp2s*XP}FRAn5s8DQ+_f&z4hVDN32 zz+U|{;qX>2#an@6!im`$2?ALIp_3a%4-Yh6`z>74W03KloNNd$xd$8lm=p~eZ1l|h z6v$PW=R7d>KmjHT2zaK#Sn$=sMt%Bbuu(f0=hHxKY9vHp$4$kPk6R2=!)gQwW(F*4 zffOXTviI?HfD#@DJlBHR~+>^sH7xsY?;hX-xz& zVWFj*cePUV*0WkL4R}r)59gJb4qCE0^eolwpnV*iB@3kr2pnpoNxijby7xr1pzk|q zBT)49=e5eNKoPq0yp}-L^V&jWywp*HU;bHZOh-*+hnR6MXjQrY9WQ9p@Y_}TDXZ9N8Z<6W&2clJy#?NucVCZ1xs$RXR<_nzJU zKksRCc}`}%ua$Mw3^J)lZ>#pvpoi+j4IX- zOQ*~az%+EGfgfni;mo9ZbLj8~T7CG1g?y;Rf&7*GP^%6`xzmSQA|9W7sP#l?1SVat z&emzm(y+LIll}IOKqu-^o@#L9s%y>C((q{WF^18Z!(`Tno5!7xwa&Pav*RaPK0<2w zztAGA%AW#ALh^OVcCpZbBW%>?Gn6|@-+rbopKBfMCipS^3#}o1 zhfjW?RdIgjhI?*EUoG0hSRL$!|89u)E>-ROV4fMs16%ej4eYD+w5#3dt2G5yZkPrB z!lId3+RLV;V|QccJK+{*_S2qIEuB*4s)>|++mR8w%b_@>)thxgYFp3-RsN!F{j{#0 z&Z$mzs)6~$r%>ns!15FQwMxp-@nLHtXlQ?+?#|>KpgoCCeRhDB=Hxc$^8s2Hr2lBA zR~@J|b8$K}NYteVER^F1LImqfKMdr0R+T~8k1EC*GE`dyi`%xL+6H8PHcU2(%xL;; zn6^yabC$*r*V5E^XKDR#t${i3EV#TNFsI14KV0kaP{Icz{*{0dPgA3jTKt2>tk*|s z(ax|FftJi_?54!WXhvmgDe8%{l$Q-<`(64j8%*19x|^-liL)(SDg-0Gc8PB@V$|S` z$NIDxt2Io{4;*-6?Vrk1hxuei652liWe$bds0n4sRO|m>Bh|| zimHGyWU!Rvb%)huBEHK3evsjy6n#@MJ)A0!&@$-9Ng(ZK=c?nbUBl1C7_3ruD$9ZN20cYr|8QKt~7qytF z^?~vtf2Njzw;yI|WukkuguaaBxmAtFlExS$6*f% z--tf!7g{z8==qFJiaRudE};h8(PDG7+3-u;kfYs1_p4`XpW&_Q9IX&2CuOeI)cuvt ziaPIk>N!{2Y~F)Z!p?~rj@{Ln2aCXE1WeL;dQgfLP0S|!LB@P-ea3b^No`v|Lc!7u zD61NMHRftg2T6I?zlTpq@99q$<^xAwro;tWZT&Lz0Y0r#P&@i@0d{+5@+<@s3?r9p z%X^%bEr7sim-6p`C;Bj4$TFOMSODQf7EXmTLzRVKs~1zJg<5QIXTJBY6aT3TY}Id} zR-O4dytV?MN(rh}k&TM>FVqsN+rH?U{|FlA8^hRf7dy&M;QP+l8=3bin1Zi$=!qPZ z{Rn*IDlgLXCh!e!_`R|5#+rHCZH}95gII#DT=K<;}dicm@s`( zzvZK^dx>NR4p7mIMG$QE(C$TAMuIe6*e#B_%Y)z+(Fz*9ga}evX?%XQ#lY*cXyjs9 zZQWw6j=|;FT>!$yjm27O>>n60!yRVX0Z%a2`9LFptKzCH0R!;|y|zR<4v*(YmujWZ z;B!m0^2#dezf?AuyHv|a%4FrGJq~!e$#HP|*p-k`*3lnJfv_%9bgou2RK_dfG5T+B zLrHo*7p5;n!0uBGh(?Cn~wJq4Os>Psz6%fv~){^b8sOK^0?_V4l~4CfM`LE#60S#XfZi~Z z=5GLL!)I;K{=nO|jaoE&B{QDqzBUK$DQX`TTBB}6;|#6pruU+w>MkgZqcIEkV0Ff3K9)m ze4SS0rj^GzsB|Zl-Ui0zFnzcUpmLaIZPUucA9S-%Fr#hQJLNfVTmiv45U81dekm=2 z^52Vd(v59e8`Nm}6&CRzz5SII<1Q*oBfo+g>oEO;yi$SI{8HX~utj=gyDYVE`~Rs_ zOZYl!jVs|)1V#lViR5DV!InAI`(MXPIXkq$7(>~eT9OYX7^`6nQvHqxxXPlBQ*ryt zYdf_kYw}XEipR!G0)|qo>qjW-V8-U75~l$6DuRI&P6}Y#cS4fiNvXSl4R=zjU0Cj& z)W?3CWxws&g}KGaw_RAb15{(TRxWz3fOZx(J4|Z)GWJGc5;>RN+YOjFOFMUKna=z7 z?3DCD6s9d2W)#SKC_@d{j*OSp&sZ@ zL-u2P)}b}~wUe-ZWFG*jKX-vP9e}2O23)K2z6NqCe%rxsL+SUgwSKKeVYkc)#0RFuFjw*)37qX?VY`~=)ZRPRtDm5+x9@#z)lR*UTHwCWpBfoysMM^e+U z2blkOxI7F!c6opvkZ#IBXcKo**Mr*QUf55t_{{Yw{$sFs(7J=#%P@hIDu9@>i#im5 zB<-RB1z=uw(TW1CMjG4LJE1%-2*ip`7{v5e5;pmMeE^>)(+*+NWm4@!P%Z7Efq_;9&-nY==KvsooJdUq9T~=p*3J2GNNlK!t-S^r%)T>^~Z#)<=<>PXmu?Em@Ux z6o!eVboD3*dM>p%re%1SFdo9_@V5|p(WTV?7}h(VRvg1e)T67%w00=k=(yG=b_Y(w zU^UpO1^Q74a{R}IdH(u16kqw2azcAYEkt0M6F|F*X~zi=-NmGQr%eJMKlwXtfr3lJ zGEZvH^YITqT!AFNp!%;~phYJk2wk~AU!T-UtCug(^^;mV&pmc&V2=$q`e=7b%cwKb z;00iNGp0rHjx?AjEdmcVi-B$;n^kTj4d&_Pt{6!xP65RXq+_R`J03(aKVXXuqDDVx zkH{Hw`S)lr#`?20Og|0(0a9CS>(L*fN{${5+lJH{!%{RsVG4s!A5L?ALVLq$?@wBD z4<^vX@nd~nOkUDyY`4P{en3eVrBgTK*koyIIoqK z`3Q>2`PPwNHAQKWcUm4IRs2~B4if+z5G37BLWK6yF7Hrzh}7_z-S`!TH5?-4SnosG zy4jQrO!$RD?L`&BLGwR>4so^DpU|+3qBDO=_)Wa7 zrBoYw(N!)80_Y-Eb7loza`?qpTOG;In-}a7llcXZ*z-C_(>z4?0ow^*!|Sle)w62- zg-60X7nG(DVjTVxP|(kpyda-?l=YVU4K2$8+WEKEu)+c<7NHQbco#3?qNlOm@)d$^ zb5ovlVUt1S8`_Al!V7>1ED7_od$6VE)4m&!ITp~(8^G}SlzI~q*#dg=CSYhMjlo?4 zc-wUom|+3AZ%IU7^_EsYe1|;3hw$6A)hw1%vMO0RA`V7{i8)`e5xmt7NWF= zgJA|T2Xba9puLM6Yxy2|ZsYULP`%supykvRj|`B@fU$=?WWfS3-E~SHfDN(omn2Us zZfhwC|CRiLvb9NZnUwcs7|hbmV0vD5Fu_fUceLlCI;S{oA;$$F2Vz2s3Or58eNv)n z)*a2I45Nj2Fqkv+%^ld_KsfJc&qN|XgYcgNreoC=j+{uv$n$rB5s%YHceMrXU-T5f zC-}cI_q5W^o$eG5Wj`g#(MR{R+U^@VT-QwR4O(|k8wJ+0$$gEYe*;S_{DmrvvJjEs z^eshq>)L%_0dR)vsi`7ZF_f){Sn(Q!_vsgDxgrLJtTKSwtAhK3Lm6bKMyjZU`Fux( zo8<-?q>4vy+xJ#g#Cpb~psnf}Z_u{0$x)zxw^VTefnj$!#EZb`rJZ7+WPSy_YV({T z7FG8;MOo!{y5c2hX5*d&NV|0s@MOXv{8qq20W{vl7HFU8pKb zJ%epA=a_e?s8G~M)j$LEvL<>!d3jJ1Nw_NLiY8*+Qw?nFAb9_U2$2kncV!`>Q>PoL z*k@8vg6`bNpJBkaBOvXZ!6_P2N{82-MDNl==*0N4km;4MRK#+AV3c}GYR;GIb~T`)mzaeHS`l3NUSoz)xb zpCd@s3O8m6TA|~*P!({{-y7nIn5%l=LD89}GH_W56E*y9}nDI#Qlf!D9pG&c>&zI2YA9)zLVJ z7JEb@OwIfTwd6g#Q9Yb-i<*Pw_s#`7BT7b`gA(N9_I5;?*}YH)EqEZ8-w9**|UP=|2wlEG4?=KWHK4q{ulXaJ&r zFI<$vh*Y~HNJBy1>pB&*Pm7{f5#kwCoD(6^%W){0fH*gHuhi~9GrO!U@kfsC8Fcj=_zs} z#oEjio32!JuX;Do{U|X9_mj?v7F9F(3juNL8-4FP`Gugm><`JlAlM4UClpM~l@y+w zuwh}J7C`EOxilf9pX#+WCIvGH3GiYp_=!|h(jT0BlEk#l>V0hMI;Ui~)=p81}p){o+6bE)Y6vYYR zG4*o5^Xm5m(ZPEa`aRfBaVX0FCuGLq)H)G(K9gQg1W4j$gG5oTVxjJLq)2Il57v1X ztBAPDV9}D%KW_<8Fkln@bper~L{s?eW+sUm!77)KIzmQjeUk)RE9xbSx#>lUXsj!n zQNU^PWc5Id9;fN=m%|cgZGy){Gn^u6;S;Zc8s+)K?}!92DMhr!_|Bw=&wUdURX7{6 zhPDtIT6cPojT_-$?BLC_mh$5dYC&XLS@kM!0Vw3Rd z!0UjzHj9d-s?SEbDJxCzxl*X3aQVf?G?A3L0md}`(pY#_mwpr~|^+5^-!UDQik1SE#m(GvqOE^bAF1P0b!K#giZ%Fj&~{;n>@xolZNL|C3- z@M0m|QzLOBWoj%Y$>ob}4o6Y{P2{=S#;3ZVSShZ;@`R(ew=>j(CQ66nr5ig0FZ1ck z8v6zhtOLyIqBf;PbtRVil@+IeQzdbBIGki`Q8o|o)z~#IF9+(Y;4#i$d z%!n_%TS_=Gb&zRbJfGep)zLf{*c5M$?k(}l4R7prb_t~zR=P8Z{X^t+LIR2JzxhM-g+Q^g*Q7o&;6XcV8<`32@OhVm+l`GGc2hF+{9mP9=C zc_3nmxH%%eswgcHRcnZ2;JJJe-BKdhN({dTc-94Ak-u1i3&%DC7&82TTg9daJ^<`c zzp7#@?&$Pa1N?8IF4eHTH_|87MA=YAo*p1Bs0?nD2ky+PCSqgQ6A60AxL7eGy}DLDS#iJGEZrP&6PMAQWCmloxA02KD* z;&38M415P@Ay5@e1JDapt|j6h1smgJ9MnNpKoFB4$seK}F30ue7)$}c?@2dQc*8%? z#Nb+@LikR1ZXE}rp8&E0J<;}BBBjx8POmG?N5CxL9WW|Jfb61aqdi_a5O%zCGIH5V z1x}IZT?1iHuMM)9OKoe5=8*w9h3Qccd;kUTpxoM`OyXt9S@TpO&5n%Fy~z12h)%ap z^HFFWaUK};Rvl3;0UwOA_S}M2T$i>yPSC(RHmobUM@)0agXpoTXqr^b!i$0`AB`+W zztQ9h+xl#-yQ*vSC2ldCpa$e=6YfRM31NHi!Ml9U0)0g;F$&uL|>$AY9I!o zSi{Q+<*56kf}`8acvO6WoP>s=2FI%8!@0>pQXPxG7?C#@q|uhMa;axS(d@qx>o=)I zrnrUXnluuR#o;i#{In*(XD}aOx_D|aFDP@!J_NPVxTy%0Zu`Y!fS%08 zB1TX-hcnyo zmZqy6pnSh_I7QHw$HWWpF^Fp-zK#9`JTLe{%sRUPUV~3Jc(&ij@wgaX`9c|>LU@>% z_Lb|e<9D`#;j_nZ7nq2=ucg8d7=jCu)?B2GkBh!4o(-CcA+Xf#Xew$WT+QvK;wh!? zIE;Y7^mQ;osNEH@RoM{x@O-j>YtJWs@zrzoW#%inZj79^bN~(`V`h3#V!2`NR^%1+ zJdNqz2?lm}fK!kOl1~EsX<$~sBpqr7$Jlig(Oi56SBvcCn4$HwwmIg#JN?{Tl=UnJ zK(S{zN?)MJ7NWFsIh=hdrEXezw+LoiHoQjy^(M3s?NI&q7UBb_ggdnqPnh;mG5GbW zUfQO?$K$J(*dZI}a!b)NVM(weW}MW6c16u%5zt3kDB7WpW$wDpnh(w_|8p8_^g%(T{Ba z_y;JxEtX*(z0g*?iNqakG3d?|)ecngD81bd9N|V9+)fM!JcK5vdqUfn$KtbniS`(_UmL zhpk%Ah=xko76WnnYRXjupapw3TLYgJZ{mJozH%kfQl1wfO5hVCJBlX_AnPKg4HzPp z@(!fAX-r4K+V?*?3c_W-!(YJiZKf430GPLu*-12m1y6wmuR|x1hFgt4>m*)6`$@}F z66sbaa6(5Zp|jYF2F`Q_XTO6&UKDk)@K3xb%9_jJ>dMw+UiXh*1pmLBX1yr#V99Cs z5_r@DwC*KvS3BwaOQNNT04);nxWSWWb`b|5+=O-&DQL1Lgn#ux92KR`FN-wie#5(eQk%5WbZ}ETM^D)D zGU!t#b-C^=Pg^olf+_M9@kTj$oDU=lXZZr66s-1kU^11_cS&Hfd9R3;MTh3s(T!I` zTIxG|)`Hc5AaJs04N3%+Wd$CbjJ$Y%Yg}n69GDu(ex$u30F%RC6=7j}B@L588DPNS zeV1}y1=`(9^g*8Q~dbz(kU$F^Kzaif6?lVCCxlwt45;%$H-V|Q~$%nlqX-2=dfJOJvmu~@d>QSj~ zVo(4m=XMh(00p0Qhqy71=5_~!?WEn^MGAxnFyFA}UhR&7FQDjkM6@2pGw;^)xq;l_w1=s=w@5tSr(Nok7-N(!K zK#M(liu6ddXb%+V(cjnSjDNV<2W^hV)?w%!P!AUZ>I`1%JKw7i;Bv*l>WLQ<)eXrZN4nEZ@-Teqws9gT)vMza%Y* zWXgqo;l$&>MZgQ*%?A@NU9<-F7k3@{Rq2bO0+kFOjBP!PatDhzCuB z_-C*fQ42>V*qI)_G;n}n-UlsXMmV65B9#}Mae>(&kUh?Fd$${Obzf>Vx;O-%K7+!B ziuAtHM}@=x_z#8K&MfevV_=YJga2lA35cKLfyC^~fkcQZ#U-!e(77xLR9>9;^FM(^ zcnlRMPA_T`qN<113>98&xQI`2(2t|y*pmg6s1#Mn$LTI}n5YL5(R7%oWWpmQ5IW{6 zeKAb5j$8zA&SKnVM@!F=M0kS z9naZGr-unX0x@WWXe!6gi{!nk+L3B5j}TA&*VGy`VWdd%*dGjS2iNd!L2R3&BSqcd zt1^TRTM>f13n*@scp~z@eFNPYC1N>t5hjh>V6P?AQBi01|5(T1I&z?ylr&n*!}zz4 zhT7@^UE;@HiW(!z%OD<2F!$_$CO?^&?hi9+oAT10VsY$SH_4}LXLB=0vRg;13p79Wn&xcrGDAs5Gdl)W5LvH zr!U3|8RHF9Gq9+cbYv{RWIKh96Xhj1$N8DMrQ^~|36o3%p{wSEqcTz{CqN0n372;= zfN}yW+K{m6bWsAJfD>-X#pDt$JK@szMI|6waEZf5iV}2RUzsvaDjB&BumBgCRNN}> zDC(y0Oe!o!k4u>kvUw5IX%@8{w+Z(8WV+CB``vXMbx>zM4W{@}7bAtTlF#}h@&U&v;Ninymrr>U z#3Sx8toP!hDAy*4NOg>BQv1k?^hS;3U}`iGrmFAgxrxw`=F|9z!03Bv+e9!{ZRz?% zQ9BCb324(0r%xhb1!18wCy6EEz6M?QAi6q9M6<#*;1ZWfUJ{c*GZqu1?89`P#0xS4 zLjy1~4Z8W|@>BcCkmt72;K=~+1C%>il+uBX`NM&SzL_lQCKW|=g7^)U6|@^ogM%E* zAb~i;hLk=<^oPM};S})>9??^!dbame(LBaR4OkbRhQOGEFyNUR8bjR zaE&q-REEYN{5nUMr-@-G_2G0;Cyl>d4hdtCxD9iN(^Gtc5IPxb5^$!6ri)?fgiF+E z26o_K8ZtvvrE=^BAP5YZ96aO8U+DWeBEAZ; znLC$D;$liI)fMtA5o)l$QBpuZD}r2bSWTY`IpQ2Op9`U274@1c%0U-BZ7vYmB|3(; z1UW`98H&$oE0zo>IhlsK0Y35bz@=QGH|B{t(TuOXmyHyU)R1tbA1EzW(VBVSn=a9B z^RQ@FD0IHy&^2}E119@ZxB24pSjgE7bNCnsqXD212?&{Vf4)e-$0sZhvEk^FYb^+z=;YLvWNJ)WOF%qz4?}1{PV65*j-zE|#_~5#ixzk~th~9VyN_yo;#* zQt_VWYBB$I)u8N`LpU(|!KHw;v*gOf65XU~xgsvQkOg+I%igPQ+Y0Ex65OP&x!40Y zXo&Ygwr?(S2}12=>zWE1KF+dGVub32I2#{7$odBa81Gwc28QpOr%!0 z#(q|8MTIx6WSV@UDe^UZ#x*;59YZlI&#p~c4l(yEWiA(qDyCz_1_(GGEf;V4zQpcf z@dau=d?trUO6RGOYCl+{{+hHxJjQ3n;R4L@5u^(+Hf5k!>a;?%hkbPA3Q;O7&=iiy z@o^YL6rzTk(kfHS>!~ayCgq8;@mxgu$MG4<4BG|*VF)8UOI`9rD{R$ed6E^{nI}4w z!=zv^yptgQIoaM>)CW7mKpNzbNKzX7xolztJ--qQkV#`#ir5GY8O<_Uf!>j|bC431 zg1D+~c`!o96kcB`DyZ0%DXT<$$;}{n3nE<jn$%>um?r{)#7P&cRJ;^#JQ;aeQ8nb(8s|b!5?y50RKSnvzTh(XD@J;W;q;S zSd*kk3SA>y9GOsZZ&lWa7%*2-9N{}(+O5Gh%cHl~h^Z~VcBjH^E0Rz7+d)|&ovB_p zPC^gc6gqdOPj3b$oW(D=eB-{_VhL29sl&&^F&u=$>0M4wtrgYE1jm7h!2;Bord!xO zv*{;GRMh#nG@r1@qsaB5b5S!Nu7^-|p7yO5@r=yjJp`tM6?&zE7@F)&cz3<1h}Pa- z7RQeJID8#X?|v!5JS8Wvu^`(VV5iS6iwiYlAE>(JOVN-)=iZk<`)6@;GGxuO^u`7l zATQDK4G{CL(%}svBb`rZ^Vv>JI0GXhnexW54fqJD!#njHC2a)XF@tJt6zLohPgand zf-4j`+xOZidNYm-K=U~&+$gSvBZ18u9G!;|fIw?VkEf8$n1H#|bu-B16`H+Syp!5I z)e(;l9gmyL{{T%$>gM2?=7F#iLbWX-3dNt;BHm84qazoWW0dbm(RgjS^%QoL3S{m1k zB2qy!cYu=hr~DlvGyGmM?kxWQ_bg z1)qnDQLsFLG2W*;yF>zSNU#$XAyr6rqGGWSS#>wI_gTu?jcs(3cJCIQz(l6+!N{Nh za+ow@e0nV1Zk`lQAM6p&^8Wi~k4Qv~OM7I+27ATh0p*AS#%cIo2xjMK>0YT8I=L5I z{bh>TCps_)N)~`mR7xg*#n?a;$5k|PA2_W`v}PaB%NhYZgff@t{5}zniv>AyaS-LT zN-sw>_k$zFJ(2qX+!)KJ`$bZGQOggsD?^n8F`tEkzzaX_7XuqMXFCQum3z<$l%RuW z1IriWfj2U1V(gqCTX}@2FlY~CpS)X2>UOXS=qPM;lUaZY0 zUtXPJE2FyzjsQPo=o;F~L);XTL`tUay@L)o8&< zXgKBXsd$V$g)XL82TzG!T*Ch&96hE`ztf^R<^2d9?NrMA2}>`NHj@7|aMu(I+3Qqs z;;PGEaMgPBSMjk!nQmEUpwq{*$2n12eR7T&9N`IPf}!x}p(70PA}!n#R+5HPP7CvR zi_%caPV;&~i_*9*%2~f$5Vv(@n)Ty#F-TRjFI%tO5IY^p9IL`@vC4=Z0b{`_>0RPu z*j^7MLT~Q~>rc(K-l5F1a&^}&C6Np9c~VHwpf?EP)FV(9t`Z~YE7R3DmWx1I`Bwqt zCU{&;l@Zn}9@k74ESP^Lxt6M@vFypNH0yAr%d22Tj`&=aEN`^yLk-I`G~P89iB?>q z>jdA^A!`);X`x09PDf!y;CZZ!YdOCU%5Zg2qsGYZ_Gd6bz>WkM2Ei2mPkC1p6v?dM z>ZK~%td*5q3O`p@aWzm7^0lz4D@v_?`lh_amvuI4Zpfd!sUuTyq^WxkIDkMej8Gd7 z7NR;eT#3Q?c4507b*+I&P5FR1u%7wUueGa!^>j_wVy*o{HSOlX*`Q^$H|&9G59r7r zYRN8Q_)OWkECLm}|DjP@?drLjEA<~rll34FiHFB(Cma9n#X3^owOLL7&%wdZ#QE@m zXl+ARm*9u`qSQ>+^nWGo&va$}D=DUttAFrAUC@F?u8fCg&N|u1H30k4dZ~%)SB8ei zn!2Vdd#uwvt}9CQf`8;!@yyS=WzQEDy=cW;_KIaaSctM) zVa4+7!gf8{&h2s1dt}y;v^02rt${1GJVr?P{yk ze<)4XlZ*AAJ>pJ0oFjN}s8-EqTm@?Se<=z##fOVhMhDlM!4LICGdj3t{442+=doB1 zmm2>(7VDudD77ON>*0|+*U?ovN*QH+-qYn$VsdWVAL53?5_G`ue8iIE+_t8@>naFW z@#F0QuFEkXeJH+$LZ`c)j>!vDVQ!?jO5Sbjm*?7^sAk`>625V{9FFWe zE_$?DFP_2rteajA`t?WL5diRnv`^f6BPysqZD%9E>gXvTJReqwtQ) z`rhSk>fm48M19(7y3;AIy4y)PwcJ&$b*4K^k>&Pz+zrAoOxfh0@IrROoQfvf;&wKx zMTWc3<0!c6ve3vpg?|w$ykKS4aj%YdoW|=%o!phF%hPVBRrYCj8{KgSg^||X;h22S zWu1A+t!m0LI{&J>6&|%;b1wrz{_Qn)Yn>5tjh^mK3evOR#dA4be%Dgenia~VC{<0yDQeruibr{sd<9} zs6b|IV4kt*#zIZ(jz5E`p&*#q{k*L3jFj0j z7AEQS@k#VslAexNJz4L`ub(9Ao%neoSuc$N-%HkKt9a(5>bOsD8I4NUUqVxt({-M! z{L*@S2-FQQ;lU*W0E&uFm)3ir0~)3O4bLi`(Te0>tH)AUA3aGOIGC#S(aR}! ztQY#|Z5((m`AEN`__F?#$cw$t_~y2}KLW3A`Fi@Awd)fc4N!6|PhWkQ;w${SxOfCZU9u%LLNl`%;FL{&CgbBE}mhMIqyHc!zXapd22QPxYc3lG(Fl{KVI+ZP)1sD1UOf7?%0jx z0IhLj`fv!BP%q=uZ_-q>cb#&kqP^>MeJa|!{!n`s+WQbq5t^!Fb)2Q|Rn)Azc6(Vs zT4=AAW@pzRu~0vP-zOL9H|$4cOIfTt9NI{h;ON>4y#mr}idpzsD%b#;UkfpM^>NE;pD=N&lO#2J6-2TEWcTsf($1BOVT92`! z*660f{eqeGNsiV#lCG}Pt5}`a>I46sV{wiNKlJ3U^it2VV8Y4~L{4U=v7x`f;f*K~ zS5j=j#mk>e542?6#y(N;S<YqR$Pu~Rys7md2>1jB7+4iG{jCr7=_e=56Aua3GM_n}CCTDniK zYd=y+?AI$9{c%gMlxSkY|9gL}I5?iTB``anE5JrBsH=CY6F7wA8*Gompd|%MUCWljM z^!@C+@cbMU*m4}Zcr2YduBXS331$O{bE?-a>GX~X9?2Rh1e~P!6M9n|Y>Yuuo(dtqjFdEYlx{8 zU#J&AYkH|rpApCyeNB&ty>!VneM-odU=|HHmJMX^E%f9cfYzckx1F{UX=YIx7eYd9 zD*J~X<5j&y>2NsXtnPp6Eum}F-7c4dSv~eB-A-@c(4R!! z)*E^$cnExV12kbay>L@6i^qVQ*s`-}#ZB4tp_|xlv&nf&Pf3^^ESFF+jPCKN?g{W9 z1HU*Px5bYp|1G_0G6u>XhhltPkW4rQ`&{GdGT{NH!{(h$*|+qXnAV-Q^p7#&ng8f2 zdo4A-trxjees^0xj+B*m^t1MaTQlzJxN&wT9l3|k*+nt;@r_fhcK0!IW#z62iiWBK2JPq|URIUq;AvFk0zQ-;JK0f2#k-IVVI8b&k~l}kMrYNUwM!Kp6q5A;Q-(KzI14wnp^3V0FTQ}j)!(bfA)FzoNR4*;5a%s0H! zGQGx&ig^m)1wg_D6+h6TFr$90A0gJDuaiDD~ z3^&r0AFap;BSckj#e0-d&8eKRo+)KaP&{XYF{5D6u|DUlzv2u~wR_Yw!6>KvZoQje zR8^Gw*6c(BzI*rSLXy!@=|hhv8|g|Pt6Q@1k5DdIuaz-wC{X2g&M?X($@{A@BM7+x zhxLk#ePFJ~2@@B9_Y%#{FnTGMt%!2QICtot83;Fq2r%mvbiZSUwWgYJRYCjnY8Y)n z%&*rl-a$97)Z}5`x8AF5Kw7_Vee$UBsR{+@-7JD5FfUg4-CT z6K2a&PZFAHPKz!eai8;k8{FQcQo(3;izHRFPc zee%+q22`K(tP9yQq8+GTLJ$4RF z{>aEs)>+#=GC(*lQnycyTKMjqPmNgRSL@49ap=2T!3KA?%1V&`^bB`5Gu6h8gB`79 zh$0BLr?(Vklk*Fsu6peNHT=RTA61BHO+}bD)sI=m(;+AL^Dm5bm|&|`U*i;`t?+(E zZ{@f(q@U4P#fI89z{tkqg@MLn6~@7&l@1_*Nq!IqHHWy7&Kh54u+o9$z(C7ehvE8X z?Hy=XDxBB*4KY&im^;LHh6nP`5TgMvXWgMjVzeBP2n+~M$9vdnTdDg{;}PX9%^zy) zDw8urLP>C{1AH614M-;~MqF%726FOm3`0=&vBQko5fekeLxTAA&&@^h2?`JC*z zpd|UXlAkwAevTYj+*(e_&ow1K_muoR#XsrX2xEHa^^qQCMPbMEHZ1aoWOsQFfgCaZ#J`}JWxxEW%{CJ}$YP~bTNTPM)4KG!jU^K83UI#us%5Rq` ze4^1DY-Fd2MvijTx;v4vz!f?-+1QRp?i8adz&m8BQC?YTJu=lub9iz>QdN8f+hFI@ zn=_0G2uC+zhVfK~1qi9nKz2&ftT*|JR%yq+rKR3xy`9V4 z#2QdhpJ}8;pe0yx*wg}&nV}Z4TlDOiMsgL-;6_fsUcfn< z9Na6))>qcElPBtI!Y<&fY-G{ZnMPdWqDrTc&Xt*t;><>1v`4@-%Q0@U)40TX#S95z z3_2YRf#g^%XJPBW`QYswLf#t+?rJ=- zz}StVISY;DoZD`ZF&$30mlqjXI8*Y@Vq+P^!GtBo2JFt`ON=Ej+4Wy)q=gR;!4X9V z?qUHR1HWQ;hg;TC;{mX~{* zYPH@-Ohf$`)Gx~A!;b3>2K| zIc%g_`c6X$#+!vV@Cwt&dBm8DAul>&kV`!`4p)U5Q`Hml*}^LO16XF~g%FRQ-uc}q zWsUyPP*~Q^{t4Uc0=;?~I2~=Fzp9!t%E~-rgy>2?tIbtolpBWPQ#XtS(U(G4NQK!0 zj0gC|?pSFRz%coy(L%>%p}#8Tcr{o>7!+q#+H@)Xh?mhS%%n z^9s(whnonOTS!ez^L>EAdefYbGwCmSOwyKz)`MXBe*pXgKv$6a9bBG0Kfap@5j2LuJKA1{TNZ}uIj@NHLa zxV~X!Z7e@C zI#r?UP_qxNwHIFVk(iqyd_SMdR%rkt1It0}VNJGXc}=eh#xO73R1sYZJD#sS!@XeD zfr));6{QJIQ|*Z`yI|v$i!}N6%%+j%en3!ol$kFedwd&jeyc?E!I!`kjbj?@^TV?_ zn!Zdlp8&I_B*D*m6m>~5cYBuu8nEc6@WG6mhEttn^G)V8lFcK@l$4e4UcT1b4Vm@ zER+DtbpajsA2D%iOEMD#GSqFE|=C|0Kn z!wmw>VEafK^y&kH+ELLghsLi|M2|Bmr4mLniyBulm!bc{N@fEHC6y|ht!mzfvpu4x z$uI)=PME%jN~b2p`=2?n%%>xRmQ}_F+^4@Pn-w4-q*O6KWa(o{RkI8Phy1Fhrg;8j z$q&Oj$7JkKH8VP{i1uLAm4e=qEI$|M;Js)m)y=Vr39!LtJid)3fYG`q-dbM+6AScp zzNXm$kH)pkS75DKP|JK4-2dHL=2kp5*EZ8bd0nulc#(LcQDJSfY{R7?@}_m(ZmeVL zm!(P_BxXvkqrIwd{SMTX>RzC_=FH@I7h2EMG0!vR%6!BOS5tB(dQxx?REpElNNt2^ z=F8T3n=&KFUVF{yz54R&r_?tgedkz<8eo55v35RcPQc1{Y-mo!(P?97g^r5XhN8jRNdAt#kmMd8rSu;0~w5TL$Q6On@ zNz&v%()5y~=@Q;@kl_XJ$!2p(ayeao36lB-201j4HnODH$UqV^QbnHL)1EN<6eVYrB&RepGeZL)P?O7cLD`KVJ)zuhgr|JFl#HVtsuFkOIuywYB+?cL1=KM4fQ?3F14s&bKxnhd5BBjhPG( zZrjE@Qe`2mwLD|8@4$@x4}+nn+Jb82SR31#4xXZ|?SM{~(Y1E?%sb?b@+44)C(U5@ z2%EWgQspR591VFATWU3}deW=`ly&Awvywx(Zbd$0j?=J*7Irl6p)G4cC$pyt*RAN6 z%r~6C=3l&Go@m?$a$lVP+_|wEp_%b7*#UGVQ~o)#?Y2P51_YF{Q?8#{IgQI~4Yw2U zGyfc8x5=-X8SoC<_NqAoBX0egIX@||OpreKkS+8hKroPYc(NScN#uPUO2{eH>UHom z{ph#X&CmPpR}=wlp+w$nE%FQ2@PT!Q49kEoL0ev|KAQARE`g6Bs z`1F6&D@EE-9E~teH{VAYxC_xGhXU!>zJ5vOD<8C$1>{%*UVRWT7*hbXL3_wDQV`-} z0uUJE`JoPygG&L*aquTX56J?ZA6GKs@SL^w4RfX1YIsO}r3ttJ{tTvo5?tgfPXoU? z?OZ{F#w|C7!PN=xM^)VYmYFXCD{J;Uam;SL? z52-JGY&HT6=X`7q21u3t#O$Pm+?ngC#ELo$*MD=aHTF}q1EF%*=Vobiw&HUWE>9O} zd|%MFi*%{4*&O0i?JTnmgsDMUX0q=xn3WS?rolCWCFcXxUO%r3bKRhHm#rOH=1>)t zS`RQ!;w#DxG}F;=n}OKPNS!pu+zGjod1{73gvC@@S3i{A8Eo#rIBEzO;uY~sqBZ?j8RhM7s&K*7V!%$Cq>1hck-StkFF@vw4D3Q0+V*Uzc)-iaY8KKx=X zDkL>gmhvUmR_B43aOu^j(JvQI!~j5f7t(N(>Eh%OW(r2QVT3u#m9xnMG6c4Lq}dQ$ z%BYd(c?so?#4P8~)sg1=@v}ox5pklG&j2Y$*b+bA*bVC^TK<_nA7#cvH9cjN*|z4- zp+)83hzjI{uxeZ^$op_cNdw5CgwdF^U&ue&Tv+qhQ0CiPNxmJS<9T}>Fmb0`fb}cN zKo}04n+`%;Su`-l>{>0r@nf4rGs}nx$E9^%3_KtQVDqpbkpQm276)kg7&EQLoDkgC z$5H{R6y=1Z!~~MinggMsoaiV9|7}r#oY~;!=1}QuGp&4yprODMp}2+mxGeBrt*{*&P1JqW6g>Y=n)$aUK3ag z{zxZmKOy75?aZPId(b?YyqtWn zym&5?#{(l@ww@ku#wj2S?@TbCjt+dI3ieRa78ycq2_2sRN;!)>6M^vOQNxL5ea`~W z4tX;yXci5c2pQoQDx7G3$W6fNgGf*iJo#=X5ATB|^!X%k)T3zHB(oOuPKPF$(auGX zkZ2T{rR-a7VYgUF!FWYaWyws6#gv8kaN{ALhvEQVqNuITWX_9#oF@C>0@$%`fBq(l zoNShPIKzRA?0jnd9~peS=JZ$A;K}F@I3Q<=`2ll$z{@=Na5|+)knql@2D_DSwg`EhcDAA2MNH zQ38IT$wl(j3Y%koAV4dJE;8SJp1W*<@dJ@oW&IFdapNMt)(yPYwO?xnUTfN~4FazX z?AK<2*JfN3*a$@C$;IZ)Fe|jkdmtGFqu?UnaS7meJ@r{)ZjF|)oI#Z2$pty$a|WW@ zv{;HDKdb2EQuC|i|7ZXMj5K}8{!0~PbDMI_$4a4)jCg1GPy7fj0<_f#NK3KH%(5Xl zY_OCmehv4Cv|eU@XD*tDIAgvjAi&X7ZMnJ9%z7Z<@^W(yEX3I>uy=>jvK81y3+Sg6 zX3t>WM)Jfo^ZgXM(kw><^UN|O>DTq5^wW7}T40TV?;F|4ZA(fP6qlT^64BB&;KFF8 z{!&|cE|fU{=5RpnQL|vBS)PfsR9G$o|HmX`9ybuO%8X0h$X8k`PF*zb5ksE0O-UfsC!9YZPu0_4{;zvy~E;JtJP-BdPNNk1WA$gkrNox zCh#Jhw4)?xMu*?%P$d`X&T8-s7b$fO_?e5;X$?ffi!^i%B>6kEbB&o94-VW3 z6bbg{fWpElPn1pXH&gOjC?z)2`)kd&QS0ZmW;AyXvI5qOxdZfLxz?Fp7aEJ*46X$I zZ3b1mW2I~{8>o@}0FU#)$?m^Kyvyu~vVZR~-vN-lw%hC)1N@~0%b5!T34r+)Y5_0e0spYu+^)>D zX74dSQbJ@Q1sX;0ecpLi*?oLWe*_KQ4?-iQqeR-i-^>byVpU38xTz1kUq5>4fcY^v zwgU&usVac`jf3XUDpn1l)6YSqm!0gE+`mw-lr{jGA=q+CB z95RPPPWbAOS@D6?8;3wiCsF)iC?xt&-@|5=3fNdU%8vQQmctK8xG`Dy*98D7twh28 zqw(CR_1$6fAk^OrtR+XxV3v*6A2q8pKG%5N7#2QawFJL8nKpuP@bRB??_peEFDX*= zF(8kN*1}_EUBy$d!~@pdnWFm(me9rH=E>w^q3j|fmkSJ|_joAd=)kK37b^XJ!hBUZ zMlHTGhrrf$_&b&^o6{2~A&-orZ%>-HfH=QC1rDtrT|I^M1b_Skq^uKG?hoLWp$5M2 zqgg{O+(^kknN`&MjaIv#%rGUbaHBK@wScg}p%q;m?lJ2l@V>YP1Hy^oN6CSw&G{-c zhvk1YA45&*`K#HmR<2z!ReKv!iON0+>_C@irvhC=a|*hRXbgZ?))Diy%){vyROKd8 ze*=u-+2uDgUKVEGFM9w;9r2sF4I^*;yP2tQsnKW5w7C4@rnxyBUSqjbfpKX#?LPyS z8s(ljYrewI&)hy=iaS0b;JOv7P*nI=7r0%@HBY48rQ%hBWuSp8#k=zb-Mst`$*ibR?=f0^5lW+D^!-Ih0LLi#k~z3sb4j2S ze=b0d12PEj(NGw~U>kPu=Hk5{i6ubgZ!STmA4Tmin@16zz5EqmisM$>E4(-7(DJJo z&Mew>72vak&RsRxVX0Q3SrK~3mkZ6~<>!SkG(yA$cPur)5IF3hU@#?sv*e&m=!;pn zSFXv9&R;X*V|jlt->FGXnK6R9N2&=8L&6{CY?i9F{0TweB7OfSR0^ZWcOCfhywwyQ zunOxQCHD>CQ+h$VSWW?7g)Vyh1(A9bIsZ0yg`o=_45Gx{u>gj7bn_@k1cqOx(Px&k+$A6D+NykeB|>^MhiJ~>!#T$X?_THmzrY7 zMPPUG`(A&tH%Cc9t6){%zh%CGi9UG?TKXJn@DHXWhdTUY_AG^w3JeOOs4pCGApx3j zF@USiJUac4c>txB-3BpfP8V*2ZO)-*?wC)-!ITNMlZ7AVCSmjAs@RFBwCxUb$vM>X zuGvd1*h|0Mg-WBjW!^I@v*cOtJ`~>YF1-(!gc9^Vl=Cy!mi9DOl-t&uif0>hx8)t4 z_b~G_9G(=E+3N7bf8^Q7fieuzJkcz!PIr0I z@W^+0DufjiQ%sfzrX|oXm!}HIQbo7tIa{?HRH9nGABFPFbVvdSPz*32a|};8sT}3> zO=t=m1{_F<4ihv`CM_yS0y&gPxg|-tfuy{Wq`W}Vnv$e7MWt+w8tY?o!STGnUH%v4 z>g1A|lLIxU+eutAl0l?4Otzk56@F}W1%d9z5f=mQP_^tOT$(Hr9Vq$oL{aOC7^A)Z&2Gm}C+%mK6y z^*EUWc;4%2t_-v0dOhv=dXt-Bo)?rqseQPo0^YL1vG{YXIpLm>thBBY=}8I?sKRoj z%^aus(C|xwrM?U9~Jp#IlT3d4tP?;o^eMtkb91%R2J5I{9fFwVEyM|*rK;HXcm zrzb$|_gK%#|L4l3*3c_;jPr~~`&Z)d8JEc$@2QYB`Fl?}6-$EY^5+D8XV_k9Hj9FF zcro770_7LS;|nfZ^KeR1Ndrh0!y^_$;s==!X)ho+PWmR%^GN4Kfkt`F^8&x&j^JM( z_??d5`GMaR@q16;H;j4yg23+v_lR;8g2{ z41g~9hU{{9-ld)8098XMw7jQ29xcmzrf`)rWo`$BHVR&+w>N6+2-#jjifgjH>mkd435WQsNqR2c`H}b6CZeU zqS;`oSQE(eE@jn}g=f|DB;svzO;6cm<}euP8}l)I8e{? zc$sqn$mPJyg@NB4@cU-qcNhH5I!SfvV^OZq+jxW&>(s${;ZyVKd)grDetl2Xk}Mz@ zWF36elT4$RdEE4B15ZN~INzM0E3KpQQiA+IxUURkVHMdv?=v&YU@E zq_evO2#`SNMcUGl7Ssn6rHYT50D^#uiiD2ze$$Im1*C{BBE2IWMNyHiA{{|R6#Rbo zoZS$f^1k2qzy22&nVov3-gEb%dBn~()5{y9AvMJ3mnEBM9xn2zgCN&3wF`w2o z*E>Q~FCW*Jxe$KZN*}1DqMlL(#zpuSPzgrRTr42KB8`TGMb7~xFtm-n3xjNytM2Q( zT)jrxWtQ@WK}i{yK^VWY34jHLLzu4$IHvxsxwxRAwQ-@X{v@-p6g2B$L$iKZ{AN$+ zS2>RZ0$^^WdYlU4)_h>>&U{YD zhf0mx>685cM@EZ0b~?Z+ITFUWty#hg8XE&}w%0SFQ7|e{kSey<;~Sw-FgV>2Ot+!? z`Osu`vARpRYSi_xQM3ccEA}U%nd!z<8ra(XplCF@yL*yJ7U& zPXFi6>n4ZacGsu7v|au_FER48lh(hC#&*)Jmvt``=i~D98`u~j`TBls(A9Hb_Q*X_ zkLO8<@SXY#T=EFtNjun;p69z8ED`hHN(?(6Xai2(|8pA?Q9|RdpbfbE(4F;}8HV2Y zTlUmJ0POIu=*7#vmyW!mw?$Vyz4fx_YRlewx14>_6OTDTkcNp4mHVYP2@;OCgAvT_ zskfnW6_0*H%ky(O-CIuu=J#iBU<;p9=|0%O3#eHipc32tbNlGOXxah)#J;+pzuT<8 z9?Ri#uh~;L=XL#WC#G)Bn|d0ikPxQ&zZ$4-2u0$Aq52FbyeVgn0IY$tx|t*OGTI^k z&XIaUE*m~ZKa;vw!qpWhIEf#u(GvIaYtCHCEF?342A;^x~L1 z3=O*rEJpnBIhx`~IGXm62PKfxK&r}m70H~+Ra8P_d60^a(^Jya9I2_|8AEGeTL&72 z6S?-|^cGQvc{WY>=|4;>#{ul!p!4JOYC+h>cv(1LTdDE-uXw>Tb%M_7DXk~ygAqGA zLCxf?33{Khs;4HltWr1+ekGUyLgC?O$4&&C-cR!<>G9ejvL@+q6%MJf>nXtjsN#53 zOzQl`TA-@>fg>gl(NmN35}6nrlkoc_1DaqVl*L7ZW6YJoXhXXCk}nWtz=9T}UhI)? zHR1V7U{N$)!)j~<_7}W&gCFAHb(8z(*Gc*)9X-XC&ct>@H{P9$ExV0YPR1&3qx+LF zE4ygW6nzfXG<7O&)4l$xQ}tpRoD{d3raxDIKPsbxYG62DhR@m5;Nx9nnFGB8-&W5B;^ma*T z0sZHq$=p-5|9GYL(+<+T4f*rF;w=n|XVSOrPXHK5xs*E}UKq=+s@4{R`p;i2JwvaT z5ZEh?~lJiaO2Jb>!sF1uk*)dm6Y{PvdaTVm*aU&())oj()GWY+fzf zfv+@$42?Ab!@wYL&I880gx;KoPQ&T#JY4%r=;S3S?V7vMTpo#=$`Pkc{LphXMxQvcG4QvddSraA5Jf43N_6^#}GA6`PA zE(EqUjw0SuAHtXejW+ndY%d(+QSG&SUmt+>_PnppM9^iCYIgM^?E0d8{eNtBGMehO z1i0q~dawkbuxL|pG;|WYNCYkMxH02-^EKKcH*kZvTA4_rp#s14|aPPC0>2(JGc3$Q8PTBh&72kS1^_aF%UK<|s`9`FI^nNhU!1Hkd&^v4Il;D=JN6~Nzy zQo9wn^B2&R75W0;Uwu{sHSNDrH%ona%ElmD0vS0K`27@qUkv8lia4D@z**mbsQa8a!~*qkbRhuwN}=a;8w(`ulw|GiQF_0@Wu6U=YdIuOA9 z{j^S>1zqNEsq!cK5rqB_zn&fOFP|8{RUZz_E^eECOM`-V*baPnCw1JR@Pko1fb$-p z2RlGN9iz8)>Tf0O05Ee@ao+8~9w)0f|LOoR$;x)e{pK!gRUj3$cLV=EK=1C>A5T0c zap+jHgE7T$_MR5bFJK&_E4%g8cyWF99`KTm`#;&EH|DB;?d9+JU)`s_1T@qC-F|%; zyZab+04I@MmK@SwqohM%JE-5^1d|RO#(0nWyB^UyhTz87`UNPsUH)&r(7$z}we?>C zIkLXsp1{w0o?H0F;!)Bs{9z~c_z>+2f9ffaw%Ql;)oFbpqHmmGp2$%;bQYM>XexdV z?2Xa>R_An>-t_nXb^)Xr5XbZ%^oUS=zw~9jK0f`@WgTuV#?e2nf+QSA{jXyCj`L5y zs{5m~ul%il*PjbV@`?w50pt9~ALy$>;c~Gc#CQY2A0fue?k}YmJ`xIGxyOjF?;wD< z1j3F#_4C2(`;vx)8r^~K{}gJJ(oXnAn33cHk2o_DX56DGFVYwRrthstBOY>q@F=6Y z_*w#gWA-_8#^p(W%P8ZH6ZL(Du>@VXWEfDS98AYe<3-RmwWKkKCw!x2!1nV5-HI_X z%HotLVbn|Z<2zl@8aPc*4GpA&(Bp)saC+*+8tH(gonnn*e7ID#96LJ;*&~r%n9l2f zYG9?1Kh{_Xj>|J~#vufy1+hs+=jc^XHwg5B1|}I<$o?S7 z$hNLZWsBnAg<*q~t8^*Jcq<*sJkgV*H9g>|Ix53~GrlbrlnvB@M)?OP8>N{wx;)j$ z#9SXwH4ZVMxS+T(J^s41-4(J173BSlO+m}Oh1+PsS<2r4R40(k%6lQmTH(fmt)f_u zk&j*t@)$Fsf5MFNF45pf(CalS;8P!ZjSSYPa5B?%px4N;)5<1cag>s&jw-k{16(ZJ zk^Wm=n1Di`IWEJv4U30}l169<*kGcJQG=74mNo8T2iGrWjKOygmcuT(Mt!r4FAy}% zR#W$Nwy_Mcg7U^L(8G-@7}qs01glgu>L7TvqEQ~5SXj|`8$o&{qXeAiHK=5iMU9@7 zj9xGbI917b26+W1j5K<(vJs1<7c1lIKhlEAMk->PD;wps`~IIR8~a_*bpEKiv8r-` z1-l4FD1cXEz@}Ts0?UYE2AQ40K4yH*o(zCEL@rIKVeG@+f37A7(ZMvZrcn&7O{-}< zrw#G{Qqve7ZVo}Kpe2C`VXs0Xa)i&Qh1amR`_S!S{vUise@zC;*k6i}L;ZOTjcE8H zu&XlB%i3~7X?Sy^3|teeZf>;HM)?0~Zu}IE%j9BP z?;fQ+iCMYlZ}y}S=R&k&J0lyxn07{W1UuUq%@G*wjgbhZwKu9D_`JRG0)m{UjgRFN z%N8rD*Sj2Ys{hxgjauQ*#meb|&QGVOx)@DkrdT{QetXHu_b3ZhS9QT`O!Xh`VpP<$ zbJM%2&!9(1^n5oX2Ccu=&B#i<2b>S0&;^>?kuY zxEB=lit&-Q)W78w<1G!ZRcWtd$1bO5UpGo3bz>i+Gk1LU8^)Umru8*mLhzuk(HcRk ze#QXFvj_dGhcot7Kb2XszwsKJ9L?zu5D)&fYIe2V>?;4@*A3_$6*ikN!1yO(0|ywj z5o{a~Xco7ttue|A5y&if)7S!lQ|~R~YzSb!y8si4u=`+RvI}SV_hH5l+D8BP!;L5n zPmNp}VYEPQm61j`m##g=075X%Uw)iXi9g?Uyc*T1@x}->4i{il;mW@{0ekBny*$w< zN82ah61qoUPcRy&f?xWI_;T^lky(@GTB%cKJjNA=WHddm|`@*sIE@|tXNFZQ*k3Lr?OLx zGU-cg*(P2C99AH!+cH*pkd2$ z6Y75Bt3S{>O_*&Y0*6^P+hF%XKU0f&Mtvu|ql}wp6wCO-D0G?fs5cWAnsYMfIyr>S z%?Ah_NvGyv#zs)|d?TrLp=T8ALY)3xIK`}=77s-Q&O$#t|7&~w=HoU)N9N8qN_hUS zYyCPOCk9i!z^LOq`ZKLrV5FI+er7&4<^ykw`7ZrTrxqB+o!f5F{RPGdaqG{{)f7GvJ7P_@Oz{8nrs%4q%! z6ytCr`z*jH111riG?wWkBn6~)Y`zIWp1K<$wnN5Ci2UVZH74&8!<)nRCtsWg0|LCG zL784H$0E!rXB`cSC|9zgP`@QcX4PP=heD%3GkE)sKjLPbN>z5-38TVL_1F?4scSG} zmojB#o&7r8>OEHfHuD{1{QK;K35*D|AqF%cLq1naPq z;j`#lZd4CnfFzC#T5fbS&Z_)7f&8D5@0)mC<=Z9zt_qsN=L8T?&)aFf1w}H!699I^ z1wx^l#$B*8sp|(u8RIA-Ahle02MHge4~$tx!3|D2iI3?>s=LBy#=54EBSHRXV9qda zEJ%V;(wr5*ddAV#71%Y9bFVOJhi^jV5L8Q9X*?6Pl+A`21T3I;RvI1@U9b`ddNFNV zX_OCNfTB(m{dJ}BQQ|nE6f%Itv-4J#TEQ|&P~nSLp02Od7OBKg4x= zg_?h8R7Nn+j?McJSluFj)N0_v9w-|HddwG^CYM1Ce;M@L@B@5zZussRqkIBBt914F zIB-{4xdoQewbvS+Xs*Io5bDrNYmJh|Qh~W-$q`0)g=Vcaszx7GbF~Ke^Gf=5t+6o? z+R-rS*PZpT+TrSqS@vA5TxYxlZo0=r2B>EKAQWBkSny&h^O5lr1iRLHqjB;m;lWrr zpOq8vCAXm@Omd#T_j+Tf##5oYO?>~)kBv^;pg@CQpK*gGDuhE+-0Kq{2x*vo=~i+v z=?;HPbE5O1ia7}^?+UH<8=EnAJvZRGxZ)tj!v8Q=eXPoI0S2-Pa4B$%ecmjm z(9OnP%;urZz{D5O_AN$SvH5BxnWv9gg-w-xcg?4(Ta0Yp+u+f|%`CpnN{Z@Un@<(C z8dcoLgE=SAOx#>925-twQ;`;iZUuTYj=tP#EXHmB<~F0OD+f!6x#mTL-B2=C5jr&Z zG!K#uf-%1=*H_6EvBUrO%XMs@z?GrNse9G>27xM|;R zBQ6lBouE1rsyYJRBQAAiFGV?ffI>nObdOOv=D1~(VX)oBI03ajqDe>>$1Qc^+oSLA zKfcFk781Yh7e4i*kP6)wi1{lVFiM4LzfrrR#xOMd-BIkyOZ4|qVExyq{7SB&O4>(M=|kHdp)&f5r|$9-|i#$0SAF8lEy?j`Fk z-?;)3WD7O93esr{y<-QTTm`YUh3a23{()fnHI;Yx8c^>owD`JFoSwLD#6*sXVLHcw zHxheZ=MsKoK@#exRyU2laA~#uCeUI(-MI+@^;62eWjqt`S2pIBQ6HKS2X7g3+0|Cw zZLkj}5ZyMWB9?W>7>$_!4p`FaQCF@6c``u4pPGKz6ELG!>Ywof`5m)p8&$8mEq3di}>Kto)}@Ap!Nk zY*L18*!oVML`^^p!FNE%* zwea=sv~X2?!XP|ZpemntIyH9l2xPRQM{_%%vBW3^$%KAA|3XQ&^|e#R22xnn)J_=|NGY&W2BF&9cC9ypMIcw_R(c2HJ?!{P!FYE& z-X$1^9xRuKimd&Kr|fucFy7LRHw(ra+41@ue}em6+fJwvEKtRc=LF+fc04l}&#>d} zU_6x3yhUccEf@>8OSgR=Zr;!M8B6OMJkbU>7F~e^5+y@tf`} z5b&KGn_0%C4w-5dFS9_Th*lLl7E7lZz7r!}ef*I16fw-AXQoLpgBsjVGEsyeJuFw&5)594{JTAH5$hx*@*%_t_yWuQAOSJY9qxw4BC`94=G{K7myX+-2@Ivg~+7%XY=JXSIaLz~WY z>jC3zoHj=^oORBkl8NH&gn2Pu<{+U5P=8kj4p54oN&ZApC&YqIE!|BNISHoQg^`7L z9UdJ!4KH6x>@-PrlSEpAbnCSo7@<9XATaVgF_^tRNg^w19Haj+Ajihh(j?KvjCkzHEKQTuJYcqC!Q!x=0CU*w8)k;~C5uc`GzGnayhPB1ocMP#&f{G2q=@F4 zr2G`XFO$Zkh|D^m{Ja=$9&`!RNYEqx0RW_d4MHKnGA%9hfg8~ zFYWCFD7P?Gq&kOzVUQ|vQh1LUyxkP3gqEP@fzAR^J3&!tq8_6WSQ3VwF(?|t+tC0T z(K$`<`@*lKi86_^V)%(sKrsHinrsl6A($zDny45jcnK0)V80=ZLsxkY|4I{{Z0seW zpt}zW)^cWyTkz+3!8jcp3tpOhHVWp(9#0qSo3wYjNJs~k%>YUcV?8@7+flBTBgD5U z)CWgm{095n;<$SLB7bpFHis!VC3HjI+?ESq1-lf05n37y_n4;C7&cq>?IOr>ivq0` zO?HbqkQp6yiyW;rX&&5}6X+ivQ70FLK~)xLFvZY(0~D;VpLE0F4)KNON6U%WKtNR@ ze0aeae@F3i%6RIVofu$$DP0d_^No&MyB?kQ2s~#nj-(eGYXx1-5bpd9Q!;rcqe-a}Vzkgbe!H|t#5%3a5Xt{CDCdSLq?Z&ase$fut$*u2H7qHr#PJNO zmhTj4c}z)>R7O4H82lo}hCLAN(>(ENY=i8+`2H>n<;+7RMOw+xfGGTiv8v(w^z-0L zzicUu@B8j?3Yn!ueBilb)<5EmS>FeXV&zoI#lp&v$ElRL2ZCj!NBA)7D)mlPOg4X& zmztaU@>H-`R3PQ}g+NLm{`(IO332M8QexMC9y(8I3*LCyj_!dkhZv{~fV{TnR z)N+0>l}1+p>}W%)Du~q3qvJ9GO%7KO{V=%=bHo$oLzsmvD0691j(9;^Mo|?-dvL|N zRTLHKEsFt~5m?(0;nJKAY^)+c4I{Fl7^~(uaSh<^W8}gB5*ZHu^3jSSHFkLnZ*jhP z9%lSqQ8dO!8dnl6@X1M)L~>Ibu=fjs=zhVm;)Af94JN-)2)I@H+rjj=gT?8WN+O#< zpQo~d=M5_Z8w2R;Sy`l~Fz8EmYi=jNwwE2+JOtddq_QaE+%%PrRTlh6>&-K=6ji7q zB0Q@=gGbNQH0(lNLM%2zdQRnE&@!Q?tB7oVZIUlwrnh-xKpz8z`Taavn;aL1Q;&Qm zq^M#E55G5iw2FxTPv3)XM(e7g75rw-sVXwr^?FrNmwnw-6P3a`qSqVdQtfIYuLHmw z+~V-PG;$o&;X$e?)H#p_xBb`{=mjgbObT=ITY~)CHyBUSGK92a*Us57943`Jf^(-= z2Mth>(cLb~~kOk6dR2Vmr4d*@zVMhhImiGr~camf4Fw0n7kfABQGe6^evtKD71!n#;k@SIMB#}Qa18>)(~Bg zcen;ny{&Y;hA4%;#n%)S5j3qSD#UL_g=QE~s1JN7)ua>E#?%zm%oeP`iPHr73EkaB zM{9~yp)Rpdg&u^-mUOSCC<9~gj9Q{5^!Pi~5{+WE1MA5}2Sf1yd>*_t;F4NjOAIWp z9J?!@6TVY8ouNMXU&g%fM;ftuEH8YcAkkcpIzJ}Lf-^q)F;Nc4G(!|$s_aNx9}{D0 z)&qitqtEyuygTAqIzCIhNrE^LE{St|lgrw4&K2aW}4t|f{yM4kMq{sqH z3EsQK5TciaXpu~dPjtc+YzDTa$HB199+z^?GS(RYeomX1X3tlO?~H?wSG=5TCWoI9 zd|lw)=8FR!?VIB_Zr|5Dd{ygt9^HjoA9)#2@`kxVKQAt5@ClS7TBZg0gn^dI0|g9P zaBXkHQ;unkFeWR*)iGhhGGBy7I2?`$An7O;4S*z@oK$=(NK3F|ARy(L;G*z37ChAqK^ z!>8DOX&_wbLsq~Y0JmQSPFVsD`YZ56aucjgpg8_arZ{{BEZXFtO$wCGwS2du4If=h zfdiWMIKJ2o@IC{h9aQFxHP(skfX59&(I-2~^|dA`JdO}%yRqlfiB7zr?8Y0)mLoa= zo#%xD9|N#&7>#KN+_@tyZYWAIz&+GZc%siv10Mm)4dy@n(GcPmP08>Qk8 zk?DylezCCr3>6O|(oK?m3sgcO4!ul8A7apJRQw_S9PVS>SzSH84bv3n1Oo7OV+Ep< zn}{Zmhd0x)slwgt!U??3k9RP& zd8*2Ov#;P``~&hRobd=BL>kOd;!l46IM9#6VF!lRw1vpV(Ef)$QbprLoRS#Z-5skJ^fg5kE!%nDaTf+7=|}a*BFFj1+}3F(9V$EC2i_ zL{qKU2=$09P7NQ9zod~*iK*cJrT#n}jjP&**GIMZ@+$G?Mf#oY)O!+v(xd!SyG-jnrlU#<9 z+KW=f|FXah(>w}%X?Et{mXcQEsDjzssXb7rHZ-z5Mplm&x5t{?r@ie(cKRg2N0NE0 zwnzoAj{zjKm!>nK9P_kzD)fOBy%g`Zg_ek;K~IYkAzX)+JPmVnyU>BBp-90$N7JpR zF`<8x^^9l|^T1O6HE;(2TyXY%difbqPFFt}TrszM!84*{<~>&R#4M)f`P{NKKn#Gi zJ-Z6ZYLJcE*vGYJL@}tlg?11F@VX2@43G|<9w1bJ)=Y+=D*h-0!e;a+{x`T@XcALg zkJfhpu3c~Xv!X^!0dQBJR?ES9Pz+@_e$qNcOpS8CN6n`8t{>kO*n3T^0&-S`8Y?krjtUv^v1%XWY{ z*Uz_lp0gM8E<=4|@2JFRFgIIt5#=F`-binE5m`B8yZBX#Ss{GovM+B5d1{E<1Vu}R z`kxuHByK4thM_}!vWut&>jJYYD96vJTvwn9$Eamj(IMp!k2E8X=PFYk<_OgH5lNLt zXnR-DB(f`{q9%4_amwi?QbKVOqH9tfe#XJ7gKt5e-3%5JV-i23&fP>};*mnsAHR5w zcoGxk7c`-pNT>wpqhxUdm|$>#uf^Z1BLBCZXkGp3&kN{LcBC@h#Xo_=OzaMMNKss; zWRzPzitRl9f@m4Qc9LtNv)oI2MZYNQ;}_gA|7(1_AiP>AuK{9&!N;fs@pt1tc;Bb{=4{)dGJbjrnJF*XNelTBxV{N z!HmV+!GpZ-Cefvrus2Rn&dZ{7tYT^cV}T`5KqP2c7kOAK!M!g_=TPT=jXsIqUoX&J*9Tf6#Awi6NNH=UxHT z{2T3f1%SCD_3bSxCNb=E!?7ikykI6^`6Da`6wJWu>y54XzW;P@u_weiY=(bHf9zQN zApBV7&wpKf08c8EFhINtb=r3a;2zvYO9qH{v8!|56oZScWgW1HGA@X%z`mh}Wa;$0nm_ zPM-XQk_L-(=kKx9Dip+jXShd$tE1_I#cOza$`}G1;55}AB9?`qsaUcar6*D7P%xVxJU;1UT!#6a|_KIjtgQfZ5b}|6So+w^A87^;Cvxq z`&M`s1^Dxg5CxB6(G0NAuxL<;VniM$J&eeM9LBihW@H2YCA*!TNNz?&c-q3{0s3o% zIAWq61jL{k4R%bng}xmrhW}e)uTi32>;W_gIT~&xv=wM#+bHo_IG$Gk+4dO?c~|sF z>j*SI8hcA2z&-N7baL_C)`O4krv%I1OenC_94$Hogvv8UiyqJ#3LS$BXBxE~BR;if z%yTCsmONuc9M9NT@f19{jTwu{iB0l37)VN=vzg%pV}Or}952$I)jVlf$t0iVN%tua zVXBk_vCm0LLh5BFq`g|8@TqIfzTXAa!0GOk>2$?BlfOD^34HP(Sj^yLDJ zt6(B(ET!!eMXdHMotP*p6}>(qfjh-d5_q@Z0UekO0(%^}rYJzZ|Ep8c2w%1eXErUNrXBLvX##mkt*4Kq>n1i5Ab5x7oeIH>1uGfU)aVjK8_Y;mMiXr|G=*`jP!K2=`9 zQfVc{f`AtyLwB)!Fjhj`3HhF6cM|S5IB0c&yT~H(!A|)nm7W7E{%d-1j`%ETCOhNg z`w!yDmiQ%b^<%jg%%jKV0p$RaTC{C0POU|^<^r zB8&7ueemIIB!#HyQS+e{osV@9H)4{pvd~?5X&LaU_h{=f(HBOhm6rp`f9ua%E(GIV z13nO~btu+(ndb(*vR(AW2f|n3=NPpK6#E?$6ACB{MF9rGz#AY71&oe?pc(*1B~!x{ zB8Lape}y>3;gc&tiy;hMC3+m&t4DiidLPT`b5k}aOV>+bZb#9KdAHS)X6VK*g-9S@eKkV zV21ebeJVP-plI{-X3)e#X!~|hfO9vCNc>*98BDy9ba}J*1hIu%L}Kia*jm`2p)9or zHwmWzG}^%}0KFsq##S-h89pS|?d;*!sMBVESJj*wmH1MFn_>($%C`u@m63F3yVx9P zv}BP+!Q)nq25Rv11Zwzi>=1oj-jT8VxSaziF_aM&1+;*}QAZ_vVa-Rw_CWA7gih?i z(1*~VVXaK0N$IX{a8|MwSx3gAZk%7A;!YY!qhuriF^ z@MchZ4bxRoGs~)p{ck0KykK3hhA3IgKP}vB9nB2~+IiS8&TP?!NsBX|q7JdYxa zuyH?1A9G^4zDO`{MIhW=+#DXsPSVPlJ+PP?%9s@pTqxgcRPBUVeX{(;HRB-AX=C|Sp^hSo#)Jo<6=df5>_-;x=nh=)}ODX%! zirN%ERW@CC%#>zVH6Pc`(2rHk1_%nOnQ>I7TJZON;9HMZHy7X)=_}RE)_4rCRt>W` zI1j^Wm<7&)IDc48^KaOu`@kImj_>u501p9JisTe}C)do@ zs`*#unr(1}`2T8aW@->vm3-2y3u>VIlV*90Vak(c+k_1e$#n%XrV2FG4A7(FY7S(HK?!~Jh;qB4e+0?wfSvF~QY;;^;Qh7F424*w58Cjn6Ms&2j z`2_~H=xHgKj@dIE3hu>Loz2SH92(r&Of=`l%94z)Kc&pW=h382=4X!< zDfur&Kw$x6$JhanK{7^xP#kZJqjO<0iN5Y^#=AJC{WC6+!n>Gra+dQY1&wq3b>j~L zRv{-tuI3ETpDLHhRgTH2^hFo5WaPBMsMghN7{TgU=%j<@cQcdepIuGy-}*nND+a!h zwskd&g>uKE*-j#jCjRNlBu_W9IJ9$o-OSDEF4UekOJeFuKW|ozogBx6hZ~?ginoL= zvGrbg-no@S>IZXO%tk>(>gUZnY0ju&Yj53fAZJlivoSX3qbo=2de=CI?S77Hz)W0J9}}-)x}S(?xQi zS>29|bYT^b7MSfb-;a$RuB87CMhK_yUS{4P;5x&&jDWy)ZY=el9-0UM^3q_lsRnFk z%@CabYII|W`H5HqZ-C&k@C^_m*U(2p%^WZTE)F%{1J9!4aI-3H8U}#2+K=A=B5P^Y z2=gC^x<;BhN98E9Jxabb%3O z)8JWVb2>ZI?EFa5vPWXw>Bwxr-=cN8)0R1A64+KJ=a@s_qW;;rxPUg(n7QUyn0+SB zGuxr~EA!MR*32_cBXPof^A!~Nd%pQ1V)_SWb^p8tW^E0T?-ru=7K(n)><>=Q;P=c* z2-d%6mWI2yQ}3ChwYY6_GV{vv{emeDnLHIB_UIgXbCKB<45CwuOm-AAcCpzq;ig@V zMTaa?P`Z0(=g{@V<~Q&Vwr>f(vzP8JF~>a8%epQpv7YnH$Ef`>^D%utR#WjA;3I(6 zd|(!%kC&P6*kh2C2#+H1H!*hske~cj)7C@cRh;{x{wXon*vl*S;Xg>9@fNJ6O z4Y*i})_IO{KE+j8wBlRZVZ!4QwUF3;qiSu&M(onUTB@P1J~OkD55=-12^vOFAa$E# znfWyqdm$Q5^intBdM>Q;?*sCqm2lo>FKjWZqTuW;>Px4#m|Kd(wwfCdduvB(`z3CyGq(&#$yB(b^t#9TiuucOEs%l^v(#UYY=!$tnaZC zj#@B~P`Q)RcHw=DZB%y`@Pg`8uuFAi`!4fS!RzdttW*Mck>~AEBR;rCl`!_2&r^pz zX0`vd*mN}euu?P1-e*pC7R1u-eP%5VfeE#Mm%%>|(y!TkH1e%X)Z=t#ad<2P`D zUI!BrsPh3cG{6ab`GD!+^sxubk{o_?z|3-Y1R45w48u?Gu2@}4y%7t&3WIfYQ0ngk zcuoYN=b#-nJZQFym}Uf!mxTw-RwgrLz%pgskw{#-bpN1P2O#aSL%7Moxa)k_OcEc( z0t~>s;%$)Vay^YWY&MFj&N2#+%s9?p9tNoY+OHqM-3yjX<74Jgpil|NaXzL|)8pn~ zb>AO24!rbFx_{ivEHN0*P%zIu68u>wyoqx3B(Rh)#KBFgjU<1wFU;|b{C)i;*6Jhw z-7n4OUB*X>s$^@ASUe*0(n&1KN3{H;c|`k!27Cj^{s|rX#*9Vq!#5bm$5i55^QQK( zKkhqV@!=hDbi?EWDmY~(Sp|%r;Jcfk+sxComp(XUHZo^3S_;((l_BTS{Zlwe(eKJ@|3Vjfe#34)!tNP!>+7Y>te?VsXA&t9aW>ENPb0xUC>rb2cU~9V1 zn5E1Um`}MXSl$mh(HxG;=({uKIJ}9|<(yfb=&ZTV{(T`Ed{eND*(}DD!-NlzQe2-! zQZoaoZlwBqpErjx*g1Jo?b)OsaF7OG#5VqnR$K&rS~TYv{SsrfCRuL8|EfBL8>gC0vh_W*&W(^ zmwq-IY2*Fbm(BL@TS~*Pnk|ce3KWYg9%R)tgk7+SSPYCmum!wMH?IPqt|je;>4|a4 zZ}G{Z+5YFQF~r(M-LIRiiUr&1g86*d#tqtcJ=jJGZsF0)DmTm>Vnm#>z-6@`O%C&i z+~gjvr{{h#2mF^qbpIDKmk$v;jN(Jo{(?UjP@m{?z+s0iyo74?qSYET+i}FttJBIc51sOt2Eid`SJA@+dZ8 zW0&MjxYi}BnrC8h;IRqAefz-9@zbv^S*l2l*a-Nt&%c-jRE3TAl>nC1SR>9T^R zqZ~vh(O!O3gSs3jQ6@GF)EIP@BEaR8xqu|m9tVARCcYB&aLX1vxKG`(JI0z0s~5Ox zsppZMICZ{9HiIGiC6AoLG+Gp!gH5J!UYUux{KP9erp}24Q)^x85Qek?OpdiTn;)^2+J%)%KkPwpLk4nhiFc;6rkc|@H2?NT|8q297nSP99 z8gVYYlOg}9P4oYdA%Rm)CvPd)V9>N!zEl_x0%2f@9B9ihIVTp4L#&yB7T{u?aeRKh zITT$2;ma9t=o*a1f)MOgAsNG0mXIOvw|FJW4sWpQ4P zA(^sj&iq)VGvm`d09@!CZj$A(2KIxB$q!($mbn=!TywqXl*PvRlYS~IGlJE_?CO|FB@W{18RcXpcq40HPA=1KPS27hy}NNK zxx*O$2ic2VA@{^8g)E#b3=dV~Id zfJB&l>EXQbKY2Aa5Qlsr5SM{`q-e-bE-b(|m%L_IfHjwWdklqtVgAIpI9B_p&LgdcEI~`F0W@x;KdMO&fPPAK**=gJN6*!e zku<5gd?^A@2{)!)r7^=aR8{$1IkL5eYzWgcS4~+N(4cxv0Kr@`YT^A@)TmHPmdDfL zoodOlMWUb9fEJnwKCcb7z!#Ro>mNS4Saz14BtjL6v^)$UMdN_FL=XE86_yhIi zVKh6dzI>UR1yk!8q;~eI%1_7sKgcxa1-Uy?FMqa>Q_Vg7&3oo zC{H4FOCwno6M3hR^y3a#*;szc<9)G-{EExgYbwj3z5J#)5QAx5Q*6~-TG>oyapt$p zqE3ukvJkqL_e1$W&w30n= z50z_;TW=`6+gi54x4vnOdtoruZG)x;)8;mEC788&xiT+pVk}GKacWr4!O4>6MH;00 zXxo6|?yX#a>|8wbEPHX=``XH$XuH}IKnI4>drx2rhtpS2$XaOBdQx`5efa8=a-9o& z?~?5$JA|CtUY13$wY@A3zW15-vI8^4YdwR@bO^0}2G<+}FV9FY#W_g!m*@b5gI0Bt z2^9I9Y|rfOba3uqCa1Ymm@}SW$DD4M=J^LchrQw)2fC;u_TbJ=|3d`_S0GgI(HouR z65u?=yT~*rIM{W%$zrg``A0W3)J@&&?RUMK?2pT;`|~oxiP63L0V(s`{S}}hgQ;V0*$3Y_(pxsys`-=p$Y(WeD)oO==IE@- z$-2wf&XZ{EtH6k=QT@Jhx|Ty<_mxc{`zziL+qf3B9uS&C%lpZSDDYiBph!7Xy1!ii z=q~%Izr4mw=M%5XQ!zjiSkTJ53%4L-m(ytH8*-VV$9fIGYC&;lfNYHZogN@}Mku`j z$nVH^J0zAiy@?V0NeAAvF}K@qN*@kE)wkqgZL0sfwT`PU4QbwVNAygm{q554a5t~{>g_#2IuQ(Z{@W*i>K$yvv`I7?%p z4e7wu3ArnifLTftlYiy#uMk|Mko<+LxZNuw1#^O=7DMI0K>eAF9W$a&Q{0UWjq?+YAB`M zU_QfdjA;OxHws>#0W5JX6`LvRAo#~jSrWlpGi5RW`Sh9CQ)~VE zW=at7%}JXh>uP)a_2qmxPr4fvO{zO%k<9V6v<_)hTE&cjRD zQ>eq|GBxacgx8W6=pNZUg~iKkhnQbI)XiNgRUKsY4}|9QQR*VsN7NccwkVt&(xq?On?{|@R*~rQeaSp zX$OwV-p-@vsm?K($bP?$$th^%@-f_`NL_P6CWi7s`;Fc>E_Wd3u`gu1;%cp!wu$!O z)OkW&SP>XO7B;%(3;71RROU;00LI z{e7PU8Y}H+^+}l!){aqZIF>o7)}!?|P@{!S_9$VCw7Cn)g;1(b*UASa~4T?)DmG8^YXDTvx;daP`?jF`;Ia@N0tzmuU!l>yyQkH!5TxBuzg+lrTm8Zm zUm6o``#4;%j4ED|i=72O(aB4)cxg23$g7|^*ck}620!#$;AEv(-`R7=9X2hCZ_)Nk zGTvEmMOBLC?*}T;!pky&GhY9hTYH#+*6_k-VQbq~9r?G`E(BV`7#?lyf@&?52ludV zMO$02irQY58PPmwyM%+zT|!6hVwo<397p(RApG{StmrJbLBKe0v2q&Kmmtima2c69%j>N&;o8TsfX8?sYL6XHu<{ zL@NJSibZ{|%6MhwsSxkrWJ1Xv+w>E%N`=IGP#pv{?h)VEy)Z}$^|GxA3{rT;zXV9sn`5#Kvpr}KG$Vpa&Si6 z&L#$I5n-(gg@2gzBj0`9o}e1nL6{X>P~|HH=a|dm!YwTSuj#HpJE-@Ws{fO!&-+N+ zkO|4bdE<7VOy(;r{}a`{!QZ6WZrZcm679Z(So z39tll^4+%KrUpm`DJQF1!{CcNzsT5B7hYj@v4yJUb2q_(MCtAv_}tKJ$o)k=O)Wc_ z5rZ^rAh0-;KSm?i1UO+&dBMiZ05C=1_C~IBM^w-nc_U!<<$*acqWlZ5(XwK6l&^Sy z`_1RR5W7U>d09~@x-V;x^zDP&KP6BcGJdX*3gbysP>lXTeZj{G&-b#vYLxn2vtZ3WmudZN85bGK{!$s&r(?HeGFkw$2iiZ_Dm~nxJ^Ax02~`3~G%qH! zGS$B$>&0x1@an8@jOSipzs+7`Xwn_ftk`>ZfEJIY%XehC&IZZGeBr6Mf)W2e_JWP)ec>a*LwTl#g57*~K>U2-G()Ys@oL3A&iecox zhuiuf-QnOkRs0hl-%T(435;kDUHTJL#U6_N3l!)3H2E(%q7?gUz^xC_hW%CS=Qs~X z)mMQ`Xn@W2sP28a9xSQb_c8db)bN4K4%ra{kEi_}fJUn4U-v+EcY!Zb%xUF=t#P-J zl}-ztRtf!2ptrUyHX1qVv{J!Cx#qNH#Ga3{xq|28FjuY|XFpV_ zR)qC>@Ha}l7A%c)50#6w24i+sMOtpCj~$omR*gF@v16)uri4I3eLJ-m18L<%8E<)x4erguS1ko87ivdqpXDVGXQOT z&=?jt<+zx@hdCOGW_CnbC2E}HvGnIs5EKH^jSZGUR8{giQ4L}QAhUnF`n-ys;85el zq(xiBQdMbX1>YF=7e)gA0f06(=9U|6B?srHcQh7eER92@B=p>F3~j-#C4&ccUUhym zvQg?pw3Qb#Cf>_CorjHC;yI$Wx|Nz?FG)Oa8wXzl9w$f~&4n`&D5=S_G)cEog3GZ^ zx5`*{M_omVT-B|AhD?b8a%B;=Krh2;H;AuMbYBOLli7p}J`v)slPmgtRk#Jm7V@C{%w|p*C_YX zmoZi!ob4vDRyx>AFT`5Ump&L_`wqr^J3Gw7LUxUR;GTiXK7O!>-X&+#jaaLST!KBo z=4to~FHG>RUOCQc0PyfyoK+F+E{n6Cjh-DgwV}iDW+SKol8Cpu;j?|>ajLh{_wiO$ z=MQld@ltXQHA=Ad2wjYy=7B&$vHCAJAI z6Q&O6R6^FqW;WQ;d8}Ru)i|)VoKVZaFP(oO588YD3wnK^-(l*2gb?&;Q<9|sZ?eT^ zKF5=-+S)BLQ>+vO)2(3FoAHK<4c9gi7_?-i1wNQLw3kXe4O<}u2EnQzcuWMP`3d=D6T_?QvDr^*P^V!*#UpT&JU* zft-5uVF_!EwuL%pSXr(uF)&GRhQ%uu$q)P3_t&v8g|WtDf}W1xToq1@NmWqdk1gLFR5oqYh=jwI9Q$CEoo(d%bZrq zT5JAGefqwXm85;?|GktIt>KDyWm;L-<~bbfrjD6bW5nLi#JSi_$1-t;9;d%D`9`J8 zGAj0D85J8;#>zq7nljciRRf@fx4c8X4?F=&YbW7t4%B$%D$tbc0xa1-DHQfsSd zRe{cj6PS76+gcSAwd3mZh~p_Jm9Kh$cxoUHN79%`IRxGt>;eUL0=sjt6NYKDre#JZ z_06`nr#}ewM#&Fi__A`==YMXP$w1=`NF8%0pD1swit31M0wn2^2rqT*>4~M(3YLfk zh03QFmXsmBS(Z(q)~R6CEUw&!Fd_vt$QkL)VZ%5*KW-^uw!F?CL6e#q)WO5e> zpZqln+K80-Z*#BG9TGAF*o#(Aa0gp)r6ZiA2t{kY9x)xu_gjFO|58+MzFM^ z)e3;}*NOmDJ1DIZa4VIVMm_GAh^D!ftdhvyS;_jqWEUnlF3`4skQ|un+m)?q@r-u2 zR5Rk@=ly(3aWqHM-pW=f$yv3TlmJT>=JONieq}4E3HLWpzJ9)MU4-rT%LQF9pTB5i zRnf@yqLEFA&~sI+Ou*~m2;}hy>>l4y56%ku2Gm3S`F%}%!jQ4Kr3V^$arR^GYd9+8!86t z^SAH<#F~nj>PxGsaW(5rV87d{S-H?8T1YW9tlEPLeh1$Lz=}~4<}t!oU_W2MgFqrs zl81@IL4R{epd=3y#|9FCk~~bD5=aC}@-T6ho#?^If^jh#35l+x(V-ev)5M7}KpN3^ z#=Mj|g;Gku5@pr2swPf`8iYq_k1z$Q@e$;yoJ0d^0wJDAvuXltOs2gx{||NV9Uw)q z^$kz=%*NT!UDZ3AVRl(y$wA4n5s(ZDDj*<;UKI?OFuRC|WRP@_xPauG97K>c3xa}* zAQ{O)KyU#A^8QYB&+fwYzW05;=dX`@-JS|vUE$Qpb;``L#vukQTJaXOqNA0+>X~H| zxD{rc_-$hxEZycu+B>21_03rJMf|;i`Lcd)6+Pe3tgHX9Dzu`Zd5qn!CN?%pYsjE= zP0Ts`Jlo9dNaLpa;%Qw|bDW#ggXY?1Ret|_+w8&58qLi0{6zJK6ItKDJ{q{z1V=Ad z(*t*yYte73x%qbXb;V>U&S_c_n5YbeVy0`^9#9b;=2qI&+^p}2DW!ArlT?^}HeI0X zPZjIY%h;SXoJ_o!z%^)Lw#qF~2YoTxd=1Ob{I>Biz6E$wc@C-b!bG*Cg<00O)iOS& zA6l3dV!woU0;781k*Jm+LvK=}mS$a0qk%24_dcfSEzLHtk%UgQG<$1;H`O-&z&0pR zoXys~m6-+!^7&R~!wy_r8HfnNdA0zB$H^whickksW=hHZt{e`9hLvFtpOsoz84O{D zU$DQus?bp^?lZm7%Dg`T`yO{zDc*UFO+l|q| z3{>D4ZQf>x_pgvc`T~1xWfid0%NQVi0rW`U?ookNsbdE-vkwPq@Y>8F@-{>DrMo<~ z-*9Yg0ej&W9^+FBa|QUs0dGK9w^RE36uGlP!zpW~MR29zM+dG&|;L#c1 z5qt78s?yPX5*)$wj@TVv(C&_AU#)ehK__#C2D8oCyD+&|DEn@3`d8@ZyUj*0R|Y$q z5v zZo3bh1JLQ;U2zvAV(Iw1o81BS|LSg*V{nXD;K&{tQ@Wc?K^lJQ4tA!1qV6}F#C~ou zpsT&w>3-~%&#C|YrVUPJ+5Hd@CeWGt&1y9cZ*&kQG{=5p0`ndmiOEJFmhf-z&uYta zwg;SYn;!VY7xZEev!Vby#5)2En!O$nr6IB*_C*Hm=wWu(2d$yXJIJjYxUMBmxjO}GM;tr~8bnr90>O}_+_A;|GIG;zwq60H>4YakqF<@me z=7usJfXIefF4fx{lhQNGp#{u*@t(x)K^KQdQ=bnaQt3c%v$TW;3!a3~VChZyf8bV5 zAq0~76;^#uAM+9(Uq5J$#Axs9Yr-fR+S}KBj6tIL!(14u^Dh%V#679WBj!XGEcQHN zUd9a!+a5K$AbdpDV`kIjJ(mziC0B0+p6c?cc@rXGa7eSSU7{BsGatYe=zAVBm*MKH zq5aHqdID~-#m?o}BN)$d+VHrUWeh9R6yj~FiliJk%1v4jR2yb?~y3sTA_6z0!fMw{~ zi{?8Fo+Dm13BE_!ubA&~_Lr}+*0FZz(rYF>%6ro9ubb1E7oj}G+tAKDb0J)wULIur zgxX!+fbIYuJZW#56Hs~io8~$F@G6=<1mFsO=d&SZAAY&~TM)`>(-Uu*i#eG()O>`C z2M;x?aP?I~&9q?YFzJ)x6QXX9Xrh7ubK~o)Q-;$8V;1c ziwcIbMy4n=!fYwVfg57ty->s9R)4S*rYmKn8N#IR7-=5TU@coY+WZ}7#1@Y+OY2?V z2LFmFwvB-hTHW8#>Ni7(C(DC{#S)_o$N~-w5C9z&I9^h3)Im5s6#*FE%-3AK1lXgj zcg$S4gnb)NPrU@6x4!R~cY$AA_m0^T0^?urmmhoog_=UGR z8x8Yr$IZItOdR1|I{}>QYT7jc6lgX5iATa}a2EAFG3vAd49Ha{npp{-re~{*iF%{Y z*EUkmiDvhx&(gubXnC}vVooMan+O2IU#`0X>^>t0^#69CWe26}#!RR;u@9MH zgunoHU`CR$Bq``&r57dvpISW0d?ap##WKGK$}8k#PBxd8`Z&qIQp3q;dl0IuX=X+vzC+=&hxxWbTDq(DPG(%+}F+Q_RN=?j7qu zLB-fzZ3_k8H6Jl&CNV<>ioqX7@x#IcrDGDVa~=O z&*n2tJ|L4c%e)hmyyqmr7p+7H~TR00`i1y+eBUGnRSunTG86_|GwDfiGybFys+huwTjv5DnXn=>kmj+ zs7MzFRs=ru81peF>`sD+EVwat#X>Xl?ggBMrS~%Y=5sVNpnYx!4Pg3|c;w1VbJ<{o zvsU#nqmZ!pt5~c!3~oxzucpmtW<_VftdJSpmm!E-p2n)_>eYeV3V) z`T5Q=vpGL^FEbnSGkH1iEuMERH*f2_YZExiDjtv00fiI&gRzg>5znLJ(O5**x)zs2 za|vg9OJw@PnLcDv`xRzpHx1y4+mcH*A5Q#x^Tj14nlH{Q(fkjaLTG-8R`F;WG#LfE zceqR*kep>4NHdNhPQ7D|JD=tlAI^ueSDC-*+QCrrS~E#Q=!z=quu=$0xA1-Q?i|$) z8p#0)`z_p@{~jj_rJF&sTV~pc#`!che(+Al8XTb~LuPi>695%^;U1?O9_Py^lbkP~ zWZ`|621o`h>!8{*=%Wyj^X|~T5GW_Yhh=>LNPItiUIRLEXH~n{gh>PKK;JI%!#~&&KZ~K z@P}qfA~RrcEmNEqBt|;Y(n?A6GULX_()djP_|qSnclaSDa&#oroS5HtcASwADO#QW6Wj@@+vLa48;FC{kYkj(C&AZj4^Q& z!MEygKtL*b6by%6eV3ya1{X|(Gn@RP@?c3q0dIFD%MN`{RD1 z0Uw)p-gSjP7unpy{|X)~c$o1;1k(${;39kd0o6VhCDLB${LYTd&yEU4&Yqo(Mhl$0 ze6D~`e|3POKQSxlEBDcDpO_zL7ec>$ViqPt(&_LgFzH(%ZJ*gT3SRY`q}Ps`u@t+{ zyoa{@4B2BCWu1q>HH>y%F(08ZC(JHX@0ht7XT;RY!wE0ihO2*e#+(I5Vb$;$rw^#vg<736 zH^PLj`Y_h%1FELe^k2*&&|Rte3+5MKDpl)gzlK{+y^L4-UNpymujN;0 z!zC2$y<+wX;jJ_c%&&Um?Bx<~EQ0^MY9AL(TroeS1;2q>PjV{mxDu}T(={}mc*UH8 zgKDbjm#>+zAv8T0;cT3Klhbe1T%%2KD)s*}{Qb;7fK_o+LXGfUr{>l_!G=zCK0ehM z9IADpC$F3PK}1zoSKJ8q0JYQU=fA;pPj_CKu3njxEUL#{P36t!f$qq-w63#A(sAVG zF>0;d7vjSNxjq$-_pfW!D!(b2I0mP#))$d>J^Lk$eSNgjCs*VC2 z9k6dCKAr7zK-V*_A#$=K`n$3HqeKV&+5yUn79CJ$K(xqgh@mq*b&SQ#q`Db~gZ_#$Qf|t?CdR~Wi5BOfxm_P4 z%4vvCawry}+`P$t@i9L3iC=VxyoTMI6-gIkBpm(XFk4rtcbxbHb<4!Vjf3k3=>B*y zJ7snNbE1e2)+9w~Uf~&lZ&(R%Z78Hp3HZirdLcnn$ScIca$Vp&Ucor@*~x%r7HOmS zZ8zUwcFkGl=86Kb?97g?xJW#p-f^!~b&|l++05NLY8@w!SvLRSV5fb>iF)ioK|ZyHHGD5*8sZVJHZSy&6rh!p=*fVJ)nPLR>{BRqa^VK(|X6ti|Ie{#%n}4`TxIz=OF^77T z60d1i!bGR~B8c zr#RV($j;WCOvBEfMK9;7)mob?I`XR#6~t&AbthI3E%p3aq5T!aJYUw~)Or{S22)Mb zVcj=4jkxxlh7A>Inp0JD*FFzjuPXNI=D0Uc@xH(k0AO`{kN zYUq1ThW6LQP;nfoUoA0E(=O4G+M-kGOR0`(1^f`7G*+i>fjM}r3Uzg=BN{`)Gq#RM zhd;)WI-)&x`tNnHDHc-ex}sbe-q?(yb%mmT)3gW`hd-EwHjs})|CeZdU7)(lw5~3| zY%v|HE4tzB#`Q!wfB0=iBTv;6tq@dYeLZnoJWyOF7QhB)WNfEv^+Y>#xJ7+&R|-0; z)UJqp$sGpLV4e*~c6ohq5FZ)cK>Up^j%p~%E6fQj;b0EcOMC~|RQ<%Cpz3pP7vBWg zeq7w?3k`w(t`L;7L1@csHWCkO{xBiS#h~Ui79D-)O*DPj1Ut2mL=!PSu~3mZh7}H{ zeVNuY5jiN^(?p#1&k8VgfP@&Y(ML^0D)LV>71gS7m$;h-V_2TcqOR@Hv`BY4HK%6= zzTDgvosBE%L(s-y_<+bOw7>>gnMHeS5sWJuBvvKlYbG=VED1$67jOA6jjP%Kqt9t2 z#_Jyi=v20}>4nxJF?z?T9gGmar!lQXX7&8jPAUM?8z#lhKQ?GKtQlZdJ^u3OAyx*P zmn#*V*}-q^!&}jZ@tfP2+(u;jSDr;;&^c9_i;U=e{8nFR-$wLxIx-OfoLaUO(~&yb zR=kB7d8{2s^(FeNqe!LY?ZjxUFx0TUxJO6P_>Ms8g*XlhkW@AHymQ|Ej}uK3>rEb{WAFo=HFP1Huv=a}wd9v&;Yi`Jmvzjqh=ncKe~ zqnIq{5x7y7pbM#T4{;kR_wFHX*J{wD9>8?RjeP+A4zj~xU&U3@@PR)7TFql@Ata(j#HFlcq*PSU--L0|u-VZB8gZd+s8+lf^# zDsrXU_L<(|HuW0D)gb~Pmb#rW(hjd(;f(g!8emPbVCChiVU-3046Mb~`F z?14TG()-(QypCIo@ZmTkj8Cx2jz&&{R{1iOe^eX?;KV;B>II7yh|#-RAcgFT7Kr}! zm?-z(Yb||Dl>P6uetk^jgeRHR_Jx$yPgE{`kF5);4_<}J)K7-5C;EvDuw4q1q$rQZ zf2f6GrBQJKNI|BP=Wt{Ipb~kWEQNH~Ta_0V8$nZk(yEr?)!t{l6@#MBwlI@SOJK%e zZkdCJ{TbjHRR$8u>JPGeh3v;^CWLk0|mQOZVyDZyKa zRK(S>kHIh>3jbnV=^^^_agko9I1iddC*fN_X;Cfj;%^`w{0C#d29{HE2E(#YFoMC&_$%PR9exy-LD2I*0P>Eh*VXIJ zA0Qm#dcbk|;7Q@QF*Ov3m8=HD4Ts49U;jugo)H0ycUJ@}y5MUPaGrnBQ_qOX+5wvW zj7U$tTCz=i6O+r=hZ{9$$1|d4qvAnCs6n_Bh>@oP?cwAJc0y~M24XlvP$ym>Cjs;2 z0c!HBcsPMU*vU)mXY8fb&x$Tl+$}F+W5hov>LwO_LrFK+smpUBn0F=B;l?ccH=akF z5KYZe&N8ga#DXgxP&qNO<`@zy#8?HmsKt8p;;B>nn&$+o{#;h9DyRk>D&oF8hh9ey zd?w3ZlnKA1=TI@4E{8&JDy(BtS%r8X9S`dF2 zL%H`_f5$3a5`zF;F#e#gU&8)4Nta&&r*xG{zbtaGO z%gdr{jlE&aV#ANkC{ArJO8KgI+Cvw~$PpjdOQl~Cwe!vf_>w{oW38Jp<})QQAatk2 z=nybloG3z~Jeu5+CI5y(i~85JztaG3owzEVj*Px}$FHTsuZX17?*qoKy!RCY&j(*{ zhzqrF8D7Gx;ti9TF5Vr^j*i5x%YRjz10ovzn#j0sAHVEw18f(@8(bL;bjZS~1P3>w z#VBKGGE)@?n1V4`iLRdmUQ)7(sgf2L+$7E!6DsRk)k3`Rnvmu9FNn{|4GKhCjW~zf<--X z8YzMmo_2uCa(Z$w#FU@uuff=l%PIFw@oa*#K3HAeJ3rH+H$@ZFIQ^!m5_8c-6z8e* z5K*>#@qRp5v>$oP94JnyJ=uSVvnO5i2Uodi^BMi@=#o40f3yF%*Gk1wyq!6o_8|(s zB?5KV2CykXH`Jc7AqT3ipp(V>liz3jTeP#8`=Te`61AY-dG9UpTB0H`Amd=opa9-a z)rSfz5}VQspQ|=QMTT+Her1e5aHuGqyc&BG%#%X&L2s6F$l_O)4i)bt1HnVShEU^x zF$8Ue;;GBqxD}jt9KG~5IFp4m>1|OHahJb(TeJ*sFKi{|#&BrE-8PJdBX3IKn$6-e zXA?Q}02?VnZ6rua;f+*8DV#jDnQqBb`^nd*sjFRfDymI&OGUM@Zpl-d>y|vV!EVXJ zCZqGiMIi5FDm(QsHMBw$0I;gj~0yqx)lVz z_=h|>Qk2bDo9v90_a7FD*$ITjVSQgYCl%ISe3uc;0iCZ&-gXot4B1la@|}7GXp(3wh248bL><3K>@;%;zJH!Q^QoQyx!M)}A-hSmt<2u2iMof3Pl1h2{-H*SgU&>zJ!^5F`*E*kuLnkZE(ynS>A|I==p zSd3s809j0T7$g1~N~&YpOHCI6aO<_Ei)v+RIASS?Dw8q>7iTwwcmSK=!c37IK0O48L9IPw`Kb5DZ3e53eW03_EW6y%_ zem?b`C5FHc@6;^J)51_xKG?Kc?hIj4{_`)ju&mAjukJ(5-Lz%47|d71&JmfVnYVKU zV|KqKF}0 zqU>L9^trjB4IbJuQHHv|2YGcX4S7#I zF1Iqjr{0RUPyC^z58A#=BzFyq8@Eaqj7J&3pn#mN!}$^(jU`A6=F{CJ|LGSsTn@GJ z4tjDqbmcqgr+I+y9TYnsXzM$=v0SC9uMkx$gt1qQYLU^AUi8aB`J5Mt9kyw{uyL~G z#(eQSuHbuqfvA`{7fb@H!MUfvzu+p31whhpRn`D}8y7${zeYzEfETz*Hx@v9c{NmT zq3~*@-C?8m|M-jUUj(V`XPUf7l#gXX39#T6zgz^P@k#P87WYLcNsHcDERyKe#lk@L z=*6N{!p*yvjxH8e?)%?wU9}ntqiD#d8jXm7$-OLVKaWtQ{U5mfZqjtN#4|EreB|l`DYAFVUAPL@>$0 zpAn8`fmt5_%v}0w1wea0Rb44c{aX!n#Dn^*#MawSBUi#^dX<*11OV2cZ7W5FvJ!!;=GB9xV!Ng-2`&Z|UO#W?XUP)q}; zIEoz2ma~nK&Cr2el)p*@8auonGNX&tf{wD0i8jNTQl~L$oaT zu}Apfc&bo~GQ=nY7o!ct!Bn;b^onsSCs2ml&ELN%jZdqzt*H^{_KqRQq5Id0w2IdQSPf@od`69|2#k{Hq=SZa zd-i3eltRvRnza_RvXDMnE2=7GGbUXP7~=aMYeoGk&xQ#ik*Y%zGLSU-Az_O26E^~19$_I*J8HLCKyLc47_ z^(#I3zG!AaVZbM$xCN{_xHxvK`s{sCU5Olw=Mm=#ogqCWUJjcZ5wHo9IV^-`F3^&Y z$m;zy?73(|O|)twRmH2nDTB}AI2o+w!^){{JMkP`^9>JO$p>Lthm*a0sPs5hUN3@v zo>}n6OyKCg^>FgR(AKV3L)*PxWU?U1bjDqSaLt$xfEmwIjSs}XOt%HVYBXzw(aypT zM8*Elkf^Rlqhq#~mB3)7GPtM3MPNi6V^o5FlLpJDDzS%o+1klAX{89zi(wwzLJ_~m z3@0Sj1Jr1PXxM;x21P`9rHd0#J@K;hG(doXkq>=@MO7W=QvG4%vUY>WRuYB>LuD+2 zQ5@SKYNRvy<3@cjf`v763_J!ZRR0j>G?$VP{t z7m8mM^M!ek)2aRhr~80`6vzgRH;O8zyH6M^VXSe+aC&v4NQ*g?%;4sQ)HiD*oc=zd z^BV#D$0=?Tgq&Zf!X|N-5~tYH1ycr$_V2P7Po`70k>+g@rBkq5qTF@nffnzW{hLG; zwI+I+jrJ6TCVwOr6aidh5x{->k+{8hBLU(lakChh?yNhwOSpn@I91gv`!g`12Pl8D zXb9ajOQ<~K*|Ey~=iFu}wJ%Uwfp}FoZ-XfdN*9Ev^v7U>HWi4NqMaNWG`Y1O3Pe4X z$D(Fr5Ed_lrTbVkD3KS>c@KRox`~TGmOKfFEb5@8i}dZs;(lBQQ|S{pmwiYhJ`uOE zE?sl9h8*P$f;XgzQOPafo6SqY0bN-ouAl`vN7j)rO}QnHkHJMLxjO>C*z|}HGx#{< z9ToBorys+6L5WqrCgZ5(NS0tS(KT!xWVo;yn-$rK%gq49RaEz*qh^vsUp=v;cbY-ds z-26)8J`*p3KZ*GqE$^dhpDRsD@6X{sQAoQ#7v*5$5bKBzap6;F zun=RtVvP%mD6&w=smm(Gcl-AHQv3{kO#83kTB06hD6j=ul#SG3i)azth>B1t0I)D` z;nsuR{@Ay9H**U&+5!G)h5dTvQ1dXVT_%QE~6BB5-Rdm?pP$%vO=n z=9YJZ;nJceBUz~8W^t>L^I}nv%B~#Frr)+=wJuTG*P>?9sAM2nc!i8Cq4gL^eZCe} zUNL&XT^`DYTVyK0g+N895=zA41hj6&32!*@Pk569RJ1KZoQyWy7WFF78AA{%s}KQF zT39SnSJ#$Icwq@i-6pDpIR@sxKy3iP_ih6K@1wkJqH-qC2fI9CPYgm|S^Qxop3mOO zDf(&~oWJH$)OOJnM~+%;hZ1f-joS{UWiGAQ4tU;AhqsHGQ9LYcaO!|OYW=3nU!f35-C{YJn`Sq1Iy?|rtC0;L3uMgRw zzRs_+%^P$Fp0vcqaItwxgd{T;s(Ir`E7 z*}G+cMt&!{Vxu4XPSm?KZ_M{_1iPhP!yiON8@RDZ#JEYSxJllKR`Q1Obi^41oH=vn zmJ!~9A4Jv4VUe8A(7CELFWio@;Z1>Ska*S=0f|tct?d$}#G&LcRpE-zNNlGXyP%?4 zNS${n|IinALA!QG?MUWm^tK8GDBmr2YL#8U zX8Mk3K}zqFRkJt~GaOz<))D@3RwJ1U~XS4!lvq3hIChy|G`^Ol3l@ zq#0CGz`~HDe5khV7ir);z$|#37QlBOq$~SHW!G8S0=X)rDhEXQl!Ht%Twp8W|GD(> z1ENv#_hG3In>s8UaACm#uz)2N$iSTbc>rpO6IA7(XqRzNrm*sAu%%9af*%ww*}jdayMd@ zIf4!x5*bOwg=}qL@X(A|wEZybTZQ!NVW*6TJhj;|6oj&Qv&60>?pGuTsO~qF1s~>vB*9tQ(6nP+EhP zuTQIviw1d1I4GaLg7U&;hU$|DoWaa%=}16?_#Bn~CK?|@)!Sk%2aW)l+izh~p zbeg*zscrcmkMOCAb)C0bsYF9{S{NraRjR2|)JY|p<1Ie6;o+Muji1mYf4R9T>x42s zS>n1pa-X{(Ys$@8U6GZ4b5@U{QDy;&u-rO|Dx4C3KupRxEm~)BOV9~GI*L%d6sP&< z5&ScpbZE?Jfg9d0(U`NMYjc1A!bMm1pdI+YV9NJT;Jpg{I|BiS9!2;tAW3-aFt~*4 z0whrPbFK>;L&bBVW8Q}mO3o^-iPP1Fs`lAl2!)JdRg(x`pQmiBQ4Bf}03A>X1nLD{ z+Z4e+kqsRU#1RD%9AlHimElw1$!q0>dBORDm*YBUe6W;5y&2pw z$OuIvK)Q{J>)btm~Xt)*Q7x7T7zfyr!I5Nb=fRWAW5(i27aDB#m zI*a2h1K^$=C7I(R94-<*ZRbTl|BMtC$-#CYfX%Dt#b%YExfv}lfLpsxV=jO!<Deps4RIOgmq(3W>Di+e%KPF)tkREMejr`EE5!>N?;FlKCCx1g8zfM^<;EOYl8r?vYQyj$Vb3@#gGA~8xZvj8R zAx3r@-}nbliH=KYFWO&#L~-nCK5IO00$1@VqjYZ&KQVpn?t z9^ULY4d(PYY}Osc4xF5K*iAx}Ha$6o<;X1T17lhWRvWa>n3rN)ob-{bm2{lH;FzM& zCqVcK_Ed`2WQDY&&c~t9z{epJfjXf47?-!zWLoa@lyn5jn9h67XPLj`96q>JD4 z`C}2d%y6_O?J_?*t%1OKZyuT#twSBjHrHP@nOq)yWMt~GKYefK-bikd_2udd%E6L} zlouwXYPzfdv8tOcbE}WL;+*Z_Z(y^4h@-azC zIqVi#W;iMV%Nl?Ut$2)htb{64a7^Ds+ziO77Y-n&M9Ol_iU2wMeO*B@jEI;l-s1Mk zAzNnq+}7O$?+|rn7t~}r4T_XW0S0xzFVh!HfHPo#_mW82DezYkQ%yuY=g?h#IMQ&f z`D8}PvOWfVsvjjwrTyEmfem<0uzt@($!cAur>O4ml0`W?3!QUTQ|&V+h2;#Vut!SN zL<;L(vJ5FIjFPwOI|`{%w7f%x@dRr5olUWV-iVe}@8+}@HXvg~3jEHUeS&EQ%<)l2 z55BD1+(thy&w*#l;#bu4g)FF)}%CWr`2iI)DR3!+w8##hKc% z1A($2&Wu~XQ@NcpR3H^XO2vt^Z$v!}uPkI;h3^4qSig__x2uDa8}VZDz-(RB-{96~ z-#OSn0D9v-K~@!yXJ9`ix%rpGM(`JL83PXa%#M*`^Hy%%>B-{lK4s8Cy?;VPtOoO( zrox>fGwyM;Kk*BvS9adBPvg~boK)w%TgJyS7UYP%RXChZTr2`Vs`m7z`1sp92EXz& z-{!&X`aB*_3<6wUD=vR`&^1q9{{%fIk`L?J@4oT72iLUN)4x_dQt#&>paA5?1U))3 z%5QH}?;>oy{gv|zcN6{K{KDNt=bT?yfg!2|teHK|`2{Mo(D?;;_1S~bC4{T9*M7Z| zs`}+U`r#e)mS1L7`wW$Mdfky0aMB$q&UQK@t)oVYFOS=a2>!Ba#f3Y$zn}>AxgF{k zzI)C2h3^h=N)hO5`cA46C+kLkr7W|?=k!FJe4IMP%W4!IFIyQO2QUfI{T!J`N6gdS z@v=YlOOTx@FJ87oeA8|5vL+rk;$>;tlpwe3>vvL?DLc}>M0oHxKInT|Bjf_O`ra?)44gRrUCOSJc;1~R=lJj}EG3&n=qq=HYUjxE{93QFGEYDC zedthGSsm$+QC^PZqsr4O$Y?H~S5bCD{mGT&EVPO`&SCQS5q58%w%b@nqLP zTbb2m0lspuIzEjNZM;(sphY!g4ldN~)JonPU7xRTOayPTyp_xdwW=vkM5D*m8pytU z7374ba;7i#)c60^W^0<;LcZWM9Ha&0<*R372jy zA4=L8fWW}6D-19>j3P9+A5Cd32XGM_d~PkO(N317c5P(^>fc6Y|JOrpdv&bRfoB*SRfuB@Ezjlvgo7ZFzxFdWpX9AO)xOrW^7TL z5YbrHz66_5j6E>{FC3(Tj?&8bH30gB+m0M_6VNMIJ(U*)+H?^$0?;uz6(X3!!W;pI zp)9SFyb}!fgPmk{?AlbD66`4nkjKV!l9ja(eb`BMh3AuXmn^3(qXu`$UlEa~%H0x& z6fcE7?JV2#4HxD;l6^1B-6O|$QK}NwF|lakFAciGR*F%M;)H*W3;*OQcPerM96eFP zsWQj=BK!td#uex6Z5ekjX8Q`2y;n9%{sLql69sk)K_D>4UJi(i%dua-SDy8;vl%5W zgGF%geKH*mLNo7^x8d>KeXl0)Gms5B0q;S;IAlQZR@m3UZ1@TT`qY8_xxImjf2ALL zgNZ4`$}{JQiRODvY<2|T5S!Tj34LVQUJKn;(4^O~NouV3Ro+5Dc8P#t6HtX#V%hEi zK`Ft%O-J+_z%sN=EZ3|1>zVH;zKNZeRjIV!>4W*cOso6IW@(Vo9V!)!WGcnIk7H9f z05IV}hzBd_$p>YSx;`kAoZrm=s;eH9{qe}`E7SPcINIedJ%^K}+yQ(Ak*BC*Uj;Ic z_C+gKXnbEem!lp&B>MskramMe*49ma7=XW(T0JbQB(Du9^98J(LDm5H_)j!slu-}M z6yF(U{}w(hb88kKQQ=(-_?&`}{w{BiZ>#fAY|DnU>Rej;0qOq&rF=gH*Rt@Eq}dy% z@X@mF|H8MIQl&@ay>#tgSQ_ z2mLp#q&m{VTfW>=GWRBk?e-Kl^!qgQDH+TGc7wq&jNJlRfuM>CsCJfeFYq9I?$)Q| zFzvTc=K<&{aA3dZ2MJk;QMnWs3YZvJyJW^em2vH?@ zLh%QlG|q&s8Yt5vw+9d(`&asIpe$1gXfcYh8*oxA;>RjIR0NPDaGM{vO}>C$FQld~ z$fT;uLyZA4LN)2Yq#Ay_v5k2Yo?$3)+nN6>>GyQ@rr9AO{(WcDHzMH!yvdZpE`N+KDB*ARt~7zFo`<3_{;Xq^)&ho0NH9<_68WW z)wBbTY*lj)!umv^Gpem|Nce1Ixg8>FFsiSkI)mkyR!;AgP&S@B$nB~;n=P2Gu!ej# z$EtiNYY>QomxUD?|c)Y{J>|}6-gbh61aG#014U{0kCScKTpL+fZl@TrLe1^`k~aS;hsU1XE9GR z{$Rgaa316Ge!^-xDUW^h#H6_#5>b5%9s2Fq$L_azg?-#h43PgSUlbLENd5Bpr?BfI zVD1CVEYt#DI?7w^CMZMr#}mV$^aqq_j;g@O?^*nNRtf`@{Z$csp$G6EXWbe18-+Sn z&0~BQ){#JoGi1wfC~8!PnI{6NVlkh8u&h0o-X9`I#;sso0&63fA8Y@X?1u=t>)(=P zA~9V)1jRk_mb9u(*}KzI!Rx8xeH5QL4B-fq8_q)0_ONh*%?5+^QqEA>J$iOp7R$+V zY3NYd7P{u|hRWOhvzTGm8K6HR^KDr-=j&Aet_VHG32o(sUPV_Ra>rmy9`tinN(=h- zZJCCXCpN}tglO#BGRX@DMrG5-Z_65Y7tgFZp&r?M`!@dPQ5!h2cxofVQ=0-kyi&;U z297K_uMy6?QuAT*MQbfJ>n<;HvF1^n;V z1?t8gP*>*$6|2Bi(V_W@W5W;TO1#Rc*>zsjK@p;nftA1?hH#WG53pqh^=QK=SrJzW z93LguBxAv$MCZY&!=FXHSTS0y|DU5}qf+<~b6glH&x10t0Z=olsg+t$fso(4K%;3b?ny z7%TT^`JvNe!QLVge#ARKh+t65j)xe#oO+Fy4H9<30?SsYMAn|Jp#|e*>3XZNmN-in zG*vDH&+oVrmN^bf6BU%k(iDal9uAf$U+bS80}K=eec_GqGOZ@7idfHpvlvm3Kn6j} z%1MxM*63j#;0wo~4=xAyI&NvvwMQ zsRub1-HQS(UO9NnJ8ayF#mCYi3n~4W^G)PIlZP0^;CS&H8tVYQf05EA%FZxyJULM| zbF_DBC(7W%McTVKr|~#|IH)M#4xB?n`=`NMHuzeW91ccXti{GDH7|P!Gt?qVajT%p zh{dMU7@5U0koy5x^(M*Ec{@tzp`D42$M|Ad^=)4)QHGxT5$;zNL84>%R54FkQOrrS z0@s37(YRdF1Rv65~@8#wog*vh0UO#OW`U)eOZU&sW93IoH;Utz&?RT*USJPAPqN&w1*?DYwSG9QHU0FT~ ztV)!<$4ArN1!szT&8N!h2~c1#yF10p>$nbK+GQ|_qaq^ALpa+;_(K%sPnBgVa1mhL z-oXry^On63NBq!?>Z$R4DlUGE!m+G#Q_+pJAvsMxr#0d8x>5G92!81zs)KEdvf&oy zWEL}S{7t86_8D8}8Y$M$7t`h4kd;zr$Qi|SHyU*JlNquFB(vz5vU1g}sc8-t*Wv8| zZ~z)@+Mg-3u~jV?ZchQ)aI$eR--9z{F4O8*(CTqBWqL8K_MpzV0^rFKq}o$Vs)wnj z_tM^(@+qd(vEaw&(p|IUaxH&yzI-2H>_T`%egwEOW`&-eEull6MK8^fJDOk#U~5A! z;(!bj*=~aCypTj9h?tX{4E6(-6hMh8#au22OXqwe6dE>HrfF`=wD3LIvYG3Cz_VV- zyWW?T5cWREO5~hV-YhHZqL5{9&P8vQSyV@0?y34bnUWE1CzEZ>MtD$%p32 zV7%-5FdNhyFTFEQW&ntm&r{I0%X$1YPv#=C?0oqip!xNMatb}TKuS~XL|kbGJj0OYhqQIE z98q=UAuJ&)TjB8lbcHB_ZpLi<{>uHGjl&N}u0KSBm&ikU{$A?4RMz6>)TOcmKhG?c ztIZv$z&$SW;REk+mXR2E9|BM4Q z_q)_Qa~0q>M0Hom%yL^&vlDnMkjHQ{)6_w^prNqS+6B6?EREg3TLlwvPtYr?WIav$ zme#F?c6}C|SPgx^4yw9FX69~F-r}R+E2HKFoKcjW4JH^Z!!HN+8PGd!jhwtJh6s{_5XmI_9RCUXsA)y(G%HUrab&S}TV`7vo(g*Fxp7ew~cblftZ8BKR;c zQPmX}7y2;-(}NZY-Mc~F=k=owGhIL|pN7_Nl56#ZKa*@-MMmO#ZvPCuTp-&-*Mb3V z-0737Np|j#V-|QAGP0NpwucNkgE1l;b%&v5 zZw1`Wr`3TicqU7L(fuIxWcwBbea(Wy7T5)d>uO}!r|__kUxLu@u@B?7ig~OG*(rz2 zrxO~|2V0~SUBe$>38n_^+9I3Ps-Yw`w90v9vY@B)_@nU~pRDQk;|Gwvy0jQ74c(}r z3hGn4tuia-eT}sN8jN=Xx614ads4GBq;ab(G#R+C)#LF;!k%r^@cU59^?S1uQ2pbr z*n}78@Kz`bFOc@Nd?EQpDgc!=gpgVaK>s3bjvR4=#(pigNyq0T#!h)000Il9U9dOw zqUPM&7FQ0E_Hj0U{ebC(>9$KhS_H9UI#D0={Be)yvtf=AQc5M9(Gn=bB_ z51Xq~Aba=?5BC?gO=ETFUwfcT)YgPz_sR|2+~@mb^~MZ!SkWHJy9xGSC;5<@yx}At zy*as1bJHW}w*4?=71I6tjyNFe#H>qUjuK2ksKo)On!w}_Iw+HI70i@_(uNV2 z_kFwp<6JBtC5mPpl(iz?sskK#m2wVA$^&UNvg1mK?yPx;yh(JwS-wpXVux1F@!R7sUr|?$+5pl(mdsvu^VqubxePb`V(j@9$+lN{61#UNkk*? zqa*k#Cfj4j+glF-THVQpbR3l6f8kT+XJOaRu@&Y}$K$fD4xR>1%XDtIgV~kpL{|lC$U7sUuGiUQ8EozsC)vjKb+d1kdO6&l;eXZ zC>#_#@bQEA0XGk9q+|Qn%~dWdst|Dnox&Wa@T-3;%vFU-Da68)x*HS6Vv|ow!cJR( zM=}muDkTO?EUeAvXnxq>=c(r@IUTWW{x~Hw8mtAN@C>GKU!oBVBIwxBKtMDjT!<@$ zEEFOX0+f6_0n81qrMpf;i+zFKI1MZD1^VW+OwC=E3hJYj=8&$JD^8LX>>l-LdqM(u z0bU85B0D3i0)85w0U;}-Zf9gO{o?mD;|%n~>uBp4X!_RDFK56Tt)s=S*oa76R;YD$jtaeZ+0N88HMft4$cPdriAu}oXtDJ`SGB3-Pq+fzP?E>9@Nq+2P z{i^-FAn`Y;@kNn^@&SBxKp`vC|6VZW3g?E#UWMTquN=E3Yo&nOg__$Qr>^Stx=}ej z7+#IvF)Pcd_wVvS=h1>zUiU^)!XNT3s0(`kAvfx{YbWzMFyD`qe;s@9JQZ9A?|6bv zUI*^kMu~re&VNp=|CSFSwft|n3AgM#enYe>h7)2({A0HU#Or6X#5 zcilS57e+PqS>cN#cloUPxD>^Ru=?ZiYJ{~{+e7z7TAT1lin6xjqKl(ZR(W7vf3$TQ zR=r8IbuDF08ed+*or>zm#%w5aA)G>P>KJ2n$D4~|tcLPK$H`|KQ!JmljPyhLJ;u5h zbJ8i+x~=MuX}Ec2ZKO9UPkT+#*6J!^JrD*AJxqvs8-UrtX4T-*zADx#Z+**`EGn&m zrdTm3l76Cdu~s<@S^BMi;qkKHYKWnP{MPe$REfg}r_qo&t1KRiV(qXru71rVUKCuUkcYL@ujeQqV_XzG!RCC4&=HN02k+X$-8CDR&bJ+~5vcBUWbh}I#S=3vb@?kk`Jq0KS@ zOg*VzDQiBuY?iiC+?lRg+WH%SQYC0j!ewor2CbP<@e|@9sru|=p0@F&{Ise%uGAZv zi@lCppmVL60F8fDupX~^EeWA40rmhXTZelrZ!wtKnAHMn3yzm7Abk|gq`S?eR76{& zDq0n(dqt}zIF}j~ty0=op`{hApJL&vGPIV}2yEGBwXE-WYN?J@xyE+ziw5AI?{C4; z0>e#XTTak#W_Z=6#Dz>Z`lcnvV#RZCRkwy?(MS5GS&1I}1On2TPHVW2scv1XG2hF} zCxBk93p})ymesXB$6P#H&st@C&)RL6r%{>hlt0kz^{q|OJ%LfMz6RaZAzM;h1FKPR zSGl03>OdC_>$ct$z3>1fxPc>1&|?j-t3Ri)4e*UKRM5bxjMOg;tTLh}$k#5&Rm^nf zqG#v!QJ;oZ=^%qps~}6$P&FAn;nQET){y;Ns~q0;-jqLG<(H=Q4Xp%!PY8eflKs~a z+S$;mW}!ndKvprv3EdG8ea0EG8d)tc;O>n8f6HiWBdaG;zcjLJq^dQ>2HO@o(%6DJ z`3z}Itr2lQu&NvL;WK_nqn(vhP@33|x;mT(83eoTW3}4@Dqv*-zR<+D<(S+Yi6rBPN;{Q)> zVFgR^Oxp_zRk3Pine!XSm|MBl+^QPQcS3tORka1EMo)@qY2A^8HB&x9(cv`%M4Y8Q zEkS?w(rYcP)Kqo57h`r63nhxtmk(11i^+Vi;7%(A zx2nE)r}c8|p=h?nBF4B*NBU$1b8v&I66rPL6k24V61-eE@_R)U*}0#NN=e zt*kT+s~ctDMl`Pj8(w?VNt)H#>Xw2l1sx3IwWqjOV6q;jHyLfL+w?srs6`vAeQHk} z{o=Q|Dh~d#cm*cUE;vaY+gV`KU<|Me09ya4v7>FQ#`@u(xpp86HUL!r?f+7}dt1xa zCmy94ZLO-ZryYA!&-d@yLHVt%Y82njio$iCrzyFe0$;$Q+ePl%Z>n#6p7!;2Ru8?2 zpQ?VCU6CT&TfG5(kF>Xj>iMVWLVN2~z|!L#ti=pV$(^jucewMQjvV!7;Sz%m29+oa z+Z1F73_~EI$Ko(V0dsklQpln^pj0dU}QlES9P`mpx|G1wicr1(_KJ|r_r=7 z!1mK%OSmNW~UBRF8aqHsz2=%)+I4!RYzM!Z+ zEJ67FD98hBf=)gi?qj9W${rbsbpC$p3B7m?7WM!Q5Y9BfrI@Ed%?K^-YGqMMFRQCN zA#NeP*2{YRpXx8~W(7iJ9>7Y*Tu5W1bw%BxoxQD6^m=b=B_^s`AFC=Rs#hO1QA7J! z!P>YVn=eG*LBev2PV;nyC&I~hCZf6Rh4s&!4BFoZRP-e6dk_$EGIU>Gu&Y|_(D;X} zD1J_T*t$=HWe*75U``4)5-`e;f)EnOu?E5*1blHzekl79YXIM}IN?#Ni3SzK_m5dM zu*?ztu%fl8LO<(kWES?b{=iPS*x!0k%#X}YcNkCQX^;-K@S(@8N|B#OXJ;5IaL0kQ z4k)ng6IOcZ;nCSC!4#l!HlaGV$go){g=wT8@4fYemF@dHIxC3=eUe_$n}pZ*Jb|rv zgX%qLJsmq3wQSG{T%Wx5NvoFjX6Ve5R;G^Dqo1~F>U;K4!>18XZ$hZo)7D*l5#7pX z(DzI9^D|b3nirkO1d!c1<_V9SV_Aj0Sh6Ca!3y@zmTVG+h{s37sL``lwbl?k4L*$w zlz9tQRn7xKN7O|#+!A|T4m!Q z`9sB>3}}tPvCNatT7#4$ylRMTlW?S=7oM});bf$9ijfaI0A>4Zzy0}k54!BATvS|O z=O|=dY-LCs&QZvQPQp2=h&Q=pfP1DhtE2pHlO0;tl54p!1Rkgq%O(lC{ud>}Y zlI;_aD7romE`ATqe;!!oD(!gQnqSW85WZ6#Z@@Q|C2WpE!bb?%FsR}youTOiLDUb> z#(@wtuF{!-)`Kc`7~<9i)7&^bQNdIk=I--?wH^o7oR(B))@sJsP{kJ+u&+|hm#n%i z;GhEigX3F(m19eB9F&i7_OLxgo)&TTpm3r$O0GeHqCB4NO)vxio^WIcos2-eG1 z)%IVeD#JPqoDQk9_uyr?BJuP)s&Suki;2U>lv|9WkBCAhz=<{Yuq=*TNpb^mh6e$3(e5R*IO zRp5#hp|-DD>ses&y$&Q*JJjrTY{dZBiP&H)*hGh&uz52Z)kCl30V^AuRR}S?X%Na$ zxEbuD(}S$N%>R7;2DpDbFTY_`Nt^-|uUaeAMt>#`wvwskVCxa!Q~FRV5!gr@3cxr?m4;dq@kYT=ps1d- z`EAI)yD4p$^%k&3{xGY7$jA1N11iI{?fvXezXdx*zYN3XZ9<8|tx^`mN1XM55lCsR z8HZyK_He6D5=z1jJH}Zh3+u*r6dG=A^x@*xO4NP?$mdC#Il}6UFJBsAJ&Z4R9cevP zx;k!ueajoZLPrN4L0isZGlxWnMkgiH^^sQXQYv3X_QDi$FM>X&ACOkjkGn=$EqpT~ z@!6@Ptb@1{^rg|(PB@yh9b;9)H(wcJmC=`;qWm#dO@V1*T*Eq~EDcVXSn^|Itoeb% z(O70!t~kUvrhz3Go8K90)#rEjkF|#I^TBb}Y&7Y6$GWZP6+VxTS3rrvuUORQ9jh7Y zz4s1=Tb*LZTMzKt{l{A``+iD;G;(@8R0P?bqm`^Zk1stgZlopX}va8LdIq3nW%5V1XRum!9RBInozrxXVsR9A@ei5=j( zH`DRV;U}5z`I71AB&$XCIQPzT7Q?aeG=sa8jzms4L2V{mX%+qlZC8u1X!c|XQ!M-z zoTB5CtxAbQ_zLYDHj<(TL(<8f4BC4eSx?}0)7fNIZ#)jYL*M;wAYD(k>JQk9bU)ve zZj4Ju|4J01e@-WX9m=zSrufOW4xSQ~2xnsIuc(uO>KtGZ)ch1Wu2%_ipZ(s`2T2P#QD|b(;rnH?IklE{T9u=B|xp>6oo5 z{A*4GRu)R2IXH8!ICSR+PC+<7Fyrcn{|?kfr*JPj*T;0?C3H)@q$bx*LD!Cu+~G1P zSQW6(HMpw(J6&<~XlEIXTVU-6-h5)A_1xX+j(2{``2m5+`GIe!ANcF&7jO-z#~1?; zFvh0)D>4*daUn&p7-nz>6{@rso}t?oS@VD*FQ#NAB^oykejQz3WW5l%C7P{wRBeeB zpmB?>3D9TcEU`YS+%p^E9@rK~QvhU??SZb)3U6l|)Z~xh7^l7d3?(cD^6yEtmqKe; zon|bxzJ%fX;bqADjz%xDa+pQasqu?hskFIkwxGkyEWUPLsTAU{1<^#=f5=f|_ zXX#Z?P*4a(5i}qIf*nCAQUyggAPNL2(mNY^M~XB7K|ur`K@?CB6p-TkJ#%+Mf&b$k>UwS1{$AC=%KsFlszb&Wj*tqfmd|Il}PLZU|9Z}?_Vn@WBG z!Rn6ZM0=u-ll~f;l0ie(+N=EAF0idbsVx?81b5JY{h%2lNwOC$; zEZUN*VT6nB7Tbe@ImI;&?rW}5#5#Kie={fbS!aK|blU!P_D1o*6=YWb5W=>}b|uz! zGnOab2i^*@*JC0IsQ-Gqj4YiH3s%V|gpRDUJ89i|dwd5FJhL8E-^2C9hHy8b{80S@ zdVb@f{P$yFgsl7>st%h!zqeTY2D9T6+kJP%4PX^R&%}g`(0l+bMA3|m=+j|(X_LJc z#cl#x<3WQ>_U2|(zP5~3Z?eA!Yv6{P!9*>gH#b8lT;X1^*&eAuu-~}_8r*dhvlTjk zP1JL%{R_+C@2ToGdovE-+GbzNVfF3yTn-bD*i&fyb`-rrv$ord92+qmwjFkGrw|lt z`LM7I0`m)X++pt@w#`h0roeVRh>Et`8&UL5dz8N08xI>)R?n1_YP{3_9Qf3wJF!g_ z(50Qw37w$$UG@)y7D3z8hzSL4*ac>EC9T+Fx6}Pyb{V}V1%@~9iUM@Rc^|UDCKrN| zkFCDD;%+eccpbg-=k~n#cl}-9eL~@j%GY(;r)iiUKN-t>pEF>q?Oj^=xjntsyX-we z>zx>=vO+>qd||rHj4$-WD}L!TZo2|aRRva*x9_p%SDfIVh^uBSb>R4wI|Dkz8ZQe| zNslEieA@6>bnjSZ@mMNgDo*cFO0?hiK>z;-y}j4I7+iM6efGXAM}M;4ZfF5u<5a;- zU)UeD%nYOzE5f5F`G5xs7&l*uNQG$UV*Rh$bJkM31JJ(yO2ZG>N7i6CrX#^*$gjjG z6(3vvr?ZmpIDi9CrehEalz65OGppI^lk1@UiAbI|IQeZ3krOk41#y5D90Z^73w?Xg zo|9BE-FO8p$g~9pL*lf6F#$tiD}ds;(jj|VC|YGb12eJ9=;=dHRHCcdK3rY5b$-#b z@{s)zMmEb~6A4=#w&VMII)4~UsfmQ2B6ROQ0%Q*>qXDDuzJw<5*k!UGwPyz$Lw{K} zTXZYTMYDbrzVz!+dw}c?n$7F)8AV=Q|C~$fPB)L*>-d5!S!u|q#0=_o3>^ItdiR+9 z0*F!H<95Sw*=zu2B_O?>p%dd_!SZItam*e3N_?e)TVL6;!fwSV^CGO-a1gfK6ZSz- zkd74Fue|#JFhU9#q6Tar|flXXcrjB_XkXUE@|gPlzYP|`%8W}?5uPSnvN}a zJdwJ6o|H*L&*7wG=ald5EwS6Q`W{$$ieCBNzC$}swf}{^XBl<*m%Y%xKzZMxE$w0q zf1btBfpqORU%OlT!5*yHp+qr_3el-N+a#QK1Je`E+v9LVS@%4&bQh@0d2HglXrvh| zJ#Qa?Ux^p&??U8TaKTgd}UYyYgoWOvCpX0lzheBJi{ZkHub?$9Aeu6!NlcLTkD2jC$D(fkRPDv zm`#(f*ee+(pSCk5B#H}gf+vQ)y<%Sr#{R=!Aa|{y6TjH2Cu|R6xt%#fO!|B~K2e6P zIu*0HjIw^km}b)pzuFthJ*cI?e&9>F((UxvXdgN+G9%n zta8=9yWANTbNG;-JA|f|QSdc;O^#M=o6#Dg+h4Q44vV?O)&g+x#l5B9ui4xC9tueG zqjMcZj1J>`E0bYAn;Xh%c7*jKGoh1c=x_E*Fwg6A-L9vAep+B=25t$bS*0??hQQ>? z$Hj;;g+9BEBh}fIc*CwIEi&uEd^mAkfO%`y_MQ~z0Rzv9cgH*308YEyEt*f;aC# z#wxYKQB=Rv^)8Mk0-Vy=NDcdl!twa2jNh?%SUPXHs|{r`%g<-F>L7i>dH0``~!s zg2y;fDFw`(Hp_f49(&ch`;#k7t|SK3JiyL{t+s;!iELGeO~!USAW315dCgXxN<;k& zKEP@NKKiR%{HPI3a{xePFg5~%|6TD6PS;WhnIM^b(LW%LMMPy(D z)A)3IErnY}Hr`pOV-*dccN=0AEUe78it_#-)qK!))GB&bybnh_JbTSdPSb`R5d(s` zIrM0}CxH5JcOf4ETaQvNUlFhEr}e{=VyMVhJd-tsjn_0>5LFBaT$H(nh1mWcsg4IJ z$4^w!KBjJdqGi-QxY~oFla=jYQt+Gzc8kq^!cm*q6JQg%rP!8M?6eIe3v3I}#fnI7 zWadE#_BphE#dk5KCyb^@Pq>csb{Q-CnSa3Xy_G5fGEg1-FSi52ASy<|T#egjkv$l(B2a5D~2#hwD1_hX=+~?yChYuT>wNg6x zbpZlXB>9e!$hUfdwz-VCl6Hi1D>@BQ&`+LOtkQ?uNYL?941mB zNoYNz?Q|O_dPcshQ3gqRl1jI7di@>6xp19}NYeag3q#?b$dv zaeadq95gVwn(HLTVRyoN1h>6$XE_dnfdw!1x^~Xe*b&V|j)TF%)!$(_B%*8G;@{A= zsyur?LLLh)i6IVW^P>>5GiM*yUI-9Au#M#J6Ocz>`u_*5E(cOGg0v_RW4RbR$+gHq z(UBt57=~j-c%#Jngv3aw> zHKf&NEidcopuM|=J}5UhOg?PWS$K7t1`=4sQKBBEA>rkunshWu)byVTLPEZ4fcpN81Qyt>DK&Eoo;=ZClU8G=?*}6yzMAvbHF*u3p)lSN$HZLYcsLcf; z8_m>3LuC7xF5bfyIKU9q5iBso6WW$3(PAaE1>hCD+xZ@dH4rSB6&mjE$^z4SI9gP% za`o(&3IQ#=b21;kE&)K`i;;Z#Xu7S&(uQ`(b`We2cA}rCW{jv6h@Ba84aG}rycl7z zZ6Ql6XhbpP#fttxvlK(n3Lbe^#)`qKmePiR(SJdoaECSd^&Gq7t(dEP$*+*RDutcWWhxxYUWD;xJo4bb6s} zls^w99j8_n#>{l5FIx5m@ZL-iX)QmF#eRke&AUS>4K>>4!w8qz1Uh6fI|PSIR&UqT z_Q$l@7yHI>RzR(IEIEU&4o~u<1MemUW0ExN=$J&ONQ*@~B~A%}&AfGTSpd*>+}+P9 z+G|c&m2v!34LR{?wQWi;zf>fyVEvFN4%B82t@M;a*NrKKM*wV9!*Qj(q-swo)WJ4V zClhQ3>2i{&81j>;3re!s4pC~dXatf}n2asxW15^S3LyMkPZnu@TOo%#X z9@TZL2KI~gDZ;6J?;ex_#v^=*Vqu|6z*!&A>0qj;9C0)zi4_=XD0bV|?uhch7fL#5vfwM&ap>72~iOE_?AEpVXL)iZjYh2V3M#c4EZz5gku^l&*tmFFpRsz0h!PW; zh@thFf-P8%WMU~DqJDivBfnx1^0hBLk}D(~>MO#zFh-+9+8_Ih{#-bKuH~ZeD=&)P zD77tH3`g)>KhZlHiP(o=>5`{dW(W#g7VP|V(yEA5x>`|mG#g4~F?s_Hs3gh<{0qQI zx6Gk!1Ec}+xvrA94IXq=j_3t3JSJCk@OFaFZQsll{ZYj)xneYeS1XGbxm6Ho90KRD zQx6tTO1D}OV92Q=vdb-lA7dl}lhkp;^cec?f-QzdR}p;Fy1fdh$|1_H3L2V2L#m3H zuxYU#H`}1!m*~-Vgl>CKhm}u^rqiLSVo}VB*hKJRm~Zq&gxw zWDSdJ3{xn0+SU<|F>?5tn6<5rLXPaZ;sq1mSn#EFbwL-V(GPXSdm%XMRgmJ#L4)gw zF2KXn^+dFNrTxQ(K0dGSoUEFvU>W!!v_!%6MOr)*a4_CTPQpEjLXZfk>@eF}>lk$d zw1C>w7x7`9S`{3BrPu3=ilRP~UPcT!TD7uOgj&Fx>Wg&jbQkK2ftc@3`J%PII~IIp zDs4G#kKw&=f4=Aj1yGg?b)Zv;kW4YfU`x#IVb^-mC3LVGqg)~b^72ZT=pO^l?*U1G zysN~=ZfN}0Myl69lurXm0jTqKB;2gmhIszFJto#DOFE*3|Z#Tkb{0+Gq31`G+q4Xq7Q2kgy zztBiDtz^Q8xpr6$cwrR7tX9eIQd1g3GrEJj8QEC$@H>Q++lQ_~T|(uWh^K&cAVtYE z@evV7)I`KHqOmxo5N&G{@d9#GY${UySJ}ZYSm@q0SeFfGDqh2TEti`LJ(7hb4`F0I zi^aB_WHaHymejGCc&7QcQ_^itHWb7|=~L4A0t?hM85Ua&D~L39hmk^E#pB*Gx`R?3 zOMp^Q8Tl()E9FEMXR74eT#NVF)(XhqtA%*fB%kW% zi58+A|MVmEvw2H1HPHOIuBB*)QHHfL*H4gA9MQB^;$6f-vGgqN!oJ&D^uVvnt;J9T zUXb~LW18}ac+AX~L?LZNIQ(=(w-FPOvZ#$nfY`OajW9qL&$kgTVyQpg76hf3-e`*@ zv5w}n6;F9ovDQaHuZyYkqawQE?pO$`ukyJNaMv~^)`Nf{2QBbYWAeco@Dcn1cA7Ob z=TXrNMZcRMWQ4aUr&*9TV}i*4r!+X>E1iaZ9)1kkh0X5!k718NM~Agnd%~smqB4dQ z|2US2$pGd&!~jlF3}6g)1+_{-CMx6c!7O3%0H6yBe?lxWCx>^!5EgJi%zSM>#L)6q zDd`l_L3HMHs|op$9YhBcqKWiZ2l1LYb7~)?ju4{%yRCK_Z?!43>`Ac=xY++GF(rBi zuMqSKEC~2x2uSc}2l>$o>w&~h;%5vxw6iEq!1&NRmSOM#)VA^h^ao{Q8$(-L`d4Sc zHava>*!{nyY6aNEzIP8S5Fwfq`Ut4Z*-?{^ZN4J9(g`yXCPFZJ#M9d|yNG^e?Fd%% z77xr3Tr{k!co_ul&#s`}8{DbggfH6>ROl{Z{0h|Vs##AXf0yo}JG!v7yO;}e%brgQ zXUX88X(>H8T*z{x2u#fv($;5$z1Lb13hf@KoX3VO76i)0YQ$m@8pS4AtW#+mRu1>{ z8TrubRd~n(-==~E7nPy^FdZOljI}LC8avMbhsX?iGZr1oVPCCJwR?z+urC9{Y|7#f z^Jw(iZ=qI6{9q3*U(I(WR*HMj9$7e-++=7^g8^X-63JKFQizYn_xqA+M{Vb4m zHih;Sscc|@)4zt)rcelgj!i@W>L+PYPm!jhUvNxT76v{|Z42q=o{-#OMbb;b+f%3m z4euqgV^Amv=!1VloYo{JSc*`g17!?D9-7el&xvQaXu$ur=m+rh_RjMnDG4JfmuKl* zB2kr{V7$QrE1)AOQS|lmpfEG2d12|kKS|pQfl4#z%0tmTd#mX8dza4L3)p1z5wAlC z{;&@SM}0cpM+^h~@X)f?UJwJk^e>{X@bg_16EcIYRa6UB@wDszkMOk9OrAEfzex1Q z1_ivJ!H?9RT&wZVG=%w2vvNj!lpFmXE7G|Eb5dEguL5u<9UT(3ydv{C~`vFbG+- zflwh7Q=NgT`c4ByUAEg?(zJOP-!xdAXS40dTG3pg;;iqUHB7MIIupM$&epTQ?H-_?>h&H^NjECZ@LlPel zh$-~qYYLq-Z~+ru6DvScU9XE`%gPw)`-PO$b$^7PXTOVi11oJM)q4XiJfQvC-Y~aj z^Yn&t-&9*OHsM5?_om>39f*wg-^Av5iqZx{BXfdU4i*IupbF*_)E4vaXU_lD!Qz>J zpIZAZ1=6u^fo|-k&2Ncz|2}WV5K;C2t#stU-*Rt@R{%p^Ov$@rvBfjTW^q}2vv~vT z*8xM2+>-sCa;u1}hh?d09$6~+9g+B7h)vV}H)2zx(h`#!64QqoOG|u^%xtPhT4M2N z(1;Rg35UP5aD+IC(;9zTQ8glprjCSC`v%P&3E-}B|2|SgXfdqoM;&}=h=XjOwl1hH zVH<0^=}s604I7GE=-O!9{^~qhM1-p&L#TqfVT}yGLa&V$)$HeDQIu~<@urM;2)oB= z`)E-k1P9TkF##^AwT<$JrS2F|-Mfgj-H*WmHOp$&95aC30d!5%#)uKHxlb4?8U+oF zRZrL9ZNWZcMRdyB_~`>~CJ)OVN3m!#g0)P{;H0y2uskTeXyI6qSh*-H#HA%#@IL~_ zbj(p$(Lozmz`9gV1^B|nOjWK3wzr~d)@mH&!H*YSc*fB(lv|qZFScV~euJC9(H?ss zgDDsHbksO?+_Qh2h_?=n3!%iv!gV^dQrO5oUX0R~xIY<>p}_bid4lMeTtZ%WcQZ+$ z&AVK+|20q8d7|iP()(1%pSHE)pIA_4Gyfe6`VgC0hCO{K4GS>m0}&f?Ug?rB%g{e| z_&^-P&8F|&6(@n1r2UH-IK{NVxuDlf6ucInwXrh7YbNSHSsY4OE|dtwG8qi@Sh(N~ z;Jq055`ekLY`QU7RKa3QpCUfB6eQ8m>GmZ0bqaK9$K6#);07vQEuJbW@M+xDsiG=} z<)=a2yOQ3VCWc^ZygLnB$Yy%!Be6<5>rR^v!pVg0#Q{>+es&jpEZzYh?LPYn&>y+4 z&JanNp0nLWR@|=)9Slw$*D=_9&xfbVb_nJa**=2L?QG}Vb!Unkt+eHvR@(TEH>XmT zZU*DQ?RhGhnP4=G_}w6 zQx8qEV`XfTK-wUf{a%_QVgUI)6f{?K3SG#LdIs?JYFj}4=0X%-Mx*Bnr&s{~kBg`@ z{Dre19LjH=3pRZjU6~7!W-m3FCq|+(2j&S!^aY%*@LV)Yv|!M9tpwLGVSQ$Sn&bXF z@p2d+Hk&8ue1`t^eBlaKycow|M|nuWt&B!JK8(PStN{nvaZvMdvC(!V|vwV~omSRkG*|6{BYu;I2b$yA-& z*oH6LCkIN-zcjv@V#V7E?)eKKxP@?%B0m z488Ut-@07vi{DQ3Sq1Yr+7qx*}*^AR^V z8>DMw!EKya%G)1ZpKLFOhm(D8L>2mDu?VMTOGG%X4uNR#oA4vzXyVi1B8Y`Gj4vdC zIxWH0%%44I>k<(c#Sd11ky2K;7$mgaA++ts1OfCZS|Vg)Z^`P&#_3!yyh4-!0AGlS z_11!TPc3GH%3S#x-9=N{FY}|93KlA3mSItRKr@z!hH4s?0YN5Fo#o;d|E#`3)Z*hg z8=K}sVF2wS`=G(R4PSwUh#bpTh;h-w6^!wytTULcU#zT12)2C@m>5JKPe_8r=t>dA zDlgt^u)q95pR5#(l?X7Jeq0F#_C0!PmADf#U#V?^T$a|1_)cpDOCYOK8hr@Qvi(*I zqZ((Z3U^b!B^hkq3`ogAiXUH@P(|(D6uihTCM6#R`^4Lu)pk@Mu(+!7&f#M_A}r7i z;TOD44O}BqZS(LJ9NY7A91wpVEm#AtW)>Y-1BnJ+_120$Fs~bmpm704Y+>Sn&5thv zD$9LbX5}RXIPsE#_NUa60%EHK#KOt8B7YMo*UhWZUF=5vxX*FF80Q8B)Oj6L-^Xdz zI%rS}=*&7OXn&^8>!G>WLocrfp}b5B*NbG!$HVE+da(8_}ke#Lzp}C6g1=7+wR7vF&&{J;=EJ_%Q$zZ?J=xL?F>$BH{PQ7Ga@?m zYAiSh_+L{!U>)aO&zB+|JDG@FzC{IR#N)=OTTGGIRt#!4UM)aCs4`(xy8jH==nHh? zj945t3N11dS{F*jd$i;`aRcvh%svabu7LhL3nu(HJ#tPMjt^9AuoZGzJxV-2T-Zx* zofCbrlCPW-8J1bMNdF#(!=tI%_o9Ji!7X~t{5j=&Xf3AGt?xxe%d}gR{x49}cioTt z3$O}(H_oe~zGxxa=41zb@q?%q@DWtFwvWQ7+znd><(`Lny$7|rAZk@wlweYAjBPa! z7ht~@%l7hydglP_gUJzsNkk1jXv=v>IX&pic~Presj`bv7N*-7xG?RhIi*x;nVQia z>*Vn!s%<R$WC`+zhb&=Ya7&HVTBH0k>Ew?h64tD_+kK5d zwN?4x*fjK_=oqPt8ZD3jJnAVZ;a|Qenq$pvy(kt}U<+|*S8TUnj`j>jhY2i2kSCSk z%A`2cb-?MLgwTl2UlA4E&Yz*T3tqsN0^sZ)D(fSc0CaNSyd@4hTYWSO zj6BrZQZK_nD+>Uyy@ZNWrW|TlpQq+g^}q#-qfm4N@yzB04eae2de;gk7|I2*W>Z;J zA#bN+&6XdO3FI>ORhbZ4eMi)^+!{&0-Vq;Q*zepG3lU`B6Dz|C%tlaIAQWVm?}5gg z9Zf;^#rkGzM}O&4n21L;T{F~IBEF`ouOxhJR9{i}TCToet>;>(zSuwY9QDPV*$1O3 znCaUrLM&!F)$@WuWgCwym7296|ayT3{jxvlG}# z@dsKO0MQOD0yjXI*~b`SEd!l_i0AVewrpiLk!GoAoP&8v1#ziXo>GXVn!d6sTs-yl zl@$<7^OczfvIY<2rR?;{@&T_0e9iI^g~*JXEqX&FM+_X`^dFGO*AJZK+Jr%K3mYzf zejM1gGEUv+b-}|BD;~HO+$6Bg2{OOoiUWP6JU zlNHj+U{4V9A6V~@X8|cTi&DF$udRrZ{befSRSSPv$6?k9(_|PaxQ3V-5Fao;pZLp| z$aQfcsK(SQIW4Neeg1N=7Y{qvI55i1j_o}xGsTa+?Y~v=R9#? zLEd~{!m834EC&JKrUc7`_U2py@Ml)_H;}xM=-AiID@O^$wvhEDdaB|!R{J7 zMK}iqg~%2FVZ4CKt9G0Wi9sRkljdwiDmETh-?Dt7c;Y68NP{!r84NRne2~Gx8Fq%q zCg^TRs5I<6S9}f(Z(yo@?jhxcnllzEzh)@sgvss5-!VZZV7*0!%W3$vBwWrw&?-V= z^-;J@Rz~15gMnr+-weJngRpWcOTHPrR8AF-jFgMDw(c#Fa-r61W!y`A@|n)-gO32g zg#zJA)&~O(2f^n0P*8=7YcD*_Ws%5$<6NHgU@jcBA{btPX$XdQ*e+X}(2Pe=SIDZh zfyr$7158#Vkpaisn-@CP!lY2u77!on(G}63ClMzxT-{6c##mg7zvXspM2oJ1Y z1;}WSL%xZq-yCulf=RmEjo>LmvX$;QGuUhfSIr~55JFzk=;E6S<5yxJ*u;n(YlYE+BO;EWmAl2l{^GZ<(F^UUC6k}CdM zvfPOg*Go|&UX>yf<302eLxQQiA`@i>^IM9ng}!B{s=>5Lm3#&3tyB-TvpJ0y+tuP) zlxmJ)WO=z78EdC0H1Cuq*(3OeX_CD%Z!&`mW)P9CBJ<3kyBUlzgH`Er7z)(LkRuUn z&QOhC$&l6YE2V-O?Gt7&$_&<zHf_SOF%6AjL)Gn=O~Z5_nZMh}~wYSW#9Jn?cLrLy;}|0{K)DBoj^$Uag36 zdx!4>>XwL2#~&N05;#<1FLGP_tZYPAlo9Tsa&3 zs;n%N@vBy4*$y)7dzC>ZW>U>6vJ-wyt|IG++5AE?8*p-K*u+e}<6;##)3S98jjk&5 z0@$o07N?ebs>&DOoW)(ex;(_8f1X^$uOqFmDV;;G<1#Gx3=@}r*m-i92!>V0!?Emk zyiB%?ha)1K@nv&p4@YE}5%gp&1%+2@Nq3aFp#_^Pi=EGj+fswtvU-IwgA7XY%l7fb zO@33S6o?5`>={Cge0FW1=senATQ+cl?FfA-F%+9JR*Z#dV+1c7b!`sb->_2ZD0<(b zj*JfU@D7obQ%Blkup21m2eujv+tA*;qd;fGyTtT5G6vvt*O5up9?qo-aW2Kba1qlw z5RYB5({E1=;dNyV)X=1^IRSh`Zf4cETX2fwn@H4yn9a~R8smgp+Wt;1(%6_P?DhtS0L4|yo z7pPQOepuLr`EnrR_*ujP$2DgRa6G{!`!H^gYamx}Sg#Sb3v}m~#?l#yVTLe7!+18L zoOX=M*+hyb%EYIc``m+!=?_PwIpY{&4{}639FfiuG`0yCyOp%KiCpJFX%8C~V50P^ zO=VsLP&$dti+SEzV%X9Y#0;Z4-Be})e}kII=9$3X@Rt(9m4s7jZ$}wmC3fH-e%sMC zL~X`{o5|RC#9#-h@s)`D)CI2V){K|`BKyOlBcU^BIeU2G=fnPGx9jLSf_RAs`v zWhk+^9LDp+tSv;M0G{*+)m(9NSpm)A2}nwB=7wT&jH;~kjl z4-IRn5O6_DMHzOtRAcz5rL69mO7;fC;nu4O9n1b-#0Vx1Y36v5=l%0eyE zRfs`5eIRn?;A77i>fc&csX7m;P*85oMi$r!qg*2-;-Kn_xG)P?F{RkIfxI6c^Cf#B zN;F&-$I!{v@-e6jGar$I`2yx6k{`LWw2?EhGt6#-%>%m5HgYP0o^9p%)~HLvQ5z~} z<7{++Yxl&oMGOoIA4IfAM0)9nj);J*sLG+)=Frqftr7`b-h+WQRy7Bf%-c6B~=$2r+_J+sT~3IdM!p@Id+Uc2W;I1;b`+ zwziY*Bkklq3tY;4{DfQ;uNHemqSfWYCwn1qzyS>`-yuPrAOSCQkg2A`?S(qc?0{_) z@ZH=&KBJCXQaZ}2(Hp{0&Ntgm2EW=dJ`JH8@(9${S`_8haN{TOdIO)j(Su+erNpZ?B z62p3qR&jzkBnpegBZTsj-SD9U^Ruf2YP|*$RW*+tECLfU(JxDG~(B0v>HHP+dlie{d6}rpi z2u^jE(-FM#wEO}=qi1AK*qJVRMh?NR%pP(q0(TEcAbUxBRs}81VDhtaEC6wUl6%TG zfUGln%1Tgb9qK9TX$Rclz2pwYxi6oWYdjMeO?iE!ExZ}-`kMTOLY8fXvIfunpM^4y z!>YZ_u&*cl%oBd^31j=nnw+nLC;Xs~%=NICzAn=xm>;v3C61HOr>lKHayC%d3-Sb% zaOYo;*P(>_y02^sce9Bvf+|^Ze5l`-U?>;SkfSob!htwGgj!&=1P<}_ys6-u;$z8w zz$z=Q!WX;CqJG$2MvbLC{bVNKexsj!5j43+fAH$_=~91LIe?cd@2)8WWTlv;T(cq_ ziP*eLAET|IzHIP*j7ALrHNp$D17vOV-SLuq&zDP4_FZQloqI_-0=_XTQC+qiv(V6^ zGA>QAcp=NMF2LM#3%<6`;+T_#ih_IYiK4CpG2j9kF%X-_Qu=wIOtQ_6136`jL$Go% zSSX^{L9#6rC@&0>8R!BHl6|4W^nY2l#IMI*#y~%%w>X$h3tyJc)F_Gr{czzLAnOcS z7=q>T?2saztoFv)7d-%|x&Py=XczX^rPS$gM9 znS)@}oA3h&uOx$I75sW`u-pTu)46ZSM-qOBOT?2XObppGh36M&Icu|VLQgZ^k~xka z;>-+IWME|$5&_P0bp9<_QU4*Xx+TTwTS=RtsW$E{5?5q_kV$f?_oI^cBhCAf;r*!Q{ixyn z$n$>G^nTRxe$@7U)G#utwHZr zlViBtQc6fv2#r6?YF-*H`C;J+BOrOXY3T@=k6*uP6Ka$m`Y$gp;M>|=DjByrJ90mY-!dJ zDvl65{HMEM-T;2m3e%sW_W;PfwC+6_)42fNX&1yL=JO3jIHbj=OMQm$XNdZY;LorJ zpHUA!qaS?6sZWjb#>379xO$j!Cdi36x!yKG-sVS=HoPx;HaQvxQjh!fana65+Yt~d z$mfeVh%S7E!Mcpy}986F!jbCV$u2m zy7hq^6?o8W?f{LNgaSphe3FdA*bh#U)$p!??}y+l_qsDbl$wT3Fnh9e0I8Q#)5&sx zVm@cl=E*>dRrLH68SAHENf(ivf*nc4I8ou4DYEYn6YlBG9FS_*gr)PYmJIg~=yP2& zsAC4T&7hVU)HH)UGpJz()yyEn4ARV?k{P6$K}9pL@q3w=Q(t<8WVvibRL+`21#(Uv zo5(R4wSg1nkeOjG-8n^TNcIQ&6adj6F_qr$qZ!n6D)bFFmY*sgt5Y1z9smL{S85OG z%x2=u6vJ6XB5-M}DK9j}ZseC(&Y8-Y*#!!mvfA+0!c;8oFR1b~Sw0Y)J>Gf)XWwm_ z%**~P&e?E&bJ+LcQ4|O!8GzDE_D_r7kR0$A$NJyJnc->LH4O~cTzd2)`PdNDgIst- zs8pAr!uFgKTJVZYx}`l7W)>XlDO6N=ou@t(Ue{Eg3a@LaPleaDAAHt%@LBi4X9M-A zg)(A?dl9?hbnrC=?ithN0Jaz__lcC2?q4#hsUG5vAA5Cz3m{gqE0v+A>-<^{bBJZBn^4PJrPf z;O2w*@kGFk@$r1$dUex$gEdQ^l4+mekCw(d{n)1iW{IcRk68X_a@ICB9 zhmXxc8dlFyq=D}+WX_e=fxDK-&%G}y!@D`lNTaE{6#Ac%QL#cl?daFeX< zIY+KlsIYjYOex8W2Yfww!~Q96B{^w-lT2Hy$>AmD#K}Du+F#?i@B|~0sHw`n!5pKVymgyMv%z@dT*nw>QM6xpuw3U z->(Mo2!d0Jx%Bl$nUTr6kV=9?Uy@|THGrR7?3Cr$o(j9%0?OWmed%jzxk+X{>X{Lz zze(>yp*fGnSz;;<0@i?+4Q2ac@O4l~@;zLQ*PkCdl)~y!AyyA2qSX4MJ*XWx6Ywz3 z+}tFaLn;BHI{8luC}iu+az?})We@`eyc4QV;M|{^u~Dq1$F|7XkzYfx!0R~>?{(r< zxCMf#|5gZ0cm`~%OpPvvjLjost_`qgqEr$%4S-*`30rY4Ttxe}%7lOgShyCvrF(TN z1m+?N*d{ZFe9g8ZU>QSQzVM6>$l#D4?;Ws;!XH?L(^R(k!qI6I0|u`Q;|By5fFumb zVV>kbY)dA1V9Nk=P!l{Rh1W(b%ZL&+t66)et6#&AY6a`oo zDbr2rq7a&!0EJCJ2#Gijieyk0W`#jFfaoRq5Zr4h zqW#-tuOUS^=u*s~uZKrRz2$frfDG`);)~Z*6#jamAN-6|i=`wL_9!4#B|m{9rGLWA zsPs?i{Rs#CeEkBpDn+zm2M+Pq(6>9F+}c3_J7seC8s_$J5NED(%G)V(8eEQJqY=b3 zOA7E1PADyyH2K+<^Jq9pjU4X>HghUBlzv$B3>|gL{wrFzQ$8M2te^GjqdhK4{Pwwos@J<f}>8(nWQ)oa-{|qeq6Da*4H3_%)7%0GgMzAEl$nzs1P7&ivU#)2%L{Q?SHmR3vzNrY1fR!|-Q`6{B$2V@+&>l(u6W$cj` z%Vjo=J^%q9SFH~~;XRX19RR~PlYTz{W0-|hco1rsnRMo$jHXWy%2c@!C;d2q#I}LU zU}#ra>8T94$Zk7?Q@oi}<&a`pUq2+PXFuo*HVBiEw5Sfcv=Bg@S;tb{|Mrl~i%~={ zq&=)x%@v39TObeXD9_@}Otn$XIxL?BJp7M9r;$SykI30dTd{zS9|6uRqMJv+FJGq2 zFL9EzB1mb(Ary@LQnqQ=5%$QyHjj20fGahD>Vn>b`BXk7O8+b~+sc1v5ysS%9z6=a ze2F?NKrrJdv}DWZ^ih_Psnszkw3gBEWAbH>`Tt+XWKAus017W3TQXA)TY9%_p$51KFA{6lp<8%*_OtIGoQ|hM z-$6*a>27%z7#4+U{V-0I@74x0-_Ub%c;-zHim|<;ja$d%5?q`2=G`+#4xBPtzvQAV0?o+Bo;W{2Y^f;b7;LU`J}|w0QIb-g?<`TF#{xK* zSa@~2`=mSGvOJWw@CQ}Q{Z|Et;T!u=JZ@~j3dd!E?kXDHz^N{f6wf=^K5|#B zC~c%qej@r@vgso`YZF1%vcsv zr@D@8^m=q%$48dKiz&69`q{3Y<8|)rp?Z!-_2F1|d{+h6jII5XB3@2Pa0T>t4d3L> ztM7=^cq~B=#&U9`IhL;Zj;FKACY6jO&>RbPx&Ebwb0uGOxwp%)0t1L^;CLOu)COue zH5#h4H4W8pdN*J{-M4bLT(rc;?~g1mA~>Bj2X}jsaGV&K+dd+GdbLSNXgVnd2PE zWg_}cbU-Ums>rer9ZM`!JRF1@_K)PK;;t~l(OI?btpv4xrMcf6?U+y|`oF6Cl12N} zjcQ+fcs%Ph!suhye(Y%QaMFJd0RjYexC<6I`Z_Tt_kq2ReHue|+I|OIEFmWIpeJTV z1O2hGF`JusV%q-b$#bTK{)Ea?64U9lV`kZyJ!c&SWn-@Y;OJB)hEPwQyZuj&9sb+} z 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 Date: Tue, 4 Nov 2025 03:22:57 +0900 Subject: [PATCH 511/663] Add _setTextSelection; it should be a member of PdfTextSelectionDelegate and we also need some counterpart on PdfTextSelection (get) Rel. #513 --- .../pdfrx/lib/src/widgets/pdf_viewer.dart | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart index 58d93085..c47bcaab 100644 --- a/packages/pdfrx/lib/src/widgets/pdf_viewer.dart +++ b/packages/pdfrx/lib/src/widgets/pdf_viewer.dart @@ -245,7 +245,7 @@ class _PdfViewerState extends State final double _hitTestMargin = 3.0; /// The starting/ending point of the text selection. - _TextSelectionPoint? _selA, _selB; + _PdfTextSelectionPoint? _selA, _selB; Offset? _textSelectAnchor; /// [_textSelA] is the rectangle of the first character in the selected paragraph and @@ -2089,7 +2089,7 @@ class _PdfViewerState extends State } /// [point] is in the document coordinates. - _TextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { + _PdfTextSelectionPoint? _findTextAndIndexForPoint(Offset? point, {double hitTestMargin = 8}) { if (point == null) return null; for (var pageIndex = 0; pageIndex < _document!.pages.length; pageIndex++) { final pageRect = _layout!.pageLayouts[pageIndex]; @@ -2105,7 +2105,7 @@ class _PdfViewerState extends State for (var i = 0; i < text.charRects.length; i++) { final charRect = text.charRects[i]; if (charRect.containsPoint(pt)) { - return _TextSelectionPoint(text, i, point); + return _PdfTextSelectionPoint(text, i); } final d2 = charRect.distanceSquaredTo(pt); if (d2 < d2Min) { @@ -2114,7 +2114,7 @@ class _PdfViewerState extends State } } if (closestIndex != null && d2Min <= hitTestMargin * hitTestMargin) { - return _TextSelectionPoint(text, closestIndex, point); + return _PdfTextSelectionPoint(text, closestIndex); } } return null; @@ -2828,6 +2828,23 @@ class _PdfViewerState extends State @override Future clearTextSelection() async => _clearTextSelections(); + void _setTextSelection(_PdfTextSelectionPoint a, _PdfTextSelectionPoint b) { + if (!a.isValid || !b.isValid) { + throw ArgumentError('Both selection points must be valid.'); + } + _selA = a; + _selB = b; + if (_selA! > _selB!) { + final temp = _selA; + _selA = _selB; + _selB = temp; + } + _textSelA = _textSelB = null; + _contextMenuDocumentPosition = null; + _isSelectingAllText = false; + _updateTextSelection(); + } + PdfPageTextRange? _loadTextSelectionForPageNumber(int pageNumber) { final a = _selA; final b = _selB; @@ -2959,16 +2976,8 @@ class _PdfViewerState extends State } final range = PdfPageTextRange(pageText: text, start: f.index, end: f.end); final selectionRect = f.bounds.toRectInDocument(page: page, pageRect: pageRect); - _selA = _TextSelectionPoint( - text, - f.index, - text.charRects[f.index].center.toOffsetInDocument(page: page, pageRect: pageRect), - ); - _selB = _TextSelectionPoint( - text, - f.end - 1, - text.charRects[f.end - 1].center.toOffsetInDocument(page: page, pageRect: pageRect), - ); + _selA = _PdfTextSelectionPoint(text, f.index); + _selB = _PdfTextSelectionPoint(text, f.end - 1); _textSelA = PdfTextSelectionAnchor( selectionRect, range.pageText.getFragmentForTextIndex(range.start)?.direction ?? PdfTextDirection.ltr, @@ -3235,43 +3244,51 @@ class _PdfViewerTransformationController extends TransformationController { /// What selection part is moving by mouse-dragging/finger-panning. enum _TextSelectionPart { none, free, a, b } +/// Represents a point in the text selection. +/// It contains the [PdfPageText] and the index of the character in that text. @immutable -class _TextSelectionPoint { - const _TextSelectionPoint(this.text, this.index, this.point); +class _PdfTextSelectionPoint { + const _PdfTextSelectionPoint(this.text, this.index); + + /// The page text associated with this selection point. final PdfPageText text; + + /// The index of the character in the [text]. final int index; - final Offset point; + + /// Whether the index is valid in the [text]. + bool get isValid => index >= 0 && index < text.charRects.length; @override bool operator ==(Object other) { if (identical(this, other)) return true; - if (other is! _TextSelectionPoint) return false; - return text == other.text && index == other.index && point == other.point; + if (other is! _PdfTextSelectionPoint) return false; + return text == other.text && index == other.index; } - bool operator <(_TextSelectionPoint other) { + bool operator <(_PdfTextSelectionPoint other) { if (text.pageNumber != other.text.pageNumber) { return text.pageNumber < other.text.pageNumber; } return index < other.index; } - bool operator >(_TextSelectionPoint other) => !(this <= other); + bool operator >(_PdfTextSelectionPoint other) => !(this <= other); - bool operator <=(_TextSelectionPoint other) { + bool operator <=(_PdfTextSelectionPoint other) { if (text.pageNumber != other.text.pageNumber) { return text.pageNumber < other.text.pageNumber; } return index <= other.index; } - bool operator >=(_TextSelectionPoint other) => !(this < other); + bool operator >=(_PdfTextSelectionPoint other) => !(this < other); @override - int get hashCode => text.hashCode ^ index.hashCode ^ point.hashCode; + int get hashCode => text.hashCode ^ index.hashCode; @override - String toString() => '$_TextSelectionPoint(text: $text, index: $index, point: $point)'; + String toString() => '$_PdfTextSelectionPoint(text: $text, index: $index)'; } /// Represents the anchor point of the text selection. From cf86449136e30802f199dca5614f41d267d874bd Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 4 Nov 2025 03:23:03 +0900 Subject: [PATCH 512/663] WIP --- packages/pdfrx/example/pdf_combine/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pdfrx/example/pdf_combine/README.md b/packages/pdfrx/example/pdf_combine/README.md index 637a5076..9b2c2306 100644 --- a/packages/pdfrx/example/pdf_combine/README.md +++ b/packages/pdfrx/example/pdf_combine/README.md @@ -40,7 +40,7 @@ The app uses the [`PdfDocument.createFromImage()`](https://pub.dev/documentation ## Running the App ```bash -cd packages/pdfrx/example/pdfcombine +cd packages/pdfrx/example/pdf_combine flutter run -d linux # or macos, windows flutter run -d chrome # for Web ``` From ac3cb585abb776a0ab65c79cc054d05ea54028ae Mon Sep 17 00:00:00 2001 From: Takashi Kawasaki Date: Tue, 4 Nov 2025 08:18:07 +0900 Subject: [PATCH 513/663] Update github-pages.yml to upload more examples --- .github/workflows/github-pages.yml | 169 ++++++++++++++++-- .../pdfrx/example/pdf_combine/web/index.html | 5 +- packages/pdfrx/example/viewer/web/index.html | 9 +- 3 files changed, 166 insertions(+), 17 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index f0bbd703..fa10ebd8 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -9,11 +9,11 @@ on: - 'doc/**' jobs: - build-and-deploy: + build: runs-on: ubuntu-latest - # NOTE: This workflow automatically update gh-pages branch and it requires write permissions - permissions: - contents: write + strategy: + matrix: + example: [viewer, pdf_combine] steps: - name: Checkout repository @@ -38,12 +38,157 @@ jobs: run: flutter pub get - name: Build Flutter Web App (WASM) run: | - cd packages/pdfrx/example/viewer/ - flutter build web --wasm --release --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION - sed -i \ - -e 's|||' \ - -e "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" \ - build/web/index.html + cd packages/pdfrx/example/${{ matrix.example }}/ + flutter build web --wasm --release --base-href "/pdfrx/${{ matrix.example }}/" --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION + sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" build/web/index.html + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.example }}-web + path: packages/pdfrx/example/${{ matrix.example }}/build/web/ + retention-days: 1 + + deploy: + runs-on: ubuntu-latest + needs: build + # NOTE: This workflow automatically update gh-pages branch and it requires write permissions + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + - name: Prepare deployment directory + run: | + mkdir -p deploy_temp + + # Copy each example to its subdirectory + cp -r artifacts/viewer-web deploy_temp/viewer + cp -r artifacts/pdf_combine-web deploy_temp/pdf_combine + + # Extract version info from artifact + VERSION_INFO=$(grep 'pdfrx-configs' artifacts/viewer-web/index.html | sed 's/.*content="\([^"]*\)".*/\1/') + PDFRX_VERSION=$(echo "$VERSION_INFO" | sed 's/.*pdfrx=\([^,]*\).*/\1/') + FLUTTER_VERSION=$(echo "$VERSION_INFO" | sed 's/.*flutter=\([^,]*\).*/\1/') + echo "PDFRX_VERSION=$PDFRX_VERSION" >> $GITHUB_ENV + echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV + + # Create root index.html + cat > deploy_temp/index.html << 'EOF' + + + + + + + pdfrx - Flutter PDF Library + + + +

XPy}0ggmXo>90OSO!jSSt7$i*CTIF$gb!R!Cv@}yR{We zFO4>g#KtE{IRQr`3;ojkARDEm_(PyuN}(ds1JcO)+baYt;K&M^lt-QhDUkT+NJ}tw zX(_E)%OX1!dPr1!5;%zzNg@`|L4_zo(3CliDpwX;>=TEN^a%rg{D}~#AMt5+DZSxu z=+E>MhQO_nV}l+?rMl^-`Vg*qJ?E8$HDqBoLz^uY7LH>@qV`&B2Ejn@eJ0xJsa-*O z+5*oc_i1G5*EWXzkTZZYb`L{XV1WA$LzxT31!d%7K^Z$`4Cdn4CJE_cW85=Zk&g-H zDEYVh zo=Y{RLx{|2iOi7BPJQo}|BAtNK(C0*@ExTv--G->()r*-=7wRBc`iIuB6CA9*-5%c z*phUMPSTy|BwYl(#UxH}l5W9Cx5db0Cp+nNlKUyTpNa0L&;3krKLz&_xF3W^$tjLlLt}Kms35m`71bnAd328{&;b0P+~!o9F1>5*wO*nje1xpBO2GLh3*> za)V<&M#VJ;nOIV1of9}wq)TR9NLM7T10t<(1?p=Gk9;dPE-O(G67T#Hm3sbLxxupA z;Sv`dfStrM8SrdIZ+H!;L?w!(gfpV_t&Dl6W^}gNcox6#Eb{FQ3R^!B?Zr4qT5dho z(?Sm1f}p_B4%}ws)_Y6Owj>b#;AZhYqAu_&*c&XQFh}g6%end&H90^C-!aiHW7;Mv zg=hRL*Ij1t2?7$pkg#FO&Ol5WWC4!;gE39k$R4Lt`T2i*ysLC&x6TU?%x*RQG_NU9JlMcG@Kh+Sl zDq$Ew0*Ib zBs|fF)sbRWss_2T(sqb?fCP}lMqA5Y_7#J#H=(+T52E3%C^mH2{fO8by0qV;F-dh2 zk8`RMtGa}APYkyp4FgcT1d&>dVWI@?5!I<2>rUc4s$)?x+eI~^($c_;6tf)&Agc!M z0>h2fz4r9xAe1+vbrh44>ZG7?zys9@O;fs(&-qSMoy;vLN;37SX%Bi6>rbwQBBa&8 z389w$)Fk~$+my1gB%L@a2@0P5&C+`W7)Y)#e8HaUjsqZEg{$5nsO3J;@L0H(A0nn6O+c4d@v536N|v1TYjSO@^ef z1m#2avBPGc+$eSYM~#YQISpUdL1tBGNGFAOA`zND=ripvHALpN9-w5b5S(g{OhKL_ zjSlt^!HDQ;wLBUA^<9GLv5-4Jn~tEE-Q9AYQlc8Mp?a>l+V^%he$xqop8=n zlp`dOkMuVfr5xemF+DNJBBnl5obXmCw1!8&m+P;?cvXWV!WHl3rWPxX2pat|iOs@5 z=e>a2DDDfZ-_K1+56ZuKpm`S;KhR!`=^aJMZ&nJQp(RPLx7-NHW8pFt zPL-0^6QHj4dP1zV6HD@X0y4zeh`pX_4Bf$J@)+~Ydj;jonQru*j5Vopy; z;yRg=G^eNV2PtpP&K2J+4E`)Qx{TM+uEnJ5aeDMwt8{1Qy)m~f*S zuh;`bEErF!3%@fdCk6aZRFak&!vkx)94<~jsPP8s8nr(&CLF5qg0${m{d0{sSXQ*4 zQo7J6yA*qyt_2e7N=+m}jQGX}JPg$|A8KvobQ|<-*1#+7)NKjcc4(=gw@Iul!6L;8 z+(F@csT>nLasJvhRBrAZLlVNisd~iv6PF;|FCo~Vj&|-&!On%MsC|SGEzA8bA@iDO zn$nXiKk`%Rqd%oL{ArU&@oSgj*B;-*s=)r%A4X9~(I4H$UFlCax6bP*NBWO>{>xde ziQ;X$wA9IcQRiikSxeJaU2r$a-G*Z1=CJiSGd$kF1@l-glW)Nj#(sU>3xp7P$nYB?^| zTq)J^v88AkfGw1WrNzK>N>JhRYw~ClR9v1 z)T%QO;A#_ouEFbWkNqS^cRT!1gV*00`%XF_hJR}CHc4(8zTMz$x8;p!I)hBj{ACU! zP;?A4P;?-E^VOEH7K@-wB9DY{l)qq&`~?qh^k&Yxi7If0a8etlBR|o)yoO{ATv$yAV z%`yN=JF3%GQDdZQ6$rB`+gdAx2w{iK3UlV=r&UT9wWE54H#U2@-S%Nw z(e{}=g|>*Ez+9W5FjST-HZRAYSj|YOVuumXlV4IRn3E{zGIQEWC~!SckenIzwRn@~ zJBhPCQtKi|esns~kfbc2bJ!#DTQitM+Wvb+KDGjHCYU>Kd-}LY%5)9;91>9~;ss@}gFVc8s zpSI5HvEqePJdhM^#gTBEqls$6 z=N5$J-WW4xxQXJ7s7(~VkM%&Q*gl&_*=VM!mJ@S1RZ=Xyg*cc1;^vnmnbjb=ES8$c z2{`mVB6Y3gxj_srR z6;@-SaP)O!z4o#IffA;9{y5QCB!_DZ0d@9-@WQOOJxQsT8RYC>oK~ru=>m-qPHg>hxZ9PiG!f(EVbqz2zB=xdUn<$N? zH1?rz=doV<+&F)+P2T^FW`ME0_+t*|weqC|jdACWlw^_1a19l3|0L*tYmY)-HHUv1 z>rIL=EACCc7z$*a(u4IajL-$DeS;IPhQBAFXBnVR6XHtdOp_if@Cy&iBPm3r)?&1?OJMd(LwQN)*~sY95Z;jY+Git< z)JEHl4%sW5ZUp^gSr8tP(}h~*6T>Fo+a0-NiSLb{aVq_#Us^xNCX$0}^+cpKE+X=3 z2h%R`z1c;%^kd~i#?S^X5(L!MH4G(b7Ih74+P$xoPwJ5bOs{JqNsGGiT(gs7l7?## zzKv=|$-}Q>jYLFo`$*K~Rlb`m!r=fF}Klny|#$esrF)Pg-sLejQng-L2z z@YRxAYh%naMQyvZHbe<>EO|MqJ_Xt-;a_bJ-_S(i6^+tX07p@r0m zfk34RlmlaoaEY>n4N?zWlNLG%Q@Js3!w7o{ySu$PQ->L8s?=qWPRJyN`~Z=Oj6g01 zYIsDqw{RvC6`@9Li5q;XUpq&>T5aL$-Cn0yVoQ(rHNQfYQJ)J=SgY3zoY><{TS!m^ zO#d1dXU_tzDxPnXAX7jvMmn?&&p0r;fsx_$q4Zvl_v!L#CV@|Z#?Yrf+-;zlB0Bki zCMjAYjVut^!!-4k#iZ%81GG@YK?yl&q;l@$LyaiPgqMb=JIT%7GC{Apnlar>-C<#s ztEGvWk-`OBvHC06HvHOKYKtboD(V$3nj}&a^iEH(bs-D1WKZ5_CYv!1$v9oCotqp> zQnJ+c&0-26H5KkW-rJvo%aodTrc_X?-v;Q-blC2_|Ypn;UT zn*bryjOt11PeZNM&Sk@ggJpSuMRDm~Rp2LxZYr3PgpR2up-Tx7c#es5%)7wprl)3l zBAY!qd~3WnBi+jtE0ft~=EUbIfp?XR!)Q=2Vlg*Jj$58+Pr7?mT>`rqP)4G^wu?3j zSczg!y_y}4g~`ho8B(DiS$JfyC(RHz){Uf7F>=K3DFQe!6sB`l5Z+$!a?uu`W}}cQ zvGEwbT=2GOKszdp*Gz?Tie5LfT`DhP`N63ruf(r#=5}N!esaZD?neY|aBr_)0h8vY zH+16oMWMqJ*)yQ$g&LS7=Q7Qdr#~+E{E60pG!m0Hxf(=YQU}YFRVq&*vVEL_w!ycY zak8y(D1io;QH;a@3iDpkD`b?Q^l;(?Z+wAfQ$-9iG8}HO>2UuE-c~-@Hq@{1nhDEyV9X`?9 z4>bGRM6X>kGD{r1rgDzu&kP$Td4pX_rEoz_+E=}1I)#L0v=2|3{-+W|rf6;mY8PR+FnY$-tZT)J3|<_*gY)T16VP z&PKeBpd(39ieWmUl?Qf~65DA2sipxc7Sboe{>ff%%~|TI>zJPQ?Dw`vozoMZ*6)1- zsHCTOo9T7l6z{;siA{ou*4xmPQ@qWSk zugjf)|H5>y$PUJl{LIzdGR}?VY00n$-L=C5MRT8kkBN>fV?xM=2A*83A8cPt{%PS~ zr+dAin2A@6WX|*iPHe1IR=hGmZ&(9i_Y5R^@oxEL5C$E06FFCcoJ5w^RP$k>^4jjX zWm?u$aJ1V}=M~dG`7QAW?F9V*6s>WDNdfs3yA)0k-+gnc*^}-+ONb{RX=RW=_g|qZLf3$Zy~f~7kbN9DWkND znzNgChOINbbvs!JYoGR(m5xMjUxpNoQo|8j*SJ+^&3r_4z>RsDU^UJTR}OlwlqC{E zV+9kF0eN6fj7iXwW_FrkYbQ0vNg*Uzzwmn!z@JECgEfz zM^09zTSG{@beDpll=PRh^6j~?_pKDa>o)dI%S|ZSGCo1PVilGtvRoM+wuv`svD#7x zOPHthwGn?v^-e5AT~v&M1oc5}ntHU3EH}i!4M1P)d-&`o-j=DSdcsMwygB)&k@?I> zju4S7{rqZ1kbS-m1)c`GTt_hN5gT#>Dws5lQ+JUnYL%@l`jTI?ND z%LsPZ#oLbNhECblTdQWb{x5Ip_zx+(` zFUTh9c{l<>rTy(RVE@1;ve^$xwc zuQx4O7xi!A7rkv$siS(s+ffMaZh;3J?&R{ zT2gu1yYjSW$xeJk4Jgy(B8h^P;Cu=}1PLmr#gN#=*+dK;@~w z^3+#(3Yyxf6B>a$#ImS8nDSxkA>OX3|LqC)J;ZCz+;UsAsLF?U{XW*79oY&j^D%1L%q#f z2;*+UD>i2t(iaZ(2HDL&=}@n;kuRki?@uWCw}--LCxj0l>b;>(-f@_>0$iT=ZLcPo z`S7pNP!@m3>z(`5gB)*@*8~)ayI*i3BE-uB(+elKOz7x7R(46nW zCA!0>)~_T^8@jXXwb%3<(`y%=wuF&skCwj-~cl?jMbM$+|kKFfpC%Nx$oa7xx`p^+4duJs_jB#x^ z=@f6e#y0PF0RdTE-0#(N`WIl~G=Ox*Nr2tG&xKs^?eJBYyV_ z-6+#%xbH`v;YPXHPrM`aeds6NE2-mphb}nNOQ$vLwdZ(WO0&S)&hx&l567S9y_`y| zy`C$ytcIbt&-d2V4E*UK;WIl3hR<$o$WPBqm|7w(1M)ct{Lf!OQP$iZc3$MoM}F3g z1F3_xnD@U(3@|+YBCsh=FShH;Jr{Ysu*R1!^7cy2c<1h9&@Wk^#t@V0vM2OHdd8M! zWKYxs7E||-K&CmdH$3Hg_nAthc>La)H^kmI=Odf_T^qC%|p2L6%3w)-(LX{ zNv(M-Jn%-ZJGJKVp>;#vzBQu~u1O9x-r(&wIv>f4|KRPvf#e_F;vJM$CGXwtYF+V! z_i%FPwL86SEO(sz)|1`@Yb;LK{ArJ!pQ?i2e%`wz6@LFQZ>!Xgd&953)`;RE}A65HeiW-Ng@f)KDKu_;{E$V3%SpILJ5CVli4fTKEeIsB*Lud5n???jS z@UlhuuAy>}KSb>gopZWN9{O6C|J2BoW6z3Gl0S>`{OYIq&sd%~CA|CR`Tn6@Hp+i% zMDoz7v-A6mOu1q6{O3ocgj6sNQl z=ZAJ%k)IoXyIX@9BKf~8<=5ZJyOc4X&hHVY{2|%c9&kvE)5%o97jJ0$&*m2le-3y1 zysMsX`Jut@-bv z3jDtw$IyA-&Hp0q3K05#Foave|2{sy5#tCM$2LPx9iLxQyB^Mj;7rTVw@=MuHu(RI zI71-Lo>otcO8+^|lrPJ7WO_cH0R8T&{N+uLqPO-t3K_1rI$xZ0)=i>fj;Bii>ev2{ zlMdV`lMceGug=ei3Y+LL^v2cs+T>;^^*V5h;<%FSwP@GVrKHwNNmo+B8Q11#LP)=G zZT_=09Y#o@qIMqI^dgL5rrq50`+Uy;vUDWSAsZeVDSlBR0v(A?pi_w<|Hk~YB0Q^a z&QH=}=)XCCip|?~cjUVO?&Cv`-IcF1J?d|F=jTYD>bfUCQ@=ailP~J`zD z{m%pWD^iu$kk<)AH#|(};1OvPhl+pBZ$3Khn7`z=9i8^hlljKv{4=#Rj=OhX(GFI& zm-@dU(+7KzrAN<=WD;x|+Dv1X`j_L=h=84a({^+FvTue9|2O|s=8|7UqaFNvzR>+h z^d?E2joK1N@nN9tGQtmrU;lgl}xu*-I#qf9QRayR_3IOqq=r| zDqqOFb5Znm=u?otGke2Fp2~0A^e@}dVb?hOdN}#%{KmZZLu#LZ-y5=G?R=+qU>z4~;1JF($8 z&*Xb@pl^dr4}%7wL@|;5NpJYIXYxzy(crOvq{A(r$+!0?TXbRw^%8%48AaNGrR8sX zHh*{SsH0Lr>##6@zawqE_ACpUwsv?f--AGLz;pQ^MFYn@m;cikD;=P+3@YRp?GJ% z7IyzTzbJV__~n1+_vGigf9H>DybN;&1}hDHw|DZnvbv+~H%=pxK40)O6B}j(c316H zkiqQ0$>YlFo?5D;VA0gtt)vfFYEYG`x-qJdcb29(5oH#>%p#d>oad6+)xF^h@34k0 z>kXUV<(JOQcsIYfe!uo^erk6NvlG;6iQs$CC2CxwhQnXKo6if&Yz7b4jxo9`#{}pY zd2Uo5Eo((Cv~7w0)T7Dli&6eZt)eHC5N6)X_o~VM_wt+Qcc1t2efs_Wd-)0az3{z! z|4bS(EbMpqs{R0;C#{+#*&mE|?@QG5e!PYszL#IvNg;KFg~dF`@Emu zuS=C$H)Xnts(gtmmEno^^INI)?ho=)Rgtx~mgZ^Uis;Ow@-6uwpRatsZ<>97XK3xl6;um`~SLkBLoe{~0_IU|MJQhVLc)&6+NHB}tOgIk!sr`;CXZ*DxOTM`ib8%dQ|Pabtns%>elOg<#y?8j?cN%{Yb+SY{*d@ct(J0o>8S8M(hKS;yR%IY`;v}| zYe?4bryq6=hI4BDPo=2!gj#?0=f3yb%4mo_J)#Eyc>N)}`$aV5J~w2Yu!+f;jvw%9HX_oQIUq>1Duk(u?zw9kqpn$&7jfQLL{2=o|1oiN? zI)9~rFt6TUmfO(0gxA*l^FDQUuR0db1IWv6uTWS$eCLwNm_dRjbx+9`URs^33qvjW z;V3!WKI0!Z@xtF}Lmx9B7HM`Nu{6*Ujymi#aQcsMzF7W#6uzDDmu~g@HLgA|4atbR z%9)QEn-39bTpQWH0U_tadtsKNbWJS3t!eQ0>?m@6I>5t>GfNzcQ#m5t7@rx_=ub(3 z@$>lYCL0@V%0^ePo-cDk4rSm-M>hHMPi1AMs$~ z!H2h3!z5LzDgE{tmlE#YCyjTm|Fu}ey zO{bH^d-=jkEkpfTt3QKM>stLT>Ug=;-@4uMzE_=>7v){p=AS~QN80?!>{+bN`n}^^ z|8-iK5$K!+UEV0`&(fa8SF`>k{hpNdcj;P<(?ucT2hSyK0O$N78tdCx|K9R>y@DV@ zb?K7f1%{Un`xXy=}_NfW} z)sVfQcQ>j`gp0=d-e*6e1qvC}YO5?^TIh2NSoJ>|Eno z{%f=oKrVF|yli6Yv|X_ih!V7{$XW0{(XPI6{+x0Zm9Z*mpAc=*o79tnE+wrP;AAjU==}8$76yLcL|*&OV8T+!^g+@V`f8P=pA)z0O|syHaQLP zRoChm%?mkya?=z4N-`m)hliie`P0S@Rp$4}h>nLR=ln(fg-CI;Q|+G`HLu~zIlpJy zPn?g4i#Uigt>z6R*5Lj(5QV#X+{Vo;oaFg4x4)n_Xf}`mj{79?sqfDZgN~3)sB@JR zoB8%)Q|So7UU{E+7r8|XxIXVs`ohtFbZ4|9~e?aSr zX@Wmdm@JJM?!1pIjWO|x~4(;H1XX!>Ts#NB* z9{Mg-#0w<+mhbPLx~Mn2+xNfn<@2PA>vPp2!^`HA7a5s^KO00~GN1C-;=|n(_OU;WoC#B|<8o@>_ zIqrD|&pAcm0=K!+IC@hWxG3v(E!)aNKBEF>Tv#dKdSYRX=?S442{z`~5^*juJfYZ= zj;10ZNzdVp9sbnRg}wNC`i0c5dc(F({||+$?{n)7$l$Jx;~k(K0m$%;PXDvr@kD?j z!*nFV+i?Fbf48QapA>wY^mSvGe?ATX`*-`-;ln@(kShp12aMjhK-@=?iDvKpc*Al* zkH0-6`NSUI7q|E&uljwx$Gy}8$4mboi_n={@YpLzT@(YJla)8538Tc>91 zWN1jN8W?;_T2?b00Ru3UVWDmq#R)j96L1g&Ja4EF7WL@ih;=K2Nr*=TzIG_i5B!@m zKZr$<6$O9h@Y{t2fA7o-oP@UJ)Lca3i{FN(7yS#8$#vmF6a3i|2aD$MukhbC{1+m# zkpn3bThK)us+oO$XE*C^y=*}i;wjFwf1mm^bbOzmOJ=T(8@^zoKZ&u+bDf%+w{2j)pB)XQW0KDvOgLwfKfUcAX8Pb<5Fl&J z{xdvylHb2x;>DBvZ;Dk;nC#DEHkM5Gb$82Ell|8-6)t&szds}M&NHA2STOW#zkfxN z2S+b{AG6Ynr~2QQtT#B#e^7`}H{h?3E_&F2e~I4vr~BPBG_+{Ce@?0b?eN8!e)sTO z+n_&Nd*h1+{eE_x#^`M7fcBOS8}tj6Y(qaA^y^aU^+g-|p+8t0E2U$yHmE=(foQB? zzUL->QJD_k#Gjzw^EUAp>-WV?K-E)whc=z%Ut80I9Kqp(njkBQQ{{j(f9irDOP*pq%+G~k!N%b&9 z78w^#(Jq%|AWe?RzB)8#TfaG}jxSo|?=kKyW1o3BCA8~wTRY~VOBeZ@3zW|<@|V{? zkR|Qeo^bE&{KcTqFShf~Pd(QY?zq@LnAaN@`#bL#FD~OoLECb|N7;FLaW=uB`a5Ax z-BzP$5CJ-w+UhJrdv5RlGnx8(Pq^!j{!Pgb!ZADf8zV<9+{ypaoWHXMWe~G){fPqc zrn1jP6S_I>w)pmrE!|7Re7cL{$_(*r%M$)|C#DEjnJGK_1N!~!&VC=29=@~xrJeqh zO7Gi1rNn{I5tE!|qLidFbRnF&i@z%MJ$!O@@xPROH*DM0e~GaSy}zr!q&D|%Pq0<6 z7xaJ6AP7E#ERe{)JG5dCf1jG<`$PBc<^M9}vdjs-5^NdlAM69@GGsY+Z@;r4`F?o* z{`7icc>DhT&a>WSfo_$3$97-!%b+JB2rQ5UV6%mx^-OvtS@sCPs0p9mjzb83y z=#{TRuNv{*$qBh|;A#GvKR#I))U_ZwHn;_A)A{2WN>3BC8-;T_E%leyuj2%v5aar# zey1?vv88@jb!8tc^}mwF0chwe-|#n8tt=%ePgZMX9tmzmt>%z&{eM&I{aIe9b;f~k z9%|k0K!1N2(Ip4^-Td5fpuZ76?;Ysx$Ilm6SmF^Y{EafwOmd7N99rQoR2KKGW2F_? zai#ww&CBB}{k`j$ht}*n;g$#aZvu>=O%C=?s+q1trlpbar$inJnwFLb%915Dx(uho zuqgsm`0Am4o*Ah-%s-Q9KJPGpOMd?6FyHGus+W3v^p=Uilwc+?WNHuQ>=oSp{jl}h z{tW}xj!)zfc8;(K;5DE|)N?Dx?m2xJZ!`aX`1ZH`@61&R&G1Zjyd-{Rh8tw*(Yrk} z^Ma)oJUg7)>0l*)$NyR8H;+Z^W6#6k-j%n3@A_8_zrFNbzk?-FcZC0qN>s0{PVA{Rwko8DCY}d7t}|T@~q7 zt=^a1wy>i}<>C^ad5dL3Fw4~sj`yd}h}1WvU)QZv>7+Z|ASeO6ceJklGg8XKCy)21 zb{zSZ%YpcyJSE(7r5Zs=C!FBVX}b3;JcT21>?ma~+KludN5dlwvwT6#-E$ zzC|3dP4BSJ>alFkDXdcfFM*8}zb`Bo;hiV2nv3D{C-^HfzyC`#*Iz!-Uz}MJJ9Yl{ zM1SkzwY~1rSqRT!YT#I=S^~dI-+wu>9prxGPilJPHQu6rZu=vDYV*)PlpyaNIMR21 zdM@+Bv5l6aNWiEPE34@m7EtNUu4JBOW$_$+3FK9Z_$~bQK?UVds^It7XSjz zKUMydU_rBa1q1eE>A8AMYSXFy5%T#&d*c)2KHNOm*cD*O|+9JT15Mv>h~^r z!K!;A8P%6{?=rK)fI+PU8q7WaNMz5G;n*yJ*=MYX?A=;88bVb{3oAc~1rm>W3?;wr z4gYegKfd9fp=3#po)?F;r}=#iH?B^WFl+I;@o9elZr1~uw7lQ0=T{LkG76WCOLys> z1ZQmqF{G@uEKI4(0w&8dx{@Uz%N>7EE%1)(!;4Pyr#3#hTJIW}4EQ#yG zF=zN+Z2bO{dbbHZ{0z9UHN$Mae1<=euhhz@hKCr={D~iw?*YB6dw@4BV6Y4zBdO9$ znA+2&*Bqqm3x-x&VR-iC$*M0X-1q6r)rMC0Reb^VZq%21t;~(?3(LUWm6GTW@A-+J zEx&2#cUfGn7TR_dC!7_KiSDdX1ut3!(QKO~lkV7TUkpUkMn!ALq4CEq4v^_l**Oa3Bc zj@BehYbnP`s4==HA(dU*>uO>R4N}v;tnsSlc|H-awJ0w%=xGiiRfGh3o>sU>ktv^h zmcMXM*NR(lQ7iS1%M3ShAysL4g{n&Ryr|WrI+)%_Aabm!R6&aHk+b}1BAGR3`#;g| zPtW$h>&a?fx~xwA(QEaW@!%-G(K+Z6+U8|rAOYq}{R)40u0LMoEoMI5B}(-B*w6deLSn$1(&(R?NzJIwgHh9QrSQDhVTB&|K&bc_EK4DK2!os)xshfUuQY2pTSa7R2Q&l7n;-n#KURX+vFD?V zU(`GF!1?~8N#MBlm;PI|&t$WAwS*U3K$a_e!;LTWC#J6I4fndxpOU!;I%JIaxC{Nx zjwdii)QAdUm7*ih+0gdOKHVE$f1!Vbyd1P$fz^|9opK?Eps^kD?1`zEeS-r?fnWNS zzp&|7y*ky!xSr_^FaMS4l2850pOBsyB*W~*z8@AY@}EbmYP#6pSUunRVkUlK_^pfm z1u3>yue;cvl)3V0H|p?(i~Zirseg&yvTHFq{r))F6_IqeTkHQhNy*}+{$`mUjHglN zFZ|-A{y1N=rVY=5X!4$oChz!5{VS)8oIKb+(IbQ=OQg!xXdBnM&-^X@wf|+dGni>j zMQmu*)BrA~3P!>>-G0aVIRIq5p}GEcqAF2_AYHmG5!poXtXk|gJ46bi&5pE&y6a^q zqME})F8BL2hd;gCpHbGVr8hKdvY=4;6`Ir&2B61m_OdNrha@At&vs31G6^qHJ((s+ zfC5Bh&~Z+~f+xi#w@Dpt12t(0vSiTci!hUs5XweUgjJ`$r%FHhrwAE>-u%n`P1>#m zhCsK17r*(9zan$%x@cOb{np=O53IU|=!_^S6R*R1Rw2D8J;m`mGd3fF>yAloVs>U; z?yAx|mpYqym-UXS!X@6(d-%|A{YlNMZWSOR0k-}M{|n7$Sjv+y1$wyR3V)Nn^PW{6 zla)2Khr`Ou@|6b9PmYg7+;#;vq~kAFPS^lNn}6qjsp-PYlmLAv;jrIfXnWOi*lHSD z`#XP|WagUV$fECWUFjbvlKc8qz88LX$e-@L8|@PcAt20ndSRH<_m1$zCwINxiN;vqjs6E zjWUOIH~6Qk!E9*DfUhn`+NWM!(%z*_x@M_I zZUe8?=E;Y@_uDhKy&XYz{EdF^Vt{Y#%nS)ydISKlpu_t0KG&xBG*?Z)Wv5(HJiMgPWjx|KJ~#Iq?__&zi>_{^)O_ipqcVzsB#- z3xC9%oLLirD9qmM3TmJD55%6X&Z$@^hE(CiTm3^bKa6)Re{n0SM*Joegsm(AGc^fk$3)o>;Oykwq z`dr6?Ew)t==sIdIj)@a6{cyL8b+goJ&lO@mb}7UjV$LpYlp((~n4b_mFJ|MUPGuq# zcCkKA=~W8!y_ktpikgAAgW*xP`E!~d6Pm=PiQ8`T7d8JJn}3Bbbl%RU%$=){XoLZ~ z-44{(Mw?UNCAa(gbRYk=ayyZv>3l;SPU}2$CrhXP99rS(>EI zoB^~9XMcK!f0nY&yVF0c`=k$4sj;1iP*&w!cPASSr{Npaa+>aG-+557n7 z_i*4I|9j#zm)+y%hEwml$60OK?)AUI*P(CS>tCE~e$3db^msryW1a6$iK{UYOM&=i z!Df&>1`XLi{GJ3!$5Ows&hKe@>>Yu{IB2+foj4UXZC{HekP)&73>!Qrzt@k6mGE>bD_;dURS)Srfj;o5ev%|{A&p+TFka;quaCbi7 z_jWum?9L)aNn`OcOg)Ip4TYvY=x>^dvxNsd=ugY6iK{s4L5CmLKInIMf({70Cs2n} zGoeGpo9guk{pmS|u>O~gAMz)$J-OFI{$TzGH@R*@po}rG)sn*2`O}B|jWd<19#eth z>WBS-iH}Cu=JGoaR}0Ckwr_qp0_uM~?9ZGd=Ri@8wf(LS35fvBB3h6N+3e`~+Wz65 zvqf&=UwGBSqk3NX^6!VO=fNZN{M8#b*z>PELeDD&);yw~hqpcg1h0;SjPRXD{M|nH z@~BBLT4?eHj=)YpNR|F}zUxvc>7Y|5mp_ekRB6$_N(JHhkNRKVRankSzZ?n^eLvYJo~y=W--lNU z(1}1C47QtHlG}^k`-5aABA+dIKbeU?5_ojX%J-olw^Z`O|Fx2RIAf=NkZf%IXwwIf zB3i2W!^UKf*1Pc=lNX$_t<9z!;rOsyf%HM_Xc6Nv5%ElZ>sUSXUpu#49`Le3YGbU9 z)dygd%pvw+o05kSqo;1NsMNeEd3=W@?_s+zbcnHsBi9@!i`jkSkP;YyV0v#(c5pX- zU`A|C_HdWHXBsyrRb>z-0P_}eVL~OP3gTXp-SL50x;Z(5O@u#84rJe_eF$CahL4h! zX55FU$v!#KomXQvd`wjlblqq%2G@(-XgUvEiv#3lI|y=7+LXUWFI zbjpc=TJX|m$>DK%!1YtJ6*|Q1Tnx+bt;x=*XIsR^-GL;Q6;l=%|VV`?D4V0lZDNiow=o&txT zzAf1&aXmCYIS>Xly}m8!7rq9g5WI%A?w_|MdlkO^5uXIgVg2@Gr@|MZM8^XW$^3bH zvS;B|c@n7JcWzJK6rZ^^fT-UW$&HP7Z{ioi`P;z4h-DfD0b`iLj0G%%hBX%9zG02AEY1Ev)yslxF_=FaK7^U%Yk3EhZT(3iEXF;2!qRz2J4dgeltda9F_A9& zGI>ej*Y60Vl|~NQksJo|+JqelJHWKZb|&jGOw9mpiMUz7P1kJ;kSo6nIy+)#@^59G z7{D+(1AmMRH%m6XvopEDLC8%>+rG7Yw)*R2ZAGV+X^L30u|x#Kz5!&{!nmD__P_3% zHHm@{JBvMVp~Pm#i8b;r zp1qE4g>4xnj)PI; zM>d|0x(&I1TvaDClQ{Q9!Csgt9P$IQ>6aWA7}&mC6-+7L+e&fpH8%vX8M>itGdLGR zYR9nvb?!N^t_nirzBZ@+Xm#_;eoXG)^OYdHTMS=dP6$~c6q!6SNEdiCC7`QPz`Xq< z6v}@i;+3|mIX@*28PL3OZKx2D|B>wc=H|7rVfZYP{c8Qwx5W-Cie?w!E$?4mxC-|% zgraA6e6{A07=kym6F1`M7=SWuqnYD3M9XG zqxt-&WdFo%H**|BJutm@C67w1TEPcaQodnVvg&9A-4-5G1kWRHr9gs)vn~>r_a8Qdb_9^953XrBA^CV#v zvSx$eQY6m9oQOS&2s=dl~;0DLt; z2bkXgtH|Nn|Gkb0elRr-aRVna1yeac2uGG>Y}7Bw4h>w3B8LNx3BjHVE22mX7#rHF zK%d~}j3l;4xCr2(kVU|qK<3h4>>_}=@tQ=rU6)t{ctQC9K7zzst|U`KU%6)Cb%rJa z2~vsPm8X~3r*-b^3sjst6Vb>eE%k$fh|6g9 z6x9K&b@pP#O=+`Vv6pSCMmA@G`nG&=S@4Bz+3uXiDYgPnZl4>bkpyS2rwpwH$ccp4 zb`-oPY7*_+KH#tbO_tXr4nwN0M&ykIUj`06@lLSHI7+z@^q&9U87iGqiQ?Aky{B7< zqu1Q}^O>4G3cXm9nCPH!q|!8TSCt?2<>r<(ASzTOCqd6|-B!@5=ZS@ba&~so)cd!D za%I=&wg&y#RRNP%Sdq|Yb-0?aPth_x9P zQRS)o0|$bT2|vQ~X;^4;5Y25CMbxk(gnk3lVj$X6uFXS$y(QYXw?rH9(R>GjemKs9 zxi_H5LYYC4g))O8zr4}(jjE&KzrJQiz)X#*I`9Y!*A6)OMa+6Js=A%{Omi@24y;6i z2*p+wCRuf||K1f0qM!oO>g_pmP%46n#CXAx3^%Hck~%!5j)>n)%Lv80n`5e`-|IiN zRt0>wTVuJX%Cm*ktDy@Hl^M>$U?q)z8+>7Q#8i1HW!-Fo%>cZncY||ak^DAW*BH3$lYr#p!$kK2TJVm^`*l5IL6Jor6 za0{I;)DouOA+Gu)CSKhFhB-a1IuEc9g!@^z5aBOlg&z0H7DGnJ)!B+Z%${N#5qxlY_*`xNTi+Dc%OV4$P!{ zb!cL-fNjOack~_MnZLrU;A?aZMTFH zTlP^2B?kOk5~@SvO^a+%B9&NL+o+&S5-jIbu+#T1Y{d(tl!KE2VJgt53dV{`#lTS9 z=AygF3Rxu|NXWe~b;`e-IOJil(o(F5oJ5@X`S@jq6sn`CHO(kgQ+dr4sj=y=0)Mq- zvZQXosx$%HGY=Q3s_3^tEDD(YpLxWW^Yorfz@RW^E0)i6G; zmmjhRYB<^XcteO5;7W)V;!23_z?Bf)i7O#mgezOT3s<&yH?C~)9$eYty|}W)`*0=X z_v1>)AHbE6FUGa76Mj619{?r$5U%J75~e}mbx3nUnD7uFGzFy?DumjJrw@nZfW0#E z4!02M<(86J$lEC~^FfCo3KF;bAl*gwRmAp`gDCJ?h%34r&*j>*Pti3A!K0X$=!OQ^ zHWR*rZX)UmVM-5{s`4UA6ZtRzgF0XeUoBM!B5WYqPNfhcA~J|Q64`Edr|@C=w^OI1 z%*`l6Q^FJNR5i4||F%;P;c{IWh9qO|E>q{>Qe3W9AZ&hhIS}Qejb=-^>QB>C1+aKS zF3IsMM}!;<)PXKUdZYk0XI7}MdqcGYxaYLKJfH96;mNHJPiLV~+>_bKT-#plF4Tt?q#$U_aVwbF^b~{80ITed zy)JMNO)PKy07p~)-Unu+Qdc-HnYWdysvAV(-~ftDSsXKpwaa@L;g%lo2#@sz}aLT(JEaK6o`7yn)kI*c>6uRbDvL{ zWThI;>-m-HQs;B?ex>T;-MuU1O+tQuoGQX*7n@9#>V$8GR;hs}Zd%8eK!pf%fgcaz zGK*XEVIYvgkIQT7e{wrzJxdGT!QXlyO34LVlkI1=)VJ%*yH)DI43d${3x<5t8VjjM zERltFr+&m6WcIICe<+xNS}_e#@6}v)7FMg*0O7a}DhK(z{%?6%?AC3F@vd|AtM(+Zh+DfCno%0w%4f3Fp_^t zq4L>gVhR{O(maz=Nuc7olsdYM@iCB>c~11c2ceQeBsj14)o}pjMql+m8w)oUX@qSn zhRAS+U_;@PU!a!QH{#oE(N<|9crm7^LDrA#IxNm=T-W2OaD7`AZ7ll^uGm?SeHYj6 zxW4C^u4#2f`e6_ZFTQLN+MwdzisWA#=os1EA<4!6AC)=3@e zXIq;k3v1IFJv4JgljR5k&meG+m?dKKt z^V1Gek| z^;6HgD=W=;{Z$Xs&|h^5Zf?i5DK+4YzeGqkit&$)FAaV_x8q-+uJX~#>49etFHMYGd?7eC~lVzRw>+e z3~o0MR*yUTHr>*ocDhFaNS}3L(+746+r>!VP)H7|zz#aco1MxaX94zw@J}tG)3=qO zF(JGJA-i>}&~Qtb^>L(KbsP$k8dP-6Yza0HC&u0N;?oNeaM!_=A8 zAWcG|X2y}5t`Rtie|~$OQl5R>d^AiQ@4RLD9;MDX`}0~-#dcs$>2kjv<~&`9Eo6W- z6dV@^-b9lo)REnlEDA57_i`M^ZDI)NOu%+d7G_El(At*5+e=v(fg#?@bB%k z)%FdlF+K1KqkP!WY9h$vYe%d4ifIcZ@)lOGYFL2D5*+V9Q$1W&+7dkq^ME#JvpX)+ z%*De&QV%o}hhr%&USJjvR~Pg?FdY~b)1=xUMkfcSiep4U#VK&1`VrvgJaK~6A2~u* z)#u)x162iWpahM>L`Nt~IMd2jdOjviDurZ#R}=wmAEEYlUNCD$sQ#rRy(eRt7+qkb zvCWg$Hgz~gU5}K|GNb1HW7U2xSl9cP+|5O0(>E)um`bB~RGE6gPM%@x}3|bymt`PQW>$JIpyJ zsGaRF;O$9afIeZeByWT{??m-*{s9?`9;T|JNu2~4s#DW|lT>@B=RI!LyTmU#I239; zcozPKAR1M9cpAUtFMi?PkInd#)pNi1P5Ae};bSxI6xA^?k@6$0xf9FH{8KS!|NxnHGe-tbuV0S+O#P zcVI2Oo_qLm@DNY)@w_?rd|8{qOL7nI%@t-Heqy0tz`8b~?Ve`>|86yZJX7^_rkUwy zs{ZL6C_EaV_!UF?Tz^l~f=odcP!c#$Kt$2qXttlJbn%v1w%9^E+cC?Ok5XNSP0KYn zF=)^NH$M0H2V0NyWQYBIM|Ml_aDf0r~ss92d9c9v?FT=tq!U6`(ETZv}ZCcSNW-fHvyS?b8tAWDh$r1|WkSA&Al>|6g0 zwHB&rTPP4);kL}qNZ2xm{ZSoRjrAbXr4`51E~x)V+InwvymhA6*{a4_ZPq-Y%FO$J zR0mb%N{cAoT6o&;6;9>~3(d2ILHAl}eeG;@Y$eLjTAT;VP1=Kf(zBvf@AXaoBqn5uId*uJ?s&(g*lSbL*&*!AAm)AaEu|=K&ZKYJ@=uPP>@*iHlfv zf|z~qm7KH;213axl=iHI6!PuKX@ktFKdDQchfTwIYMi>qr7y!zwj~s@=>Kz`s&eiz zAD;)tjqcd?Owsvje-}L&a=!Xwmyu`ztF9J{&-H#S%ea<$!puNb7?v5zwQd%w-0JgH z|1i=qnw{H9w5k2GI^Ug^GE@Hy0b@tfeD`OFCo@wDYSd|0)kJA=3#hf}Ca{Wki z&1f~OShPT(mF19T!)SF-awdigWnsU1u><*2)O#K3TD?g(wWDIxFhE(g9mk~}5~S2OC!Y4`6wTx91V<7O#;tra+Z;U>q-~BlcP#X^YjI(w!R35(jqzCNHmndX0>#Q zl_^3*WGIw4J{$S@>;>l>V2?S%oILMFq+q;Ibt@Uk^DCepfNA1rwPJJJh3ZtN*gSNh zI=nYOcX?)DiRG-eO4rsE&sB+g8jRbq%FY)_m9QMtqAAx_IqD**^2v)-gHvRFzDQNu zV`!=`R(+5!U=N zcU`QGE`>l9g8d71iJ}oonAA8`Q6+~DWnth&6FB%~Kg=)VTCrcY&(_}|o@5}guQOZM0vv(II!OHT2`AxP%b%S8Xa{4zDk ziJR!()pT6U-_?jPRDCTW3M_jIU ziClg76{@#;t#2;B0`-1n3a?bXompnkl|Z@K=7cNN&`v;cWWFOB`Qa+YcVxn2xazPg z2&LBw_XHl6Vg6$_Vv^YWBjRKloE zqzCglHvR2t^>@c9Y}$E^`ohf{iTYeT{p30|G+az!ON3h?Y-Yd>>J`+LKS`bK@Ld5` zVl6x=p~NfZ^2usAH7m1nvYOiXby?zkP*O`m9gs$txXWRY-yNJ}G;Pz`5VnCJu`c?G zy$3Ys;}C!NI5yA)kLKH^moq9RYEkDC>Fh2pxqFjzUlgAAlf1&bvEhdflQPbMHJq& zuEwdQW@jSZ#k_Z?dfVQfRT)ucMYUgX&!>x&i}BrZx0=WC{qilpGDak?4w;?vapoOy zDUo%Y`V`&x2@!GujOkgOb73}r!3#DcXlurRt} zVZ8Ve=xKw@9z+TgXurLFwbV<^`q@WRwQBp$nmh3d4A(q+a7p_;z6{K#{4)J0 zR>7czX5b*G7&6f*x7+B zeNkWYA5|nTPq1XQ zxpJ90(#pX+JRX34-<4tvx#71kuZlq3QgueAHPRzghovQ@n-|1TJnc%i!rrpD>D@_J z#1X~{SaIMldJ-mUz~53U;g_OTvbQDO-zy0;KIkdc2XwsYf~S;m&;bxLZ&J#v zIoqi;omZ${dps)NhqO@+ssU}mm`q7Q{y-=fx)}1N$_>7U5_HQ7)dz{lUR?qGdyx5j zg*uobMXzVnkgCm9){C&Y3WT6SKBdlz)5|xRDbJ|B?u2&chiS(+AE&x@1*r%n<~gXzd!isCNt=cC^$XE~GjTIdpMtF@`_ZPRk)^M@FILu?ybNL zS~g3cQ{{!M+cM8(7{%w*RS}+XXs+H-SzgGN?7JuA-C!Q~xbu0X3fZiEv)q0KUMepL zf_?Gio-YWFeeu+uF9@1_@w9zWO8`QN%YMV?3AZh|B4r+bL5&zp?FEy<;tbOR=k=OW zcD9;GwhLPx;%1uzrpXbAq#d^*JMro*=Fk__ap2v3cY8{*#D@< zz*aIV)xi}nb|!5DgN8W-Q|mSW>d(#PD^*peRh2x>m;bE0uK=sxh3y#BqJ_4vGRs%0 zYETkxxrSZ_zMYQ+NOcnMft*LY7gFY%mFlck2hl2X<|?t{UA9VfE4U4wAW)t-{6-J1 z!tv09NJN?F2hIF5k$d$nUL8CLyfyb~!MC~3Amv)8R~{5HpeGD56y)=0T@*h#YdEnud06Ns0=#96O3~lZ!rujA1)fsKZ zjVdWKzrLo9aqmr;V_sK0%(MA*P{0>EHx;f?`#PBUnf{{wfrTmsMuqOXw%^tlfo=z7zn+jQYmRwCsq)r0?_b;U=BhV@tlavBIv7Cg1hy4A zLKG&)q1{6`iIx?SFRTQFz&H+5z%LkVDMq6EN-a-2^X;6G zC@2_k3J9Fp4=}XC_{JA?tTG?It@Z_@XsvRaM}R_@{0|D|%=3HBS?)*7L#LFKlmgRG zOw1?J_|xXfcT}Gaua$f2B*98y8afWA2r&R4!;bevZNiKj?YGY-iprulPRD5ZoOe|o z5=6z{Ro(M}cvzM22AZ=f(q(4EyJ|H?pk@Om3nriqs;m2G-0av^k~II=pvntSqmR|; zEs2?4H@OvN=?09_NK;%I>EqmCHZH2}aunKPv^`*P_kslvtOmo$sR3=HcM086V>?Ieh&dOv+2!nSI_@C!^i*@2g{?BLUAkvnDTH zZnnO!208nhE+44Dg<$MH_tK5=*5#RFnqj=U{sYx3e$Jz=%JY zc^lQ7@Sm{I4%(zT^nCMem|6z_j%NM?=Whpy1S7hSbX6ZfEWqB{c zMw9*MZ8LY1N)5UFMmJmFJp#KfNG{PFu1Rc1gt8FE^iaV7ES~piG`o1R3)|cypr7PU z|B7et#ixbdBj%S)>Zo&;&O#0szC|^PYaU-rL4gvf+sWRDZ4`1E@YOM)AZ?%$zzGq4 zU_82q_P05vKyB(s>!aTDQ3SSm&zXldtFxkS7U1C=uco3KrqS{KOAbO@mq2v19!i5W zLO2ud4@ksf-E{nibT17uGd_X^VWjEsF{}_Ho60^>o?~k?es#1$uu!TrhkdHj<)}Fh zqaGw-9zPw}Ca(BY?H?f&#)(7)al`hLHJ`#33eVT4s(&|u68Y7HhQm7xQ-ZSq!u=Kihl>b?y{c&i%by^{i`lx<%aXxy2@KUWKjrrSy* zHlX3PU6mCbxQtzp5H>;=90bW(wiFed(yx@#B%u(SKw;%|P>JikX)gXeMS#a`LrBD0 zbI&$xU|gRSix2i zm+6u*>Ss$n&$1$H3ym3x2ygIdx0u4FlrzO zs9~?SFn^;7X?f2AG!&cZx5bjBgp-g3AWWZb7JdPhYMse8X4+HjTKWgor8M~ZuG}!% zaiXr$w({Wh|FQDBepC6TA1do2VhDWlOVzEX1s~W}Ko7YGdq>DyQvg#dLOd8bDIp%9 zj}(G2lI1&8y3#h9#wg=E`3gVzRoLvfwuH!Z?hbXXyJWg~WrrH!K04jh?1XosX}URM zr|Ry#QP3|Wfgbrp#^uw^tvgjMK6+-S8d?xcwCgns+Hg2FPAE&pAZmHrU31o~Skj#N zl^S){hi=a7{-um&FkZp;VDMxiB#a_jh;)N;AmLVqk-4*lAq{{RWBp^|=yJsN+0ov{ zl<~h-L)iXBN2V)y^@pk6X7Sf5Ke7(;iaF~m)x~W7T2-iZP&q`tl1hWs09&KL)<1ruIUs}n~VfQy=6{-t`Eo^`NWuJ~3}m27}i-J%-2moi(vReb}()vGXCQOueGT1W@o znJ2UxVj731^NMH<25t+p9c1Qzr;>%N%Sz}QQs&>^scx{qe({~sok8v>sQ-@j?!A^z z(6$)wwlS`O#=Ax<*%yYg6SjUD@?jzhC01299?b|1Fip|&CCaf?Vv(F3)DRq~kw(uV z2pdR*fIK`9)VPN5$h%OOQC%hqpke$%aXKi)1&OS7m>3CKy|x|%5-i+f#`kns6jzAZ z-Wz7&{Oa-qTE)lom##L?&aW1cNS3^@*#!8+?FZ3)3B@oKp|ltW=rg@xwnId!DhNV|_h-s%+coUGC5 z3W^%&8La&ThiCy;K?K@gy1)*P3*O?gU4_Ru>4||Xh{r0*fTl2ncmmR|QP%QS(S*I` z{z9wWlRBeD5$Y-Qq9o)x0s!(n{;x50yrtkM6Y`eQ>clemy)m9a;%WGGK^qRm0q(de zNOBHz&`cqWj$u}du9th8;nHHS1BMaywz}q+Z>p2#y|J-wr@C1%Se*1O;gx+Ly}}LB z2W}K-@dy;abL_cUf+!qO%uEvgsA08DLZNVX!O zKmwNDO>r}zKBmn+D|Nfh(q@5x<6Xz7Zl41VhgZvhzY@nW-5hX6VC37Ec8o(F-kIK= zYuaN014xDvWkQ@>v`PHrB$_Xu*X=DQKV|>w8DZ ztkQ07YsBc|J5<0^KG0+KN_10v&w6X6K^p-r<@_u#oaAnJ6~Az^jRzy0RTRJ#;9Zxa9HN{e8G8fd<46rR$N(YA{{~@Ck(h?PGi&-Ux6q`hU1qDQHi&s zyN3$B>nVjEj&&R6^aR<{V%hK?FhKmCWnpLhOEXKdTZobkHU;iA*uZ=BZf)3 zS)Gjt9C_gej2QOIATFr|SF^I`pCF-Km(;J1?MN6x4DgK#onWpHw_YDipRJ(!f2jb` z#T<#oJ!(K2J=TB|xpKHqcMn!UcbZ1=3z9hg&?@hZr{|3>S?p%V+7~mh+kjEwIWu-B z(E^+jvI>UdmmMA`2f_odNXupFvTu=x8|F1F_I!;tP6=dL7~wf`CRd{sv=I z#{m%%C*z$)Ycj#YKcz%9o=J&nU(lHqAXq8FBmL0-LVh=f(ao@{5jo)@M8{A73r#BU za~1fl6%bQ$kAboHOb{L;1d#sM#gQw@8oye)w*=0tS?mEdk7HX7cgu9F3Tnn)ljF5R-}>G*h|*3MbC&l;56VP zq9s#_Pys_gJoyF;?e#bl%Kqv_>e_%>LC2|E;}58)pq9-VFSXUPdJuIUfMy?92?Jwt zU?YLTs$~83!1*=V{~OM6h%*)rN=SFlWjCV|Qkv+R`vi(0ehI zabimg-U-HFq6v;2poHIvwPkm{6!QKVLr;qE0>k_4&z8eUTNKm0RHXNu&CybZl3>jJ z+?coGl-ppg{QH4? z+!8BISc`!b6^egQ$GV$R=8r$9i`>mA^TrQ|emKhP`axCg10*c81jJhli!hBCbm7Cs zw?35ykqd~q>H8ymgV%R3r~Rnzb=Sttrb1moixX360WKcM0fyjBEHXWRQroJ2DY3#5 z0$UP%jaGp0nPLxM9I#6rSh^X?6YF+y>OmeD*>ZE`E+}=Mrp(j3;QhpP_b%1B;Q8$` zkWud)v+vI$rw;uYR+H^!^v|k}N)Cc8u!-542I1zzoKcs$|7TU7ezyQqNn&mFXut|6 zKxlTR$R6^%4JQ1H>fZi-QC{E$#q`4`WR-+`Gwt{om4wrNQ4Ps;$p20z0pU9gq@ZSw zV(ZPneo=J=0HOetBn)H7N53GjdyuL6RUPiEH)sEfVTR87tE%<4r|eb`3j)|#_tD;$ zRwAcJ{hoRKS50Tnaq8UVJ& z(RD=@NO-joI~A8VhXl=_5&=c}V&ThR_GI~DhD#lonZz)M6bhUuF6Nw;otWclWuAAm zCk^-vosVHa%qB-G=UwxYqwCYG6K@zUkDv4pLICOFmXmm3GCUMp6!XphxH{>6kuuX< z-9Wt_46!aZS#_2|VY1A2SI>uaW=dEeLoe^fu)d=GNYU}da}5wdzOY1Kmme3=J+Osh zenbzZxpiYiS4Ko9`8A?%Lmjt9Z5`XAdXW2B%Jhh7TKYeX>3QutBCV%Ie`q{#BL{{Z zlc$kHHf5IO>5g3c@8#*i&T`Wst}73m0doSdI*Pds_XvT)L;3_Xq96vtANKwohtAR{ zS_KqoF0KpWn)^nk#Pw+h?<#i-8=Zex?`ob62*`zF6o5AQh($ivM!<47o2|*wZ4l*& z5q|IhcFWftoeRzIeEo;AwLfybLq(xO6&3)#e=uK5I9?CGli4__q?{UNu)TspZYYr{ zCSrl3Ds$99l{hAWVJUf}%eFc=w;^v8nVa00=*Z3AM%4l2^%Am`_Pm& zZMfzMMzMeq&!DtfTA<7N29*a!XvS)mkIpuIX8o#hH(auScfZT18ZIMsEwg983&}3NnN^+&Pd>@Oay3iy1;6X$j-1k zJPt8ZIMoW<#+c=kyUggXgVKhykUv3N1V0ARk_+}lA*;4K49BaY0JvZy{K%ud?_;8t z0t@Z#w0eI2CMt<~N8$@o9@-NWwaHp&%mu-XWJ#W$1gl! zG!6rC8X-%fd6+YU_Wr;*I6lqu9Lr}2wqAA*bs?L5rC0G z9L~@{KraR#y=)`Hfml#X6eAHJ!MuwUiTMecgWm8x%HXUxyT~FRO)U7PyOlsBfsx-L zfnZw^1soqO+94nWw2PqeT3oCo;)~EF0Ar)+vfBR@U^D7pCGg)@rAvDVB*2ID0VEbf z_b?SO5Dy7pNfgP0JMt3%3;rb??A{uCPaGyPx@OHE{gfw&_C`l8uQ>7A^eon?MvN*v z`WbEm70FlH5<4@$6#mM%{=oIt64#$&=-5)2%H-o?E|PQ!a5oNQgR2gNl?w_$VF1AF z?V`YtXC2x|BHmk&HnNdgp5FwR^p z0c|SSgEleg#UL0D5JJxbeyDAN{1*h|9}wcTRSJ2U3-uDL%`q7)8AOSb7>JaTBL>VL z4N1*oqY#YXDMfG0)J&#(KF1CrRSb&g@0T@l+`&@=D_vA{FV<(5GVunWL;z8MH?bVv za8DaxpaO>}B$g9@u#yFNz_RSv0eP%Ngd>B^E7&E*9SD%C2zkQHtcaGHK{r>G1C7x* z{!zdTW&bfQ>mG(~MZ8Z6u;m(U(J+*QgTy$%U%C~g`D$Dk3?X~IaN7e>;VYn zcY+c697GCF>~!lyKsqacVfyQVu?j?J(}E6Fe$NsB<3k!dC4!a$hVybt&zY;rx|2MC zpmI?uX$urV+On-+Xzv%#92+q^2gZ`Utr#E|4+p>LU7^KrU#iNoW>SToG~{c#nI6q|iUVZ= zlDgmz>$M|*Uu1_cCop(S_>zxIN-W3GsWS^pqd547_5ylhvtqxby)K^4GdnLV6Lwp2 zzLJ2qcKgZ>%Bc4Nwg)C4N5UitZG)o-u^d0Jt0=Hk!66e*V}Z@_E39xy&k$CWgb7na zL(vx7AR`1w??srQCm$7p!U*nT24dD$XqL_^Ykvj;+xamb!f41L%z)y+XvpPFO*%u1 z56p(eSUN#_l=<-%i%RT=OcaxYB*$mEL+0f1@G!R*mQ^yD21gK#fj*!%tjSE@SyFZk zs4GIU#mHDrvDHeJGV*koFQMXz!5s#5>bVsC2OM0aUUj{nQNBm03?vjq@C)0nkmV?0 zrYm2WfvZ$uzR*wT-g&0$lnyC!vWU5|vtMma{k%ib%=aHD=#04n?z%qGF@!J)xyb86 zs>EW!#9^7}u2cwNl=IwVHNef^^9ZmBQ`0&tKs z1c^+%9mF2cszT0{IsM3*RCigrQA~)eC9DwflmY1Tsm9r$Hew$*G)y6V(-jOaF2LPt z)36reU*m<*xVgNOKEOQr9}R)-<95C|`tQXtv$>N#!ui&8@2pS6<=W2r8hV%a(P>=j z_R)QC8ENkt_t96wOX}3=BON>))IX_7O@l#* zT^8Qb5oSs^y$^zMAM2*O@%v5Pb!|6F+|qr}Mk{y2bOh|8H(m}Q%|!-uN|;TT=yG#N z56zg6H3O=a)!<`Hu+WFVgSCeMe?w0_rt>17+D)IX2==+t=c2(4qWN5}&5OJIrpkSF zUz}v;n-hBJj+M9hII4;zQEsjenKAf%yWcdUmp*~eZ|SW&IJ=u-`{~L2u4(4}`cwyi zebGl>h2y-&9-w`cnsR{dP|23qcmFERYPIuXdX30T%Ca;Iq>uJ2MtJk-jHQoo73meYMF8dJMka~omJdEQp zt>P4h-D%IKp!5tU#$iSqL-NBz6}b1VW=#=Z99*uk53ljfn!dW0&3)fjZ$l41>Zh+j z4@dQv9$wvFUyq0R1AsTbm>UPk!v_cGi||EL%|P88;izuR>hT<=&WmebIK8V5$|^&0XmNBYooMo>UE_4k5e&0#v-)H#J$OW)A>i~QLcM~Z&nW1=Oa30 z{}K8YbY=Aj-9PdZBWVkdkww(=7(LLv!*99(S7OKm$Lh;4RJF&+P#t<4md`G8-*FlN z<4u*v>wb>?`s5RIPy1`q39`bzI6>cPAKZANEV7SJ)DN^{OAL5uv(D}~N!HoQld#V0 z2S1*yYulk1nS3psAc2az?H1Q1!jz5FNAvmFBX!4u$-eh9!s9*MPaCOwyZ8C#xsm!E48puKG*5W^^b9?Z<9Pj< z`T)LNai%_>6IC}#PidKuYyBonNXF%Zum7k|<8{Q@`UHM(&)JyK`~0TxIr1WSugAvd z(VT??qT8z@6(a(my-+vgx{U zTDe^1^Z!TpcfvRL8FR+zS|OtYR;=UUq`yn_Wo+Vam+EJE?e|x`pyLxh1Q1GNi5p_! z!p7)TVjO|b*g8e);gK}I{8eWlQMp&aY+`r*xu60Wrz@=U_|t3V!6E#2CRy$VP?K~x z8_Qyu^$0mtnf!DNLr#;R^Vdwl=yXEz3t)P%Kbn-;&lGV}M50G1^hmm$RFp2Umr{HP zRS_y0+HFL`HX0tP9ky}pMnGCu=m*8T6w3BKgj+#7>S5s~P=a1U&O86t5DMq1^kyV)z_9*C9ruYniR)QKPKiPOlkAtf}-+W-@;IpB@ zCtwBkW@JiP7?EFH-99sI;ZiBQS6Sp6ML3sVWHAKF5U~m>AJ0S(g)5Ok?vaEYJO^W& zFF!|PPZHV>L*Pz3V#E*2bGuRLJ&ZIgE`I)S@DIm+Ac!d=iAfLkUY3Y}V{X1oHw5@q zfzvN_Sz*~hbcmxh$1gL*f7j#umZbZtZ(N3T`zQgHQCD{ zwd^uCU#_pjUBwmpDBPWWg`VmoOpi`ch!5N-2dl}DMn*oMHu2IqbISFL9cK3nCmZuD zqBVzQCn=;ReD+irPK6#f#1Et~gCa+~){I7!f50HmBW z%!q?_-U|Of_jxFh*|}jRf@B!zd5!Xc|9oL0*B_(2Jemj?FwIEa*_CFh_4oFzGtl5J8)23${t zfocM-H>TMqM)Mg1AW#Fk=&?F6cObP$#4p8X>@r?b?8YsJ>J=P-l85E;I&g!_g&Y?0 zY?BR7E+bgt3=qc}RoPx`?TqQ%Wcm{iJ}Y> z05G|ye1HR0W|#m>m)IeIna{eo18AsYyebVOt|`1-;fSn3WknHG;F{7oZpyrSonO~j zM!>>Isv&^2pLN&j)SkVt2-2b5KAyKU2Q*4Ns+X?JA?%W%*=F1SMlXD+{7HtJ_>VF zOGQ)N!;9Rug@dB$w)L{}}l1MJlzu$O=@ zX7nwt2nX4W!$E;f?3HE8dp6<}Z_sPzz6&@ACBg8cUEDWaIXY8*zk=ULA%gx@mhjMIDBdJ z0#rN_5KDdzry%-mITb9fja}UZVNi}}c2Rh=fQ%D%aEBIy`7D5RWasm%RHne20==Fn zAZXwgheM7FQWu4hPOvVW#I<%bj<%ogXWVoc+zU3lm@qF!jpoefdEV;<5cl#fi{bKt7$-PT4hxs{Ga>CDcoM2H7B!_B@j)=gFQ|ncfqb|M|8av>W2AHt z16;&LA-b{6a*Pb30&TlYj#bWf^Y8;EXKA$>t)}y-97r8o4w0hF^83tpKO&LQFWLhj>Hla0o6rzv;6FMe_Wwr=C{7F;_E32P>C`@!O z`v~Q8PA}=5hh8$2!c3oArOa#BY0p$%SXN{%o~%3W19o0b;h60}v12E#be40O3L-mZ z*)6&zAH~Fmhg@cpp-sx#5fPT6FO*GyUzU9oS>U;4qJ=JUTMp=);vc95_`3k~uPYKM z&OAeCQdhK$`*L}=NZ!rmJ5naF1yV{<_v?@fo)v?tmSt~Q3EyBCcWIQz>LJq*IT+<( zdz`@pZE!5jh-GJk7_(sZK>!xx_gpo!yb1g{%Nv&*&Gx}#U*g1oSwuP4Ti{oc&9|_S5tchY@4sp*&l{ zugCnB_0um<)qp<;z6%zRNGMo9dBlEx!-Iw|;>m|72p#5Xgh!Xc7Jv;+ zqX-riv-}B*L6$$Z9s|8P|0dm?;t8N*=k0T|=q9~SV~c1Kwur}*;VgJOVgvAfgOuME z|15khQbKk|Yq<4C_&FpD9WJ|pzbHc0@{UC~usAu5zetKfZAd}jD&WN&IZc<2;zzcB zkXHaCH+25a5VnC8-ZU(d7Fej^VAfC0RLSILs%K^@ficYuSdJmT(mXj$S61RU*{<&K znVLP{vDoZ|HD!$){C8SA>v!;LW>B&nJS)?Ig$B>afUC+LrjOIQ8 z=uz8T0EQ(btz$%ug-#{G4k%&)c98r(1+h_}_hio(3i`Q%evm8@1O<<4bB__6MFt?O z&8r5X;oBvSEN4tJ4ks0khxg{?g(dqTAXh}~JtznX251SUzI%uX--nWp^VnlrO^akJ zS+VyVOmHM1T#3ytEi!o`4AHMlGAoNgh+}Pz3f8ubmbKKcfaKV+W^>R?-JvsS6NMc# z&i%9^ANUC{B#LrCK?rA550Ck{tee@eR+qsa^u$bEQ-8yEB9rg+xd;X}f~3vo)YK?b zNlndj%6zGb(y0S$;Vj_WE~{{dDL#Opv7Z=++!#YI$A}0mFT#(Ub5z6!gp#^Vg$V)NOZtb#Mj(E?aZw|yeN#gy^EQC(L4-cOp|9A{n$mrok zq`nPV%<&##M^(CmUrvT!2_lOh5v;ySn!+mmDwx7r1nuFCtkrLIR3n2(ExWv=d7D#61 z!qTp_cRuc*wnE^*J!B#XWUdcY_hi(+(3ht?LpFqLk*S27_AO^F@P5gWCkrVew^qM5J1z%=$`JgzB$L}j&86>t>uu+g1Qq`bj+DQ@ln3B@2F2P$_wQVxS-OWNi^+n6GR!25Y{dNFP`U~rP& z6Auu<)<#IFl>Po#BH5dDvQg%4kESW^Fo}&7cX~JciW@cDd%^7PtJ>%1RQ) z+qQX9h?XtFrqSp)VA*&xrWS+4K}=U+DN-C_$!R#~+_o?cQVb?SG#4lYA~Z&Ti{Vy< zk)mEsTd?X+RT`ok?9|-6s{{XLb}k60bQuJc<1WPR73X056nP0tjw?*4o!dS1TtMvr z%`%~ONuhQM)DHN8Jh&k$0s;^A%H(I#=8Wa(f(Yo9YaSX>0*lq$s-)>~s~%v*S=n_` z)<(wo9RV~)EBBNr_ad5s=ot6Qyj=))q8d)o5~}1+lGT9c&uxHR;XUl2Ahn8^w+pI@ z873MO+ul+PrU=9$uPLZ%FS~#DQsxmahpMbHkMV?wKSJ+%%#=2j`IV*KRLoU@_jL-X zMbf44mhwIH^QalSsM@~>77%+2WwwpF&dF-;(>#LrqN_qUzSjZviO_tn(@IeBqPgWo zbDI}s-p0I2noUEpGHj#0=js~RY59=WRX&WEgsH57#_E^wF?Kuzk4Mb~5tXeNcoBxh zw}jYKLY6}J)*hiS__~cf88FQa=d8|i@hK}VB#TAP+DHbsu8-IVR#(XC3h}y~z|AES(hV(O8%!yf#vw{d(AS!;KF8Pu4Rf@Y2U8 z2@1)J2?FHMej#j|;f3}>Q~@Kh<9#AzifwYU9OQgLT0h#`0&x`mc6)^SWI!3{Yyp5c z&Y_XCL%24mfrz%C@%7>}#GVhw+sbaCI-BSZ;b1OA-Y`J!5n^Cth?|lyjDD2vCiVnh zflOA>Bn0~+w!JWlCVmhqkYA9OvDYyRc^X^;&|CmMV*Q|dbV-cNGWNY>d;meOmK_gV z;Ga?%b!F#8;v%G(v%>^^5q8E1a4O`zU1Gxjx-f6RNF(EWOF_IO>3OTAU{LPP-5f)V>UR={#L98 z!b*aMu<)5kAXVS!2!cRpfmj_%Yz*T#D_K6)eaj_6>=o4xn8zvlnOdGj7*Z!vRtN_( z?S7Up(-Od>Q)k{Rt!lT=Ai57i4G?F!gnwIjWjqsuo^IBZR!M{NXr9S zg-HSsvx{Gri=jUgav9=YHPe2@aS`Hh0?|hwpy%Id^I|ys!G%k+`JK=ixJ*%vP*Kn{ z^UBxlx(Xw)rcohO$OL5;>bfIji6BL{mE3Fb18K`4QP7xx!2VX73(JTIfik%@0&7!* zYb3%o62AkXE)WJs-=Vt}Kw)411onSoG0eL|`$^d6X)nMZXv}!|`1i;ciI;;G>DtmT zR`MY6CjpB>Fc6r6J9XEASSeGsVL*U)T#YZxqh6!pjL2O3mLsgSFw_(VH|;yOYrL; z;+?lhAH=RNSfsg6|0Q&)0;Sq&-iSn->oa!LXo;_PpFQ#%R-%l@-n>d(+Yrv z(of9D1fb^s2Gl_RHm}@;h$0J9@)R1=u!Q_pxdYx?+ga~7HM9l0=>JqG2jCTV+Y!m# zFTg#m4#B+z*zDM=+gnkJBho`u#ABr&#@T%-e^ zl8qk%QGsL=6ap7~mwA4Q@6*XJ6c*F#_L!tw%rmFlr>paAF>J6g|F};p=4ynt$nUM= zK1L6DL=Kd}P6Z){WSorPuqCkHh4J(b6(xk!m%f@R%Xd%*NTIiS&i%R<)Hb&PH%mbS zNmpnH&qa-5=UlkeBnNW56`l&&?m+^^3q8(}xrf}}$#z#s1o@a9P!l5tX4 zozH32*Ka9;q3aSu$BiW^T zuF##Bo~0b2@Bo~qNtDi|fdTz&$wSpr(q?K5O4>YBkR4=??w(Z!`G9SePh-z1+#xJE z{sZJfm!faX(|wVq6D360lU)B3lWu8agu>pATy%&vh)hI`7Raipc|f=C4nhhwgBT=_ zg_6DwEha}wU&kp$B&DS!rK29usiTE5kEfy|MwOSz;>N=%N0o}kl{krWP^T1=I$11N z=gUa4%{s*(zWM>(C4Z)mF>|~*2y=Wuf8p+U%zU|6*Mnr$J*a!(a>|4H2B+S9h>LqG zva&v;PwRqZy7pNivbsXo@7hZwh|`bg5> zphxDZrHCEGd-X3pi(o$VFNF9GGF$#-!OYc|dIUkC=;8K%3B4cnh}P`f6_04yB}-d( z0t!4t>O@Qf&Sudli8FY+T6q(c&E!an8StKa7E2>N|>U^~SuVrPq4v#hY% z^t{e3HH}SaZbY2>gw7y8n;o|}wU;vU&J()5wtL8>UsFF00(xMa#4es79an0(PPJy~ zyJ=!gr5Uwc7vMCRG0SxZ$)ct%*W7)4_i|v?I9m`jN(1p|_d{fyb`>n$pXV=SLXAMQMt`_68SN z+;YKAF>kHu@U%{HatRDxStAb;SW@TNU~43g3|?F230an7%0t&cd4%cU7-M6e)*Vv_ zX+YLp?`zm$ZH>9;ktm}_V&>{@eg(L==KGHf`YWpCjE?7!-&{Ke_AE6hFV^)Y}s`~u^yruRtU@uIY|e6 zn#I3ui33ASnqx{omn?3#J)`f>wNJ@mc(Ybmfh`J(Y#wpYq@>u(2I@@&>J6L0NzqU` zCdGnmL;yVdq>?Pr0id=3SYrs;tu{1kT`q=Je%S^dlm9Hlg+ZqCvwA^7SQ(;J$qm;$ ztIN8Aa|Kxj`BWGezR&Tk@oiP_;(zPvylKz_s95*@x9&B1FAW-)3noniD5{zR>EC(@ zc_1SZigwW7wneR8D4)|kw67(6P#|h0OkU_-6m&0P-s_U;ZGQf@E-$%^hdff03Ivwy zap`11fC4U1V8j31*OwUytE$h6Z+m*BGBhsAZRNA2kJb;zB;e4MFhs6nG>H!kpJ7CnH!$h zBb_DY)91x+vhNGJJ@Q##t<#kRGYe5Il70KLrc+*k&V*h0<6qRzxpO}@hrXmcyKnmD zf|qo4;!Pin0TY9qe`djs%B;4D!^!|TVKBKcQVY@LG6s6=o5EW$b zakw5vf?n~nZW6lPyO+-)qL+vtwN&(~d2OWtyk(_M7qBj@J-}tkR>{zwwn}#>`PkYh z&^_n_Zi6X)w_Q*3*eXp1v~m?9oJRN~tK}1n5*w^&ln5uxN zfr*ln+}JAsV+SvfwF~+CoiBxs;WM~Ny?LTaVCHDalRP9i=>|1ncR7j7p2H%}$vyxj z7?ya%j9RTD0Q}t5`e9r)l_0A*lJT4rkuy2gd?{3J$*a0^`OrhtQSuAPrlfyU@_>ml1N~BrOs3>lVm23@Bt1^;s(u{$^ z8*K}M>>&MYAP)pV8pwJ3MHgmk(gp7wAFJ4((xK8k-K0wUkw%l)X79Jlm1-tHzxzN= zdMJSWcPlo{z0>W>Fxo){c4fH8zkP9jM|G^M9Ptl3Y1;IGlKIGj$K9)h1YMKIc*x}0 z^dsAHxD?pVEj|zfK?jm_b?3{bd7Z9L;5%{&bpCD=Uavb=ZGD*o1`PU(>;Yir`=G#L zlU=XNU3_`cdRaetZ)1-a4rYB@pHK%-=xM~V1}ONNxFU(X!*U1)u^1}UJHA=+wjNSE zsR!D?#EXpj#GH3@zs`G9fUnW6%f2v#0XZ144)XZ74}?VoYwwi8aC+mdH3Spd)qf7H(<>#t}=@@U^F_J*Ei_#&Tezed)T3A z?;^X)kXZNY_w?8po{aiPx5wq#P0{wH;BO%yi+2`)PGDnaqe*wEso1j+&(Ucq1a|c0 zM|!e*chp?@u}%$vlLR^EaR?54HuQM`d0?1=tA;_ot>?4gh76mWjoFwn->yAF2in(@ zAM5jmgmb6w{J+!_ZmGv@Q_ql3^cjteyn_{nOG-fF`8>HQ;YbQV3%3Q0>%)S^b%U<9 zfClc2_085;9r!wTaFH=1C#I1Sqm% z;f1v43(2k_)XIDb*p{6GB@<_IGw5dw;l(L7WTy01wSpr%&VdIrkQ5{99wv*7s1Z2K zpw%ptG*BSQ9xzWJ@{nMY1Wux_As&a#xF0JK*02ShdjP~0Cg-ww=#Y4D5fQ~^W7%0a zTnF?AryTJGkGcW>qXiy)rGgGKbD4Z~xco{6I_-`TGF#@}gI38K2F$W(oy_i-m< z5BL|b$xkacSABu)h4+{zzR=w}ZEgou?odmCB|}c!Gu$}9-`+oq&?_YNHw9nnU-3o$ z4qZ*4_t~Kj2BjFYL(f9(B%df3^KPy(g*){T?saK%(oQ`<-BJZY6z0hAoz3qcr5@Oc zZIvk9`77N$Ig)4YVd%(HfqcU7v&%ki`tw(iEZwWin;!fc8!5~FiFSyPVedzrJ<97Z z=9h2uneH4!V0;VP!PTHAB#9w(27V;amfgD{%6UJSDc|ZF33k&{SUfB;TCQdX96GZbKm$s z>I$_TalvFjSSf&l*AeK$hwaQ!Kk5TZzd(FF+x6aRN2jg#ty%q}?p_q!69v5Q`RKs? z-fG8hn*lcV%ee$@gtxIB!#UX6R)lci2vmfHyN9MRSb5%S?aTo`>46GK1llJ$MQIX&(`vD|+j7A2c2u3yI?CXU3JG8~iupmMg z`?jpoWGj)NvcsM2%JvW7l-FF^I{;yw`;F6P0P>{Y&Y|3D8DqcoFZ%e}=~cj~m^Vvy zat%IRZf4k<(L~ zzFRjsLLO$Xt!%J#qPa?XcGTaN^6jQ$U}>64xH_X%A;! z=v4M;kmIJK0UpCRE4v=V(A5%Fi6EFf?-Lx3O|p)K(Sp{8drgt}K!Ksmqu&kWLYoU#E9&0woDlcv1YQhr9 zky0ElDuF|x9-)!gT*~Gk<8lBHVv0*B6X_AU+CfA<*zN=eJ4kV@MNSP|d>na=OCQF5 zKy2pkdK-ZPwSY}P;4fl6wsKs=<6Yo2Lf$A4AtDA6O{DP0Wa3t?AXOGp_o%}yM2fr$ z+lz?Nh+rzTU!k#pY(v7y6sIF$ayj9go?m!GCQ(I66&Y8o=#z=kK4y7+ffd={9rv2Q>N~uP zQ`pGmo`9^uuu~=y6oCVVS_mA4PLk1p{~vpA9w$|CHGX&B?peEM=FSE)Rm05fW(I~G z6afK&7WXCM8jYeD(I~i~Xh@>Ypoq930$Qk`ByJc{afP^!(I_h5xW!%3xS(Q0Ln0x6IQ)eY75H?jhPqf-mZNt6SLxrkAJVe{BBszBm z6)xK0rY!Rz9z^LJmchcCfgr$BmM<~#_!xGw9pfm8C2_&CbvMP=65Y&BkL{*3Z|CD@ zVud69l&%+3ox+utv@7f&?E*&3N?0`@x@*TSGnU?R0w9O?FILsZupc66wy*$l>1lF7 zX0z1Su$*yB9mrdYiM18N&9WV)B4Mx=g3S{7MKQ-{;~!zlKn6Z2f(0D zH*;RGtcqP;XB9gWtZE>LcS0pAF=6rN;4PktG6P)P3e3X^B~CVePdI?^8w`o5rjU@_ zZdm62@s+(=gkcg%IN}%(W?k4C1k_|Tn0{GkL1?nxLiYl}GCkbmT|40#ccObcz)(g( zG42U+rT4GJbDe+`zWZc=oP@j5Zb6HMDW)O(h}|5!;R?JHNCW~34gj`ZdjTowRGrLU zNX;UkB1#H17%RKE6EzPUv%&)7p-VTDm#D3TxQd$8ap1?*FiiH$gNAb2h?Wdy20=5$ zX_*)6Nt7Rr={)vG4t(Hk@I8trGB-#xCZI>6)xC47t{e=QWw7bTtPovIMsJr3i3)wT z&555b)l4c}wL^6M zAV5}3oh)&l2UX58@O|MTAi8npH@PbhE903dGj#(Vwb(fOu#nO})BaJ=r=rNh3uxHd zWm;%XysheMg(n2rFW@HJA$~zymG}_;eaci*-Y2NCC(NUZN(Y+r%hciUjNU0zgT1FK zOjlnWnBPRJ{s42Tuex}bRhwD9>Q}so*op98iiooPh_A-N35b=e)4iuE%(QYPp?}{h zSGAJ-s$BJymeLjKfD$g=;*dV{Rx`aqy#@_>dxbfwQi)U7|5U0K={qWfvk&gx9h{w& zK9f_OtN2otN@-WFaIkk4)FME2C~FavavWr?HRaXn=e?HUYC(WnOsHAANN8a#4D?b^ zRU@ZlCXyz+qq)3VEyROQ|1KQvqn4l8MHS%!s>1xbs~U}S=;dA2G12{uuHmpBWk%O1 z34St>hlGK;s76hzoL!xgnRjj_D!ALst~S-R3YT-$W>~HIh1YtwnP00$dhHLI=WEr# z!>+2vppm_sNlp%GgQ9+snBS3AI9W2{WVj-6o6ZZ<$mse-@<{~o`2kjv5o~@Kw&WI& zYYtbcPC~k~+Qifdx^T)diAhvbof_{fGf%0#y$!fVo zs+UjerZz;Q|1c*sDBaBJfnJVx_au1cL%b6u<5Z#b5|U5%vh*^4_4WCv(jjfsZtsyBh)_v)@TGF_t^)%fU(W>KROk;Z+Es$maY zLKI}$5-=BdJ`N&YfXSgW@3Q5ZgIh0~B-8)Soc-{!_E0BB(|;Q* zySz{aS!q=ZvX~nr#)1p;`^}!}_?j1-|KP3Pj&D|7%je24=9I{&WEgWxmY?6O_Kg-u zD$Rt7n&+W$>(VCIXoI-Ni;Y=5H_y`m4so=Ws<4R+8}#^JIVZUE>$7e~Seq z{EeELOZZtJ_N4qJ+h=}09-t15{&V@*f$F*ZVgF=!v?uIUHoBdt7n7E~!kwBa(s+d7 zR+7xiO&!c!j`v&T@kUE0IN&$I2Dys z-~Q$o;2D&O8dQ=0NC_u`FAp5I$OGz-miF3FU0xt8!9q9{-|VQGIue=TN*!>J*lz%> zxX`EdL(_#cMt|sLri!bJoK^DTAi3%iZ&O@AlRP4e;hMcYTmxi|gZst7ZEmC8vOSQl z6ITOt^N0w! z9w|u+aW3N�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