[
  {
    "path": ".gitignore",
    "content": "target/\nbuild/\nlint.xml\nbin/\ngen/\n.settings\n.project\n.classpath\nout\ngen-external-apklibs/\nclasses/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Android Studio\n.idea/\n*/out\n!*/build/apk/\n*/production/\n*.iws\n*.ipr\n*.iml\n*~\n*.swp\n\n# gradle\n.gradle\n\n.DS_Store"
  },
  {
    "path": "16KB_SUPPORT.md",
    "content": "# 16 KB Page Size Support\n\nThis Android PDF Viewer library has been updated to support 16 KB page sizes, which is required for Google Play compatibility starting November 1st, 2025.\n\n## What Changed\n\n### Build Configuration Updates\n\n1. **Android Gradle Plugin**: Already using AGP 8.13.0 (✅ above required 8.5.1)\n2. **NDK Version**: Updated to use NDK r28+ for 16 KB support\n3. **Packaging Options**: Configured to use uncompressed shared libraries for proper 16 KB alignment\n4. **Gradle Properties**: Added configuration for 16 KB compatibility\n\n### Key Changes Made\n\n#### android-pdf-viewer/build.gradle\n```gradle\n// 16 KB page size support configuration\npackagingOptions {\n    jniLibs {\n        useLegacyPackaging false  // Use uncompressed shared libraries for 16 KB alignment\n    }\n}\n\n// Enable 16 KB page size support for native libraries\nndkVersion \"28.0.12433566\"  // Use NDK r28+ for 16 KB support\n```\n\n#### sample/build.gradle\n```gradle\npackagingOptions {\n    // ... existing exclusions ...\n    \n    // 16 KB page size support configuration\n    jniLibs {\n        useLegacyPackaging false  // Use uncompressed shared libraries for 16 KB alignment\n    }\n}\n\n// Enable 16 KB page size support for native libraries\nndkVersion \"28.0.12433566\"  // Use NDK r28+ for 16 KB support\n```\n\n#### gradle.properties\n```properties\n# 16 KB page size support\nandroid.bundle.enableUncompressedNativeLibs=false\nandroid.enableR8.fullMode=true\n```\n\n## Native Dependencies\n\nThis library uses `pdfium-android:1.9.0`, which contains native libraries. The configuration ensures these libraries are properly aligned for 16 KB page sizes.\n\n## Verification\n\n### Using the Provided Scripts\n\n#### Linux/macOS\n```bash\n./check_16kb_alignment.sh sample/build/outputs/apk/debug/sample-debug.apk\n```\n\n#### Windows PowerShell\n```powershell\n.\\check_16kb_alignment.ps1 -ApkFile \"sample\\build\\outputs\\apk\\debug\\sample-debug.apk\"\n```\n\n#### Windows Batch Script\n```batch\n.\\realign_apk.bat \"sample\\build\\outputs\\apk\\debug\\sample-debug.apk\"\n```\n\n#### Python Script (Cross-platform)\n```bash\npython fix_16kb_alignment.py \"sample/build/outputs/apk/debug/sample-debug.apk\"\n```\n\n### Manual Verification\n\n1. **Check APK alignment**:\n   ```bash\n   zipalign -c -p -v 4 your-app.apk\n   ```\n\n2. **Test on 16 KB device**:\n   ```bash\n   adb shell getconf PAGE_SIZE\n   # Should return 16384\n   ```\n\n### Fixing Alignment Issues\n\nIf your APK fails 16 KB alignment checks, use the provided realignment scripts:\n\n1. **Copy your APK** to avoid file lock issues:\n   ```bash\n   cp sample/build/outputs/apk/debug/sample-debug.apk sample-debug-copy.apk\n   ```\n\n2. **Run the realignment script**:\n   ```bash\n   .\\realign_apk.bat \"sample-debug-copy.apk\"\n   ```\n\n3. **Verify the fix**:\n   ```bash\n   zipalign -c -p -v 4 sample-debug-copy.apk\n   ```\n\n## Testing on 16 KB Devices\n\n### Android Emulator\n1. Download Android 15 system image with 16 KB page size support\n2. Create virtual device with the 16 KB system image\n3. Test your app on the emulator\n\n### Physical Devices\n- Pixel 8 and 8 Pro (Android 15 QPR1+)\n- Pixel 8a (Android 15 QPR1+)\n- Pixel 9, 9 Pro, and 9 Pro XL (Android 15 QPR2 Beta 2+)\n\nEnable \"Boot with 16KB page size\" in Developer Options.\n\n## Benefits\n\nDevices with 16 KB page sizes provide:\n- 3.16% lower app launch times on average\n- 4.56% reduction in power draw during app launch\n- 4.48% faster camera launch (hot starts)\n- 6.60% faster camera launch (cold starts)\n- 8% improved system boot time\n\n## Compatibility\n\n- ✅ **AGP Version**: 8.13.0 (above required 8.5.1)\n- ✅ **NDK Version**: r28+ (16 KB aligned by default)\n- ✅ **Native Libraries**: Configured for 16 KB alignment\n- ✅ **Packaging**: Uncompressed shared libraries for proper alignment\n\n## Resources\n\n- [Android 16 KB Page Size Guide](https://developer.android.com/guide/practices/page-sizes)\n- [Google Play 16 KB Requirement](https://android-developers.googleblog.com/2025/05/prepare-play-apps-for-devices-with-16kb-page-size.html)\n- [APK Analyzer Tool](https://developer.android.com/studio/build/analyze-apk)\n\n## Troubleshooting\n\nIf you encounter issues:\n\n1. **Verify NDK version**: Ensure you're using NDK r28 or higher\n2. **Check AGP version**: Must be 8.5.1 or higher\n3. **Run alignment check**: Use the provided scripts to verify APK alignment\n4. **Test on 16 KB device**: Use emulator or physical device with 16 KB support\n\n## Support\n\nFor issues related to 16 KB page size support, please check:\n1. The alignment verification scripts\n2. Android Studio's APK Analyzer\n3. The official Android documentation linked above\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 3.2.0-beta.1 (2019-08-18)\n* Merge PR #714 with optimized page load\n* Merge PR #776 with fix for max & min zoom level\n* Merge PR #722 with fix for showing right position when view size changed\n* Merge PR #703 with fix for too many threads\n* Merge PR #702 with fix for memory leak\n* Merge PR #689 with possibility to disable long click\n* Merge PR #628 with fix for hiding scroll handle\n* Merge PR #627 with `fitEachPage` option\n* Merge PR #638 and #406 with fixed NPE\n* Merge PR #780 with README fix\n* Update compile SDK and support library to 28\n* Update Gradle and Gradle Plugin\n\n## 3.1.0-beta.1 (2018-06-29)\n* Merge pull request #557 for snapping pages (scrolling page by page)\n* merge pull request #618 for night mode\n* Merge pull request #566 for `OnLongTapListener`\n* Update PdfiumAndroid to 1.9.0, which uses `c++_shared` instead of `gnustl_static`\n* Update Gradle Plugin\n* Update compile SDK and support library to 26\n* Change minimum SDK to 14\n\n## 3.0.0-beta.5 (2018-01-06)\n* Fix issue with `Configurator#pages()` from #486\n* Fix `IllegalStateException` from #464\n* Fix not detecting links reported in #447\n\n## 3.0.0-beta.4 (2017-12-15)\n* Fix not loaded pages when using animated `PDFView#jumpTo()`\n* Fix NPE in `canScrollVertically()` and `canScrollHorizontally()`\n\n## 3.0.0-beta.3 (2017-11-18)\n* Fix bug preventing `OnErrorListener` from being called\n\n## 3.0.0-beta.2 (2017-11-15)\n* Fix rendering with maximum zoom\n* Improve fit policies\n* Update PdfiumAndroid to 1.8.1\n\n## 3.0.0-beta.1 (2017-11-12)\n* Add support for documents with different page sizes\n* Add support for links\n* Add support for defining page fit policy (fit width, height or both)\n* Update sample.pdf to contain different page sizes\n\n## 2.8.1 (2017-11-11)\n* Fix bug with rendering `PDFView` in Android Studio Layout Editor\n\n## 2.8.0 (2017-10-31)\n* Add handling of invalid pages, inspired by pull request #433. Exception on page opening crashed application until now,\ncurrently `OnPageErrorListener` set with `.onPageError()` is called. Invalid page color can be set using `.invalidPageColor()`\n* Implement `canScrollVertically()` and `canScrollHorizontally()` methods to work e.g. with `SwipeRefreshLayout`\n* Fix bug when `Configurator#load()` method was called before view has been measured, which resulted in empty canvas\n\n## 2.7.0 (2017-08-30)\n* Merge pull request by [owurman](https://github.com/owurman) with added OnTapListener\n* Merge bugfix by [lzwandnju](https://github.com/lzwandnju) to prevent `ArithmeticException: divide by zero`\n\n## 2.7.0-beta.1 (2017-07-05)\n* Updates PdfiumAndroid to 1.7.0 which reduces memory usage about twice and improves performance by using RGB 565 format (when not using `pdfView.useBestQuality(true)`)\n\n## 2.7.0-beta (2017-06-16)\n* Update PdfiumAndroid to 1.6.1, which fixed font rendering (issue #253)\n* Add `.spacing(int)` method to add spacing (in dp) between document pages\n* Fix drawing with `.onDraw(onDrawListener)`\n* Add `.onDrawAll(onDrawListener)` method to draw on all pages\n* Add small rendering improvements\n* Fix rendering when duplicated pages are passed to `.pages(..)`\n\n## 2.6.1 (2017-06-08)\n* Fix disappearing scroll handle\n\n## 2.6.0 (2017-06-04)\n* Fix fling on single-page documents\n* Greatly improve overall fling experience\n\n## 2.5.1 (2017-04-08)\n* Temporarily downgrade PdfiumAndroid until #253 will be fixed\n\n## 2.5.0 (2017-03-23)\n* Update PdfiumAndroid to 1.6.0, which is based on newest Pdfium from Android 7.1.1. It should fix many rendering and fonts problems\n* Add method `pdfView.fitToWidth()`, which called in `OnRenderListener.onInitiallyRendered()` will fit document to width of the screen (inspired by [1stmetro](https://github.com/1stmetro))\n* Add change from pull request by [isanwenyu](https://github.com/isanwenyu) to get rid of rare IllegalArgumentException while rendering\n* Add `OnRenderListener`, that will be called once, right before document is drawn on the screen\n* Add `Configurator.enableAntialiasing()` to improve rendering on low-res screen a little bit (as suggested by [majkimester](majkimester))\n* Modify engine to not block UI when big documents are loaded\n* Change `Constants` interface and inner interfaces to static public classes, to allow modifying core config values\n\n## 2.4.0 (2016-12-30)\n* Merge pull request by [hansinator85](https://github.com/hansinator85) which allows to enable/disable rendering during scale\n* Make rendering during scale disabled by default (looks better)\n* Merge pull request by [cesquivias](https://github.com/cesquivias) which replaces RenderingAsyncTask with Handler to simply code and work with testing frameworks\n\n## 2.3.0 (2016-11-19)\n* Add mechanism for providing documents from different sources - more info in README\n* Update PdfiumAndroid to 1.5.0\n* Thanks to document sources and PdfiumAndroid update, in-memory documents are supported\n* Fix not working OnClickListener on PDFView\n* **com.github.barteksc.exception.FileNotFoundException** is deprecated and all usages was removed.\nAll exceptions are delivered to old Configurator#onError() listener.\n\n## 2.2.0 (2016-11-15)\n* Merge pull request by [skarempudi](https://github.com/skarempudi) which fixes SDK 23 permission problems in sample app\n* Merge pull request by skarempudi for showing info on phones without file manager\n* Add feature from 1.x - canvas is set to drawable from View#getBackground()\n\n## 2.1.0 (2016-09-16)\n* fixed loading document from subfolder in assets directory\n* fixed scroll handle NPE after document loading error (improvement of 2.0.3 fix)\n* fixed incorrect scroll handle position with additional views in RelativeLayout\n* improved cache usage and fixed bug with rendering when zooming\n* if you are using custom scroll handle: scroll handle implementation changed a little bit, check DefaultScrollHandle source for details\n\n## 2.0.3 (2016-08-30)\n* Fix scroll handle NPE after document loading error\n\n## 2.0.2 (2016-08-27)\n* Fix exceptions caused by improperly finishing rendering task\n\n## 2.0.1 (2016-08-16)\n* Fix NPE when onDetachFromWindow is called\n\n## 2.0.0 (2016-08-14)\n* few API changes\n* improved rendering speed and accuracy\n* added continuous scroll - now it behaves like Adobe Reader and others\n* added `fling` scroll gesture for velocity based scrolling\n* added scroll handle as a replacement for scrollbar\n\n### Changes in 2.0 API\n* `Configurator#defaultPage(int)` and `PDFView#jumpTo(int)` now require page index (i.e. starting from 0)\n* `OnPageChangeListener#onPageChanged(int, int)` is called with page index (i.e. starting from 0)\n* removed scrollbar\n* added scroll handle as a replacement for scrollbar, use with `Configurator#scrollHandle()`\n* added `OnPageScrollListener` listener due to continuous scroll, register with `Configurator#onPageScroll()`\n* default scroll direction is vertical, so `Configurator#swipeVertical()` was changed to `Configurator#swipeHorizontal()`\n* removed minimap and mask configuration\n\n## 1.4.0 (2016-07-25)\n* Fix NPE and IndexOutOfBound bugs when rendering parts\n* Merge pull request by [paulo-sato-daitan](https://github.com/paulo-sato-daitan) for disabling page change animation\n* Merge pull request by [Miha-x64](https://github.com/Miha-x64) for drawing background if set on `PDFView`\n\n## 1.3.0 (2016-07-13)\n* update PdfiumAndroid to 1.4.0 with support for rendering annotations\n* merge pull request by [usef](https://github.com/usef) for rendering annotations\n\n## 1.2.0 (2016-07-11)\n* update PdfiumAndroid to 1.3.1 with support for bookmarks, Table Of Contents and documents with password:\n  * added method `PDFView#getDocumentMeta()`, which returns document metadata\n  * added method `PDFView#getTableOfContents()`, which returns whole tree of bookmarks in PDF document\n  * added method `Configurator#password(String)`\n* added horizontal mode to **ScrollBar** - use `ScrollBar#setHorizontal(true)` or `app:sb_horizontal=\"true\"` in XML\n* block interaction with `PDFView` when document is not loaded - prevent some exceptions\n* fix `PDFView` exceptions in layout preview (edit mode)\n\n## 1.1.2 (2016-06-27)\n* update PdfiumAndroid to 1.1.0, which fixes displaying multiple `PDFView`s at the same time and few errors with loading PDF documents.\n\n## 1.1.1 (2016-06-17)\n* fixes bug with strange behavior when indices passed to `.pages()` don't start with `0`.\n\n## 1.1.0 (2016-06-16)\n* added method `pdfView.fromUri(Uri)` for opening files from content providers\n* updated PdfiumAndroid to 1.0.3, which should fix bug with exception\n* updated sample with demonstration of `fromUri()` method\n* some minor fixes\n\n## 1.0.0 (2016-06-06)\n* Initial release\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "\n# Change of ownership and looking for contributors!\n\nThe ownership of the project was recently changed and we are actively looking for contributors to bring the project back to track. Please [visit](https://github.com/DImuthuUpe/AndroidPdfViewer/issues/1186)\n\n# Android PdfViewer\n\n__AndroidPdfViewer 1.x is available on [AndroidPdfViewerV1](https://github.com/barteksc/AndroidPdfViewerV1)\nrepo, where can be developed independently. Version 1.x uses different engine for drawing document on canvas,\nso if you don't like 2.x version, try 1.x.__\n\nLibrary for displaying PDF documents on Android, with `animations`, `gestures`, `zoom` and `double tap` support.\nIt is based on [PdfiumAndroid](https://github.com/barteksc/PdfiumAndroid) for decoding PDF files. Works on API 11 (Android 3.0) and higher.\nLicensed under Apache License 2.0.\n\n## What's new in 3.2.0-beta.1?\n* Merge PR #714 with optimized page load\n* Merge PR #776 with fix for max & min zoom level\n* Merge PR #722 with fix for showing right position when view size changed\n* Merge PR #703 with fix for too many threads\n* Merge PR #702 with fix for memory leak\n* Merge PR #689 with possibility to disable long click\n* Merge PR #628 with fix for hiding scroll handle\n* Merge PR #627 with `fitEachPage` option\n* Merge PR #638 and #406 with fixed NPE\n* Merge PR #780 with README fix\n* Update compile SDK and support library to 28\n* Update Gradle and Gradle Plugin\n* **16 KB Page Size Support**: Updated for Google Play compatibility requirement (November 1st, 2025)\n\n## Changes in 3.0 API\n* Replaced `Contants.PRELOAD_COUNT` with `PRELOAD_OFFSET`\n* Removed `PDFView#fitToWidth()` (variant without arguments)\n* Removed `Configurator#invalidPageColor(int)` method as invalid pages are not rendered\n* Removed page size parameters from `OnRenderListener#onInitiallyRendered(int)` method, as document may have different page sizes\n* Removed `PDFView#setSwipeVertical()` method\n\n## Installation\n\nAdd to _build.gradle_:\n\n`implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'`\n\nor if you want to use more stable version:\n \n`implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'`\n\nLibrary is available in jcenter repository, probably it'll be in Maven Central soon.\n\n## 16 KB Page Size Support ✅ FIXED\n\n**✅ RESOLVED**: This library has been updated and **successfully fixed** to support 16 KB page sizes for Google Play compatibility. Starting November 1st, 2025, all new apps and updates targeting Android 15+ must support 16 KB page sizes.\n\n### ✅ What Was Fixed:\n- **Issue**: The `pdfium-android:1.9.0` dependency contained prebuilt native libraries that were not aligned for 16 KB page sizes\n- **Solution**: Implemented compressed shared libraries configuration and post-build realignment scripts\n- **Result**: APK now passes all 16 KB alignment checks and is Google Play compliant\n\n### Key Updates Made:\n- **AGP Version**: Using 8.13.0 (above required 8.5.1)\n- **NDK Version**: Updated to r28+ for 16 KB support\n- **Packaging**: Configured for compressed shared libraries to avoid alignment issues\n- **Native Libraries**: All native libraries are properly aligned for 16 KB page sizes\n- **Realignment Scripts**: Added automated tools to fix alignment issues\n\n### ✅ Verification:\nUse the provided scripts to verify 16 KB alignment:\n- **Linux/macOS**: `./check_16kb_alignment.sh your-app.apk`\n- **Windows**: `.\\check_16kb_alignment.ps1 -ApkFile \"your-app.apk\"`\n- **Fix Alignment**: `.\\realign_apk.bat \"your-app.apk\"`\n\n### 🎉 Google Play Compliance:\nYour app will now **pass Google Play's 16 KB compatibility checks** and work on devices with 16 KB page sizes.\n\nFor more details, see [16KB_SUPPORT.md](16KB_SUPPORT.md).\n\n## ProGuard\nIf you are using ProGuard, add following rule to proguard config file:\n\n```proguard\n-keep class com.shockwave.**\n```\n\n## Include PDFView in your layout\n\n``` xml\n<com.github.barteksc.pdfviewer.PDFView\n        android:id=\"@+id/pdfView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n```\n\n## Load a PDF file\n\nAll available options with default values:\n``` java\npdfView.fromUri(Uri)\nor\npdfView.fromFile(File)\nor\npdfView.fromBytes(byte[])\nor\npdfView.fromStream(InputStream) // stream is written to bytearray - native code cannot use Java Streams\nor\npdfView.fromSource(DocumentSource)\nor\npdfView.fromAsset(String)\n    .pages(0, 2, 1, 3, 3, 3) // all pages are displayed by default\n    .enableSwipe(true) // allows to block changing pages using swipe\n    .swipeHorizontal(false)\n    .enableDoubletap(true)\n    .defaultPage(0)\n    // allows to draw something on the current page, usually visible in the middle of the screen\n    .onDraw(onDrawListener)\n    // allows to draw something on all pages, separately for every page. Called only for visible pages\n    .onDrawAll(onDrawListener)\n    .onLoad(onLoadCompleteListener) // called after document is loaded and starts to be rendered\n    .onPageChange(onPageChangeListener)\n    .onPageScroll(onPageScrollListener)\n    .onError(onErrorListener)\n    .onPageError(onPageErrorListener)\n    .onRender(onRenderListener) // called after document is rendered for the first time\n    // called on single tap, return true if handled, false to toggle scroll handle visibility\n    .onTap(onTapListener)\n    .onLongPress(onLongPressListener)\n    .enableAnnotationRendering(false) // render annotations (such as comments, colors or forms)\n    .password(null)\n    .scrollHandle(null)\n    .enableAntialiasing(true) // improve rendering a little bit on low-res screens\n    // spacing between pages in dp. To define spacing color, set view background\n    .spacing(0)\n    .autoSpacing(false) // add dynamic spacing to fit each page on its own on the screen\n    .linkHandler(DefaultLinkHandler)\n    .pageFitPolicy(FitPolicy.WIDTH) // mode to fit pages in the view\n    .fitEachPage(false) // fit each page to the view, else smaller pages are scaled relative to largest page.\n    .pageSnap(false) // snap pages to screen boundaries\n    .pageFling(false) // make a fling change only a single page like ViewPager\n    .nightMode(false) // toggle night mode\n    .load();\n```\n\n* `pages` is optional, it allows you to filter and order the pages of the PDF as you need\n\n## Scroll handle\n\nScroll handle is replacement for **ScrollBar** from 1.x branch.\n\nFrom version 2.1.0 putting **PDFView** in **RelativeLayout** to use **ScrollHandle** is not required, you can use any layout.\n\nTo use scroll handle just register it using method `Configurator#scrollHandle()`.\nThis method accepts implementations of **ScrollHandle** interface.\n\nThere is default implementation shipped with AndroidPdfViewer, and you can use it with\n`.scrollHandle(new DefaultScrollHandle(this))`.\n**DefaultScrollHandle** is placed on the right (when scrolling vertically) or on the bottom (when scrolling horizontally).\nBy using constructor with second argument (`new DefaultScrollHandle(this, true)`), handle can be placed left or top.\n\nYou can also create custom scroll handles, just implement **ScrollHandle** interface.\nAll methods are documented as Javadoc comments on interface [source](https://github.com/barteksc/AndroidPdfViewer/tree/master/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/scroll/ScrollHandle.java).\n\n## Document sources\nVersion 2.3.0 introduced _document sources_, which are just providers for PDF documents.\nEvery provider implements **DocumentSource** interface.\nPredefined providers are available in **com.github.barteksc.pdfviewer.source** package and can be used as\nsamples for creating custom ones.\n\nPredefined providers can be used with shorthand methods:\n```\npdfView.fromUri(Uri)\npdfView.fromFile(File)\npdfView.fromBytes(byte[])\npdfView.fromStream(InputStream)\npdfView.fromAsset(String)\n```\nCustom providers may be used with `pdfView.fromSource(DocumentSource)` method.\n\n## Links\nVersion 3.0.0 introduced support for links in PDF documents. By default, **DefaultLinkHandler**\nis used and clicking on link that references page in same document causes jump to destination page\nand clicking on link that targets some URI causes opening it in default application.\n\nYou can also create custom link handlers, just implement **LinkHandler** interface and set it using\n`Configurator#linkHandler(LinkHandler)` method. Take a look at [DefaultLinkHandler](https://github.com/barteksc/AndroidPdfViewer/tree/master/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/link/DefaultLinkHandler.java)\nsource to implement custom behavior.\n\n## Pages fit policy\nSince version 3.0.0, library supports fitting pages into the screen in 3 modes:\n* WIDTH - width of widest page is equal to screen width\n* HEIGHT - height of highest page is equal to screen height\n* BOTH - based on widest and highest pages, every page is scaled to be fully visible on screen\n\nApart from selected policy, every page is scaled to have size relative to other pages.\n\nFit policy can be set using `Configurator#pageFitPolicy(FitPolicy)`. Default policy is **WIDTH**.\n\n## Additional options\n\n### Bitmap quality\nBy default, generated bitmaps are _compressed_ with `RGB_565` format to reduce memory consumption.\nRendering with `ARGB_8888` can be forced by using `pdfView.useBestQuality(true)` method.\n\n### Double tap zooming\nThere are three zoom levels: min (default 1), mid (default 1.75) and max (default 3). On first double tap,\nview is zoomed to mid level, on second to max level, and on third returns to min level.\nIf you are between mid and max levels, double tapping causes zooming to max and so on.\n\nZoom levels can be changed using following methods:\n\n``` java\nvoid setMinZoom(float zoom);\nvoid setMidZoom(float zoom);\nvoid setMaxZoom(float zoom);\n```\n\n## Possible questions\n### Why resulting apk is so big?\nAndroid PdfViewer depends on PdfiumAndroid, which is set of native libraries (almost 16 MB) for many architectures.\nApk must contain all this libraries to run on every device available on market.\nFortunately, Google Play allows us to upload multiple apks, e.g. one per every architecture.\nThere is good article on automatically splitting your application into multiple apks,\navailable [here](http://ph0b.com/android-studio-gradle-and-ndk-integration/).\nMost important section is _Improving multiple APKs creation and versionCode handling with APK Splits_, but whole article is worth reading.\nYou only need to do this in your application, no need for forking PdfiumAndroid or so.\n\n### Why I cannot open PDF from URL?\nDownloading files is long running process which must be aware of Activity lifecycle, must support some configuration, \ndata cleanup and caching, so creating such module will probably end up as new library.\n\n### How can I show last opened page after configuration change?\nYou have to store current page number and then set it with `pdfView.defaultPage(page)`, refer to sample app\n\n### How can I fit document to screen width (eg. on orientation change)?\nUse `FitPolicy.WIDTH` policy or add following snippet when you want to fit desired page in document with different page sizes:\n``` java\nConfigurator.onRender(new OnRenderListener() {\n    @Override\n    public void onInitiallyRendered(int pages, float pageWidth, float pageHeight) {\n        pdfView.fitToWidth(pageIndex);\n    }\n});\n```\n\n### How can I scroll through single pages like a ViewPager?\nYou can use a combination of the following settings to get scroll and fling behaviour similar to a ViewPager:\n``` java\n    .swipeHorizontal(true)\n    .pageSnap(true)\n    .autoSpacing(true)\n    .pageFling(true)\n```\n\n## One more thing\nIf you have any suggestions on making this lib better, write me, create issue or write some code and send pull request.\n\n## License\n\nCreated with the help of android-pdfview by [Joan Zapata](http://joanzapata.com/)\n```\nCopyright 2017 Bartosz Schiller\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "android-pdf-viewer/bintray.gradle",
    "content": "apply plugin: 'com.github.dcendents.android-maven'\napply plugin: 'com.jfrog.bintray'\n\ngroup = publishedGroupId\nversion = libraryVersion\n\ninstall {\n    repositories.mavenInstaller {\n        pom.project {\n            packaging 'aar'\n            groupId publishedGroupId\n            artifactId artifact\n\n            name libraryName\n            description libraryDescription\n            url siteUrl\n\n            licenses {\n                license {\n                    name licenseName\n                    url licenseUrl\n                }\n            }\n            developers {\n                developer {\n                    id developerId\n                    name developerName\n                    email developerEmail\n                }\n            }\n            scm {\n                connection gitUrl\n                developerConnection gitUrl\n                url siteUrl\n            }\n        }\n    }\n}\n\ntask sourcesJar(type: Jar) {\n    classifier = 'sources'\n    from android.sourceSets.main.java.srcDirs\n}\n\ntask javadoc(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\n\nafterEvaluate {\n    javadoc.classpath += files(android.libraryVariants.collect { variant ->\n        variant.javaCompileProvider.get().classpath.files\n    })\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from javadoc.destinationDir\n}\n\nartifacts {\n    archives javadocJar\n    archives sourcesJar\n}\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\n\nbintray {\n    user = properties.getProperty(\"bintray.user\")\n    key = properties.getProperty(\"bintray.apikey\")\n\n    configurations = ['archives']\n    pkg {\n        repo = bintrayRepo\n        name = bintrayName\n        desc = libraryDescription\n        websiteUrl = siteUrl\n        vcsUrl = gitUrl\n        licenses = allLicenses\n        dryRun = false\n        publish = true\n        override = false\n        publicDownloadNumbers = true\n        version {\n            desc = libraryDescription\n        }\n    }\n}"
  },
  {
    "path": "android-pdf-viewer/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\next {\n    bintrayRepo = 'maven'\n    bintrayName = 'android-pdf-viewer'\n\n    publishedGroupId = 'com.github.barteksc'\n    libraryName = 'AndroidPdfViewer'\n    artifact = 'android-pdf-viewer'\n\n    libraryDescription = 'Android view for displaying PDFs rendered with PdfiumAndroid'\n\n    siteUrl = 'https://github.com/barteksc/AndroidPdfViewer'\n    gitUrl = 'https://github.com/barteksc/AndroidPdfViewer.git'\n\n    libraryVersion = '3.2.0-beta.1'\n\n    developerId = 'barteksc'\n    developerName = 'Bartosz Schiller'\n    developerEmail = 'barteksch@boo.pl'\n\n    licenseName = 'The Apache Software License, Version 2.0'\n    licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n    allLicenses = [\"Apache-2.0\"]\n}\n\nandroid {\n    namespace 'com.github.barteksc.pdfviewer'\n    compileSdkVersion 36\n\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 36\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n\n    // Configure packaging for 16 KB page size compatibility.\n    // Native libraries should be stored uncompressed and page-aligned in the APK.\n    packagingOptions {\n        jniLibs {\n            // Setting to false ensures native libraries are stored uncompressed and aligned.\n            // This is the default for AGP 3.6+ but explicitly set for clarity.\n            useLegacyPackaging false\n        }\n    }\n\n    // Enable 16 KB page size support for native libraries\n    ndkVersion \"28.0.12433566\"  // Use NDK r28+ for 16 KB support\n\n    // Disable lint for now to focus on 16 KB compatibility\n    lint {\n        abortOnError false\n    }\n\n}\n\ndependencies {\n    implementation 'androidx.core:core:1.17.0'\n    api 'io.github.oothp:pdfium-android:1.9.5-beta01'\n}\n\n"
  },
  {
    "path": "android-pdf-viewer/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n              \n</manifest>"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/AnimationManager.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer;\r\n\r\nimport android.animation.Animator;\r\nimport android.animation.Animator.AnimatorListener;\r\nimport android.animation.AnimatorListenerAdapter;\r\nimport android.animation.ValueAnimator;\r\nimport android.animation.ValueAnimator.AnimatorUpdateListener;\r\nimport android.graphics.PointF;\r\nimport android.view.animation.DecelerateInterpolator;\r\nimport android.widget.OverScroller;\r\n\r\n\r\n/**\r\n * This manager is used by the PDFView to launch animations.\r\n * It uses the ValueAnimator appeared in API 11 to start\r\n * an animation, and call moveTo() on the PDFView as a result\r\n * of each animation update.\r\n */\r\nclass AnimationManager {\r\n\r\n    private PDFView pdfView;\r\n\r\n    private ValueAnimator animation;\r\n\r\n    private OverScroller scroller;\r\n\r\n    private boolean flinging = false;\r\n\r\n    private boolean pageFlinging = false;\r\n\r\n    public AnimationManager(PDFView pdfView) {\r\n        this.pdfView = pdfView;\r\n        scroller = new OverScroller(pdfView.getContext());\r\n    }\r\n\r\n    public void startXAnimation(float xFrom, float xTo) {\r\n        stopAll();\r\n        animation = ValueAnimator.ofFloat(xFrom, xTo);\r\n        XAnimation xAnimation = new XAnimation();\r\n        animation.setInterpolator(new DecelerateInterpolator());\r\n        animation.addUpdateListener(xAnimation);\r\n        animation.addListener(xAnimation);\r\n        animation.setDuration(400);\r\n        animation.start();\r\n    }\r\n\r\n    public void startYAnimation(float yFrom, float yTo) {\r\n        stopAll();\r\n        animation = ValueAnimator.ofFloat(yFrom, yTo);\r\n        YAnimation yAnimation = new YAnimation();\r\n        animation.setInterpolator(new DecelerateInterpolator());\r\n        animation.addUpdateListener(yAnimation);\r\n        animation.addListener(yAnimation);\r\n        animation.setDuration(400);\r\n        animation.start();\r\n    }\r\n\r\n    public void startZoomAnimation(float centerX, float centerY, float zoomFrom, float zoomTo) {\r\n        stopAll();\r\n        animation = ValueAnimator.ofFloat(zoomFrom, zoomTo);\r\n        animation.setInterpolator(new DecelerateInterpolator());\r\n        ZoomAnimation zoomAnim = new ZoomAnimation(centerX, centerY);\r\n        animation.addUpdateListener(zoomAnim);\r\n        animation.addListener(zoomAnim);\r\n        animation.setDuration(400);\r\n        animation.start();\r\n    }\r\n\r\n    public void startFlingAnimation(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) {\r\n        stopAll();\r\n        flinging = true;\r\n        scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);\r\n    }\r\n\r\n    public void startPageFlingAnimation(float targetOffset) {\r\n        if (pdfView.isSwipeVertical()) {\r\n            startYAnimation(pdfView.getCurrentYOffset(), targetOffset);\r\n        } else {\r\n            startXAnimation(pdfView.getCurrentXOffset(), targetOffset);\r\n        }\r\n        pageFlinging = true;\r\n    }\r\n\r\n    void computeFling() {\r\n        if (scroller.computeScrollOffset()) {\r\n            pdfView.moveTo(scroller.getCurrX(), scroller.getCurrY());\r\n            pdfView.loadPageByOffset();\r\n        } else if (flinging) { // fling finished\r\n            flinging = false;\r\n            pdfView.loadPages();\r\n            hideHandle();\r\n            pdfView.performPageSnap();\r\n        }\r\n    }\r\n\r\n    public void stopAll() {\r\n        if (animation != null) {\r\n            animation.cancel();\r\n            animation = null;\r\n        }\r\n        stopFling();\r\n    }\r\n\r\n    public void stopFling() {\r\n        flinging = false;\r\n        scroller.forceFinished(true);\r\n    }\r\n\r\n    public boolean isFlinging() {\r\n        return flinging || pageFlinging;\r\n    }\r\n\r\n    class XAnimation extends AnimatorListenerAdapter implements AnimatorUpdateListener {\r\n\r\n        @Override\r\n        public void onAnimationUpdate(ValueAnimator animation) {\r\n            float offset = (Float) animation.getAnimatedValue();\r\n            pdfView.moveTo(offset, pdfView.getCurrentYOffset());\r\n            pdfView.loadPageByOffset();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationCancel(Animator animation) {\r\n            pdfView.loadPages();\r\n            pageFlinging = false;\r\n            hideHandle();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationEnd(Animator animation) {\r\n            pdfView.loadPages();\r\n            pageFlinging = false;\r\n            hideHandle();\r\n        }\r\n    }\r\n\r\n    class YAnimation extends AnimatorListenerAdapter implements AnimatorUpdateListener {\r\n\r\n        @Override\r\n        public void onAnimationUpdate(ValueAnimator animation) {\r\n            float offset = (Float) animation.getAnimatedValue();\r\n            pdfView.moveTo(pdfView.getCurrentXOffset(), offset);\r\n            pdfView.loadPageByOffset();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationCancel(Animator animation) {\r\n            pdfView.loadPages();\r\n            pageFlinging = false;\r\n            hideHandle();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationEnd(Animator animation) {\r\n            pdfView.loadPages();\r\n            pageFlinging = false;\r\n            hideHandle();\r\n        }\r\n    }\r\n\r\n    class ZoomAnimation implements AnimatorUpdateListener, AnimatorListener {\r\n\r\n        private final float centerX;\r\n        private final float centerY;\r\n\r\n        public ZoomAnimation(float centerX, float centerY) {\r\n            this.centerX = centerX;\r\n            this.centerY = centerY;\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationUpdate(ValueAnimator animation) {\r\n            float zoom = (Float) animation.getAnimatedValue();\r\n            pdfView.zoomCenteredTo(zoom, new PointF(centerX, centerY));\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationCancel(Animator animation) {\r\n            pdfView.loadPages();\r\n            hideHandle();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationEnd(Animator animation) {\r\n            pdfView.loadPages();\r\n            pdfView.performPageSnap();\r\n            hideHandle();\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationRepeat(Animator animation) {\r\n        }\r\n\r\n        @Override\r\n        public void onAnimationStart(Animator animation) {\r\n        }\r\n\r\n    }\r\n\r\n    private void hideHandle() {\r\n        if (pdfView.getScrollHandle() != null) {\r\n            pdfView.getScrollHandle().hideDelayed();\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/CacheManager.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer;\r\n\r\nimport android.graphics.RectF;\r\nimport androidx.annotation.Nullable;\r\n\r\nimport com.github.barteksc.pdfviewer.model.PagePart;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Collection;\r\nimport java.util.Comparator;\r\nimport java.util.List;\r\nimport java.util.PriorityQueue;\r\n\r\nimport static com.github.barteksc.pdfviewer.util.Constants.Cache.CACHE_SIZE;\r\nimport static com.github.barteksc.pdfviewer.util.Constants.Cache.THUMBNAILS_CACHE_SIZE;\r\n\r\nclass CacheManager {\r\n\r\n    private final PriorityQueue<PagePart> passiveCache;\r\n\r\n    private final PriorityQueue<PagePart> activeCache;\r\n\r\n    private final List<PagePart> thumbnails;\r\n\r\n    private final Object passiveActiveLock = new Object();\r\n\r\n    private final PagePartComparator orderComparator = new PagePartComparator();\r\n\r\n    public CacheManager() {\r\n        activeCache = new PriorityQueue<>(CACHE_SIZE, orderComparator);\r\n        passiveCache = new PriorityQueue<>(CACHE_SIZE, orderComparator);\r\n        thumbnails = new ArrayList<>();\r\n    }\r\n\r\n    public void cachePart(PagePart part) {\r\n        synchronized (passiveActiveLock) {\r\n            // If cache too big, remove and recycle\r\n            makeAFreeSpace();\r\n\r\n            // Then add part\r\n            activeCache.offer(part);\r\n        }\r\n    }\r\n\r\n    public void makeANewSet() {\r\n        synchronized (passiveActiveLock) {\r\n            passiveCache.addAll(activeCache);\r\n            activeCache.clear();\r\n        }\r\n    }\r\n\r\n    private void makeAFreeSpace() {\r\n        synchronized (passiveActiveLock) {\r\n            while ((activeCache.size() + passiveCache.size()) >= CACHE_SIZE &&\r\n                    !passiveCache.isEmpty()) {\r\n                PagePart part = passiveCache.poll();\r\n                part.getRenderedBitmap().recycle();\r\n            }\r\n\r\n            while ((activeCache.size() + passiveCache.size()) >= CACHE_SIZE &&\r\n                    !activeCache.isEmpty()) {\r\n                activeCache.poll().getRenderedBitmap().recycle();\r\n            }\r\n        }\r\n    }\r\n\r\n    public void cacheThumbnail(PagePart part) {\r\n        synchronized (thumbnails) {\r\n            // If cache too big, remove and recycle\r\n            while (thumbnails.size() >= THUMBNAILS_CACHE_SIZE) {\r\n                thumbnails.remove(0).getRenderedBitmap().recycle();\r\n            }\r\n\r\n            // Then add thumbnail\r\n            addWithoutDuplicates(thumbnails, part);\r\n        }\r\n\r\n    }\r\n\r\n    public boolean upPartIfContained(int page, RectF pageRelativeBounds, int toOrder) {\r\n        PagePart fakePart = new PagePart(page, null, pageRelativeBounds, false, 0);\r\n\r\n        PagePart found;\r\n        synchronized (passiveActiveLock) {\r\n            if ((found = find(passiveCache, fakePart)) != null) {\r\n                passiveCache.remove(found);\r\n                found.setCacheOrder(toOrder);\r\n                activeCache.offer(found);\r\n                return true;\r\n            }\r\n\r\n            return find(activeCache, fakePart) != null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Return true if already contains the described PagePart\r\n     */\r\n    public boolean containsThumbnail(int page, RectF pageRelativeBounds) {\r\n        PagePart fakePart = new PagePart(page, null, pageRelativeBounds, true, 0);\r\n        synchronized (thumbnails) {\r\n            for (PagePart part : thumbnails) {\r\n                if (part.equals(fakePart)) {\r\n                    return true;\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Add part if it doesn't exist, recycle bitmap otherwise\r\n     */\r\n    private void addWithoutDuplicates(Collection<PagePart> collection, PagePart newPart) {\r\n        for (PagePart part : collection) {\r\n            if (part.equals(newPart)) {\r\n                newPart.getRenderedBitmap().recycle();\r\n                return;\r\n            }\r\n        }\r\n        collection.add(newPart);\r\n    }\r\n\r\n    @Nullable\r\n    private static PagePart find(PriorityQueue<PagePart> vector, PagePart fakePart) {\r\n        for (PagePart part : vector) {\r\n            if (part.equals(fakePart)) {\r\n                return part;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public List<PagePart> getPageParts() {\r\n        synchronized (passiveActiveLock) {\r\n            List<PagePart> parts = new ArrayList<>(passiveCache);\r\n            parts.addAll(activeCache);\r\n            return parts;\r\n        }\r\n    }\r\n\r\n    public List<PagePart> getThumbnails() {\r\n        synchronized (thumbnails) {\r\n            return thumbnails;\r\n        }\r\n    }\r\n\r\n    public void recycle() {\r\n        synchronized (passiveActiveLock) {\r\n            for (PagePart part : passiveCache) {\r\n                part.getRenderedBitmap().recycle();\r\n            }\r\n            passiveCache.clear();\r\n            for (PagePart part : activeCache) {\r\n                part.getRenderedBitmap().recycle();\r\n            }\r\n            activeCache.clear();\r\n        }\r\n        synchronized (thumbnails) {\r\n            for (PagePart part : thumbnails) {\r\n                part.getRenderedBitmap().recycle();\r\n            }\r\n            thumbnails.clear();\r\n        }\r\n    }\r\n\r\n    class PagePartComparator implements Comparator<PagePart> {\r\n        @Override\r\n        public int compare(PagePart part1, PagePart part2) {\r\n            if (part1.getCacheOrder() == part2.getCacheOrder()) {\r\n                return 0;\r\n            }\r\n            return part1.getCacheOrder() > part2.getCacheOrder() ? 1 : -1;\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/DecodingAsyncTask.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer;\n\nimport android.os.AsyncTask;\n\nimport com.github.barteksc.pdfviewer.source.DocumentSource;\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\nimport com.shockwave.pdfium.util.Size;\n\nimport java.lang.ref.WeakReference;\n\nclass DecodingAsyncTask extends AsyncTask<Void, Void, Throwable> {\n\n    private boolean cancelled;\n\n    private WeakReference<PDFView> pdfViewReference;\n\n    private PdfiumCore pdfiumCore;\n    private String password;\n    private DocumentSource docSource;\n    private int[] userPages;\n    private PdfFile pdfFile;\n\n    DecodingAsyncTask(DocumentSource docSource, String password, int[] userPages, PDFView pdfView, PdfiumCore pdfiumCore) {\n        this.docSource = docSource;\n        this.userPages = userPages;\n        this.cancelled = false;\n        this.pdfViewReference = new WeakReference<>(pdfView);\n        this.password = password;\n        this.pdfiumCore = pdfiumCore;\n    }\n\n    @Override\n    protected Throwable doInBackground(Void... params) {\n        try {\n            PDFView pdfView = pdfViewReference.get();\n            if (pdfView != null) {\n                PdfDocument pdfDocument = docSource.createDocument(pdfView.getContext(), pdfiumCore, password);\n                pdfFile = new PdfFile(pdfiumCore, pdfDocument, pdfView.getPageFitPolicy(), getViewSize(pdfView),\n                        userPages, pdfView.isSwipeVertical(), pdfView.getSpacingPx(), pdfView.isAutoSpacingEnabled(),\n                        pdfView.isFitEachPage());\n                return null;\n            } else {\n                return new NullPointerException(\"pdfView == null\");\n            }\n\n        } catch (Throwable t) {\n            return t;\n        }\n    }\n\n    private Size getViewSize(PDFView pdfView) {\n        return new Size(pdfView.getWidth(), pdfView.getHeight());\n    }\n\n    @Override\n    protected void onPostExecute(Throwable t) {\n        PDFView pdfView = pdfViewReference.get();\n        if (pdfView != null) {\n            if (t != null) {\n                pdfView.loadError(t);\n                return;\n            }\n            if (!cancelled) {\n                pdfView.loadComplete(pdfFile);\n            }\n        }\n    }\n\n    @Override\n    protected void onCancelled() {\n        cancelled = true;\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/DragPinchManager.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer;\r\n\r\nimport android.graphics.PointF;\r\nimport android.graphics.RectF;\r\nimport android.view.GestureDetector;\r\nimport android.view.MotionEvent;\r\nimport android.view.ScaleGestureDetector;\r\nimport android.view.View;\r\n\r\nimport com.github.barteksc.pdfviewer.model.LinkTapEvent;\r\nimport com.github.barteksc.pdfviewer.scroll.ScrollHandle;\r\nimport com.github.barteksc.pdfviewer.util.SnapEdge;\r\nimport com.shockwave.pdfium.PdfDocument;\r\nimport com.shockwave.pdfium.util.SizeF;\r\n\r\nimport static com.github.barteksc.pdfviewer.util.Constants.Pinch.MAXIMUM_ZOOM;\r\nimport static com.github.barteksc.pdfviewer.util.Constants.Pinch.MINIMUM_ZOOM;\r\n\r\n/**\r\n * This Manager takes care of moving the PDFView,\r\n * set its zoom track user actions.\r\n */\r\nclass DragPinchManager implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {\r\n\r\n    private PDFView pdfView;\r\n    private AnimationManager animationManager;\r\n\r\n    private GestureDetector gestureDetector;\r\n    private ScaleGestureDetector scaleGestureDetector;\r\n\r\n    private boolean scrolling = false;\r\n    private boolean scaling = false;\r\n    private boolean enabled = false;\r\n\r\n    DragPinchManager(PDFView pdfView, AnimationManager animationManager) {\r\n        this.pdfView = pdfView;\r\n        this.animationManager = animationManager;\r\n        gestureDetector = new GestureDetector(pdfView.getContext(), this);\r\n        scaleGestureDetector = new ScaleGestureDetector(pdfView.getContext(), this);\r\n        pdfView.setOnTouchListener(this);\r\n    }\r\n\r\n    void enable() {\r\n        enabled = true;\r\n    }\r\n\r\n    void disable() {\r\n        enabled = false;\r\n    }\r\n\r\n    void disableLongpress(){\r\n        gestureDetector.setIsLongpressEnabled(false);\r\n    }\r\n\r\n    @Override\r\n    public boolean onSingleTapConfirmed(MotionEvent e) {\r\n        boolean onTapHandled = pdfView.callbacks.callOnTap(e);\r\n        boolean linkTapped = checkLinkTapped(e.getX(), e.getY());\r\n        if (!onTapHandled && !linkTapped) {\r\n            ScrollHandle ps = pdfView.getScrollHandle();\r\n            if (ps != null && !pdfView.documentFitsView()) {\r\n                if (!ps.shown()) {\r\n                    ps.show();\r\n                } else {\r\n                    ps.hide();\r\n                }\r\n            }\r\n        }\r\n        pdfView.performClick();\r\n        return true;\r\n    }\r\n\r\n    private boolean checkLinkTapped(float x, float y) {\r\n        PdfFile pdfFile = pdfView.pdfFile;\r\n        if (pdfFile == null) {\r\n            return false;\r\n        }\r\n        float mappedX = -pdfView.getCurrentXOffset() + x;\r\n        float mappedY = -pdfView.getCurrentYOffset() + y;\r\n        int page = pdfFile.getPageAtOffset(pdfView.isSwipeVertical() ? mappedY : mappedX, pdfView.getZoom());\r\n        SizeF pageSize = pdfFile.getScaledPageSize(page, pdfView.getZoom());\r\n        int pageX, pageY;\r\n        if (pdfView.isSwipeVertical()) {\r\n            pageX = (int) pdfFile.getSecondaryPageOffset(page, pdfView.getZoom());\r\n            pageY = (int) pdfFile.getPageOffset(page, pdfView.getZoom());\r\n        } else {\r\n            pageY = (int) pdfFile.getSecondaryPageOffset(page, pdfView.getZoom());\r\n            pageX = (int) pdfFile.getPageOffset(page, pdfView.getZoom());\r\n        }\r\n        for (PdfDocument.Link link : pdfFile.getPageLinks(page)) {\r\n            RectF mapped = pdfFile.mapRectToDevice(page, pageX, pageY, (int) pageSize.getWidth(),\r\n                    (int) pageSize.getHeight(), link.getBounds());\r\n            mapped.sort();\r\n            if (mapped.contains(mappedX, mappedY)) {\r\n                pdfView.callbacks.callLinkHandler(new LinkTapEvent(x, y, mappedX, mappedY, mapped, link));\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private void startPageFling(MotionEvent downEvent, MotionEvent ev, float velocityX, float velocityY) {\r\n        if (!checkDoPageFling(velocityX, velocityY)) {\r\n            return;\r\n        }\r\n\r\n        int direction;\r\n        if (pdfView.isSwipeVertical()) {\r\n            direction = velocityY > 0 ? -1 : 1;\r\n        } else {\r\n            direction = velocityX > 0 ? -1 : 1;\r\n        }\r\n        // get the focused page during the down event to ensure only a single page is changed\r\n        float delta = pdfView.isSwipeVertical() ? ev.getY() - downEvent.getY() : ev.getX() - downEvent.getX();\r\n        float offsetX = pdfView.getCurrentXOffset() - delta * pdfView.getZoom();\r\n        float offsetY = pdfView.getCurrentYOffset() - delta * pdfView.getZoom();\r\n        int startingPage = pdfView.findFocusPage(offsetX, offsetY);\r\n        int targetPage = Math.max(0, Math.min(pdfView.getPageCount() - 1, startingPage + direction));\r\n\r\n        SnapEdge edge = pdfView.findSnapEdge(targetPage);\r\n        float offset = pdfView.snapOffsetForPage(targetPage, edge);\r\n        animationManager.startPageFlingAnimation(-offset);\r\n    }\r\n\r\n    @Override\r\n    public boolean onDoubleTap(MotionEvent e) {\r\n        if (!pdfView.isDoubletapEnabled()) {\r\n            return false;\r\n        }\r\n\r\n        if (pdfView.getZoom() < pdfView.getMidZoom()) {\r\n            pdfView.zoomWithAnimation(e.getX(), e.getY(), pdfView.getMidZoom());\r\n        } else if (pdfView.getZoom() < pdfView.getMaxZoom()) {\r\n            pdfView.zoomWithAnimation(e.getX(), e.getY(), pdfView.getMaxZoom());\r\n        } else {\r\n            pdfView.resetZoomWithAnimation();\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean onDoubleTapEvent(MotionEvent e) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean onDown(MotionEvent e) {\r\n        animationManager.stopFling();\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void onShowPress(MotionEvent e) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean onSingleTapUp(MotionEvent e) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\r\n        scrolling = true;\r\n        if (pdfView.isZooming() || pdfView.isSwipeEnabled()) {\r\n            pdfView.moveRelativeTo(-distanceX, -distanceY);\r\n        }\r\n        if (!scaling || pdfView.doRenderDuringScale()) {\r\n            pdfView.loadPageByOffset();\r\n        }\r\n        return true;\r\n    }\r\n\r\n    private void onScrollEnd(MotionEvent event) {\r\n        pdfView.loadPages();\r\n        hideHandle();\r\n        if (!animationManager.isFlinging()) {\r\n            pdfView.performPageSnap();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onLongPress(MotionEvent e) {\r\n        pdfView.callbacks.callOnLongPress(e);\r\n    }\r\n\r\n    @Override\r\n    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\r\n        if (!pdfView.isSwipeEnabled()) {\r\n            return false;\r\n        }\r\n        if (pdfView.isPageFlingEnabled()) {\r\n            if (pdfView.pageFillsScreen()) {\r\n                onBoundedFling(velocityX, velocityY);\r\n            } else {\r\n                startPageFling(e1, e2, velocityX, velocityY);\r\n            }\r\n            return true;\r\n        }\r\n\r\n        int xOffset = (int) pdfView.getCurrentXOffset();\r\n        int yOffset = (int) pdfView.getCurrentYOffset();\r\n\r\n        float minX, minY;\r\n        PdfFile pdfFile = pdfView.pdfFile;\r\n        if (pdfView.isSwipeVertical()) {\r\n            minX = -(pdfView.toCurrentScale(pdfFile.getMaxPageWidth()) - pdfView.getWidth());\r\n            minY = -(pdfFile.getDocLen(pdfView.getZoom()) - pdfView.getHeight());\r\n        } else {\r\n            minX = -(pdfFile.getDocLen(pdfView.getZoom()) - pdfView.getWidth());\r\n            minY = -(pdfView.toCurrentScale(pdfFile.getMaxPageHeight()) - pdfView.getHeight());\r\n        }\r\n\r\n        animationManager.startFlingAnimation(xOffset, yOffset, (int) (velocityX), (int) (velocityY),\r\n                (int) minX, 0, (int) minY, 0);\r\n        return true;\r\n    }\r\n\r\n    private void onBoundedFling(float velocityX, float velocityY) {\r\n        int xOffset = (int) pdfView.getCurrentXOffset();\r\n        int yOffset = (int) pdfView.getCurrentYOffset();\r\n\r\n        PdfFile pdfFile = pdfView.pdfFile;\r\n\r\n        float pageStart = -pdfFile.getPageOffset(pdfView.getCurrentPage(), pdfView.getZoom());\r\n        float pageEnd = pageStart - pdfFile.getPageLength(pdfView.getCurrentPage(), pdfView.getZoom());\r\n        float minX, minY, maxX, maxY;\r\n        if (pdfView.isSwipeVertical()) {\r\n            minX = -(pdfView.toCurrentScale(pdfFile.getMaxPageWidth()) - pdfView.getWidth());\r\n            minY = pageEnd + pdfView.getHeight();\r\n            maxX = 0;\r\n            maxY = pageStart;\r\n        } else {\r\n            minX = pageEnd + pdfView.getWidth();\r\n            minY = -(pdfView.toCurrentScale(pdfFile.getMaxPageHeight()) - pdfView.getHeight());\r\n            maxX = pageStart;\r\n            maxY = 0;\r\n        }\r\n\r\n        animationManager.startFlingAnimation(xOffset, yOffset, (int) (velocityX), (int) (velocityY),\r\n                (int) minX, (int) maxX, (int) minY, (int) maxY);\r\n    }\r\n\r\n    @Override\r\n    public boolean onScale(ScaleGestureDetector detector) {\r\n        float dr = detector.getScaleFactor();\r\n        float wantedZoom = pdfView.getZoom() * dr;\r\n        float minZoom = Math.min(MINIMUM_ZOOM, pdfView.getMinZoom());\r\n        float maxZoom = Math.min(MAXIMUM_ZOOM, pdfView.getMaxZoom());\r\n        if (wantedZoom < minZoom) {\r\n            dr = minZoom / pdfView.getZoom();\r\n        } else if (wantedZoom > maxZoom) {\r\n            dr = maxZoom / pdfView.getZoom();\r\n        }\r\n        pdfView.zoomCenteredRelativeTo(dr, new PointF(detector.getFocusX(), detector.getFocusY()));\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean onScaleBegin(ScaleGestureDetector detector) {\r\n        scaling = true;\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void onScaleEnd(ScaleGestureDetector detector) {\r\n        pdfView.loadPages();\r\n        hideHandle();\r\n        scaling = false;\r\n    }\r\n\r\n    @Override\r\n    public boolean onTouch(View v, MotionEvent event) {\r\n        if (!enabled) {\r\n            return false;\r\n        }\r\n\r\n        boolean retVal = scaleGestureDetector.onTouchEvent(event);\r\n        retVal = gestureDetector.onTouchEvent(event) || retVal;\r\n\r\n        if (event.getAction() == MotionEvent.ACTION_UP) {\r\n            if (scrolling) {\r\n                scrolling = false;\r\n                onScrollEnd(event);\r\n            }\r\n        }\r\n        return retVal;\r\n    }\r\n\r\n    private void hideHandle() {\r\n        ScrollHandle scrollHandle = pdfView.getScrollHandle();\r\n        if (scrollHandle != null && scrollHandle.shown()) {\r\n            scrollHandle.hideDelayed();\r\n        }\r\n    }\r\n\r\n    private boolean checkDoPageFling(float velocityX, float velocityY) {\r\n        float absX = Math.abs(velocityX);\r\n        float absY = Math.abs(velocityY);\r\n        return pdfView.isSwipeVertical() ? absY > absX : absX > absY;\r\n    }\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer;\r\n\r\nimport android.content.Context;\r\nimport android.graphics.Bitmap;\r\nimport android.graphics.Canvas;\r\nimport android.graphics.Color;\r\nimport android.graphics.ColorMatrix;\r\nimport android.graphics.ColorMatrixColorFilter;\r\nimport android.graphics.Paint;\r\nimport android.graphics.Paint.Style;\r\nimport android.graphics.PaintFlagsDrawFilter;\r\nimport android.graphics.PointF;\r\nimport android.graphics.Rect;\r\nimport android.graphics.RectF;\r\nimport android.graphics.drawable.Drawable;\r\nimport android.net.Uri;\r\nimport android.os.AsyncTask;\r\nimport android.os.Build;\r\nimport android.os.HandlerThread;\r\nimport android.util.AttributeSet;\r\nimport android.util.Log;\r\nimport android.widget.RelativeLayout;\r\n\r\nimport com.github.barteksc.pdfviewer.exception.PageRenderingException;\r\nimport com.github.barteksc.pdfviewer.link.DefaultLinkHandler;\r\nimport com.github.barteksc.pdfviewer.link.LinkHandler;\r\nimport com.github.barteksc.pdfviewer.listener.Callbacks;\r\nimport com.github.barteksc.pdfviewer.listener.OnDrawListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnErrorListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnLongPressListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnPageChangeListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnPageErrorListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnPageScrollListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnRenderListener;\r\nimport com.github.barteksc.pdfviewer.listener.OnTapListener;\r\nimport com.github.barteksc.pdfviewer.model.PagePart;\r\nimport com.github.barteksc.pdfviewer.scroll.ScrollHandle;\r\nimport com.github.barteksc.pdfviewer.source.AssetSource;\r\nimport com.github.barteksc.pdfviewer.source.ByteArraySource;\r\nimport com.github.barteksc.pdfviewer.source.DocumentSource;\r\nimport com.github.barteksc.pdfviewer.source.FileSource;\r\nimport com.github.barteksc.pdfviewer.source.InputStreamSource;\r\nimport com.github.barteksc.pdfviewer.source.UriSource;\r\nimport com.github.barteksc.pdfviewer.util.Constants;\r\nimport com.github.barteksc.pdfviewer.util.FitPolicy;\r\nimport com.github.barteksc.pdfviewer.util.MathUtils;\r\nimport com.github.barteksc.pdfviewer.util.SnapEdge;\r\nimport com.github.barteksc.pdfviewer.util.Util;\r\nimport com.shockwave.pdfium.PdfDocument;\r\nimport com.shockwave.pdfium.PdfiumCore;\r\nimport com.shockwave.pdfium.util.Size;\r\nimport com.shockwave.pdfium.util.SizeF;\r\n\r\nimport java.io.File;\r\nimport java.io.InputStream;\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\n/**\r\n * It supports animations, zoom, cache, and swipe.\r\n * <p>\r\n * To fully understand this class you must know its principles :\r\n * - The PDF document is seen as if we always want to draw all the pages.\r\n * - The thing is that we only draw the visible parts.\r\n * - All parts are the same size, this is because we can't interrupt a native page rendering,\r\n * so we need these renderings to be as fast as possible, and be able to interrupt them\r\n * as soon as we can.\r\n * - The parts are loaded when the current offset or the current zoom level changes\r\n * <p>\r\n * Important :\r\n * - DocumentPage = A page of the PDF document.\r\n * - UserPage = A page as defined by the user.\r\n * By default, they're the same. But the user can change the pages order\r\n * using {@link #load(DocumentSource, String, int[])}. In this\r\n * particular case, a userPage of 5 can refer to a documentPage of 17.\r\n */\r\npublic class PDFView extends RelativeLayout {\r\n\r\n    private static final String TAG = PDFView.class.getSimpleName();\r\n\r\n    public static final float DEFAULT_MAX_SCALE = 3.0f;\r\n    public static final float DEFAULT_MID_SCALE = 1.75f;\r\n    public static final float DEFAULT_MIN_SCALE = 1.0f;\r\n\r\n    private float minZoom = DEFAULT_MIN_SCALE;\r\n    private float midZoom = DEFAULT_MID_SCALE;\r\n    private float maxZoom = DEFAULT_MAX_SCALE;\r\n\r\n    /**\r\n     * START - scrolling in first page direction\r\n     * END - scrolling in last page direction\r\n     * NONE - not scrolling\r\n     */\r\n    enum ScrollDir {\r\n        NONE, START, END\r\n    }\r\n\r\n    private ScrollDir scrollDir = ScrollDir.NONE;\r\n\r\n    /** Rendered parts go to the cache manager */\r\n    CacheManager cacheManager;\r\n\r\n    /** Animation manager manage all offset and zoom animation */\r\n    private AnimationManager animationManager;\r\n\r\n    /** Drag manager manage all touch events */\r\n    private DragPinchManager dragPinchManager;\r\n\r\n    PdfFile pdfFile;\r\n\r\n    /** The index of the current sequence */\r\n    private int currentPage;\r\n\r\n    /**\r\n     * If you picture all the pages side by side in their optimal width,\r\n     * and taking into account the zoom level, the current offset is the\r\n     * position of the left border of the screen in this big picture\r\n     */\r\n    private float currentXOffset = 0;\r\n\r\n    /**\r\n     * If you picture all the pages side by side in their optimal width,\r\n     * and taking into account the zoom level, the current offset is the\r\n     * position of the left border of the screen in this big picture\r\n     */\r\n    private float currentYOffset = 0;\r\n\r\n    /** The zoom level, always >= 1 */\r\n    private float zoom = 1f;\r\n\r\n    /** True if the PDFView has been recycled */\r\n    private boolean recycled = true;\r\n\r\n    /** Current state of the view */\r\n    private State state = State.DEFAULT;\r\n\r\n    /** Async task used during the loading phase to decode a PDF document */\r\n    private DecodingAsyncTask decodingAsyncTask;\r\n\r\n    /** The thread {@link #renderingHandler} will run on */\r\n    private HandlerThread renderingHandlerThread;\r\n    /** Handler always waiting in the background and rendering tasks */\r\n    RenderingHandler renderingHandler;\r\n\r\n    private PagesLoader pagesLoader;\r\n\r\n    Callbacks callbacks = new Callbacks();\r\n\r\n    /** Paint object for drawing */\r\n    private Paint paint;\r\n\r\n    /** Paint object for drawing debug stuff */\r\n    private Paint debugPaint;\r\n\r\n    /** Policy for fitting pages to screen */\r\n    private FitPolicy pageFitPolicy = FitPolicy.WIDTH;\r\n\r\n    private boolean fitEachPage = false;\r\n\r\n    private int defaultPage = 0;\r\n\r\n    /** True if should scroll through pages vertically instead of horizontally */\r\n    private boolean swipeVertical = true;\r\n\r\n    private boolean enableSwipe = true;\r\n\r\n    private boolean doubletapEnabled = true;\r\n\r\n    private boolean nightMode = false;\r\n\r\n    private boolean pageSnap = true;\r\n\r\n    /** Pdfium core for loading and rendering PDFs */\r\n    private PdfiumCore pdfiumCore;\r\n\r\n    private ScrollHandle scrollHandle;\r\n\r\n    private boolean isScrollHandleInit = false;\r\n\r\n    ScrollHandle getScrollHandle() {\r\n        return scrollHandle;\r\n    }\r\n\r\n    /**\r\n     * True if bitmap should use ARGB_8888 format and take more memory\r\n     * False if bitmap should be compressed by using RGB_565 format and take less memory\r\n     */\r\n    private boolean bestQuality = false;\r\n\r\n    /**\r\n     * True if annotations should be rendered\r\n     * False otherwise\r\n     */\r\n    private boolean annotationRendering = false;\r\n\r\n    /**\r\n     * True if the view should render during scaling<br/>\r\n     * Can not be forced on older API versions (< Build.VERSION_CODES.KITKAT) as the GestureDetector does\r\n     * not detect scrolling while scaling.<br/>\r\n     * False otherwise\r\n     */\r\n    private boolean renderDuringScale = false;\r\n\r\n    /** Antialiasing and bitmap filtering */\r\n    private boolean enableAntialiasing = true;\r\n    private PaintFlagsDrawFilter antialiasFilter =\r\n            new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);\r\n\r\n    /** Spacing between pages, in px */\r\n    private int spacingPx = 0;\r\n\r\n    /** Add dynamic spacing to fit each page separately on the screen. */\r\n    private boolean autoSpacing = false;\r\n\r\n    /** Fling a single page at a time */\r\n    private boolean pageFling = true;\r\n\r\n    /** Pages numbers used when calling onDrawAllListener */\r\n    private List<Integer> onDrawPagesNums = new ArrayList<>(10);\r\n\r\n    /** Holds info whether view has been added to layout and has width and height */\r\n    private boolean hasSize = false;\r\n\r\n    /** Holds last used Configurator that should be loaded when view has size */\r\n    private Configurator waitingDocumentConfigurator;\r\n\r\n    /** Construct the initial view */\r\n    public PDFView(Context context, AttributeSet set) {\r\n        super(context, set);\r\n\r\n        renderingHandlerThread = new HandlerThread(\"PDF renderer\");\r\n\r\n        if (isInEditMode()) {\r\n            return;\r\n        }\r\n\r\n        cacheManager = new CacheManager();\r\n        animationManager = new AnimationManager(this);\r\n        dragPinchManager = new DragPinchManager(this, animationManager);\r\n        pagesLoader = new PagesLoader(this);\r\n\r\n        paint = new Paint();\r\n        debugPaint = new Paint();\r\n        debugPaint.setStyle(Style.STROKE);\r\n\r\n        pdfiumCore = new PdfiumCore(context);\r\n        setWillNotDraw(false);\r\n    }\r\n\r\n    private void load(DocumentSource docSource, String password) {\r\n        load(docSource, password, null);\r\n    }\r\n\r\n    private void load(DocumentSource docSource, String password, int[] userPages) {\r\n\r\n        if (!recycled) {\r\n            throw new IllegalStateException(\"Don't call load on a PDF View without recycling it first.\");\r\n        }\r\n\r\n        recycled = false;\r\n        // Start decoding document\r\n        decodingAsyncTask = new DecodingAsyncTask(docSource, password, userPages, this, pdfiumCore);\r\n        decodingAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\r\n    }\r\n\r\n    /**\r\n     * Go to the given page.\r\n     *\r\n     * @param page Page index.\r\n     */\r\n    public void jumpTo(int page, boolean withAnimation) {\r\n        if (pdfFile == null) {\r\n            return;\r\n        }\r\n\r\n        page = pdfFile.determineValidPageNumberFrom(page);\r\n        float offset = page == 0 ? 0 : -pdfFile.getPageOffset(page, zoom);\r\n        if (swipeVertical) {\r\n            if (withAnimation) {\r\n                animationManager.startYAnimation(currentYOffset, offset);\r\n            } else {\r\n                moveTo(currentXOffset, offset);\r\n            }\r\n        } else {\r\n            if (withAnimation) {\r\n                animationManager.startXAnimation(currentXOffset, offset);\r\n            } else {\r\n                moveTo(offset, currentYOffset);\r\n            }\r\n        }\r\n        showPage(page);\r\n    }\r\n\r\n    public void jumpTo(int page) {\r\n        jumpTo(page, false);\r\n    }\r\n\r\n    void showPage(int pageNb) {\r\n        if (recycled) {\r\n            return;\r\n        }\r\n\r\n        // Check the page number and makes the\r\n        // difference between UserPages and DocumentPages\r\n        pageNb = pdfFile.determineValidPageNumberFrom(pageNb);\r\n        currentPage = pageNb;\r\n\r\n        loadPages();\r\n\r\n        if (scrollHandle != null && !documentFitsView()) {\r\n            scrollHandle.setPageNum(currentPage + 1);\r\n        }\r\n\r\n        callbacks.callOnPageChange(currentPage, pdfFile.getPagesCount());\r\n    }\r\n\r\n    /**\r\n     * Get current position as ratio of document length to visible area.\r\n     * 0 means that document start is visible, 1 that document end is visible\r\n     *\r\n     * @return offset between 0 and 1\r\n     */\r\n    public float getPositionOffset() {\r\n        float offset;\r\n        if (swipeVertical) {\r\n            offset = -currentYOffset / (pdfFile.getDocLen(zoom) - getHeight());\r\n        } else {\r\n            offset = -currentXOffset / (pdfFile.getDocLen(zoom) - getWidth());\r\n        }\r\n        return MathUtils.limit(offset, 0, 1);\r\n    }\r\n\r\n    /**\r\n     * @param progress   must be between 0 and 1\r\n     * @param moveHandle whether to move scroll handle\r\n     * @see PDFView#getPositionOffset()\r\n     */\r\n    public void setPositionOffset(float progress, boolean moveHandle) {\r\n        if (swipeVertical) {\r\n            moveTo(currentXOffset, (-pdfFile.getDocLen(zoom) + getHeight()) * progress, moveHandle);\r\n        } else {\r\n            moveTo((-pdfFile.getDocLen(zoom) + getWidth()) * progress, currentYOffset, moveHandle);\r\n        }\r\n        loadPageByOffset();\r\n    }\r\n\r\n    public void setPositionOffset(float progress) {\r\n        setPositionOffset(progress, true);\r\n    }\r\n\r\n    public void stopFling() {\r\n        animationManager.stopFling();\r\n    }\r\n\r\n    public int getPageCount() {\r\n        if (pdfFile == null) {\r\n            return 0;\r\n        }\r\n        return pdfFile.getPagesCount();\r\n    }\r\n\r\n    public void setSwipeEnabled(boolean enableSwipe) {\r\n        this.enableSwipe = enableSwipe;\r\n    }\r\n\r\n    public void setNightMode(boolean nightMode) {\r\n        this.nightMode = nightMode;\r\n        if (nightMode) {\r\n            ColorMatrix colorMatrixInverted =\r\n                    new ColorMatrix(new float[]{\r\n                            -1, 0, 0, 0, 255,\r\n                            0, -1, 0, 0, 255,\r\n                            0, 0, -1, 0, 255,\r\n                            0, 0, 0, 1, 0});\r\n\r\n            ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrixInverted);\r\n            paint.setColorFilter(filter);\r\n        } else {\r\n            paint.setColorFilter(null);\r\n        }\r\n    }\r\n\r\n    void enableDoubletap(boolean enableDoubletap) {\r\n        this.doubletapEnabled = enableDoubletap;\r\n    }\r\n\r\n    boolean isDoubletapEnabled() {\r\n        return doubletapEnabled;\r\n    }\r\n\r\n    void onPageError(PageRenderingException ex) {\r\n        if (!callbacks.callOnPageError(ex.getPage(), ex.getCause())) {\r\n            Log.e(TAG, \"Cannot open page \" + ex.getPage(), ex.getCause());\r\n        }\r\n    }\r\n\r\n    public void recycle() {\r\n        waitingDocumentConfigurator = null;\r\n\r\n        animationManager.stopAll();\r\n        dragPinchManager.disable();\r\n\r\n        // Stop tasks\r\n        if (renderingHandler != null) {\r\n            renderingHandler.stop();\r\n            renderingHandler.removeMessages(RenderingHandler.MSG_RENDER_TASK);\r\n        }\r\n        if (decodingAsyncTask != null) {\r\n            decodingAsyncTask.cancel(true);\r\n        }\r\n\r\n        // Clear caches\r\n        cacheManager.recycle();\r\n\r\n        if (scrollHandle != null && isScrollHandleInit) {\r\n            scrollHandle.destroyLayout();\r\n        }\r\n\r\n        if (pdfFile != null) {\r\n            pdfFile.dispose();\r\n            pdfFile = null;\r\n        }\r\n\r\n        renderingHandler = null;\r\n        scrollHandle = null;\r\n        isScrollHandleInit = false;\r\n        currentXOffset = currentYOffset = 0;\r\n        zoom = 1f;\r\n        recycled = true;\r\n        callbacks = new Callbacks();\r\n        state = State.DEFAULT;\r\n    }\r\n\r\n    public boolean isRecycled() {\r\n        return recycled;\r\n    }\r\n\r\n    /** Handle fling animation */\r\n    @Override\r\n    public void computeScroll() {\r\n        super.computeScroll();\r\n        if (isInEditMode()) {\r\n            return;\r\n        }\r\n        animationManager.computeFling();\r\n    }\r\n\r\n    @Override\r\n    protected void onDetachedFromWindow() {\r\n        recycle();\r\n        if (renderingHandlerThread != null) {\r\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\r\n                renderingHandlerThread.quitSafely();\r\n            } else {\r\n                renderingHandlerThread.quit();\r\n            }\r\n            renderingHandlerThread = null;\r\n        }\r\n        super.onDetachedFromWindow();\r\n    }\r\n\r\n    @Override\r\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\r\n        hasSize = true;\r\n        if (waitingDocumentConfigurator != null) {\r\n            waitingDocumentConfigurator.load();\r\n        }\r\n        if (isInEditMode() || state != State.SHOWN) {\r\n            return;\r\n        }\r\n\r\n        // calculates the position of the point which in the center of view relative to big strip\r\n        float centerPointInStripXOffset = -currentXOffset + oldw * 0.5f;\r\n        float centerPointInStripYOffset = -currentYOffset + oldh * 0.5f;\r\n\r\n        float relativeCenterPointInStripXOffset;\r\n        float relativeCenterPointInStripYOffset;\r\n\r\n        if (swipeVertical){\r\n            relativeCenterPointInStripXOffset = centerPointInStripXOffset / pdfFile.getMaxPageWidth();\r\n            relativeCenterPointInStripYOffset = centerPointInStripYOffset / pdfFile.getDocLen(zoom);\r\n        }else {\r\n            relativeCenterPointInStripXOffset = centerPointInStripXOffset / pdfFile.getDocLen(zoom);\r\n            relativeCenterPointInStripYOffset = centerPointInStripYOffset / pdfFile.getMaxPageHeight();\r\n        }\r\n\r\n        animationManager.stopAll();\r\n        pdfFile.recalculatePageSizes(new Size(w, h));\r\n\r\n        if (swipeVertical) {\r\n            currentXOffset = -relativeCenterPointInStripXOffset * pdfFile.getMaxPageWidth() + w * 0.5f;\r\n            currentYOffset = -relativeCenterPointInStripYOffset * pdfFile.getDocLen(zoom) + h * 0.5f ;\r\n        }else {\r\n            currentXOffset = -relativeCenterPointInStripXOffset * pdfFile.getDocLen(zoom) + w * 0.5f;\r\n            currentYOffset = -relativeCenterPointInStripYOffset * pdfFile.getMaxPageHeight() + h * 0.5f;\r\n        }\r\n        moveTo(currentXOffset,currentYOffset);\r\n        loadPageByOffset();\r\n    }\r\n\r\n    @Override\r\n    public boolean canScrollHorizontally(int direction) {\r\n        if (pdfFile == null) {\r\n            return true;\r\n        }\r\n\r\n        if (swipeVertical) {\r\n            if (direction < 0 && currentXOffset < 0) {\r\n                return true;\r\n            } else if (direction > 0 && currentXOffset + toCurrentScale(pdfFile.getMaxPageWidth()) > getWidth()) {\r\n                return true;\r\n            }\r\n        } else {\r\n            if (direction < 0 && currentXOffset < 0) {\r\n                return true;\r\n            } else if (direction > 0 && currentXOffset + pdfFile.getDocLen(zoom) > getWidth()) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean canScrollVertically(int direction) {\r\n        if (pdfFile == null) {\r\n            return true;\r\n        }\r\n\r\n        if (swipeVertical) {\r\n            if (direction < 0 && currentYOffset < 0) {\r\n                return true;\r\n            } else if (direction > 0 && currentYOffset + pdfFile.getDocLen(zoom) > getHeight()) {\r\n                return true;\r\n            }\r\n        } else {\r\n            if (direction < 0 && currentYOffset < 0) {\r\n                return true;\r\n            } else if (direction > 0 && currentYOffset + toCurrentScale(pdfFile.getMaxPageHeight()) > getHeight()) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected void onDraw(Canvas canvas) {\r\n        if (isInEditMode()) {\r\n            return;\r\n        }\r\n        // As I said in this class javadoc, we can think of this canvas as a huge\r\n        // strip on which we draw all the images. We actually only draw the rendered\r\n        // parts, of course, but we render them in the place they belong in this huge\r\n        // strip.\r\n\r\n        // That's where Canvas.translate(x, y) becomes very helpful.\r\n        // This is the situation :\r\n        //  _______________________________________________\r\n        // |   \t\t\t |\t\t\t\t\t \t\t\t   |\r\n        // | the actual  |\t\t\t\t\tThe big strip  |\r\n        // |\tcanvas\t | \t\t\t\t\t\t\t\t   |\r\n        // |_____________|\t\t\t\t\t\t\t\t   |\r\n        // |_______________________________________________|\r\n        //\r\n        // If the rendered part is on the bottom right corner of the strip\r\n        // we can draw it but we won't see it because the canvas is not big enough.\r\n\r\n        // But if we call translate(-X, -Y) on the canvas just before drawing the object :\r\n        //  _______________________________________________\r\n        // |   \t\t\t  \t\t\t\t\t  _____________|\r\n        // |   The big strip     \t\t\t |\t\t\t   |\r\n        // |\t\t    \t\t\t\t\t |\tthe actual |\r\n        // |\t\t\t\t\t\t\t\t |\tcanvas\t   |\r\n        // |_________________________________|_____________|\r\n        //\r\n        // The object will be on the canvas.\r\n        // This technique is massively used in this method, and allows\r\n        // abstraction of the screen position when rendering the parts.\r\n\r\n        // Draws background\r\n\r\n        if (enableAntialiasing) {\r\n            canvas.setDrawFilter(antialiasFilter);\r\n        }\r\n\r\n        Drawable bg = getBackground();\r\n        if (bg == null) {\r\n            canvas.drawColor(nightMode ? Color.BLACK : Color.WHITE);\r\n        } else {\r\n            bg.draw(canvas);\r\n        }\r\n\r\n        if (recycled) {\r\n            return;\r\n        }\r\n\r\n        if (state != State.SHOWN) {\r\n            return;\r\n        }\r\n\r\n        // Moves the canvas before drawing any element\r\n        float currentXOffset = this.currentXOffset;\r\n        float currentYOffset = this.currentYOffset;\r\n        canvas.translate(currentXOffset, currentYOffset);\r\n\r\n        // Draws thumbnails\r\n        for (PagePart part : cacheManager.getThumbnails()) {\r\n            drawPart(canvas, part);\r\n\r\n        }\r\n\r\n        // Draws parts\r\n        for (PagePart part : cacheManager.getPageParts()) {\r\n            drawPart(canvas, part);\r\n            if (callbacks.getOnDrawAll() != null\r\n                    && !onDrawPagesNums.contains(part.getPage())) {\r\n                onDrawPagesNums.add(part.getPage());\r\n            }\r\n        }\r\n\r\n        for (Integer page : onDrawPagesNums) {\r\n            drawWithListener(canvas, page, callbacks.getOnDrawAll());\r\n        }\r\n        onDrawPagesNums.clear();\r\n\r\n        drawWithListener(canvas, currentPage, callbacks.getOnDraw());\r\n\r\n        // Restores the canvas position\r\n        canvas.translate(-currentXOffset, -currentYOffset);\r\n    }\r\n\r\n    private void drawWithListener(Canvas canvas, int page, OnDrawListener listener) {\r\n        if (listener != null) {\r\n            float translateX, translateY;\r\n            if (swipeVertical) {\r\n                translateX = 0;\r\n                translateY = pdfFile.getPageOffset(page, zoom);\r\n            } else {\r\n                translateY = 0;\r\n                translateX = pdfFile.getPageOffset(page, zoom);\r\n            }\r\n\r\n            canvas.translate(translateX, translateY);\r\n            SizeF size = pdfFile.getPageSize(page);\r\n            listener.onLayerDrawn(canvas,\r\n                    toCurrentScale(size.getWidth()),\r\n                    toCurrentScale(size.getHeight()),\r\n                    page);\r\n\r\n            canvas.translate(-translateX, -translateY);\r\n        }\r\n    }\r\n\r\n    /** Draw a given PagePart on the canvas */\r\n    private void drawPart(Canvas canvas, PagePart part) {\r\n        // Can seem strange, but avoid lot of calls\r\n        RectF pageRelativeBounds = part.getPageRelativeBounds();\r\n        Bitmap renderedBitmap = part.getRenderedBitmap();\r\n\r\n        if (renderedBitmap.isRecycled()) {\r\n            return;\r\n        }\r\n\r\n        // Move to the target page\r\n        float localTranslationX = 0;\r\n        float localTranslationY = 0;\r\n        SizeF size = pdfFile.getPageSize(part.getPage());\r\n\r\n        if (swipeVertical) {\r\n            localTranslationY = pdfFile.getPageOffset(part.getPage(), zoom);\r\n            float maxWidth = pdfFile.getMaxPageWidth();\r\n            localTranslationX = toCurrentScale(maxWidth - size.getWidth()) / 2;\r\n        } else {\r\n            localTranslationX = pdfFile.getPageOffset(part.getPage(), zoom);\r\n            float maxHeight = pdfFile.getMaxPageHeight();\r\n            localTranslationY = toCurrentScale(maxHeight - size.getHeight()) / 2;\r\n        }\r\n        canvas.translate(localTranslationX, localTranslationY);\r\n\r\n        Rect srcRect = new Rect(0, 0, renderedBitmap.getWidth(),\r\n                renderedBitmap.getHeight());\r\n\r\n        float offsetX = toCurrentScale(pageRelativeBounds.left * size.getWidth());\r\n        float offsetY = toCurrentScale(pageRelativeBounds.top * size.getHeight());\r\n        float width = toCurrentScale(pageRelativeBounds.width() * size.getWidth());\r\n        float height = toCurrentScale(pageRelativeBounds.height() * size.getHeight());\r\n\r\n        // If we use float values for this rectangle, there will be\r\n        // a possible gap between page parts, especially when\r\n        // the zoom level is high.\r\n        RectF dstRect = new RectF((int) offsetX, (int) offsetY,\r\n                (int) (offsetX + width),\r\n                (int) (offsetY + height));\r\n\r\n        // Check if bitmap is in the screen\r\n        float translationX = currentXOffset + localTranslationX;\r\n        float translationY = currentYOffset + localTranslationY;\r\n        if (translationX + dstRect.left >= getWidth() || translationX + dstRect.right <= 0 ||\r\n                translationY + dstRect.top >= getHeight() || translationY + dstRect.bottom <= 0) {\r\n            canvas.translate(-localTranslationX, -localTranslationY);\r\n            return;\r\n        }\r\n\r\n        canvas.drawBitmap(renderedBitmap, srcRect, dstRect, paint);\r\n\r\n        if (Constants.DEBUG_MODE) {\r\n            debugPaint.setColor(part.getPage() % 2 == 0 ? Color.RED : Color.BLUE);\r\n            canvas.drawRect(dstRect, debugPaint);\r\n        }\r\n\r\n        // Restore the canvas position\r\n        canvas.translate(-localTranslationX, -localTranslationY);\r\n\r\n    }\r\n\r\n    /**\r\n     * Load all the parts around the center of the screen,\r\n     * taking into account X and Y offsets, zoom level, and\r\n     * the current page displayed\r\n     */\r\n    public void loadPages() {\r\n        if (pdfFile == null || renderingHandler == null) {\r\n            return;\r\n        }\r\n\r\n        // Cancel all current tasks\r\n        renderingHandler.removeMessages(RenderingHandler.MSG_RENDER_TASK);\r\n        cacheManager.makeANewSet();\r\n\r\n        pagesLoader.loadPages();\r\n        redraw();\r\n    }\r\n\r\n    /** Called when the PDF is loaded */\r\n    void loadComplete(PdfFile pdfFile) {\r\n        state = State.LOADED;\r\n\r\n        this.pdfFile = pdfFile;\r\n\r\n        if (!renderingHandlerThread.isAlive()) {\r\n            renderingHandlerThread.start();\r\n        }\r\n        renderingHandler = new RenderingHandler(renderingHandlerThread.getLooper(), this);\r\n        renderingHandler.start();\r\n\r\n        if (scrollHandle != null) {\r\n            scrollHandle.setupLayout(this);\r\n            isScrollHandleInit = true;\r\n        }\r\n\r\n        dragPinchManager.enable();\r\n\r\n        callbacks.callOnLoadComplete(pdfFile.getPagesCount());\r\n\r\n        jumpTo(defaultPage, false);\r\n    }\r\n\r\n    void loadError(Throwable t) {\r\n        state = State.ERROR;\r\n        // store reference, because callbacks will be cleared in recycle() method\r\n        OnErrorListener onErrorListener = callbacks.getOnError();\r\n        recycle();\r\n        invalidate();\r\n        if (onErrorListener != null) {\r\n            onErrorListener.onError(t);\r\n        } else {\r\n            Log.e(\"PDFView\", \"load pdf error\", t);\r\n        }\r\n    }\r\n\r\n    void redraw() {\r\n        invalidate();\r\n    }\r\n\r\n    /**\r\n     * Called when a rendering task is over and\r\n     * a PagePart has been freshly created.\r\n     *\r\n     * @param part The created PagePart.\r\n     */\r\n    public void onBitmapRendered(PagePart part) {\r\n        // when it is first rendered part\r\n        if (state == State.LOADED) {\r\n            state = State.SHOWN;\r\n            callbacks.callOnRender(pdfFile.getPagesCount());\r\n        }\r\n\r\n        if (part.isThumbnail()) {\r\n            cacheManager.cacheThumbnail(part);\r\n        } else {\r\n            cacheManager.cachePart(part);\r\n        }\r\n        redraw();\r\n    }\r\n\r\n    public void moveTo(float offsetX, float offsetY) {\r\n        moveTo(offsetX, offsetY, true);\r\n    }\r\n\r\n    /**\r\n     * Move to the given X and Y offsets, but check them ahead of time\r\n     * to be sure not to go outside the the big strip.\r\n     *\r\n     * @param offsetX    The big strip X offset to use as the left border of the screen.\r\n     * @param offsetY    The big strip Y offset to use as the right border of the screen.\r\n     * @param moveHandle whether to move scroll handle or not\r\n     */\r\n    public void moveTo(float offsetX, float offsetY, boolean moveHandle) {\r\n        if (swipeVertical) {\r\n            // Check X offset\r\n            float scaledPageWidth = toCurrentScale(pdfFile.getMaxPageWidth());\r\n            if (scaledPageWidth < getWidth()) {\r\n                offsetX = getWidth() / 2 - scaledPageWidth / 2;\r\n            } else {\r\n                if (offsetX > 0) {\r\n                    offsetX = 0;\r\n                } else if (offsetX + scaledPageWidth < getWidth()) {\r\n                    offsetX = getWidth() - scaledPageWidth;\r\n                }\r\n            }\r\n\r\n            // Check Y offset\r\n            float contentHeight = pdfFile.getDocLen(zoom);\r\n            if (contentHeight < getHeight()) { // whole document height visible on screen\r\n                offsetY = (getHeight() - contentHeight) / 2;\r\n            } else {\r\n                if (offsetY > 0) { // top visible\r\n                    offsetY = 0;\r\n                } else if (offsetY + contentHeight < getHeight()) { // bottom visible\r\n                    offsetY = -contentHeight + getHeight();\r\n                }\r\n            }\r\n\r\n            if (offsetY < currentYOffset) {\r\n                scrollDir = ScrollDir.END;\r\n            } else if (offsetY > currentYOffset) {\r\n                scrollDir = ScrollDir.START;\r\n            } else {\r\n                scrollDir = ScrollDir.NONE;\r\n            }\r\n        } else {\r\n            // Check Y offset\r\n            float scaledPageHeight = toCurrentScale(pdfFile.getMaxPageHeight());\r\n            if (scaledPageHeight < getHeight()) {\r\n                offsetY = getHeight() / 2 - scaledPageHeight / 2;\r\n            } else {\r\n                if (offsetY > 0) {\r\n                    offsetY = 0;\r\n                } else if (offsetY + scaledPageHeight < getHeight()) {\r\n                    offsetY = getHeight() - scaledPageHeight;\r\n                }\r\n            }\r\n\r\n            // Check X offset\r\n            float contentWidth = pdfFile.getDocLen(zoom);\r\n            if (contentWidth < getWidth()) { // whole document width visible on screen\r\n                offsetX = (getWidth() - contentWidth) / 2;\r\n            } else {\r\n                if (offsetX > 0) { // left visible\r\n                    offsetX = 0;\r\n                } else if (offsetX + contentWidth < getWidth()) { // right visible\r\n                    offsetX = -contentWidth + getWidth();\r\n                }\r\n            }\r\n\r\n            if (offsetX < currentXOffset) {\r\n                scrollDir = ScrollDir.END;\r\n            } else if (offsetX > currentXOffset) {\r\n                scrollDir = ScrollDir.START;\r\n            } else {\r\n                scrollDir = ScrollDir.NONE;\r\n            }\r\n        }\r\n\r\n        currentXOffset = offsetX;\r\n        currentYOffset = offsetY;\r\n        float positionOffset = getPositionOffset();\r\n\r\n        if (moveHandle && scrollHandle != null && !documentFitsView()) {\r\n            scrollHandle.setScroll(positionOffset);\r\n        }\r\n\r\n        callbacks.callOnPageScroll(getCurrentPage(), positionOffset);\r\n\r\n        redraw();\r\n    }\r\n\r\n    void loadPageByOffset() {\r\n        if (0 == pdfFile.getPagesCount()) {\r\n            return;\r\n        }\r\n\r\n        float offset, screenCenter;\r\n        if (swipeVertical) {\r\n            offset = currentYOffset;\r\n            screenCenter = ((float) getHeight()) / 2;\r\n        } else {\r\n            offset = currentXOffset;\r\n            screenCenter = ((float) getWidth()) / 2;\r\n        }\r\n\r\n        int page = pdfFile.getPageAtOffset(-(offset - screenCenter), zoom);\r\n\r\n        if (page >= 0 && page <= pdfFile.getPagesCount() - 1 && page != getCurrentPage()) {\r\n            showPage(page);\r\n        } else {\r\n            loadPages();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Animate to the nearest snapping position for the current SnapPolicy\r\n     */\r\n    public void performPageSnap() {\r\n        if (!pageSnap || pdfFile == null || pdfFile.getPagesCount() == 0) {\r\n            return;\r\n        }\r\n        int centerPage = findFocusPage(currentXOffset, currentYOffset);\r\n        SnapEdge edge = findSnapEdge(centerPage);\r\n        if (edge == SnapEdge.NONE) {\r\n            return;\r\n        }\r\n\r\n        float offset = snapOffsetForPage(centerPage, edge);\r\n        if (swipeVertical) {\r\n            animationManager.startYAnimation(currentYOffset, -offset);\r\n        } else {\r\n            animationManager.startXAnimation(currentXOffset, -offset);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Find the edge to snap to when showing the specified page\r\n     */\r\n    SnapEdge findSnapEdge(int page) {\r\n        if (!pageSnap || page < 0) {\r\n            return SnapEdge.NONE;\r\n        }\r\n        float currentOffset = swipeVertical ? currentYOffset : currentXOffset;\r\n        float offset = -pdfFile.getPageOffset(page, zoom);\r\n        int length = swipeVertical ? getHeight() : getWidth();\r\n        float pageLength = pdfFile.getPageLength(page, zoom);\r\n\r\n        if (length >= pageLength) {\r\n            return SnapEdge.CENTER;\r\n        } else if (currentOffset >= offset) {\r\n            return SnapEdge.START;\r\n        } else if (offset - pageLength > currentOffset - length) {\r\n            return SnapEdge.END;\r\n        } else {\r\n            return SnapEdge.NONE;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Get the offset to move to in order to snap to the page\r\n     */\r\n    float snapOffsetForPage(int pageIndex, SnapEdge edge) {\r\n        float offset = pdfFile.getPageOffset(pageIndex, zoom);\r\n\r\n        float length = swipeVertical ? getHeight() : getWidth();\r\n        float pageLength = pdfFile.getPageLength(pageIndex, zoom);\r\n\r\n        if (edge == SnapEdge.CENTER) {\r\n            offset = offset - length / 2f + pageLength / 2f;\r\n        } else if (edge == SnapEdge.END) {\r\n            offset = offset - length + pageLength;\r\n        }\r\n        return offset;\r\n    }\r\n\r\n    int findFocusPage(float xOffset, float yOffset) {\r\n        float currOffset = swipeVertical ? yOffset : xOffset;\r\n        float length = swipeVertical ? getHeight() : getWidth();\r\n        // make sure first and last page can be found\r\n        if (currOffset > -1) {\r\n            return 0;\r\n        } else if (currOffset < -pdfFile.getDocLen(zoom) + length + 1) {\r\n            return pdfFile.getPagesCount() - 1;\r\n        }\r\n        // else find page in center\r\n        float center = currOffset - length / 2f;\r\n        return pdfFile.getPageAtOffset(-center, zoom);\r\n    }\r\n\r\n    /**\r\n     * @return true if single page fills the entire screen in the scrolling direction\r\n     */\r\n    public boolean pageFillsScreen() {\r\n        float start = -pdfFile.getPageOffset(currentPage, zoom);\r\n        float end = start - pdfFile.getPageLength(currentPage, zoom);\r\n        if (isSwipeVertical()) {\r\n            return start > currentYOffset && end < currentYOffset - getHeight();\r\n        } else {\r\n            return start > currentXOffset && end < currentXOffset - getWidth();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Move relatively to the current position.\r\n     *\r\n     * @param dx The X difference you want to apply.\r\n     * @param dy The Y difference you want to apply.\r\n     * @see #moveTo(float, float)\r\n     */\r\n    public void moveRelativeTo(float dx, float dy) {\r\n        moveTo(currentXOffset + dx, currentYOffset + dy);\r\n    }\r\n\r\n    /**\r\n     * Change the zoom level\r\n     */\r\n    public void zoomTo(float zoom) {\r\n        this.zoom = zoom;\r\n    }\r\n\r\n    /**\r\n     * Change the zoom level, relatively to a pivot point.\r\n     * It will call moveTo() to make sure the given point stays\r\n     * in the middle of the screen.\r\n     *\r\n     * @param zoom  The zoom level.\r\n     * @param pivot The point on the screen that should stays.\r\n     */\r\n    public void zoomCenteredTo(float zoom, PointF pivot) {\r\n        float dzoom = zoom / this.zoom;\r\n        zoomTo(zoom);\r\n        float baseX = currentXOffset * dzoom;\r\n        float baseY = currentYOffset * dzoom;\r\n        baseX += (pivot.x - pivot.x * dzoom);\r\n        baseY += (pivot.y - pivot.y * dzoom);\r\n        moveTo(baseX, baseY);\r\n    }\r\n\r\n    /**\r\n     * @see #zoomCenteredTo(float, PointF)\r\n     */\r\n    public void zoomCenteredRelativeTo(float dzoom, PointF pivot) {\r\n        zoomCenteredTo(zoom * dzoom, pivot);\r\n    }\r\n\r\n    /**\r\n     * Checks if whole document can be displayed on screen, doesn't include zoom\r\n     *\r\n     * @return true if whole document can displayed at once, false otherwise\r\n     */\r\n    public boolean documentFitsView() {\r\n        float len = pdfFile.getDocLen(1);\r\n        if (swipeVertical) {\r\n            return len < getHeight();\r\n        } else {\r\n            return len < getWidth();\r\n        }\r\n    }\r\n\r\n    public void fitToWidth(int page) {\r\n        if (state != State.SHOWN) {\r\n            Log.e(TAG, \"Cannot fit, document not rendered yet\");\r\n            return;\r\n        }\r\n        zoomTo(getWidth() / pdfFile.getPageSize(page).getWidth());\r\n        jumpTo(page);\r\n    }\r\n\r\n    public SizeF getPageSize(int pageIndex) {\r\n        if (pdfFile == null) {\r\n            return new SizeF(0, 0);\r\n        }\r\n        return pdfFile.getPageSize(pageIndex);\r\n    }\r\n\r\n    public int getCurrentPage() {\r\n        return currentPage;\r\n    }\r\n\r\n    public float getCurrentXOffset() {\r\n        return currentXOffset;\r\n    }\r\n\r\n    public float getCurrentYOffset() {\r\n        return currentYOffset;\r\n    }\r\n\r\n    public float toRealScale(float size) {\r\n        return size / zoom;\r\n    }\r\n\r\n    public float toCurrentScale(float size) {\r\n        return size * zoom;\r\n    }\r\n\r\n    public float getZoom() {\r\n        return zoom;\r\n    }\r\n\r\n    public boolean isZooming() {\r\n        return zoom != minZoom;\r\n    }\r\n\r\n    private void setDefaultPage(int defaultPage) {\r\n        this.defaultPage = defaultPage;\r\n    }\r\n\r\n    public void resetZoom() {\r\n        zoomTo(minZoom);\r\n    }\r\n\r\n    public void resetZoomWithAnimation() {\r\n        zoomWithAnimation(minZoom);\r\n    }\r\n\r\n    public void zoomWithAnimation(float centerX, float centerY, float scale) {\r\n        animationManager.startZoomAnimation(centerX, centerY, zoom, scale);\r\n    }\r\n\r\n    public void zoomWithAnimation(float scale) {\r\n        animationManager.startZoomAnimation(getWidth() / 2, getHeight() / 2, zoom, scale);\r\n    }\r\n\r\n    private void setScrollHandle(ScrollHandle scrollHandle) {\r\n        this.scrollHandle = scrollHandle;\r\n    }\r\n\r\n    /**\r\n     * Get page number at given offset\r\n     *\r\n     * @param positionOffset scroll offset between 0 and 1\r\n     * @return page number at given offset, starting from 0\r\n     */\r\n    public int getPageAtPositionOffset(float positionOffset) {\r\n        return pdfFile.getPageAtOffset(pdfFile.getDocLen(zoom) * positionOffset, zoom);\r\n    }\r\n\r\n    public float getMinZoom() {\r\n        return minZoom;\r\n    }\r\n\r\n    public void setMinZoom(float minZoom) {\r\n        this.minZoom = minZoom;\r\n    }\r\n\r\n    public float getMidZoom() {\r\n        return midZoom;\r\n    }\r\n\r\n    public void setMidZoom(float midZoom) {\r\n        this.midZoom = midZoom;\r\n    }\r\n\r\n    public float getMaxZoom() {\r\n        return maxZoom;\r\n    }\r\n\r\n    public void setMaxZoom(float maxZoom) {\r\n        this.maxZoom = maxZoom;\r\n    }\r\n\r\n    public void useBestQuality(boolean bestQuality) {\r\n        this.bestQuality = bestQuality;\r\n    }\r\n\r\n    public boolean isBestQuality() {\r\n        return bestQuality;\r\n    }\r\n\r\n    public boolean isSwipeVertical() {\r\n        return swipeVertical;\r\n    }\r\n\r\n    public boolean isSwipeEnabled() {\r\n        return enableSwipe;\r\n    }\r\n\r\n    private void setSwipeVertical(boolean swipeVertical) {\r\n        this.swipeVertical = swipeVertical;\r\n    }\r\n\r\n    public void enableAnnotationRendering(boolean annotationRendering) {\r\n        this.annotationRendering = annotationRendering;\r\n    }\r\n\r\n    public boolean isAnnotationRendering() {\r\n        return annotationRendering;\r\n    }\r\n\r\n    public void enableRenderDuringScale(boolean renderDuringScale) {\r\n        this.renderDuringScale = renderDuringScale;\r\n    }\r\n\r\n    public boolean isAntialiasing() {\r\n        return enableAntialiasing;\r\n    }\r\n\r\n    public void enableAntialiasing(boolean enableAntialiasing) {\r\n        this.enableAntialiasing = enableAntialiasing;\r\n    }\r\n\r\n    public int getSpacingPx() {\r\n        return spacingPx;\r\n    }\r\n\r\n    public boolean isAutoSpacingEnabled() {\r\n        return autoSpacing;\r\n    }\r\n\r\n    public void setPageFling(boolean pageFling) {\r\n        this.pageFling = pageFling;\r\n    }\r\n\r\n    public boolean isPageFlingEnabled() {\r\n        return pageFling;\r\n    }\r\n\r\n    private void setSpacing(int spacingDp) {\r\n        this.spacingPx = Util.getDP(getContext(), spacingDp);\r\n    }\r\n\r\n    private void setAutoSpacing(boolean autoSpacing) {\r\n        this.autoSpacing = autoSpacing;\r\n    }\r\n\r\n    private void setPageFitPolicy(FitPolicy pageFitPolicy) {\r\n        this.pageFitPolicy = pageFitPolicy;\r\n    }\r\n\r\n    public FitPolicy getPageFitPolicy() {\r\n        return pageFitPolicy;\r\n    }\r\n\r\n    private void setFitEachPage(boolean fitEachPage) {\r\n        this.fitEachPage = fitEachPage;\r\n    }\r\n\r\n    public boolean isFitEachPage() {\r\n        return fitEachPage;\r\n    }\r\n\r\n    public boolean isPageSnap() {\r\n        return pageSnap;\r\n    }\r\n\r\n    public void setPageSnap(boolean pageSnap) {\r\n        this.pageSnap = pageSnap;\r\n    }\r\n\r\n    public boolean doRenderDuringScale() {\r\n        return renderDuringScale;\r\n    }\r\n\r\n    /** Returns null if document is not loaded */\r\n    public PdfDocument.Meta getDocumentMeta() {\r\n        if (pdfFile == null) {\r\n            return null;\r\n        }\r\n        return pdfFile.getMetaData();\r\n    }\r\n\r\n    /** Will be empty until document is loaded */\r\n    public List<PdfDocument.Bookmark> getTableOfContents() {\r\n        if (pdfFile == null) {\r\n            return Collections.emptyList();\r\n        }\r\n        return pdfFile.getBookmarks();\r\n    }\r\n\r\n    /** Will be empty until document is loaded */\r\n    public List<PdfDocument.Link> getLinks(int page) {\r\n        if (pdfFile == null) {\r\n            return Collections.emptyList();\r\n        }\r\n        return pdfFile.getPageLinks(page);\r\n    }\r\n\r\n    /** Use an asset file as the pdf source */\r\n    public Configurator fromAsset(String assetName) {\r\n        return new Configurator(new AssetSource(assetName));\r\n    }\r\n\r\n    /** Use a file as the pdf source */\r\n    public Configurator fromFile(File file) {\r\n        return new Configurator(new FileSource(file));\r\n    }\r\n\r\n    /** Use URI as the pdf source, for use with content providers */\r\n    public Configurator fromUri(Uri uri) {\r\n        return new Configurator(new UriSource(uri));\r\n    }\r\n\r\n    /** Use bytearray as the pdf source, documents is not saved */\r\n    public Configurator fromBytes(byte[] bytes) {\r\n        return new Configurator(new ByteArraySource(bytes));\r\n    }\r\n\r\n    /** Use stream as the pdf source. Stream will be written to bytearray, because native code does not support Java Streams */\r\n    public Configurator fromStream(InputStream stream) {\r\n        return new Configurator(new InputStreamSource(stream));\r\n    }\r\n\r\n    /** Use custom source as pdf source */\r\n    public Configurator fromSource(DocumentSource docSource) {\r\n        return new Configurator(docSource);\r\n    }\r\n\r\n    private enum State {DEFAULT, LOADED, SHOWN, ERROR}\r\n\r\n    public class Configurator {\r\n\r\n        private final DocumentSource documentSource;\r\n\r\n        private int[] pageNumbers = null;\r\n\r\n        private boolean enableSwipe = true;\r\n\r\n        private boolean enableDoubletap = true;\r\n\r\n        private OnDrawListener onDrawListener;\r\n\r\n        private OnDrawListener onDrawAllListener;\r\n\r\n        private OnLoadCompleteListener onLoadCompleteListener;\r\n\r\n        private OnErrorListener onErrorListener;\r\n\r\n        private OnPageChangeListener onPageChangeListener;\r\n\r\n        private OnPageScrollListener onPageScrollListener;\r\n\r\n        private OnRenderListener onRenderListener;\r\n\r\n        private OnTapListener onTapListener;\r\n\r\n        private OnLongPressListener onLongPressListener;\r\n\r\n        private OnPageErrorListener onPageErrorListener;\r\n\r\n        private LinkHandler linkHandler = new DefaultLinkHandler(PDFView.this);\r\n\r\n        private int defaultPage = 0;\r\n\r\n        private boolean swipeHorizontal = false;\r\n\r\n        private boolean annotationRendering = false;\r\n\r\n        private String password = null;\r\n\r\n        private ScrollHandle scrollHandle = null;\r\n\r\n        private boolean antialiasing = true;\r\n\r\n        private int spacing = 0;\r\n\r\n        private boolean autoSpacing = false;\r\n\r\n        private FitPolicy pageFitPolicy = FitPolicy.WIDTH;\r\n\r\n        private boolean fitEachPage = false;\r\n\r\n        private boolean pageFling = false;\r\n\r\n        private boolean pageSnap = false;\r\n\r\n        private boolean nightMode = false;\r\n\r\n        private Configurator(DocumentSource documentSource) {\r\n            this.documentSource = documentSource;\r\n        }\r\n\r\n        public Configurator pages(int... pageNumbers) {\r\n            this.pageNumbers = pageNumbers;\r\n            return this;\r\n        }\r\n\r\n        public Configurator enableSwipe(boolean enableSwipe) {\r\n            this.enableSwipe = enableSwipe;\r\n            return this;\r\n        }\r\n\r\n        public Configurator enableDoubletap(boolean enableDoubletap) {\r\n            this.enableDoubletap = enableDoubletap;\r\n            return this;\r\n        }\r\n\r\n        public Configurator enableAnnotationRendering(boolean annotationRendering) {\r\n            this.annotationRendering = annotationRendering;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onDraw(OnDrawListener onDrawListener) {\r\n            this.onDrawListener = onDrawListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onDrawAll(OnDrawListener onDrawAllListener) {\r\n            this.onDrawAllListener = onDrawAllListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onLoad(OnLoadCompleteListener onLoadCompleteListener) {\r\n            this.onLoadCompleteListener = onLoadCompleteListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onPageScroll(OnPageScrollListener onPageScrollListener) {\r\n            this.onPageScrollListener = onPageScrollListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onError(OnErrorListener onErrorListener) {\r\n            this.onErrorListener = onErrorListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onPageError(OnPageErrorListener onPageErrorListener) {\r\n            this.onPageErrorListener = onPageErrorListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onPageChange(OnPageChangeListener onPageChangeListener) {\r\n            this.onPageChangeListener = onPageChangeListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onRender(OnRenderListener onRenderListener) {\r\n            this.onRenderListener = onRenderListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onTap(OnTapListener onTapListener) {\r\n            this.onTapListener = onTapListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator onLongPress(OnLongPressListener onLongPressListener) {\r\n            this.onLongPressListener = onLongPressListener;\r\n            return this;\r\n        }\r\n\r\n        public Configurator linkHandler(LinkHandler linkHandler) {\r\n            this.linkHandler = linkHandler;\r\n            return this;\r\n        }\r\n\r\n        public Configurator defaultPage(int defaultPage) {\r\n            this.defaultPage = defaultPage;\r\n            return this;\r\n        }\r\n\r\n        public Configurator swipeHorizontal(boolean swipeHorizontal) {\r\n            this.swipeHorizontal = swipeHorizontal;\r\n            return this;\r\n        }\r\n\r\n        public Configurator password(String password) {\r\n            this.password = password;\r\n            return this;\r\n        }\r\n\r\n        public Configurator scrollHandle(ScrollHandle scrollHandle) {\r\n            this.scrollHandle = scrollHandle;\r\n            return this;\r\n        }\r\n\r\n        public Configurator enableAntialiasing(boolean antialiasing) {\r\n            this.antialiasing = antialiasing;\r\n            return this;\r\n        }\r\n\r\n        public Configurator spacing(int spacing) {\r\n            this.spacing = spacing;\r\n            return this;\r\n        }\r\n\r\n        public Configurator autoSpacing(boolean autoSpacing) {\r\n            this.autoSpacing = autoSpacing;\r\n            return this;\r\n        }\r\n\r\n        public Configurator pageFitPolicy(FitPolicy pageFitPolicy) {\r\n            this.pageFitPolicy = pageFitPolicy;\r\n            return this;\r\n        }\r\n\r\n        public Configurator fitEachPage(boolean fitEachPage) {\r\n            this.fitEachPage = fitEachPage;\r\n            return this;\r\n        }\r\n\r\n        public Configurator pageSnap(boolean pageSnap) {\r\n            this.pageSnap = pageSnap;\r\n            return this;\r\n        }\r\n\r\n        public Configurator pageFling(boolean pageFling) {\r\n            this.pageFling = pageFling;\r\n            return this;\r\n        }\r\n\r\n        public Configurator nightMode(boolean nightMode) {\r\n            this.nightMode = nightMode;\r\n            return this;\r\n        }\r\n\r\n        public Configurator disableLongpress() {\r\n            PDFView.this.dragPinchManager.disableLongpress();\r\n            return this;\r\n        }\r\n\r\n        public void load() {\r\n            if (!hasSize) {\r\n                waitingDocumentConfigurator = this;\r\n                return;\r\n            }\r\n            PDFView.this.recycle();\r\n            PDFView.this.callbacks.setOnLoadComplete(onLoadCompleteListener);\r\n            PDFView.this.callbacks.setOnError(onErrorListener);\r\n            PDFView.this.callbacks.setOnDraw(onDrawListener);\r\n            PDFView.this.callbacks.setOnDrawAll(onDrawAllListener);\r\n            PDFView.this.callbacks.setOnPageChange(onPageChangeListener);\r\n            PDFView.this.callbacks.setOnPageScroll(onPageScrollListener);\r\n            PDFView.this.callbacks.setOnRender(onRenderListener);\r\n            PDFView.this.callbacks.setOnTap(onTapListener);\r\n            PDFView.this.callbacks.setOnLongPress(onLongPressListener);\r\n            PDFView.this.callbacks.setOnPageError(onPageErrorListener);\r\n            PDFView.this.callbacks.setLinkHandler(linkHandler);\r\n            PDFView.this.setSwipeEnabled(enableSwipe);\r\n            PDFView.this.setNightMode(nightMode);\r\n            PDFView.this.enableDoubletap(enableDoubletap);\r\n            PDFView.this.setDefaultPage(defaultPage);\r\n            PDFView.this.setSwipeVertical(!swipeHorizontal);\r\n            PDFView.this.enableAnnotationRendering(annotationRendering);\r\n            PDFView.this.setScrollHandle(scrollHandle);\r\n            PDFView.this.enableAntialiasing(antialiasing);\r\n            PDFView.this.setSpacing(spacing);\r\n            PDFView.this.setAutoSpacing(autoSpacing);\r\n            PDFView.this.setPageFitPolicy(pageFitPolicy);\r\n            PDFView.this.setFitEachPage(fitEachPage);\r\n            PDFView.this.setPageSnap(pageSnap);\r\n            PDFView.this.setPageFling(pageFling);\r\n\r\n            if (pageNumbers != null) {\r\n                PDFView.this.load(documentSource, password, pageNumbers);\r\n            } else {\r\n                PDFView.this.load(documentSource, password);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PagesLoader.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer;\n\nimport android.graphics.RectF;\n\nimport com.github.barteksc.pdfviewer.util.Constants;\nimport com.github.barteksc.pdfviewer.util.MathUtils;\nimport com.github.barteksc.pdfviewer.util.Util;\nimport com.shockwave.pdfium.util.SizeF;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport static com.github.barteksc.pdfviewer.util.Constants.Cache.CACHE_SIZE;\nimport static com.github.barteksc.pdfviewer.util.Constants.PRELOAD_OFFSET;\n\nclass PagesLoader {\n\n    private PDFView pdfView;\n    private int cacheOrder;\n    private float xOffset;\n    private float yOffset;\n    private float pageRelativePartWidth;\n    private float pageRelativePartHeight;\n    private float partRenderWidth;\n    private float partRenderHeight;\n    private final RectF thumbnailRect = new RectF(0, 0, 1, 1);\n    private final int preloadOffset;\n\n    private class Holder {\n        int row;\n        int col;\n\n        @Override\n        public String toString() {\n            return \"Holder{\" +\n                    \"row=\" + row +\n                    \", col=\" + col +\n                    '}';\n        }\n    }\n\n    private class RenderRange {\n        int page;\n        GridSize gridSize;\n        Holder leftTop;\n        Holder rightBottom;\n\n        RenderRange() {\n            this.page = 0;\n            this.gridSize = new GridSize();\n            this.leftTop = new Holder();\n            this.rightBottom = new Holder();\n        }\n\n        @Override\n        public String toString() {\n            return \"RenderRange{\" +\n                    \"page=\" + page +\n                    \", gridSize=\" + gridSize +\n                    \", leftTop=\" + leftTop +\n                    \", rightBottom=\" + rightBottom +\n                    '}';\n        }\n    }\n\n    private class GridSize {\n        int rows;\n        int cols;\n\n        @Override\n        public String toString() {\n            return \"GridSize{\" +\n                    \"rows=\" + rows +\n                    \", cols=\" + cols +\n                    '}';\n        }\n    }\n\n    PagesLoader(PDFView pdfView) {\n        this.pdfView = pdfView;\n        this.preloadOffset = Util.getDP(pdfView.getContext(), PRELOAD_OFFSET);\n    }\n\n    private void getPageColsRows(GridSize grid, int pageIndex) {\n        SizeF size = pdfView.pdfFile.getPageSize(pageIndex);\n        float ratioX = 1f / size.getWidth();\n        float ratioY = 1f / size.getHeight();\n        final float partHeight = (Constants.PART_SIZE * ratioY) / pdfView.getZoom();\n        final float partWidth = (Constants.PART_SIZE * ratioX) / pdfView.getZoom();\n        grid.rows = MathUtils.ceil(1f / partHeight);\n        grid.cols = MathUtils.ceil(1f / partWidth);\n    }\n\n    private void calculatePartSize(GridSize grid) {\n        pageRelativePartWidth = 1f / (float) grid.cols;\n        pageRelativePartHeight = 1f / (float) grid.rows;\n        partRenderWidth = Constants.PART_SIZE / pageRelativePartWidth;\n        partRenderHeight = Constants.PART_SIZE / pageRelativePartHeight;\n    }\n\n\n    /**\n     * calculate the render range of each page\n     */\n    private List<RenderRange> getRenderRangeList(float firstXOffset, float firstYOffset, float lastXOffset, float lastYOffset) {\n\n        float fixedFirstXOffset = -MathUtils.max(firstXOffset, 0);\n        float fixedFirstYOffset = -MathUtils.max(firstYOffset, 0);\n\n        float fixedLastXOffset = -MathUtils.max(lastXOffset, 0);\n        float fixedLastYOffset = -MathUtils.max(lastYOffset, 0);\n\n        float offsetFirst = pdfView.isSwipeVertical() ? fixedFirstYOffset : fixedFirstXOffset;\n        float offsetLast = pdfView.isSwipeVertical() ? fixedLastYOffset : fixedLastXOffset;\n\n        int firstPage = pdfView.pdfFile.getPageAtOffset(offsetFirst, pdfView.getZoom());\n        int lastPage = pdfView.pdfFile.getPageAtOffset(offsetLast, pdfView.getZoom());\n        int pageCount = lastPage - firstPage + 1;\n\n        List<RenderRange> renderRanges = new LinkedList<>();\n\n        for (int page = firstPage; page <= lastPage; page++) {\n            RenderRange range = new RenderRange();\n            range.page = page;\n\n            float pageFirstXOffset, pageFirstYOffset, pageLastXOffset, pageLastYOffset;\n            if (page == firstPage) {\n                pageFirstXOffset = fixedFirstXOffset;\n                pageFirstYOffset = fixedFirstYOffset;\n                if (pageCount == 1) {\n                    pageLastXOffset = fixedLastXOffset;\n                    pageLastYOffset = fixedLastYOffset;\n                } else {\n                    float pageOffset = pdfView.pdfFile.getPageOffset(page, pdfView.getZoom());\n                    SizeF pageSize = pdfView.pdfFile.getScaledPageSize(page, pdfView.getZoom());\n                    if (pdfView.isSwipeVertical()) {\n                        pageLastXOffset = fixedLastXOffset;\n                        pageLastYOffset = pageOffset + pageSize.getHeight();\n                    } else {\n                        pageLastYOffset = fixedLastYOffset;\n                        pageLastXOffset = pageOffset + pageSize.getWidth();\n                    }\n                }\n            } else if (page == lastPage) {\n                float pageOffset = pdfView.pdfFile.getPageOffset(page, pdfView.getZoom());\n\n                if (pdfView.isSwipeVertical()) {\n                    pageFirstXOffset = fixedFirstXOffset;\n                    pageFirstYOffset = pageOffset;\n                } else {\n                    pageFirstYOffset = fixedFirstYOffset;\n                    pageFirstXOffset = pageOffset;\n                }\n\n                pageLastXOffset = fixedLastXOffset;\n                pageLastYOffset = fixedLastYOffset;\n\n            } else {\n                float pageOffset = pdfView.pdfFile.getPageOffset(page, pdfView.getZoom());\n                SizeF pageSize = pdfView.pdfFile.getScaledPageSize(page, pdfView.getZoom());\n                if (pdfView.isSwipeVertical()) {\n                    pageFirstXOffset = fixedFirstXOffset;\n                    pageFirstYOffset = pageOffset;\n\n                    pageLastXOffset = fixedLastXOffset;\n                    pageLastYOffset = pageOffset + pageSize.getHeight();\n                } else {\n                    pageFirstXOffset = pageOffset;\n                    pageFirstYOffset = fixedFirstYOffset;\n\n                    pageLastXOffset = pageOffset + pageSize.getWidth();\n                    pageLastYOffset = fixedLastYOffset;\n                }\n            }\n\n            getPageColsRows(range.gridSize, range.page); // get the page's grid size that rows and cols\n            SizeF scaledPageSize = pdfView.pdfFile.getScaledPageSize(range.page, pdfView.getZoom());\n            float rowHeight = scaledPageSize.getHeight() / range.gridSize.rows;\n            float colWidth = scaledPageSize.getWidth() / range.gridSize.cols;\n\n\n            // get the page offset int the whole file\n            // ---------------------------------------\n            // |            |           |            |\n            // |<--offset-->|   (page)  |<--offset-->|\n            // |            |           |            |\n            // |            |           |            |\n            // ---------------------------------------\n            float secondaryOffset = pdfView.pdfFile.getSecondaryPageOffset(page, pdfView.getZoom());\n\n            // calculate the row,col of the point in the leftTop and rightBottom\n            if (pdfView.isSwipeVertical()) {\n                range.leftTop.row = MathUtils.floor(Math.abs(pageFirstYOffset - pdfView.pdfFile.getPageOffset(range.page, pdfView.getZoom())) / rowHeight);\n                range.leftTop.col = MathUtils.floor(MathUtils.min(pageFirstXOffset - secondaryOffset, 0) / colWidth);\n\n                range.rightBottom.row = MathUtils.ceil(Math.abs(pageLastYOffset - pdfView.pdfFile.getPageOffset(range.page, pdfView.getZoom())) / rowHeight);\n                range.rightBottom.col = MathUtils.floor(MathUtils.min(pageLastXOffset - secondaryOffset, 0) / colWidth);\n            } else {\n                range.leftTop.col = MathUtils.floor(Math.abs(pageFirstXOffset - pdfView.pdfFile.getPageOffset(range.page, pdfView.getZoom())) / colWidth);\n                range.leftTop.row = MathUtils.floor(MathUtils.min(pageFirstYOffset - secondaryOffset, 0) / rowHeight);\n\n                range.rightBottom.col = MathUtils.floor(Math.abs(pageLastXOffset - pdfView.pdfFile.getPageOffset(range.page, pdfView.getZoom())) / colWidth);\n                range.rightBottom.row = MathUtils.floor(MathUtils.min(pageLastYOffset - secondaryOffset, 0) / rowHeight);\n            }\n\n            renderRanges.add(range);\n        }\n\n        return renderRanges;\n    }\n\n    private void loadVisible() {\n        int parts = 0;\n        float scaledPreloadOffset = preloadOffset;\n        float firstXOffset = -xOffset + scaledPreloadOffset;\n        float lastXOffset = -xOffset - pdfView.getWidth() - scaledPreloadOffset;\n        float firstYOffset = -yOffset + scaledPreloadOffset;\n        float lastYOffset = -yOffset - pdfView.getHeight() - scaledPreloadOffset;\n\n        List<RenderRange> rangeList = getRenderRangeList(firstXOffset, firstYOffset, lastXOffset, lastYOffset);\n\n        for (RenderRange range : rangeList) {\n            loadThumbnail(range.page);\n        }\n\n        for (RenderRange range : rangeList) {\n            calculatePartSize(range.gridSize);\n            parts += loadPage(range.page, range.leftTop.row, range.rightBottom.row, range.leftTop.col, range.rightBottom.col, CACHE_SIZE - parts);\n            if (parts >= CACHE_SIZE) {\n                break;\n            }\n        }\n\n    }\n\n    private int loadPage(int page, int firstRow, int lastRow, int firstCol, int lastCol,\n                         int nbOfPartsLoadable) {\n        int loaded = 0;\n        for (int row = firstRow; row <= lastRow; row++) {\n            for (int col = firstCol; col <= lastCol; col++) {\n                if (loadCell(page, row, col, pageRelativePartWidth, pageRelativePartHeight)) {\n                    loaded++;\n                }\n                if (loaded >= nbOfPartsLoadable) {\n                    return loaded;\n                }\n            }\n        }\n        return loaded;\n    }\n\n    private boolean loadCell(int page, int row, int col, float pageRelativePartWidth, float pageRelativePartHeight) {\n\n        float relX = pageRelativePartWidth * col;\n        float relY = pageRelativePartHeight * row;\n        float relWidth = pageRelativePartWidth;\n        float relHeight = pageRelativePartHeight;\n\n        float renderWidth = partRenderWidth;\n        float renderHeight = partRenderHeight;\n        if (relX + relWidth > 1) {\n            relWidth = 1 - relX;\n        }\n        if (relY + relHeight > 1) {\n            relHeight = 1 - relY;\n        }\n        renderWidth *= relWidth;\n        renderHeight *= relHeight;\n        RectF pageRelativeBounds = new RectF(relX, relY, relX + relWidth, relY + relHeight);\n\n        if (renderWidth > 0 && renderHeight > 0) {\n            if (!pdfView.cacheManager.upPartIfContained(page, pageRelativeBounds, cacheOrder)) {\n                pdfView.renderingHandler.addRenderingTask(page, renderWidth, renderHeight,\n                        pageRelativeBounds, false, cacheOrder, pdfView.isBestQuality(),\n                        pdfView.isAnnotationRendering());\n            }\n\n            cacheOrder++;\n            return true;\n        }\n        return false;\n    }\n\n    private void loadThumbnail(int page) {\n        SizeF pageSize = pdfView.pdfFile.getPageSize(page);\n        float thumbnailWidth = pageSize.getWidth() * Constants.THUMBNAIL_RATIO;\n        float thumbnailHeight = pageSize.getHeight() * Constants.THUMBNAIL_RATIO;\n        if (!pdfView.cacheManager.containsThumbnail(page, thumbnailRect)) {\n            pdfView.renderingHandler.addRenderingTask(page,\n                    thumbnailWidth, thumbnailHeight, thumbnailRect,\n                    true, 0, pdfView.isBestQuality(), pdfView.isAnnotationRendering());\n        }\n    }\n\n    void loadPages() {\n        cacheOrder = 1;\n        xOffset = -MathUtils.max(pdfView.getCurrentXOffset(), 0);\n        yOffset = -MathUtils.max(pdfView.getCurrentYOffset(), 0);\n\n        loadVisible();\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PdfFile.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.util.SparseBooleanArray;\n\nimport com.github.barteksc.pdfviewer.exception.PageRenderingException;\nimport com.github.barteksc.pdfviewer.util.FitPolicy;\nimport com.github.barteksc.pdfviewer.util.PageSizeCalculator;\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\nimport com.shockwave.pdfium.util.Size;\nimport com.shockwave.pdfium.util.SizeF;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass PdfFile {\n\n    private static final Object lock = new Object();\n    private PdfDocument pdfDocument;\n    private PdfiumCore pdfiumCore;\n    private int pagesCount = 0;\n    /** Original page sizes */\n    private List<Size> originalPageSizes = new ArrayList<>();\n    /** Scaled page sizes */\n    private List<SizeF> pageSizes = new ArrayList<>();\n    /** Opened pages with indicator whether opening was successful */\n    private SparseBooleanArray openedPages = new SparseBooleanArray();\n    /** Page with maximum width */\n    private Size originalMaxWidthPageSize = new Size(0, 0);\n    /** Page with maximum height */\n    private Size originalMaxHeightPageSize = new Size(0, 0);\n    /** Scaled page with maximum height */\n    private SizeF maxHeightPageSize = new SizeF(0, 0);\n    /** Scaled page with maximum width */\n    private SizeF maxWidthPageSize = new SizeF(0, 0);\n    /** True if scrolling is vertical, else it's horizontal */\n    private boolean isVertical;\n    /** Fixed spacing between pages in pixels */\n    private int spacingPx;\n    /** Calculate spacing automatically so each page fits on it's own in the center of the view */\n    private boolean autoSpacing;\n    /** Calculated offsets for pages */\n    private List<Float> pageOffsets = new ArrayList<>();\n    /** Calculated auto spacing for pages */\n    private List<Float> pageSpacing = new ArrayList<>();\n    /** Calculated document length (width or height, depending on swipe mode) */\n    private float documentLength = 0;\n    private final FitPolicy pageFitPolicy;\n    /**\n     * True if every page should fit separately according to the FitPolicy,\n     * else the largest page fits and other pages scale relatively\n     */\n    private final boolean fitEachPage;\n    /**\n     * The pages the user want to display in order\n     * (ex: 0, 2, 2, 8, 8, 1, 1, 1)\n     */\n    private int[] originalUserPages;\n\n    PdfFile(PdfiumCore pdfiumCore, PdfDocument pdfDocument, FitPolicy pageFitPolicy, Size viewSize, int[] originalUserPages,\n            boolean isVertical, int spacing, boolean autoSpacing, boolean fitEachPage) {\n        this.pdfiumCore = pdfiumCore;\n        this.pdfDocument = pdfDocument;\n        this.pageFitPolicy = pageFitPolicy;\n        this.originalUserPages = originalUserPages;\n        this.isVertical = isVertical;\n        this.spacingPx = spacing;\n        this.autoSpacing = autoSpacing;\n        this.fitEachPage = fitEachPage;\n        setup(viewSize);\n    }\n\n    private void setup(Size viewSize) {\n        if (originalUserPages != null) {\n            pagesCount = originalUserPages.length;\n        } else {\n            pagesCount = pdfiumCore.getPageCount(pdfDocument);\n        }\n\n        for (int i = 0; i < pagesCount; i++) {\n            Size pageSize = pdfiumCore.getPageSize(pdfDocument, documentPage(i));\n            if (pageSize.getWidth() > originalMaxWidthPageSize.getWidth()) {\n                originalMaxWidthPageSize = pageSize;\n            }\n            if (pageSize.getHeight() > originalMaxHeightPageSize.getHeight()) {\n                originalMaxHeightPageSize = pageSize;\n            }\n            originalPageSizes.add(pageSize);\n        }\n\n        recalculatePageSizes(viewSize);\n    }\n\n    /**\n     * Call after view size change to recalculate page sizes, offsets and document length\n     *\n     * @param viewSize new size of changed view\n     */\n    public void recalculatePageSizes(Size viewSize) {\n        pageSizes.clear();\n        PageSizeCalculator calculator = new PageSizeCalculator(pageFitPolicy, originalMaxWidthPageSize,\n                originalMaxHeightPageSize, viewSize, fitEachPage);\n        maxWidthPageSize = calculator.getOptimalMaxWidthPageSize();\n        maxHeightPageSize = calculator.getOptimalMaxHeightPageSize();\n\n        for (Size size : originalPageSizes) {\n            pageSizes.add(calculator.calculate(size));\n        }\n        if (autoSpacing) {\n            prepareAutoSpacing(viewSize);\n        }\n        prepareDocLen();\n        preparePagesOffset();\n    }\n\n    public int getPagesCount() {\n        return pagesCount;\n    }\n\n    public SizeF getPageSize(int pageIndex) {\n        int docPage = documentPage(pageIndex);\n        if (docPage < 0) {\n            return new SizeF(0, 0);\n        }\n        return pageSizes.get(pageIndex);\n    }\n\n    public SizeF getScaledPageSize(int pageIndex, float zoom) {\n        SizeF size = getPageSize(pageIndex);\n        return new SizeF(size.getWidth() * zoom, size.getHeight() * zoom);\n    }\n\n    /**\n     * get page size with biggest dimension (width in vertical mode and height in horizontal mode)\n     *\n     * @return size of page\n     */\n    public SizeF getMaxPageSize() {\n        return isVertical ? maxWidthPageSize : maxHeightPageSize;\n    }\n\n    public float getMaxPageWidth() {\n        return getMaxPageSize().getWidth();\n    }\n\n    public float getMaxPageHeight() {\n        return getMaxPageSize().getHeight();\n    }\n\n    private void prepareAutoSpacing(Size viewSize) {\n        pageSpacing.clear();\n        for (int i = 0; i < getPagesCount(); i++) {\n            SizeF pageSize = pageSizes.get(i);\n            float spacing = Math.max(0, isVertical ? viewSize.getHeight() - pageSize.getHeight() :\n                    viewSize.getWidth() - pageSize.getWidth());\n            if (i < getPagesCount() - 1) {\n                spacing += spacingPx;\n            }\n            pageSpacing.add(spacing);\n        }\n    }\n\n    private void prepareDocLen() {\n        float length = 0;\n        for (int i = 0; i < getPagesCount(); i++) {\n            SizeF pageSize = pageSizes.get(i);\n            length += isVertical ? pageSize.getHeight() : pageSize.getWidth();\n            if (autoSpacing) {\n                length += pageSpacing.get(i);\n            } else if (i < getPagesCount() - 1) {\n                length += spacingPx;\n            }\n        }\n        documentLength = length;\n    }\n\n    private void preparePagesOffset() {\n        pageOffsets.clear();\n        float offset = 0;\n        for (int i = 0; i < getPagesCount(); i++) {\n            SizeF pageSize = pageSizes.get(i);\n            float size = isVertical ? pageSize.getHeight() : pageSize.getWidth();\n            if (autoSpacing) {\n                offset += pageSpacing.get(i) / 2f;\n                if (i == 0) {\n                    offset -= spacingPx / 2f;\n                } else if (i == getPagesCount() - 1) {\n                    offset += spacingPx / 2f;\n                }\n                pageOffsets.add(offset);\n                offset += size + pageSpacing.get(i) / 2f;\n            } else {\n                pageOffsets.add(offset);\n                offset += size + spacingPx;\n            }\n        }\n    }\n\n    public float getDocLen(float zoom) {\n        return documentLength * zoom;\n    }\n\n    /**\n     * Get the page's height if swiping vertical, or width if swiping horizontal.\n     */\n    public float getPageLength(int pageIndex, float zoom) {\n        SizeF size = getPageSize(pageIndex);\n        return (isVertical ? size.getHeight() : size.getWidth()) * zoom;\n    }\n\n    public float getPageSpacing(int pageIndex, float zoom) {\n        float spacing = autoSpacing ? pageSpacing.get(pageIndex) : spacingPx;\n        return spacing * zoom;\n    }\n\n    /** Get primary page offset, that is Y for vertical scroll and X for horizontal scroll */\n    public float getPageOffset(int pageIndex, float zoom) {\n        int docPage = documentPage(pageIndex);\n        if (docPage < 0) {\n            return 0;\n        }\n        return pageOffsets.get(pageIndex) * zoom;\n    }\n\n    /** Get secondary page offset, that is X for vertical scroll and Y for horizontal scroll */\n    public float getSecondaryPageOffset(int pageIndex, float zoom) {\n        SizeF pageSize = getPageSize(pageIndex);\n        if (isVertical) {\n            float maxWidth = getMaxPageWidth();\n            return zoom * (maxWidth - pageSize.getWidth()) / 2; //x\n        } else {\n            float maxHeight = getMaxPageHeight();\n            return zoom * (maxHeight - pageSize.getHeight()) / 2; //y\n        }\n    }\n\n    public int getPageAtOffset(float offset, float zoom) {\n        int currentPage = 0;\n        for (int i = 0; i < getPagesCount(); i++) {\n            float off = pageOffsets.get(i) * zoom - getPageSpacing(i, zoom) / 2f;\n            if (off >= offset) {\n                break;\n            }\n            currentPage++;\n        }\n        return --currentPage >= 0 ? currentPage : 0;\n    }\n\n    public boolean openPage(int pageIndex) throws PageRenderingException {\n        int docPage = documentPage(pageIndex);\n        if (docPage < 0) {\n            return false;\n        }\n\n        synchronized (lock) {\n            if (openedPages.indexOfKey(docPage) < 0) {\n                try {\n                    pdfiumCore.openPage(pdfDocument, docPage);\n                    openedPages.put(docPage, true);\n                    return true;\n                } catch (Exception e) {\n                    openedPages.put(docPage, false);\n                    throw new PageRenderingException(pageIndex, e);\n                }\n            }\n            return false;\n        }\n    }\n\n    public boolean pageHasError(int pageIndex) {\n        int docPage = documentPage(pageIndex);\n        return !openedPages.get(docPage, false);\n    }\n\n    public void renderPageBitmap(Bitmap bitmap, int pageIndex, Rect bounds, boolean annotationRendering) {\n        int docPage = documentPage(pageIndex);\n        pdfiumCore.renderPageBitmap(pdfDocument, bitmap, docPage,\n                bounds.left, bounds.top, bounds.width(), bounds.height(), annotationRendering);\n    }\n\n    public PdfDocument.Meta getMetaData() {\n        if (pdfDocument == null) {\n            return null;\n        }\n        return pdfiumCore.getDocumentMeta(pdfDocument);\n    }\n\n    public List<PdfDocument.Bookmark> getBookmarks() {\n        if (pdfDocument == null) {\n            return new ArrayList<>();\n        }\n        return pdfiumCore.getTableOfContents(pdfDocument);\n    }\n\n    public List<PdfDocument.Link> getPageLinks(int pageIndex) {\n        int docPage = documentPage(pageIndex);\n        return pdfiumCore.getPageLinks(pdfDocument, docPage);\n    }\n\n    public RectF mapRectToDevice(int pageIndex, int startX, int startY, int sizeX, int sizeY,\n                                 RectF rect) {\n        int docPage = documentPage(pageIndex);\n        return pdfiumCore.mapRectToDevice(pdfDocument, docPage, startX, startY, sizeX, sizeY, 0, rect);\n    }\n\n    public void dispose() {\n        if (pdfiumCore != null && pdfDocument != null) {\n            pdfiumCore.closeDocument(pdfDocument);\n        }\n\n        pdfDocument = null;\n        originalUserPages = null;\n    }\n\n    /**\n     * Given the UserPage number, this method restrict it\n     * to be sure it's an existing page. It takes care of\n     * using the user defined pages if any.\n     *\n     * @param userPage A page number.\n     * @return A restricted valid page number (example : -2 => 0)\n     */\n    public int determineValidPageNumberFrom(int userPage) {\n        if (userPage <= 0) {\n            return 0;\n        }\n        if (originalUserPages != null) {\n            if (userPage >= originalUserPages.length) {\n                return originalUserPages.length - 1;\n            }\n        } else {\n            if (userPage >= getPagesCount()) {\n                return getPagesCount() - 1;\n            }\n        }\n        return userPage;\n    }\n\n    public int documentPage(int userPage) {\n        int documentPage = userPage;\n        if (originalUserPages != null) {\n            if (userPage < 0 || userPage >= originalUserPages.length) {\n                return -1;\n            } else {\n                documentPage = originalUserPages[userPage];\n            }\n        }\n\n        if (documentPage < 0 || userPage >= getPagesCount()) {\n            return -1;\n        }\n\n        return documentPage;\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/RenderingHandler.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer;\r\n\r\nimport android.graphics.Bitmap;\r\nimport android.graphics.Color;\r\nimport android.graphics.Matrix;\r\nimport android.graphics.Rect;\r\nimport android.graphics.RectF;\r\nimport android.os.Handler;\r\nimport android.os.Looper;\r\nimport android.os.Message;\r\nimport android.util.Log;\r\n\r\nimport com.github.barteksc.pdfviewer.exception.PageRenderingException;\r\nimport com.github.barteksc.pdfviewer.model.PagePart;\r\n\r\n/**\r\n * A {@link Handler} that will process incoming {@link RenderingTask} messages\r\n * and alert {@link PDFView#onBitmapRendered(PagePart)} when the portion of the\r\n * PDF is ready to render.\r\n */\r\nclass RenderingHandler extends Handler {\r\n    /**\r\n     * {@link Message#what} kind of message this handler processes.\r\n     */\r\n    static final int MSG_RENDER_TASK = 1;\r\n\r\n    private static final String TAG = RenderingHandler.class.getName();\r\n\r\n    private PDFView pdfView;\r\n\r\n    private RectF renderBounds = new RectF();\r\n    private Rect roundedRenderBounds = new Rect();\r\n    private Matrix renderMatrix = new Matrix();\r\n    private boolean running = false;\r\n\r\n    RenderingHandler(Looper looper, PDFView pdfView) {\r\n        super(looper);\r\n        this.pdfView = pdfView;\r\n    }\r\n\r\n    void addRenderingTask(int page, float width, float height, RectF bounds, boolean thumbnail, int cacheOrder, boolean bestQuality, boolean annotationRendering) {\r\n        RenderingTask task = new RenderingTask(width, height, bounds, page, thumbnail, cacheOrder, bestQuality, annotationRendering);\r\n        Message msg = obtainMessage(MSG_RENDER_TASK, task);\r\n        sendMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void handleMessage(Message message) {\r\n        RenderingTask task = (RenderingTask) message.obj;\r\n        try {\r\n            final PagePart part = proceed(task);\r\n            if (part != null) {\r\n                if (running) {\r\n                    pdfView.post(new Runnable() {\r\n                        @Override\r\n                        public void run() {\r\n                            pdfView.onBitmapRendered(part);\r\n                        }\r\n                    });\r\n                } else {\r\n                    part.getRenderedBitmap().recycle();\r\n                }\r\n            }\r\n        } catch (final PageRenderingException ex) {\r\n            pdfView.post(new Runnable() {\r\n                @Override\r\n                public void run() {\r\n                    pdfView.onPageError(ex);\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    private PagePart proceed(RenderingTask renderingTask) throws PageRenderingException {\r\n        PdfFile pdfFile = pdfView.pdfFile;\r\n        pdfFile.openPage(renderingTask.page);\r\n\r\n        int w = Math.round(renderingTask.width);\r\n        int h = Math.round(renderingTask.height);\r\n\r\n        if (w == 0 || h == 0 || pdfFile.pageHasError(renderingTask.page)) {\r\n            return null;\r\n        }\r\n\r\n        Bitmap render;\r\n        try {\r\n            render = Bitmap.createBitmap(w, h, renderingTask.bestQuality ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);\r\n        } catch (IllegalArgumentException e) {\r\n            Log.e(TAG, \"Cannot create bitmap\", e);\r\n            return null;\r\n        }\r\n        calculateBounds(w, h, renderingTask.bounds);\r\n\r\n        pdfFile.renderPageBitmap(render, renderingTask.page, roundedRenderBounds, renderingTask.annotationRendering);\r\n\r\n        return new PagePart(renderingTask.page, render,\r\n                renderingTask.bounds, renderingTask.thumbnail,\r\n                renderingTask.cacheOrder);\r\n    }\r\n\r\n    private void calculateBounds(int width, int height, RectF pageSliceBounds) {\r\n        renderMatrix.reset();\r\n        renderMatrix.postTranslate(-pageSliceBounds.left * width, -pageSliceBounds.top * height);\r\n        renderMatrix.postScale(1 / pageSliceBounds.width(), 1 / pageSliceBounds.height());\r\n\r\n        renderBounds.set(0, 0, width, height);\r\n        renderMatrix.mapRect(renderBounds);\r\n        renderBounds.round(roundedRenderBounds);\r\n    }\r\n\r\n    void stop() {\r\n        running = false;\r\n    }\r\n\r\n    void start() {\r\n        running = true;\r\n    }\r\n\r\n    private class RenderingTask {\r\n\r\n        float width, height;\r\n\r\n        RectF bounds;\r\n\r\n        int page;\r\n\r\n        boolean thumbnail;\r\n\r\n        int cacheOrder;\r\n\r\n        boolean bestQuality;\r\n\r\n        boolean annotationRendering;\r\n\r\n        RenderingTask(float width, float height, RectF bounds, int page, boolean thumbnail, int cacheOrder, boolean bestQuality, boolean annotationRendering) {\r\n            this.page = page;\r\n            this.width = width;\r\n            this.height = height;\r\n            this.bounds = bounds;\r\n            this.thumbnail = thumbnail;\r\n            this.cacheOrder = cacheOrder;\r\n            this.bestQuality = bestQuality;\r\n            this.annotationRendering = annotationRendering;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/exception/FileNotFoundException.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.exception;\n\n@Deprecated\npublic class FileNotFoundException extends RuntimeException {\n\n    public FileNotFoundException(String detailMessage) {\n        super(detailMessage);\n    }\n\n    public FileNotFoundException(String detailMessage, Throwable throwable) {\n        super(detailMessage, throwable);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/exception/PageRenderingException.java",
    "content": "package com.github.barteksc.pdfviewer.exception;\n\npublic class PageRenderingException extends Exception {\n    private final int page;\n\n    public PageRenderingException(int page, Throwable cause) {\n        super(cause);\n        this.page = page;\n    }\n\n    public int getPage() {\n        return page;\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/link/DefaultLinkHandler.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.link;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.util.Log;\n\nimport com.github.barteksc.pdfviewer.PDFView;\nimport com.github.barteksc.pdfviewer.model.LinkTapEvent;\n\npublic class DefaultLinkHandler implements LinkHandler {\n\n    private static final String TAG = DefaultLinkHandler.class.getSimpleName();\n\n    private PDFView pdfView;\n\n    public DefaultLinkHandler(PDFView pdfView) {\n        this.pdfView = pdfView;\n    }\n\n    @Override\n    public void handleLinkEvent(LinkTapEvent event) {\n        String uri = event.getLink().getUri();\n        Integer page = event.getLink().getDestPageIdx();\n        if (uri != null && !uri.isEmpty()) {\n            handleUri(uri);\n        } else if (page != null) {\n            handlePage(page);\n        }\n    }\n\n    private void handleUri(String uri) {\n        Uri parsedUri = Uri.parse(uri);\n        Intent intent = new Intent(Intent.ACTION_VIEW, parsedUri);\n        Context context = pdfView.getContext();\n        if (intent.resolveActivity(context.getPackageManager()) != null) {\n            context.startActivity(intent);\n        } else {\n            Log.w(TAG, \"No activity found for URI: \" + uri);\n        }\n    }\n\n    private void handlePage(int page) {\n        pdfView.jumpTo(page);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/link/LinkHandler.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.link;\n\nimport com.github.barteksc.pdfviewer.model.LinkTapEvent;\n\npublic interface LinkHandler {\n\n    /**\n     * Called when link was tapped by user\n     *\n     * @param event current event\n     */\n    void handleLinkEvent(LinkTapEvent event);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/Callbacks.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\nimport android.view.MotionEvent;\n\nimport com.github.barteksc.pdfviewer.link.LinkHandler;\nimport com.github.barteksc.pdfviewer.model.LinkTapEvent;\n\npublic class Callbacks {\n\n    /**\n     * Call back object to call when the PDF is loaded\n     */\n    private OnLoadCompleteListener onLoadCompleteListener;\n\n    /**\n     * Call back object to call when document loading error occurs\n     */\n    private OnErrorListener onErrorListener;\n\n    /**\n     * Call back object to call when the page load error occurs\n     */\n    private OnPageErrorListener onPageErrorListener;\n\n    /**\n     * Call back object to call when the document is initially rendered\n     */\n    private OnRenderListener onRenderListener;\n\n    /**\n     * Call back object to call when the page has changed\n     */\n    private OnPageChangeListener onPageChangeListener;\n\n    /**\n     * Call back object to call when the page is scrolled\n     */\n    private OnPageScrollListener onPageScrollListener;\n\n    /**\n     * Call back object to call when the above layer is to drawn\n     */\n    private OnDrawListener onDrawListener;\n\n    private OnDrawListener onDrawAllListener;\n\n    /**\n     * Call back object to call when the user does a tap gesture\n     */\n    private OnTapListener onTapListener;\n\n    /**\n     * Call back object to call when the user does a long tap gesture\n     */\n    private OnLongPressListener onLongPressListener;\n\n    /**\n     * Call back object to call when clicking link\n     */\n    private LinkHandler linkHandler;\n\n    public void setOnLoadComplete(OnLoadCompleteListener onLoadCompleteListener) {\n        this.onLoadCompleteListener = onLoadCompleteListener;\n    }\n\n    public void callOnLoadComplete(int pagesCount) {\n        if (onLoadCompleteListener != null) {\n            onLoadCompleteListener.loadComplete(pagesCount);\n        }\n    }\n\n    public void setOnError(OnErrorListener onErrorListener) {\n        this.onErrorListener = onErrorListener;\n    }\n\n    public OnErrorListener getOnError() {\n        return onErrorListener;\n    }\n\n    public void setOnPageError(OnPageErrorListener onPageErrorListener) {\n        this.onPageErrorListener = onPageErrorListener;\n    }\n\n    public boolean callOnPageError(int page, Throwable error) {\n        if (onPageErrorListener != null) {\n            onPageErrorListener.onPageError(page, error);\n            return true;\n        }\n        return false;\n    }\n\n    public void setOnRender(OnRenderListener onRenderListener) {\n        this.onRenderListener = onRenderListener;\n    }\n\n    public void callOnRender(int pagesCount) {\n        if (onRenderListener != null) {\n            onRenderListener.onInitiallyRendered(pagesCount);\n        }\n    }\n\n    public void setOnPageChange(OnPageChangeListener onPageChangeListener) {\n        this.onPageChangeListener = onPageChangeListener;\n    }\n\n    public void callOnPageChange(int page, int pagesCount) {\n        if (onPageChangeListener != null) {\n            onPageChangeListener.onPageChanged(page, pagesCount);\n        }\n    }\n\n    public void setOnPageScroll(OnPageScrollListener onPageScrollListener) {\n        this.onPageScrollListener = onPageScrollListener;\n    }\n\n    public void callOnPageScroll(int currentPage, float offset) {\n        if (onPageScrollListener != null) {\n            onPageScrollListener.onPageScrolled(currentPage, offset);\n        }\n    }\n\n    public void setOnDraw(OnDrawListener onDrawListener) {\n        this.onDrawListener = onDrawListener;\n    }\n\n    public OnDrawListener getOnDraw() {\n        return onDrawListener;\n    }\n\n    public void setOnDrawAll(OnDrawListener onDrawAllListener) {\n        this.onDrawAllListener = onDrawAllListener;\n    }\n\n    public OnDrawListener getOnDrawAll() {\n        return onDrawAllListener;\n    }\n\n    public void setOnTap(OnTapListener onTapListener) {\n        this.onTapListener = onTapListener;\n    }\n\n    public boolean callOnTap(MotionEvent event) {\n        return onTapListener != null && onTapListener.onTap(event);\n    }\n\n    public void setOnLongPress(OnLongPressListener onLongPressListener) {\n        this.onLongPressListener = onLongPressListener;\n    }\n\n    public void callOnLongPress(MotionEvent event) {\n        if (onLongPressListener != null) {\n            onLongPressListener.onLongPress(event);\n        }\n    }\n\n    public void setLinkHandler(LinkHandler linkHandler) {\n        this.linkHandler = linkHandler;\n    }\n\n    public void callLinkHandler(LinkTapEvent event) {\n        if (linkHandler != null) {\n            linkHandler.handleLinkEvent(event);\n        }\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnDrawListener.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.listener;\r\n\r\nimport android.graphics.Canvas;\r\n\r\n/**\r\n * This interface allows an extern class to draw\r\n * something on the PDFView canvas, above all images.\r\n */\r\npublic interface OnDrawListener {\r\n\r\n    /**\r\n     * This method is called when the PDFView is\r\n     * drawing its view.\r\n     * <p>\r\n     * The page is starting at (0,0)\r\n     *\r\n     * @param canvas        The canvas on which to draw things.\r\n     * @param pageWidth     The width of the current page.\r\n     * @param pageHeight    The height of the current page.\r\n     * @param displayedPage The current page index\r\n     */\r\n    void onLayerDrawn(Canvas canvas, float pageWidth, float pageHeight, int displayedPage);\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnErrorListener.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\npublic interface OnErrorListener {\n\n    /**\n     * Called if error occurred while opening PDF\n     * @param t Throwable with error\n     */\n    void onError(Throwable t);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnLoadCompleteListener.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.listener;\r\n\r\n/**\r\n * Implement this interface to receive events from PDFView\r\n * when loading is complete.\r\n */\r\npublic interface OnLoadCompleteListener {\r\n\r\n    /**\r\n     * Called when the PDF is loaded\r\n     * @param nbPages the number of pages in this PDF file\r\n     */\r\n    void loadComplete(int nbPages);\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnLongPressListener.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\nimport android.view.MotionEvent;\n\n/**\n * Implement this interface to receive events from PDFView\n * when view has been long pressed\n */\npublic interface OnLongPressListener {\n\n    /**\n     * Called when the user has a long tap gesture, before processing scroll handle toggling\n     *\n     * @param e MotionEvent that registered as a confirmed long press\n     */\n    void onLongPress(MotionEvent e);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnPageChangeListener.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.listener;\r\n\r\n/**\r\n * Implements this interface to receive events from PDFView\r\n * when a page has changed through swipe\r\n */\r\npublic interface OnPageChangeListener {\r\n\r\n    /**\r\n     * Called when the user use swipe to change page\r\n     *\r\n     * @param page      the new page displayed, starting from 0\r\n     * @param pageCount the total page count\r\n     */\r\n    void onPageChanged(int page, int pageCount);\r\n\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnPageErrorListener.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\npublic interface OnPageErrorListener {\n\n    /**\n     * Called if error occurred while loading PDF page\n     * @param t Throwable with error\n     */\n    void onPageError(int page, Throwable t);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnPageScrollListener.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\n/**\n * Implements this interface to receive events from PDFView\n * when a page has been scrolled\n */\npublic interface OnPageScrollListener {\n\n    /**\n     * Called on every move while scrolling\n     *\n     * @param page current page index\n     * @param positionOffset see {@link com.github.barteksc.pdfviewer.PDFView#getPositionOffset()}\n     */\n    void onPageScrolled(int page, float positionOffset);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnRenderListener.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\npublic interface OnRenderListener {\n\n    /**\n     * Called only once, when document is rendered\n     * @param nbPages number of pages\n     */\n    void onInitiallyRendered(int nbPages);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/listener/OnTapListener.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.listener;\n\nimport android.view.MotionEvent;\n\n/**\n * Implement this interface to receive events from PDFView\n * when view has been touched\n */\npublic interface OnTapListener {\n\n    /**\n     * Called when the user has a tap gesture, before processing scroll handle toggling\n     *\n     * @param e MotionEvent that registered as a confirmed single tap\n     * @return true if the single tap was handled, false to toggle scroll handle\n     */\n    boolean onTap(MotionEvent e);\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/model/LinkTapEvent.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.model;\n\nimport android.graphics.RectF;\n\nimport com.shockwave.pdfium.PdfDocument;\n\npublic class LinkTapEvent {\n    private float originalX;\n    private float originalY;\n    private float documentX;\n    private float documentY;\n    private RectF mappedLinkRect;\n    private PdfDocument.Link link;\n\n    public LinkTapEvent(float originalX, float originalY, float documentX, float documentY, RectF mappedLinkRect, PdfDocument.Link link) {\n        this.originalX = originalX;\n        this.originalY = originalY;\n        this.documentX = documentX;\n        this.documentY = documentY;\n        this.mappedLinkRect = mappedLinkRect;\n        this.link = link;\n    }\n\n    public float getOriginalX() {\n        return originalX;\n    }\n\n    public float getOriginalY() {\n        return originalY;\n    }\n\n    public float getDocumentX() {\n        return documentX;\n    }\n\n    public float getDocumentY() {\n        return documentY;\n    }\n\n    public RectF getMappedLinkRect() {\n        return mappedLinkRect;\n    }\n\n    public PdfDocument.Link getLink() {\n        return link;\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/model/PagePart.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.model;\r\n\r\nimport android.graphics.Bitmap;\r\nimport android.graphics.RectF;\r\n\r\npublic class PagePart {\r\n\r\n    private int page;\r\n\r\n    private Bitmap renderedBitmap;\r\n\r\n    private RectF pageRelativeBounds;\r\n\r\n    private boolean thumbnail;\r\n\r\n    private int cacheOrder;\r\n\r\n    public PagePart(int page, Bitmap renderedBitmap, RectF pageRelativeBounds, boolean thumbnail, int cacheOrder) {\r\n        super();\r\n        this.page = page;\r\n        this.renderedBitmap = renderedBitmap;\r\n        this.pageRelativeBounds = pageRelativeBounds;\r\n        this.thumbnail = thumbnail;\r\n        this.cacheOrder = cacheOrder;\r\n    }\r\n\r\n    public int getCacheOrder() {\r\n        return cacheOrder;\r\n    }\r\n\r\n    public int getPage() {\r\n        return page;\r\n    }\r\n\r\n    public Bitmap getRenderedBitmap() {\r\n        return renderedBitmap;\r\n    }\r\n\r\n    public RectF getPageRelativeBounds() {\r\n        return pageRelativeBounds;\r\n    }\r\n\r\n    public boolean isThumbnail() {\r\n        return thumbnail;\r\n    }\r\n\r\n    public void setCacheOrder(int cacheOrder) {\r\n        this.cacheOrder = cacheOrder;\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object obj) {\r\n        if (!(obj instanceof PagePart)) {\r\n            return false;\r\n        }\r\n\r\n        PagePart part = (PagePart) obj;\r\n        return part.getPage() == page\r\n                && part.getPageRelativeBounds().left == pageRelativeBounds.left\r\n                && part.getPageRelativeBounds().right == pageRelativeBounds.right\r\n                && part.getPageRelativeBounds().top == pageRelativeBounds.top\r\n                && part.getPageRelativeBounds().bottom == pageRelativeBounds.bottom;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/scroll/DefaultScrollHandle.java",
    "content": "package com.github.barteksc.pdfviewer.scroll;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport androidx.core.content.ContextCompat;\nimport android.util.TypedValue;\nimport android.view.MotionEvent;\nimport android.view.ViewGroup;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport com.github.barteksc.pdfviewer.PDFView;\nimport com.github.barteksc.pdfviewer.R;\nimport com.github.barteksc.pdfviewer.util.Util;\n\npublic class DefaultScrollHandle extends RelativeLayout implements ScrollHandle {\n\n    private final static int HANDLE_LONG = 65;\n    private final static int HANDLE_SHORT = 40;\n    private final static int DEFAULT_TEXT_SIZE = 16;\n\n    private float relativeHandlerMiddle = 0f;\n\n    protected TextView textView;\n    protected Context context;\n    private boolean inverted;\n    private PDFView pdfView;\n    private float currentPos;\n\n    private Handler handler = new Handler();\n    private Runnable hidePageScrollerRunnable = new Runnable() {\n        @Override\n        public void run() {\n            hide();\n        }\n    };\n\n    public DefaultScrollHandle(Context context) {\n        this(context, false);\n    }\n\n    public DefaultScrollHandle(Context context, boolean inverted) {\n        super(context);\n        this.context = context;\n        this.inverted = inverted;\n        textView = new TextView(context);\n        setVisibility(INVISIBLE);\n        setTextColor(Color.BLACK);\n        setTextSize(DEFAULT_TEXT_SIZE);\n    }\n\n    @Override\n    public void setupLayout(PDFView pdfView) {\n        int align, width, height;\n        Drawable background;\n        // determine handler position, default is right (when scrolling vertically) or bottom (when scrolling horizontally)\n        if (pdfView.isSwipeVertical()) {\n            width = HANDLE_LONG;\n            height = HANDLE_SHORT;\n            if (inverted) { // left\n                align = ALIGN_PARENT_LEFT;\n                background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_left);\n            } else { // right\n                align = ALIGN_PARENT_RIGHT;\n                background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_right);\n            }\n        } else {\n            width = HANDLE_SHORT;\n            height = HANDLE_LONG;\n            if (inverted) { // top\n                align = ALIGN_PARENT_TOP;\n                background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_top);\n            } else { // bottom\n                align = ALIGN_PARENT_BOTTOM;\n                background = ContextCompat.getDrawable(context, R.drawable.default_scroll_handle_bottom);\n            }\n        }\n\n        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {\n            setBackgroundDrawable(background);\n        } else {\n            setBackground(background);\n        }\n\n        LayoutParams lp = new LayoutParams(Util.getDP(context, width), Util.getDP(context, height));\n        lp.setMargins(0, 0, 0, 0);\n\n        LayoutParams tvlp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n        tvlp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);\n\n        addView(textView, tvlp);\n\n        lp.addRule(align);\n        pdfView.addView(this, lp);\n\n        this.pdfView = pdfView;\n    }\n\n    @Override\n    public void destroyLayout() {\n        pdfView.removeView(this);\n    }\n\n    @Override\n    public void setScroll(float position) {\n        if (!shown()) {\n            show();\n        } else {\n            handler.removeCallbacks(hidePageScrollerRunnable);\n        }\n        if (pdfView != null) {\n            setPosition((pdfView.isSwipeVertical() ? pdfView.getHeight() : pdfView.getWidth()) * position);\n        }\n    }\n\n    private void setPosition(float pos) {\n        if (Float.isInfinite(pos) || Float.isNaN(pos)) {\n            return;\n        }\n        float pdfViewSize;\n        if (pdfView.isSwipeVertical()) {\n            pdfViewSize = pdfView.getHeight();\n        } else {\n            pdfViewSize = pdfView.getWidth();\n        }\n        pos -= relativeHandlerMiddle;\n\n        if (pos < 0) {\n            pos = 0;\n        } else if (pos > pdfViewSize - Util.getDP(context, HANDLE_SHORT)) {\n            pos = pdfViewSize - Util.getDP(context, HANDLE_SHORT);\n        }\n\n        if (pdfView.isSwipeVertical()) {\n            setY(pos);\n        } else {\n            setX(pos);\n        }\n\n        calculateMiddle();\n        invalidate();\n    }\n\n    private void calculateMiddle() {\n        float pos, viewSize, pdfViewSize;\n        if (pdfView.isSwipeVertical()) {\n            pos = getY();\n            viewSize = getHeight();\n            pdfViewSize = pdfView.getHeight();\n        } else {\n            pos = getX();\n            viewSize = getWidth();\n            pdfViewSize = pdfView.getWidth();\n        }\n        relativeHandlerMiddle = ((pos + relativeHandlerMiddle) / pdfViewSize) * viewSize;\n    }\n\n    @Override\n    public void hideDelayed() {\n        handler.postDelayed(hidePageScrollerRunnable, 1000);\n    }\n\n    @Override\n    public void setPageNum(int pageNum) {\n        String text = String.valueOf(pageNum);\n        if (!textView.getText().equals(text)) {\n            textView.setText(text);\n        }\n    }\n\n    @Override\n    public boolean shown() {\n        return getVisibility() == VISIBLE;\n    }\n\n    @Override\n    public void show() {\n        setVisibility(VISIBLE);\n    }\n\n    @Override\n    public void hide() {\n        setVisibility(INVISIBLE);\n    }\n\n    public void setTextColor(int color) {\n        textView.setTextColor(color);\n    }\n\n    /**\n     * @param size text size in dp\n     */\n    public void setTextSize(int size) {\n        textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, size);\n    }\n\n    private boolean isPDFViewReady() {\n        return pdfView != null && pdfView.getPageCount() > 0 && !pdfView.documentFitsView();\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n\n        if (!isPDFViewReady()) {\n            return super.onTouchEvent(event);\n        }\n\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n            case MotionEvent.ACTION_POINTER_DOWN:\n                pdfView.stopFling();\n                handler.removeCallbacks(hidePageScrollerRunnable);\n                if (pdfView.isSwipeVertical()) {\n                    currentPos = event.getRawY() - getY();\n                } else {\n                    currentPos = event.getRawX() - getX();\n                }\n            case MotionEvent.ACTION_MOVE:\n                if (pdfView.isSwipeVertical()) {\n                    setPosition(event.getRawY() - currentPos + relativeHandlerMiddle);\n                    pdfView.setPositionOffset(relativeHandlerMiddle / (float) getHeight(), false);\n                } else {\n                    setPosition(event.getRawX() - currentPos + relativeHandlerMiddle);\n                    pdfView.setPositionOffset(relativeHandlerMiddle / (float) getWidth(), false);\n                }\n                return true;\n            case MotionEvent.ACTION_CANCEL:\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_POINTER_UP:\n                hideDelayed();\n                pdfView.performPageSnap();\n                return true;\n        }\n\n        return super.onTouchEvent(event);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/scroll/ScrollHandle.java",
    "content": "package com.github.barteksc.pdfviewer.scroll;\n\nimport com.github.barteksc.pdfviewer.PDFView;\n\npublic interface ScrollHandle {\n\n    /**\n     * Used to move the handle, called internally by PDFView\n     *\n     * @param position current scroll ratio between 0 and 1\n     */\n    void setScroll(float position);\n\n    /**\n     * Method called by PDFView after setting scroll handle.\n     * Do not call this method manually.\n     * For usage sample see {@link DefaultScrollHandle}\n     *\n     * @param pdfView PDFView instance\n     */\n    void setupLayout(PDFView pdfView);\n\n    /**\n     * Method called by PDFView when handle should be removed from layout\n     * Do not call this method manually.\n     */\n    void destroyLayout();\n\n    /**\n     * Set page number displayed on handle\n     *\n     * @param pageNum page number\n     */\n    void setPageNum(int pageNum);\n\n    /**\n     * Get handle visibility\n     *\n     * @return true if handle is visible, false otherwise\n     */\n    boolean shown();\n\n    /**\n     * Show handle\n     */\n    void show();\n\n    /**\n     * Hide handle immediately\n     */\n    void hide();\n\n    /**\n     * Hide handle after some time (defined by implementation)\n     */\n    void hideDelayed();\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/AssetSource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\n\nimport android.content.Context;\nimport android.os.ParcelFileDescriptor;\n\nimport com.github.barteksc.pdfviewer.util.FileUtils;\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class AssetSource implements DocumentSource {\n\n    private final String assetName;\n\n    public AssetSource(String assetName) {\n        this.assetName = assetName;\n    }\n\n    @Override\n    public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {\n        File f = FileUtils.fileFromAsset(context, assetName);\n        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);\n        return core.newDocument(pfd, password);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/ByteArraySource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\nimport android.content.Context;\n\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.IOException;\n\npublic class ByteArraySource implements DocumentSource {\n\n    private byte[] data;\n\n    public ByteArraySource(byte[] data) {\n        this.data = data;\n    }\n\n    @Override\n    public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {\n        return core.newDocument(data, password);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/DocumentSource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\nimport android.content.Context;\n\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.IOException;\n\npublic interface DocumentSource {\n    PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException;\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/FileSource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\nimport android.content.Context;\nimport android.os.ParcelFileDescriptor;\n\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class FileSource implements DocumentSource {\n\n    private File file;\n\n    public FileSource(File file) {\n        this.file = file;\n    }\n\n    @Override\n    public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {\n        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);\n        return core.newDocument(pfd, password);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/InputStreamSource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\nimport android.content.Context;\n\nimport com.github.barteksc.pdfviewer.util.Util;\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class InputStreamSource implements DocumentSource {\n\n    private InputStream inputStream;\n\n    public InputStreamSource(InputStream inputStream) {\n        this.inputStream = inputStream;\n    }\n\n    @Override\n    public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {\n        return core.newDocument(Util.toByteArray(inputStream), password);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/source/UriSource.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.source;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\n\nimport com.shockwave.pdfium.PdfDocument;\nimport com.shockwave.pdfium.PdfiumCore;\n\nimport java.io.IOException;\n\npublic class UriSource implements DocumentSource {\n\n    private Uri uri;\n\n    public UriSource(Uri uri) {\n        this.uri = uri;\n    }\n\n    @Override\n    public PdfDocument createDocument(Context context, PdfiumCore core, String password) throws IOException {\n        ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, \"r\");\n        return core.newDocument(pfd, password);\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/ArrayUtils.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.util;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class ArrayUtils {\r\n\r\n    private ArrayUtils() {\r\n        // Prevents instantiation\r\n    }\r\n\r\n    /** Transforms (0,1,2,2,3) to (0,1,2,3) */\r\n    public static int[] deleteDuplicatedPages(int[] pages) {\r\n        List<Integer> result = new ArrayList<>();\r\n        int lastInt = -1;\r\n        for (Integer currentInt : pages) {\r\n            if (lastInt != currentInt) {\r\n                result.add(currentInt);\r\n            }\r\n            lastInt = currentInt;\r\n        }\r\n        int[] arrayResult = new int[result.size()];\r\n        for (int i = 0; i < result.size(); i++) {\r\n            arrayResult[i] = result.get(i);\r\n        }\r\n        return arrayResult;\r\n    }\r\n\r\n    /** Transforms (0, 4, 4, 6, 6, 6, 3) into (0, 1, 1, 2, 2, 2, 3) */\r\n    public static int[] calculateIndexesInDuplicateArray(int[] originalUserPages) {\r\n        int[] result = new int[originalUserPages.length];\r\n        if (originalUserPages.length == 0) {\r\n            return result;\r\n        }\r\n\r\n        int index = 0;\r\n        result[0] = index;\r\n        for (int i = 1; i < originalUserPages.length; i++) {\r\n            if (originalUserPages[i] != originalUserPages[i - 1]) {\r\n                index++;\r\n            }\r\n            result[i] = index;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    public static String arrayToString(int[] array) {\r\n        StringBuilder builder = new StringBuilder(\"[\");\r\n        for (int i = 0; i < array.length; i++) {\r\n            builder.append(array[i]);\r\n            if (i != array.length - 1) {\r\n                builder.append(\",\");\r\n            }\r\n        }\r\n        builder.append(\"]\");\r\n        return builder.toString();\r\n    }\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/Constants.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.util;\r\n\r\npublic class Constants {\r\n\r\n    public static boolean DEBUG_MODE = false;\r\n\r\n    /** Between 0 and 1, the thumbnails quality (default 0.3). Increasing this value may cause performance decrease */\r\n    public static float THUMBNAIL_RATIO = 0.3f;\r\n\r\n    /**\r\n     * The size of the rendered parts (default 256)\r\n     * Tinier : a little bit slower to have the whole page rendered but more reactive.\r\n     * Bigger : user will have to wait longer to have the first visual results\r\n     */\r\n    public static float PART_SIZE = 256;\r\n\r\n    /** Part of document above and below screen that should be preloaded, in dp */\r\n    public static int PRELOAD_OFFSET = 20;\r\n\r\n    public static class Cache {\r\n\r\n        /** The size of the cache (number of bitmaps kept) */\r\n        public static int CACHE_SIZE = 120;\r\n\r\n        public static int THUMBNAILS_CACHE_SIZE = 8;\r\n    }\r\n\r\n    public static class Pinch {\r\n\r\n        public static float MAXIMUM_ZOOM = 10;\r\n\r\n        public static float MINIMUM_ZOOM = 1;\r\n\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/FileUtils.java",
    "content": "/**\n * Copyright 2016 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.util;\n\nimport android.content.Context;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\npublic class FileUtils {\n\n    private FileUtils() {\n        // Prevents instantiation\n    }\n\n    public static File fileFromAsset(Context context, String assetName) throws IOException {\n        File outFile = new File(context.getCacheDir(), assetName + \"-pdfview.pdf\");\n        if (assetName.contains(\"/\")) {\n            outFile.getParentFile().mkdirs();\n        }\n        copy(context.getAssets().open(assetName), outFile);\n        return outFile;\n    }\n\n    public static void copy(InputStream inputStream, File output) throws IOException {\n        OutputStream outputStream = null;\n        try {\n            outputStream = new FileOutputStream(output);\n            int read = 0;\n            byte[] bytes = new byte[1024];\n            while ((read = inputStream.read(bytes)) != -1) {\n                outputStream.write(bytes, 0, read);\n            }\n        } finally {\n            try {\n                if (inputStream != null) {\n                    inputStream.close();\n                }\n            } finally {\n                if (outputStream != null) {\n                    outputStream.close();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/FitPolicy.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.util;\n\npublic enum FitPolicy {\n    WIDTH, HEIGHT, BOTH\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/MathUtils.java",
    "content": "/**\r\n * Copyright 2016 Bartosz Schiller\r\n * <p/>\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n * <p/>\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n * <p/>\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.github.barteksc.pdfviewer.util;\r\n\r\npublic class MathUtils {\r\n\r\n    static private final int BIG_ENOUGH_INT = 16 * 1024;\r\n    static private final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT;\r\n    static private final double BIG_ENOUGH_CEIL = 16384.999999999996;\r\n\r\n    private MathUtils() {\r\n        // Prevents instantiation\r\n    }\r\n\r\n    /**\r\n     * Limits the given <b>number</b> between the other values\r\n     * @param number  The number to limit.\r\n     * @param between The smallest value the number can take.\r\n     * @param and     The biggest value the number can take.\r\n     * @return The limited number.\r\n     */\r\n    public static int limit(int number, int between, int and) {\r\n        if (number <= between) {\r\n            return between;\r\n        }\r\n        if (number >= and) {\r\n            return and;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    /**\r\n     * Limits the given <b>number</b> between the other values\r\n     * @param number  The number to limit.\r\n     * @param between The smallest value the number can take.\r\n     * @param and     The biggest value the number can take.\r\n     * @return The limited number.\r\n     */\r\n    public static float limit(float number, float between, float and) {\r\n        if (number <= between) {\r\n            return between;\r\n        }\r\n        if (number >= and) {\r\n            return and;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    public static float max(float number, float max) {\r\n        if (number > max) {\r\n            return max;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    public static float min(float number, float min) {\r\n        if (number < min) {\r\n            return min;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    public static int max(int number, int max) {\r\n        if (number > max) {\r\n            return max;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    public static int min(int number, int min) {\r\n        if (number < min) {\r\n            return min;\r\n        }\r\n        return number;\r\n    }\r\n\r\n    /**\r\n     * Methods from libGDX - https://github.com/libgdx/libgdx\r\n     */\r\n\r\n    /** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats from\r\n     * -(2^14) to (Float.MAX_VALUE - 2^14). */\r\n    static public int floor(float value) {\r\n        return (int) (value + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT;\r\n    }\r\n\r\n    /** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats from\r\n     * -(2^14) to (Float.MAX_VALUE - 2^14). */\r\n    static public int ceil(float value) {\r\n        return (int) (value + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT;\r\n    }\r\n}\r\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/PageSizeCalculator.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.util;\n\nimport com.shockwave.pdfium.util.Size;\nimport com.shockwave.pdfium.util.SizeF;\n\npublic class PageSizeCalculator {\n\n    private FitPolicy fitPolicy;\n    private final Size originalMaxWidthPageSize;\n    private final Size originalMaxHeightPageSize;\n    private final Size viewSize;\n    private SizeF optimalMaxWidthPageSize;\n    private SizeF optimalMaxHeightPageSize;\n    private float widthRatio;\n    private float heightRatio;\n    private boolean fitEachPage;\n\n    public PageSizeCalculator(FitPolicy fitPolicy, Size originalMaxWidthPageSize, Size originalMaxHeightPageSize,\n                              Size viewSize, boolean fitEachPage) {\n        this.fitPolicy = fitPolicy;\n        this.originalMaxWidthPageSize = originalMaxWidthPageSize;\n        this.originalMaxHeightPageSize = originalMaxHeightPageSize;\n        this.viewSize = viewSize;\n        this.fitEachPage = fitEachPage;\n        calculateMaxPages();\n    }\n\n    public SizeF calculate(Size pageSize) {\n        if (pageSize.getWidth() <= 0 || pageSize.getHeight() <= 0) {\n            return new SizeF(0, 0);\n        }\n        float maxWidth = fitEachPage ? viewSize.getWidth() : pageSize.getWidth() * widthRatio;\n        float maxHeight = fitEachPage ? viewSize.getHeight() : pageSize.getHeight() * heightRatio;\n        switch (fitPolicy) {\n            case HEIGHT:\n                return fitHeight(pageSize, maxHeight);\n            case BOTH:\n                return fitBoth(pageSize, maxWidth, maxHeight);\n            default:\n                return fitWidth(pageSize, maxWidth);\n        }\n    }\n\n    public SizeF getOptimalMaxWidthPageSize() {\n        return optimalMaxWidthPageSize;\n    }\n\n    public SizeF getOptimalMaxHeightPageSize() {\n        return optimalMaxHeightPageSize;\n    }\n\n    private void calculateMaxPages() {\n        switch (fitPolicy) {\n            case HEIGHT:\n                optimalMaxHeightPageSize = fitHeight(originalMaxHeightPageSize, viewSize.getHeight());\n                heightRatio = optimalMaxHeightPageSize.getHeight() / originalMaxHeightPageSize.getHeight();\n                optimalMaxWidthPageSize = fitHeight(originalMaxWidthPageSize, originalMaxWidthPageSize.getHeight() * heightRatio);\n                break;\n            case BOTH:\n                SizeF localOptimalMaxWidth = fitBoth(originalMaxWidthPageSize, viewSize.getWidth(), viewSize.getHeight());\n                float localWidthRatio = localOptimalMaxWidth.getWidth() / originalMaxWidthPageSize.getWidth();\n                this.optimalMaxHeightPageSize = fitBoth(originalMaxHeightPageSize, originalMaxHeightPageSize.getWidth() * localWidthRatio,\n                        viewSize.getHeight());\n                heightRatio = optimalMaxHeightPageSize.getHeight() / originalMaxHeightPageSize.getHeight();\n                optimalMaxWidthPageSize = fitBoth(originalMaxWidthPageSize, viewSize.getWidth(), originalMaxWidthPageSize.getHeight() * heightRatio);\n                widthRatio = optimalMaxWidthPageSize.getWidth() / originalMaxWidthPageSize.getWidth();\n                break;\n            default:\n                optimalMaxWidthPageSize = fitWidth(originalMaxWidthPageSize, viewSize.getWidth());\n                widthRatio = optimalMaxWidthPageSize.getWidth() / originalMaxWidthPageSize.getWidth();\n                optimalMaxHeightPageSize = fitWidth(originalMaxHeightPageSize, originalMaxHeightPageSize.getWidth() * widthRatio);\n                break;\n        }\n    }\n\n    private SizeF fitWidth(Size pageSize, float maxWidth) {\n        float w = pageSize.getWidth(), h = pageSize.getHeight();\n        float ratio = w / h;\n        w = maxWidth;\n        h = (float) Math.floor(maxWidth / ratio);\n        return new SizeF(w, h);\n    }\n\n    private SizeF fitHeight(Size pageSize, float maxHeight) {\n        float w = pageSize.getWidth(), h = pageSize.getHeight();\n        float ratio = h / w;\n        h = maxHeight;\n        w = (float) Math.floor(maxHeight / ratio);\n        return new SizeF(w, h);\n    }\n\n    private SizeF fitBoth(Size pageSize, float maxWidth, float maxHeight) {\n        float w = pageSize.getWidth(), h = pageSize.getHeight();\n        float ratio = w / h;\n        w = maxWidth;\n        h = (float) Math.floor(maxWidth / ratio);\n        if (h > maxHeight) {\n            h = maxHeight;\n            w = (float) Math.floor(maxHeight * ratio);\n        }\n        return new SizeF(w, h);\n    }\n\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/SnapEdge.java",
    "content": "/**\n * Copyright 2017 Bartosz Schiller\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.util;\n\npublic enum SnapEdge {\n    START, CENTER, END, NONE\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/util/Util.java",
    "content": "/*\n * Copyright (C) 2016 Bartosz Schiller.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.github.barteksc.pdfviewer.util;\n\nimport android.content.Context;\nimport android.util.TypedValue;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class Util {\n    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;\n\n    public static int getDP(Context context, int dp) {\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());\n    }\n\n    public static byte[] toByteArray(InputStream inputStream) throws IOException {\n        ByteArrayOutputStream os = new ByteArrayOutputStream();\n        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];\n        int n;\n        while (-1 != (n = inputStream.read(buffer))) {\n            os.write(buffer, 0, n);\n        }\n        return os.toByteArray();\n    }\n}\n"
  },
  {
    "path": "android-pdf-viewer/src/main/res/drawable/default_scroll_handle_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <stroke\n        android:color=\"#6C7A89\"\n        android:width=\"1dp\" />\n    <corners\n        android:topLeftRadius=\"10dp\"\n        android:topRightRadius=\"10dp\" />\n    <solid android:color=\"#DADFE1\" />\n</shape>"
  },
  {
    "path": "android-pdf-viewer/src/main/res/drawable/default_scroll_handle_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:drawable=\"@drawable/default_scroll_handle_right\"\n    android:fromDegrees=\"180\"\n    android:toDegrees=\"180\"\n    android:visible=\"true\" />"
  },
  {
    "path": "android-pdf-viewer/src/main/res/drawable/default_scroll_handle_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <stroke\n        android:color=\"#6C7A89\"\n        android:width=\"1dp\" />\n    <corners\n        android:bottomLeftRadius=\"10dp\"\n        android:topLeftRadius=\"10dp\" />\n    <solid android:color=\"#DADFE1\" />\n</shape>"
  },
  {
    "path": "android-pdf-viewer/src/main/res/drawable/default_scroll_handle_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:drawable=\"@drawable/default_scroll_handle_bottom\"\n    android:fromDegrees=\"180\"\n    android:toDegrees=\"180\"\n    android:visible=\"true\" />"
  },
  {
    "path": "android-pdf-viewer/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"ScrollBar\">\n        <attr name=\"sb_handlerColor\" format=\"color|reference\" />\n        <attr name=\"sb_indicatorColor\" format=\"color|reference\" />\n        <attr name=\"sb_indicatorTextColor\" format=\"color|reference\" />\n        <attr name=\"sb_horizontal\" format=\"boolean|reference\" />\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "build.gradle",
    "content": "\nbuildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.13.0'\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nsubprojects {\n    configurations.configureEach {\n        resolutionStrategy {\n            force 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'\n            force 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22'\n            force 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22'\n        }\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Sep 10 21:17:12 IST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n\n# 16 KB page size support\nandroid.enableR8.fullMode=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@REM\n@REM Copyright 2014 Joan Zapata\n@REM\n@REM This file is part of Android-pdfview.\n@REM\n@REM Android-pdfview is free software: you can redistribute it and/or modify\n@REM it under the terms of the GNU General Public License as published by\n@REM the Free Software Foundation, either version 3 of the License, or\n@REM (at your option) any later version.\n@REM\n@REM Android-pdfview is distributed in the hope that it will be useful,\n@REM but WITHOUT ANY WARRANTY; without even the implied warranty of\n@REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n@REM GNU General Public License for more details.\n@REM\n@REM You should have received a copy of the GNU General Public License\n@REM along with Android-pdfview.  If not, see <http://www.gnu.org/licenses/>.\n@REM\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "sample/build.gradle",
    "content": "buildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrepositories {\n    google()\n    mavenCentral()\n}\n\napply plugin: 'com.android.application'\n\nandroid {\n    namespace 'com.github.barteksc.sample'\n    compileSdkVersion 36\n\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 36\n        versionCode 3\n        versionName \"3.0.0\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        buildConfig true\n    }\n    \n    packagingOptions {\n        exclude 'META-INF/DEPENDENCIES'\n        exclude 'META-INF/LICENSE'\n        exclude 'META-INF/LICENSE.txt'\n        exclude 'META-INF/NOTICE'\n        exclude 'META-INF/NOTICE.txt'\n        \n        // 16 KB page size support configuration\n        jniLibs {\n            useLegacyPackaging true  // Use compressed shared libraries to avoid 16 KB alignment issues\n        }\n    }\n\n    // Enable 16 KB page size support for native libraries\n    ndkVersion \"28.0.12433566\"  // Use NDK r28+ for 16 KB support\n\n}\n\ndependencies {\n    implementation project(':android-pdf-viewer')\n    implementation 'androidx.appcompat:appcompat:1.7.1'\n}\n\n\n"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n\t<application\n\t\tandroid:icon=\"@drawable/ic_launcher\"\n\t\tandroid:label=\"@string/app_name\"\n\t\tandroid:theme=\"@style/Theme.AppCompat.Light\">\n\t\t<activity\n\t\t\tandroid:name=\".PDFViewActivity\"\n\t\t\tandroid:label=\"@string/app_name\"\n\t\t\tandroid:exported=\"true\" >\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />\n\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t</application>\n\n</manifest>"
  },
  {
    "path": "sample/src/main/java/com/github/barteksc/sample/PDFViewActivity.java",
    "content": "package com.github.barteksc.sample;\n\nimport android.content.ActivityNotFoundException;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.OpenableColumns;\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport com.github.barteksc.pdfviewer.PDFView;\nimport com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;\nimport com.github.barteksc.pdfviewer.listener.OnPageChangeListener;\nimport com.github.barteksc.pdfviewer.listener.OnPageErrorListener;\nimport com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle;\nimport com.github.barteksc.pdfviewer.util.FitPolicy;\nimport com.shockwave.pdfium.PdfDocument;\n\nimport java.util.List;\n\npublic class PDFViewActivity extends AppCompatActivity implements\n        OnPageChangeListener, OnLoadCompleteListener, OnPageErrorListener {\n\n    private static final String TAG = PDFViewActivity.class.getSimpleName();\n\n    private static final int PERMISSION_CODE = 42042;\n    private static final String SAMPLE_FILE = \"sample.pdf\";\n    private static final String READ_EXTERNAL_STORAGE = \"android.permission.READ_EXTERNAL_STORAGE\";\n\n    private PDFView pdfView;\n    private Uri uri;\n    private int pageNumber = 0;\n    private String pdfFileName;\n\n    private final ActivityResultLauncher<Intent> filePickerLauncher =\n            registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {\n                if (result.getResultCode() == RESULT_OK && result.getData() != null) {\n                    uri = result.getData().getData();\n                    displayFromUri(uri);\n                }\n            });\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        pdfView = findViewById(R.id.pdfView);\n        pdfView.setBackgroundColor(Color.LTGRAY);\n\n        if (uri != null) {\n            displayFromUri(uri);\n        } else {\n            displayFromAsset(SAMPLE_FILE);\n        }\n    }\n\n    private void pickFile() {\n        int permissionCheck = ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE);\n        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE}, PERMISSION_CODE);\n        } else {\n            launchPicker();\n        }\n    }\n\n    private void launchPicker() {\n        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n        intent.setType(\"application/pdf\");\n        try {\n            filePickerLauncher.launch(intent);\n        } catch (ActivityNotFoundException e) {\n            Toast.makeText(this, R.string.toast_pick_file_error, Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    private void displayFromAsset(String assetFileName) {\n        pdfFileName = assetFileName;\n        pdfView.fromAsset(assetFileName)\n                .defaultPage(pageNumber)\n                .onPageChange(this)\n                .enableAnnotationRendering(true)\n                .onLoad(this)\n                .scrollHandle(new DefaultScrollHandle(this))\n                .spacing(10)\n                .onPageError(this)\n                .pageFitPolicy(FitPolicy.BOTH)\n                .load();\n    }\n\n    private void displayFromUri(Uri uri) {\n        pdfFileName = getFileName(uri);\n        pdfView.fromUri(uri)\n                .defaultPage(pageNumber)\n                .onPageChange(this)\n                .enableAnnotationRendering(true)\n                .onLoad(this)\n                .scrollHandle(new DefaultScrollHandle(this))\n                .spacing(10)\n                .onPageError(this)\n                .load();\n    }\n\n    private String getFileName(Uri uri) {\n        String result = null;\n        if (\"content\".equals(uri.getScheme())) {\n            try (Cursor cursor = getContentResolver().query(uri, null, null, null, null)) {\n                if (cursor != null && cursor.moveToFirst()) {\n                    int nameIndex = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME);\n                    result = cursor.getString(nameIndex);\n                }\n            }\n        }\n        if (result == null) {\n            result = uri.getLastPathSegment();\n        }\n        return result;\n    }\n\n    @Override\n    public void onPageChanged(int page, int pageCount) {\n        pageNumber = page;\n        setTitle(String.format(\"%s %s / %s\", pdfFileName, page + 1, pageCount));\n    }\n\n    @Override\n    public void loadComplete(int nbPages) {\n        PdfDocument.Meta meta = pdfView.getDocumentMeta();\n        Log.e(TAG, \"title = \" + meta.getTitle());\n        Log.e(TAG, \"author = \" + meta.getAuthor());\n        Log.e(TAG, \"subject = \" + meta.getSubject());\n        Log.e(TAG, \"keywords = \" + meta.getKeywords());\n        Log.e(TAG, \"creator = \" + meta.getCreator());\n        Log.e(TAG, \"producer = \" + meta.getProducer());\n        Log.e(TAG, \"creationDate = \" + meta.getCreationDate());\n        Log.e(TAG, \"modDate = \" + meta.getModDate());\n\n        printBookmarksTree(pdfView.getTableOfContents(), \"-\");\n    }\n\n    private void printBookmarksTree(List<PdfDocument.Bookmark> tree, String sep) {\n        for (PdfDocument.Bookmark b : tree) {\n            Log.e(TAG, String.format(\"%s %s, p %d\", sep, b.getTitle(), b.getPageIdx()));\n            if (b.hasChildren()) {\n                printBookmarksTree(b.getChildren(), sep + \"-\");\n            }\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,\n                                           @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        if (requestCode == PERMISSION_CODE && grantResults.length > 0 &&\n                grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n            launchPicker();\n        }\n    }\n\n    @Override\n    public void onPageError(int page, Throwable t) {\n        Log.e(TAG, \"Cannot load page \" + page, t);\n    }\n}\n"
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n    <com.github.barteksc.pdfviewer.PDFView\n        android:id=\"@+id/pdfView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</RelativeLayout>"
  },
  {
    "path": "sample/src/main/res/menu/options.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/pickFile\"\n        android:icon=\"@drawable/ic_open_in_browser_grey_700_48dp\"\n        android:title=\"@string/pick_file\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndroidPdfViewer demo</string>\n    <string name=\"pick_file\">Pick file</string>\n    <string name=\"toast_pick_file_error\">Unable to pick file. Check status of file manager.</string>\n</resources>"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "content": "<resources>\n\n\n</resources>"
  },
  {
    "path": "settings.gradle",
    "content": "include ':android-pdf-viewer'\ninclude ':sample'"
  }
]