commit d7eed01e8071785303a282c3c8bfa3ccb4dc7cec Author: Daniel Micay Date: Thu Jun 27 23:22:08 2019 -0400 initial commit with overhauled / rebranded project diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c737277 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://grapheneos.org/donate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fd942e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.iml +.gradle +local.properties +build/ +signing.properties +*.jks +/.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..21a6e0f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Daniel Micay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/PDFJS_LICENSE b/PDFJS_LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/PDFJS_LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..32be588 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,62 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion "29.0.0" + defaultConfig { + applicationId "org.grapheneos.pdfviewer" + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName versionCode.toString() + resConfigs "en" + } + + signingConfigs { + release + } + buildTypes { + debug { + applicationIdSuffix ".debug" + } + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.0.2' +} + +def props = new Properties() +def propFile = new File('signing.properties') + +if (propFile.canRead()) { + props.load(new FileInputStream(propFile)) + + if (props != null && + props.containsKey('STORE_FILE') && + props.containsKey('STORE_PASSWORD') && + props.containsKey('KEY_ALIAS') && + props.containsKey('KEY_PASSWORD')) { + android.signingConfigs.release.storeFile = rootProject.file(props['STORE_FILE']) + android.signingConfigs.release.storePassword = props['STORE_PASSWORD'] + android.signingConfigs.release.keyAlias = props['KEY_ALIAS'] + android.signingConfigs.release.keyPassword = props['KEY_PASSWORD'] + } else { + println 'signing.properties found but some entries are missing' + android.buildTypes.release.signingConfig = null + } +} else { + println 'signing.properties not found' + android.buildTypes.release.signingConfig = null +} diff --git a/app/lint.xml b/app/lint.xml new file mode 100644 index 0000000..26fc1e1 --- /dev/null +++ b/app/lint.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..5e9f79b --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,3 @@ +-keepclassmembers class * { + @android.webkit.JavascriptInterface ; +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..dc055a6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/pdf.js b/app/src/main/assets/pdf.js new file mode 100644 index 0000000..61af55c --- /dev/null +++ b/app/src/main/assets/pdf.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf",[],t):"object"==typeof exports?exports["pdfjs-dist/build/pdf"]=t():e["pdfjs-dist/build/pdf"]=e.pdfjsDistBuildPdf=t()}("undefined"!=typeof self?self:this,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=75)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.unreachable=t.warn=t.utf8StringToString=t.stringToUTF8String=t.stringToPDFString=t.stringToBytes=t.string32=t.shadow=t.setVerbosityLevel=t.ReadableStream=t.removeNullCharacters=t.readUint32=t.readUint16=t.readInt8=t.log2=t.loadJpegStream=t.isEvalSupported=t.isLittleEndian=t.createValidAbsoluteUrl=t.isSameOrigin=t.isNodeJS=t.isSpace=t.isString=t.isNum=t.isEmptyObj=t.isBool=t.isArrayBuffer=t.info=t.getVerbosityLevel=t.getLookupTableFactory=t.deprecated=t.createObjectURL=t.createPromiseCapability=t.createBlob=t.bytesToString=t.assert=t.arraysToBytes=t.arrayByteLength=t.FormatError=t.XRefParseException=t.Util=t.UnknownErrorException=t.UnexpectedResponseException=t.TextRenderingMode=t.StreamType=t.StatTimer=t.PasswordResponses=t.PasswordException=t.PageViewport=t.NotImplementedException=t.NativeImageDecoding=t.MissingPDFException=t.MissingDataException=t.MessageHandler=t.InvalidPDFException=t.AbortException=t.CMapCompressionType=t.ImageKind=t.FontType=t.AnnotationType=t.AnnotationFlag=t.AnnotationFieldFlag=t.AnnotationBorderStyleType=t.UNSUPPORTED_FEATURES=t.VERBOSITY_LEVELS=t.OPS=t.IDENTITY_MATRIX=t.FONT_IDENTITY_MATRIX=void 0;var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};r(76);var i=r(116),a={errors:0,warnings:1,infos:5},o=a.warnings;function s(e){o>=a.warnings&&console.log("Warning: "+e)}function l(e){throw new Error(e)}function u(e,t){e||l(t)}var c=function(){function e(e,t){this.name="PasswordException",this.message=e,this.code=t}return e.prototype=new Error,e.constructor=e,e}(),h=function(){function e(e,t){this.name="UnknownErrorException",this.message=e,this.details=t}return e.prototype=new Error,e.constructor=e,e}(),d=function(){function e(e){this.name="InvalidPDFException",this.message=e}return e.prototype=new Error,e.constructor=e,e}(),f=function(){function e(e){this.name="MissingPDFException",this.message=e}return e.prototype=new Error,e.constructor=e,e}(),p=function(){function e(e,t){this.name="UnexpectedResponseException",this.message=e,this.status=t}return e.prototype=new Error,e.constructor=e,e}(),m=function(){function e(e){this.message=e}return e.prototype=new Error,e.prototype.name="NotImplementedException",e.constructor=e,e}(),v=function(){function e(e,t){this.begin=e,this.end=t,this.message="Missing data ["+e+", "+t+")"}return e.prototype=new Error,e.prototype.name="MissingDataException",e.constructor=e,e}(),g=function(){function e(e){this.message=e}return e.prototype=new Error,e.prototype.name="XRefParseException",e.constructor=e,e}(),b=function(){function e(e){this.message=e}return e.prototype=new Error,e.prototype.name="FormatError",e.constructor=e,e}(),y=function(){function e(e){this.name="AbortException",this.message=e}return e.prototype=new Error,e.constructor=e,e}(),_=/\x00/g;function A(e){u("string"==typeof e,"Invalid argument for stringToBytes");for(var t=e.length,r=new Uint8Array(t),n=0;ne[2]&&(t[0]=e[2],t[2]=e[0]),e[1]>e[3]&&(t[1]=e[3],t[3]=e[1]),t},e.intersect=function(t,r){function n(e,t){return e-t}var i=[t[0],t[2],r[0],r[2]].sort(n),a=[t[1],t[3],r[1],r[3]].sort(n),o=[];return t=e.normalizeRect(t),r=e.normalizeRect(r),(i[0]===t[0]&&i[1]===r[0]||i[0]===r[0]&&i[1]===t[0])&&(o[0]=i[1],o[2]=i[2],(a[0]===t[1]&&a[1]===r[1]||a[0]===r[1]&&a[1]===t[1])&&(o[1]=a[1],o[3]=a[2],o))};var r=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"];return e.toRoman=function(e,t){u(Number.isInteger(e)&&e>0,"The number should be a positive integer.");for(var n,i=[];e>=1e3;)e-=1e3,i.push("M");n=e/100|0,e%=100,i.push(r[n]),n=e/10|0,e%=10,i.push(r[10+n]),i.push(r[20+e]);var a=i.join("");return t?a.toLowerCase():a},e.appendToArray=function(e,t){Array.prototype.push.apply(e,t)},e.prependToArray=function(e,t){Array.prototype.unshift.apply(e,t)},e.extendObj=function(e,t){for(var r in t)e[r]=t[r]},e.getInheritableProperty=function(e,t,r){for(;e&&!e.has(t);)e=e.get("Parent");return e?r?e.getArray(t):e.get(t):null},e.inherit=function(e,t,r){for(var n in e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r)e.prototype[n]=r[n]},e.loadScript=function(e,t){var r=document.createElement("script"),n=!1;r.setAttribute("src",e),t&&(r.onload=function(){n||t(),n=!0}),document.getElementsByTagName("head")[0].appendChild(r)},e}(),P=function(){function e(e,t,r,n,i,a){this.viewBox=e,this.scale=t,this.rotation=r,this.offsetX=n,this.offsetY=i;var o,s,l,u,c,h,d,f,p=(e[2]+e[0])/2,m=(e[3]+e[1])/2;switch(r=(r%=360)<0?r+360:r){case 180:o=-1,s=0,l=0,u=1;break;case 90:o=0,s=1,l=1,u=0;break;case 270:o=0,s=-1,l=-1,u=0;break;default:o=1,s=0,l=0,u=-1}a&&(l=-l,u=-u),0===o?(c=Math.abs(m-e[1])*t+n,h=Math.abs(p-e[0])*t+i,d=Math.abs(e[3]-e[1])*t,f=Math.abs(e[2]-e[0])*t):(c=Math.abs(p-e[0])*t+n,h=Math.abs(m-e[1])*t+i,d=Math.abs(e[2]-e[0])*t,f=Math.abs(e[3]-e[1])*t),this.transform=[o*t,s*t,l*t,u*t,c-o*t*p-l*t*m,h-s*t*p-u*t*m],this.width=d,this.height=f,this.fontScale=t}return e.prototype={clone:function(t){var r="scale"in(t=t||{})?t.scale:this.scale,n="rotation"in t?t.rotation:this.rotation;return new e(this.viewBox.slice(),r,n,this.offsetX,this.offsetY,t.dontFlip)},convertToViewportPoint:function(e,t){return w.applyTransform([e,t],this.transform)},convertToViewportRectangle:function(e){var t=w.applyTransform([e[0],e[1]],this.transform),r=w.applyTransform([e[2],e[3]],this.transform);return[t[0],t[1],r[0],r[1]]},convertToPdfPoint:function(e,t){return w.applyInverseTransform([e,t],this.transform)}},e}(),k=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364];function C(){var e={};return e.promise=new Promise(function(t,r){e.resolve=t,e.reject=r}),e}var R,x=function(){function e(e,t,r){for(;e.lengtha&&(a=o.length)}for(t=0,r=n.length;t2&&void 0!==arguments[2]&&arguments[2])&&URL.createObjectURL){var r=T(e,t);return URL.createObjectURL(r)}for(var n="data:"+t+";base64,",i=0,a=e.length;i>2]+R[(3&o)<<4|s>>4]+R[i+1>6:64]+R[i+22&&void 0!==arguments[2]?arguments[2]:null;return e?new Promise(function(n,i){n(e.apply(r,t))}):Promise.resolve(void 0)}function L(e){if("object"!==(void 0===e?"undefined":n(e)))return e;switch(e.name){case"AbortException":return new y(e.message);case"MissingPDFException":return new f(e.message);case"UnexpectedResponseException":return new p(e.message,e.status);default:return new h(e.message,e.details)}}function I(e,t,r){t?e.resolve():e.reject(r)}function F(e,t,r){var n=this;this.sourceName=e,this.targetName=t,this.comObj=r,this.callbackId=1,this.streamId=1,this.postMessageTransfers=!0,this.streamSinks=Object.create(null),this.streamControllers=Object.create(null);var i=this.callbacksCapabilities=Object.create(null),a=this.actionHandler=Object.create(null);this._onComObjOnMessage=function(e){var t=e.data;if(t.targetName===n.sourceName)if(t.stream)n._processStreamMessage(t);else if(t.isReply){var o=t.callbackId;if(!(t.callbackId in i))throw new Error("Cannot resolve callback "+o);var s=i[o];delete i[o],"error"in t?s.reject(L(t.error)):s.resolve(t.data)}else{if(!(t.action in a))throw new Error("Unknown action from worker: "+t.action);var l=a[t.action];if(t.callbackId){var u=n.sourceName,c=t.sourceName;Promise.resolve().then(function(){return l[0].call(l[1],t.data)}).then(function(e){r.postMessage({sourceName:u,targetName:c,isReply:!0,callbackId:t.callbackId,data:e})},function(e){r.postMessage({sourceName:u,targetName:c,isReply:!0,callbackId:t.callbackId,error:function(e){return!(e instanceof Error)||e instanceof y||e instanceof f||e instanceof p||e instanceof h?e:new h(e.message,e.toString())}(e)})})}else t.streamId?n._createStreamSink(t):l[0].call(l[1],t.data)}},r.addEventListener("message",this._onComObjOnMessage)}F.prototype={on:function(e,t,r){var n=this.actionHandler;if(n[e])throw new Error('There is already an actionName called "'+e+'"');n[e]=[t,r]},send:function(e,t,r){var n={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t};this.postMessage(n,r)},sendWithPromise:function(e,t,r){var n=this.callbackId++,i={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t,callbackId:n},a=C();this.callbacksCapabilities[n]=a;try{this.postMessage(i,r)}catch(e){a.reject(e)}return a.promise},sendWithStream:function(e,t,r,n){var a=this,o=this.streamId++,s=this.sourceName,l=this.targetName;return new i.ReadableStream({start:function(r){var n=C();return a.streamControllers[o]={controller:r,startCall:n,isClosed:!1},a.postMessage({sourceName:s,targetName:l,action:e,streamId:o,data:t,desiredSize:r.desiredSize}),n.promise},pull:function(e){var t=C();return a.streamControllers[o].pullCall=t,a.postMessage({sourceName:s,targetName:l,stream:"pull",streamId:o,desiredSize:e.desiredSize}),t.promise},cancel:function(e){var t=C();return a.streamControllers[o].cancelCall=t,a.streamControllers[o].isClosed=!0,a.postMessage({sourceName:s,targetName:l,stream:"cancel",reason:e,streamId:o}),t.promise}},r)},_createStreamSink:function(e){var t=this,r=this,n=this.actionHandler[e.action],i=e.streamId,a=e.desiredSize,o=this.sourceName,s=e.sourceName,l=function(e){var r=e.stream,n=e.chunk,a=e.transfers,l=e.success,u=e.reason;t.postMessage({sourceName:o,targetName:s,stream:r,streamId:i,chunk:n,success:l,reason:u},a)},u={enqueue:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,r=arguments[2];if(!this.isCancelled){var n=this.desiredSize;this.desiredSize-=t,n>0&&this.desiredSize<=0&&(this.sinkCapability=C(),this.ready=this.sinkCapability.promise),l({stream:"enqueue",chunk:e,transfers:r})}},close:function(){this.isCancelled||(this.isCancelled=!0,l({stream:"close"}),delete r.streamSinks[i])},error:function(e){this.isCancelled||(this.isCancelled=!0,l({stream:"error",reason:e}))},sinkCapability:C(),onPull:null,onCancel:null,isCancelled:!1,desiredSize:a,ready:null};u.sinkCapability.resolve(),u.ready=u.sinkCapability.promise,this.streamSinks[i]=u,O(n[0],[e.data,u],n[1]).then(function(){l({stream:"start_complete",success:!0})},function(e){l({stream:"start_complete",success:!1,reason:e})})},_processStreamMessage:function(e){var t=this,r=this.sourceName,n=e.sourceName,i=e.streamId,a=function(e){var a=e.stream,o=e.success,s=e.reason;t.comObj.postMessage({sourceName:r,targetName:n,stream:a,success:o,streamId:i,reason:s})},o=function(){Promise.all([t.streamControllers[e.streamId].startCall,t.streamControllers[e.streamId].pullCall,t.streamControllers[e.streamId].cancelCall].map(function(e){return e&&(t=e.promise,Promise.resolve(t).catch(function(){}));var t})).then(function(){delete t.streamControllers[e.streamId]})};switch(e.stream){case"start_complete":I(this.streamControllers[e.streamId].startCall,e.success,L(e.reason));break;case"pull_complete":I(this.streamControllers[e.streamId].pullCall,e.success,L(e.reason));break;case"pull":if(!this.streamSinks[e.streamId]){a({stream:"pull_complete",success:!0});break}this.streamSinks[e.streamId].desiredSize<=0&&e.desiredSize>0&&this.streamSinks[e.streamId].sinkCapability.resolve(),this.streamSinks[e.streamId].desiredSize=e.desiredSize,O(this.streamSinks[e.streamId].onPull).then(function(){a({stream:"pull_complete",success:!0})},function(e){a({stream:"pull_complete",success:!1,reason:e})});break;case"enqueue":u(this.streamControllers[e.streamId],"enqueue should have stream controller"),this.streamControllers[e.streamId].isClosed||this.streamControllers[e.streamId].controller.enqueue(e.chunk);break;case"close":if(u(this.streamControllers[e.streamId],"close should have stream controller"),this.streamControllers[e.streamId].isClosed)break;this.streamControllers[e.streamId].isClosed=!0,this.streamControllers[e.streamId].controller.close(),o();break;case"error":u(this.streamControllers[e.streamId],"error should have stream controller"),this.streamControllers[e.streamId].controller.error(L(e.reason)),o();break;case"cancel_complete":I(this.streamControllers[e.streamId].cancelCall,e.success,L(e.reason)),o();break;case"cancel":if(!this.streamSinks[e.streamId])break;O(this.streamSinks[e.streamId].onCancel,[L(e.reason)]).then(function(){a({stream:"cancel_complete",success:!0})},function(e){a({stream:"cancel_complete",success:!1,reason:e})}),this.streamSinks[e.streamId].sinkCapability.reject(L(e.reason)),this.streamSinks[e.streamId].isCancelled=!0,delete this.streamSinks[e.streamId];break;default:throw new Error("Unexpected stream case")}},postMessage:function(e,t){t&&this.postMessageTransfers?this.comObj.postMessage(e,t):this.comObj.postMessage(e)},destroy:function(){this.comObj.removeEventListener("message",this._onComObjOnMessage)}},t.FONT_IDENTITY_MATRIX=[.001,0,0,.001,0,0],t.IDENTITY_MATRIX=[1,0,0,1,0,0],t.OPS={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotations:78,endAnnotations:79,beginAnnotation:80,endAnnotation:81,paintJpegXObject:82,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91},t.VERBOSITY_LEVELS=a,t.UNSUPPORTED_FEATURES={unknown:"unknown",forms:"forms",javaScript:"javaScript",smask:"smask",shadingPattern:"shadingPattern",font:"font"},t.AnnotationBorderStyleType={SOLID:1,DASHED:2,BEVELED:3,INSET:4,UNDERLINE:5},t.AnnotationFieldFlag={READONLY:1,REQUIRED:2,NOEXPORT:4,MULTILINE:4096,PASSWORD:8192,NOTOGGLETOOFF:16384,RADIO:32768,PUSHBUTTON:65536,COMBO:131072,EDIT:262144,SORT:524288,FILESELECT:1048576,MULTISELECT:2097152,DONOTSPELLCHECK:4194304,DONOTSCROLL:8388608,COMB:16777216,RICHTEXT:33554432,RADIOSINUNISON:33554432,COMMITONSELCHANGE:67108864},t.AnnotationFlag={INVISIBLE:1,HIDDEN:2,PRINT:4,NOZOOM:8,NOROTATE:16,NOVIEW:32,READONLY:64,LOCKED:128,TOGGLENOVIEW:256,LOCKEDCONTENTS:512},t.AnnotationType={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},t.FontType={UNKNOWN:0,TYPE1:1,TYPE1C:2,CIDFONTTYPE0:3,CIDFONTTYPE0C:4,TRUETYPE:5,CIDFONTTYPE2:6,TYPE3:7,OPENTYPE:8,TYPE0:9,MMTYPE1:10},t.ImageKind={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},t.CMapCompressionType={NONE:0,BINARY:1,STREAM:2},t.AbortException=y,t.InvalidPDFException=d,t.MessageHandler=F,t.MissingDataException=v,t.MissingPDFException=f,t.NativeImageDecoding={NONE:"none",DECODE:"decode",DISPLAY:"display"},t.NotImplementedException=m,t.PageViewport=P,t.PasswordException=c,t.PasswordResponses={NEED_PASSWORD:1,INCORRECT_PASSWORD:2},t.StatTimer=x,t.StreamType={UNKNOWN:0,FLATE:1,LZW:2,DCT:3,JPX:4,JBIG:5,A85:6,AHX:7,CCF:8,RL:9},t.TextRenderingMode={FILL:0,STROKE:1,FILL_STROKE:2,INVISIBLE:3,FILL_ADD_TO_PATH:4,STROKE_ADD_TO_PATH:5,FILL_STROKE_ADD_TO_PATH:6,ADD_TO_PATH:7,FILL_STROKE_MASK:3,ADD_TO_PATH_FLAG:4},t.UnexpectedResponseException=p,t.UnknownErrorException=h,t.Util=w,t.XRefParseException=g,t.FormatError=b,t.arrayByteLength=S,t.arraysToBytes=function(e){if(1===e.length&&e[0]instanceof Uint8Array)return e[0];var t,r,n,i=0,a=e.length;for(t=0;t=a.infos&&console.log("Info: "+e)},t.isArrayBuffer=function(e){return"object"===(void 0===e?"undefined":n(e))&&null!==e&&void 0!==e.byteLength},t.isBool=function(e){return"boolean"==typeof e},t.isEmptyObj=function(e){for(var t in e)return!1;return!0},t.isNum=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSpace=function(e){return 32===e||9===e||13===e||10===e},t.isNodeJS=function(){return"object"===("undefined"==typeof process?"undefined":n(process))&&process+""=="[object process]"},t.isSameOrigin=function(e,t){try{var r=new URL(e);if(!r.origin||"null"===r.origin)return!1}catch(e){return!1}var n=new URL(t,r);return r.origin===n.origin},t.createValidAbsoluteUrl=function(e,t){if(!e)return null;try{var r=t?new URL(e,t):new URL(e);if(function(e){if(!e)return!1;switch(e.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(r))return r}catch(e){}return null},t.isLittleEndian=function(){var e=new Uint8Array(4);return e[0]=1,1===new Uint32Array(e.buffer,0,1)[0]},t.isEvalSupported=function(){try{return new Function(""),!0}catch(e){return!1}},t.loadJpegStream=function(e,t,r){var n=new Image;n.onload=function(){r.resolve(e,n)},n.onerror=function(){r.resolve(e,null),s("Error during JPEG image loading")},n.src=t},t.log2=function(e){for(var t=1,r=0;e>t;)t<<=1,r++;return r},t.readInt8=function(e,t){return e[t]<<24>>24},t.readUint16=function(e,t){return e[t]<<8|e[t+1]},t.readUint32=function(e,t){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0},t.removeNullCharacters=function(e){return"string"!=typeof e?(s("The argument for removeNullCharacters must be a string."),e):e.replace(_,"")},t.ReadableStream=i.ReadableStream,t.setVerbosityLevel=function(e){o=e},t.shadow=function(e,t,r){return Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!1}),r},t.string32=function(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e)},t.stringToBytes=A,t.stringToPDFString=function(e){var t,r=e.length,n=[];if("þ"===e[0]&&"ÿ"===e[1])for(t=2;t0?i(n(e),9007199254740991):0}},function(e,t,r){"use strict";var n=r(7),i=r(45),a=r(33),o=Object.defineProperty;t.f=r(8)?Object.defineProperty:function(e,t,r){if(n(e),t=a(t,!0),n(r),i)try{return o(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(e[t]=r.value),e}},function(e,t,r){"use strict";var n=r(1),i=r(6),a=r(10),o=r(16)("src"),s=Function.toString,l=(""+s).split("toString");r(4).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,r,s){var u="function"==typeof r;u&&(a(r,"name")||i(r,"name",t)),e[t]!==r&&(u&&(a(r,o)||i(r,o,e[t]?""+e[t]:l.join(String(t)))),e===n?e[t]=r:s?e[t]?e[t]=r:i(e,t,r):(delete e[t],i(e,t,r)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[o]||s.call(this)})},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DOMSVGFactory=t.DOMCMapReaderFactory=t.DOMCanvasFactory=t.DEFAULT_LINK_REL=t.getDefaultSetting=t.LinkTarget=t.getFilenameFromUrl=t.isValidUrl=t.isExternalLinkTargetSet=t.addLinkAttributes=t.RenderingCancelledException=t.CustomStyle=void 0;var n,i=function(){function e(e,t){for(var r=0;r0&&t>0,"Invalid SVG dimensions");var r=document.createElementNS(c,"svg:svg");return r.setAttribute("version","1.1"),r.setAttribute("width",e+"px"),r.setAttribute("height",t+"px"),r.setAttribute("preserveAspectRatio","none"),r.setAttribute("viewBox","0 0 "+e+" "+t),r}},{key:"createElement",value:function(e){return(0,a.assert)("string"==typeof e,"Invalid SVG element type"),document.createElementNS(c,e)}}]),e}(),p=function(){var e=["ms","Moz","Webkit","O"],t=Object.create(null);function r(){}return r.getProp=function(r,n){if(1===arguments.length&&"string"==typeof t[r])return t[r];var i,a,o=(n=n||document.documentElement).style;if("string"==typeof o[r])return t[r]=r;a=r.charAt(0).toUpperCase()+r.slice(1);for(var s=0,l=e.length;s0?t:e.length,r>0?r:e.length);return e.substring(e.lastIndexOf("/",n)+1,n)},t.LinkTarget=v,t.getDefaultSetting=b,t.DEFAULT_LINK_REL=u,t.DOMCanvasFactory=h,t.DOMCMapReaderFactory=d,t.DOMSVGFactory=f},function(e,t,r){"use strict";var n=0,i=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+i).toString(36))}},function(e,t,r){"use strict";var n=Math.ceil,i=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?i:n)(e)}},function(e,t,r){"use strict";var n=r(35);e.exports=function(e){return Object(n(e))}},function(e,t,r){"use strict";e.exports={}},function(e,t,r){"use strict";e.exports="undefined"!=typeof window&&window.Math===Math?window:"undefined"!=typeof global&&global.Math===Math?global:"undefined"!=typeof self&&self.Math===Math?self:{}},function(e,t,r){"use strict";e.exports=!1},function(e,t,r){"use strict";e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,r){"use strict";var n=r(14);e.exports=function(e,t,r){for(var i in t)n(e,i,t[i],r);return e}},function(e,t,r){"use strict";e.exports=function(e,t,r,n){if(!(e instanceof t)||void 0!==n&&n in e)throw TypeError(r+": incorrect invocation!");return e}},function(e,t,r){"use strict";var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,r){"use strict";var n=r(13).f,i=r(10),a=r(3)("toStringTag");e.exports=function(e,t,r){e&&!i(e=r?e:e.prototype,a)&&n(e,a,{configurable:!0,value:t})}},function(e,t,r){"use strict";e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,r){"use strict";var n=r(34),i=r(35);e.exports=function(e){return n(i(e))}},function(e,t,r){"use strict";var n=r(17),i=Math.max,a=Math.min;e.exports=function(e,t){return(e=n(e))<0?i(e+t,0):a(e,t)}},function(e,t,r){"use strict";var n=r(25),i=r(3)("toStringTag"),a="Arguments"==n(function(){return arguments}());e.exports=function(e){var t,r,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?r:a?n(t):"Object"==(o=n(t))&&"function"==typeof t.callee?"Arguments":o}},function(e,t,r){"use strict";var n=r(11),i=r(98),a=r(53),o=r(7),s=r(12),l=r(57),u={},c={},h=e.exports=function(e,t,r,h,d){var f,p,m,v,g=d?function(){return e}:l(e),b=n(r,h,t?2:1),y=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(a(g)){for(f=s(e.length);f>y;y++)if((v=t?b(o(p=e[y])[0],p[1]):b(e[y]))===u||v===c)return v}else for(m=g.call(e);!(p=m.next()).done;)if((v=i(m,b,p.value,t))===u||v===c)return v};h.BREAK=u,h.RETURN=c},function(e,t,r){"use strict";var n=r(2),i=r(1).document,a=n(i)&&n(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},function(e,t,r){"use strict";var n=r(2);e.exports=function(e,t){if(!n(e))return e;var r,i;if(t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;if("function"==typeof(r=e.valueOf)&&!n(i=r.call(e)))return i;if(!t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},function(e,t,r){"use strict";var n=r(25);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t,r){"use strict";e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,r){"use strict";var n=r(51)("keys"),i=r(16);e.exports=function(e){return n[e]||(n[e]=i(e))}},function(e,t,r){"use strict";e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,r){"use strict";var n=r(49),i=r(37);e.exports=Object.keys||function(e){return n(e,i)}},function(e,t,r){"use strict";var n=r(11),i=r(34),a=r(18),o=r(12),s=r(82);e.exports=function(e,t){var r=1==e,l=2==e,u=3==e,c=4==e,h=6==e,d=5==e||h,f=t||s;return function(t,s,p){for(var m,v,g=a(t),b=i(g),y=n(s,p,3),_=o(b.length),A=0,S=r?f(t,_):l?f(t,0):void 0;_>A;A++)if((d||A in b)&&(v=y(m=b[A],A,g),e))if(r)S[A]=v;else if(v)switch(e){case 3:return!0;case 5:return m;case 6:return A;case 2:S.push(m)}else if(c)return!1;return h?-1:u||c?c:S}}},function(e,t,r){"use strict";var n=r(7),i=r(22),a=r(3)("species");e.exports=function(e,t){var r,o=n(e).constructor;return void 0===o||void 0==(r=n(o)[a])?t:i(r)}},function(e,t,r){"use strict";var n=r(3)("iterator"),i=!1;try{var a=[7][n]();a.return=function(){i=!0},Array.from(a,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!i)return!1;var r=!1;try{var a=[7],o=a[n]();o.next=function(){return{done:r=!0}},a[n]=function(){return o},e(a)}catch(e){}return r}},function(e,t,r){"use strict";var n=r(22);e.exports.f=function(e){return new function(e){var t,r;this.promise=new e(function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n}),this.resolve=n(t),this.reject=n(r)}(e)}},function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=r(16)("meta"),a=r(2),o=r(10),s=r(13).f,l=0,u=Object.isExtensible||function(){return!0},c=!r(9)(function(){return u(Object.preventExtensions({}))}),h=function(e){s(e,i,{value:{i:"O"+ ++l,w:{}}})},d=e.exports={KEY:i,NEED:!1,fastKey:function(e,t){if(!a(e))return"symbol"==(void 0===e?"undefined":n(e))?e:("string"==typeof e?"S":"P")+e;if(!o(e,i)){if(!u(e))return"F";if(!t)return"E";h(e)}return e[i].i},getWeak:function(e,t){if(!o(e,i)){if(!u(e))return!0;if(!t)return!1;h(e)}return e[i].w},onFreeze:function(e){return c&&d.NEED&&u(e)&&!o(e,i)&&h(e),e}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateResponseStatus=t.validateRangeRequestCapabilities=t.createResponseStatusError=void 0;var n=r(0);t.createResponseStatusError=function(e,t){return 404===e||0===e&&/^file:/.test(t)?new n.MissingPDFException('Missing PDF "'+t+'".'):new n.UnexpectedResponseException("Unexpected server response ("+e+') while retrieving PDF "'+t+'".',e)},t.validateRangeRequestCapabilities=function(e){var t=e.getResponseHeader,r=e.isHttp,i=e.rangeChunkSize,a=e.disableRange;(0,n.assert)(i>0);var o={allowRangeRequests:!1,suggestedLength:void 0};if(a||!r)return o;if("bytes"!==t("Accept-Ranges"))return o;if("identity"!==(t("Content-Encoding")||"identity"))return o;var s=parseInt(t("Content-Length"),10);return Number.isInteger(s)?(o.suggestedLength=s,s<=2*i?o:(o.allowRangeRequests=!0,o)):o},t.validateResponseStatus=function(e){return 200===e||206===e}},function(e,t,r){"use strict";e.exports=!r(8)&&!r(9)(function(){return 7!=Object.defineProperty(r(32)("div"),"a",{get:function(){return 7}}).a})},function(e,t,r){"use strict";for(var n,i=r(1),a=r(6),o=r(16),s=o("typed_array"),l=o("view"),u=!(!i.ArrayBuffer||!i.DataView),c=u,h=0,d="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");h<9;)(n=i[d[h++]])?(a(n.prototype,s,!0),a(n.prototype,l,!0)):c=!1;e.exports={ABV:u,CONSTR:c,TYPED:s,VIEW:l}},function(e,t,r){"use strict";var n=r(17),i=r(12);e.exports=function(e){if(void 0===e)return 0;var t=n(e),r=i(t);if(t!==r)throw RangeError("Wrong length!");return r}},function(e,t,r){"use strict";var n=r(49),i=r(37).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return n(e,i)}},function(e,t,r){"use strict";var n=r(10),i=r(28),a=r(50)(!1),o=r(36)("IE_PROTO");e.exports=function(e,t){var r,s=i(e),l=0,u=[];for(r in s)r!=o&&n(s,r)&&u.push(r);for(;t.length>l;)n(s,r=t[l++])&&(~a(u,r)||u.push(r));return u}},function(e,t,r){"use strict";var n=r(28),i=r(12),a=r(29);e.exports=function(e){return function(t,r,o){var s,l=n(t),u=i(l.length),c=a(o,u);if(e&&r!=r){for(;u>c;)if((s=l[c++])!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===r)return e||c||0;return!e&&-1}}},function(e,t,r){"use strict";var n=r(4),i=r(1),a=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:n.version,mode:r(21)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(e,t,r){"use strict";var n=r(18),i=r(29),a=r(12);e.exports=function(e){for(var t=n(this),r=a(t.length),o=arguments.length,s=i(o>1?arguments[1]:void 0,r),l=o>2?arguments[2]:void 0,u=void 0===l?r:i(l,r);u>s;)t[s++]=e;return t}},function(e,t,r){"use strict";var n=r(19),i=r(3)("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(n.Array===e||a[i]===e)}},function(e,t,r){"use strict";var n=r(7),i=r(81),a=r(37),o=r(36)("IE_PROTO"),s=function(){},l=function(){var e,t=r(32)("iframe"),n=a.length;for(t.style.display="none",r(55).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + +
+ + + diff --git a/app/src/main/assets/viewer.js b/app/src/main/assets/viewer.js new file mode 100644 index 0000000..d055081 --- /dev/null +++ b/app/src/main/assets/viewer.js @@ -0,0 +1,193 @@ +"use strict"; + +let pdfDoc = null; +let pageRendering = false; +let renderPending = false; +let renderPendingLazy = false; +const canvas = document.getElementById('content'); +let zoomLevel = 100; +let textLayerDiv = document.getElementById("text"); +const zoomLevels = [50, 75, 100, 125, 150]; +let task = null; + +let newPageNumber = 0; +let newZoomLevel = 0; +let useRender; + +const cache = []; +const maxCached = 6; + +function maybeRenderNextPage() { + if (renderPending) { + pageRendering = false; + renderPending = false; + renderPage(channel.getPage(), renderPendingLazy, false); + return true; + } + return false; +} + +function handleRenderingError(error) { + console.log("rendering error: " + error); + + pageRendering = false; + maybeRenderNextPage(); +} + +function doPrerender(pageNumber, prerenderTrigger) { + if (useRender) { + if (pageNumber + 1 <= pdfDoc.numPages) { + renderPage(pageNumber + 1, false, true, pageNumber); + } else if (pageNumber - 1 > 0) { + renderPage(pageNumber - 1, false, true, pageNumber); + } + } else if (pageNumber == prerenderTrigger + 1) { + if (prerenderTrigger - 1 > 0) { + renderPage(prerenderTrigger - 1, false, true, prerenderTrigger); + } + } +} + +function display(newCanvas) { + canvas.height = newCanvas.height; + canvas.width = newCanvas.width; + canvas.style.height = newCanvas.style.height; + canvas.style.width = newCanvas.style.width; + canvas.getContext("2d", { alpha: false }).drawImage(newCanvas, 0, 0); + scrollTo(0, 0); +} + +function renderPage(pageNumber, lazy, prerender, prerenderTrigger=0) { + pageRendering = true; + useRender = !prerender; + + newPageNumber = pageNumber; + newZoomLevel = zoomLevels[channel.getZoomLevel()]; + console.log("page: " + pageNumber + ", zoom: " + newZoomLevel + ", prerender: " + prerender); + for (let i = 0; i < cache.length; i++) { + const cached = cache[i]; + if (cached.pageNumber === pageNumber && cached.zoomLevel === newZoomLevel) { + if (useRender) { + cache.splice(i, 1); + cache.push(cached); + + display(cached.canvas); + + textLayerDiv.replaceWith(cached.textLayerDiv); + textLayerDiv = cached.textLayerDiv; + } + + pageRendering = false; + doPrerender(pageNumber, prerenderTrigger); + return; + } + } + + pdfDoc.getPage(pageNumber).then(function(page) { + if (maybeRenderNextPage()) { + return; + } + + const newCanvas = document.createElement("canvas"); + const viewport = page.getViewport(newZoomLevel / 100) + const ratio = window.devicePixelRatio; + newCanvas.height = viewport.height * ratio; + newCanvas.width = viewport.width * ratio; + newCanvas.style.height = viewport.height + "px"; + newCanvas.style.width = viewport.width + "px"; + const newContext = newCanvas.getContext("2d", { alpha: false }); + newContext.scale(ratio, ratio); + + if (useRender) { + if (newZoomLevel != zoomLevel) { + canvas.style.height = viewport.height + "px"; + canvas.style.width = viewport.width + "px"; + } + zoomLevel = newZoomLevel; + } + + task = page.render({ + canvasContext: newContext, + viewport: viewport + }); + + task.then(function() { + task = null; + + let rendered = false; + function render() { + if (!useRender || rendered) { + return; + } + display(newCanvas); + rendered = true; + } + render(); + + const textLayerFrag = document.createDocumentFragment(); + task = PDFJS.renderTextLayer({ + textContentStream: page.streamTextContent(), + container: textLayerFrag, + viewport: viewport + }); + task.promise.then(function() { + task = null; + + render(); + + const newTextLayerDiv = textLayerDiv.cloneNode(); + newTextLayerDiv.style.height = newCanvas.style.height; + newTextLayerDiv.style.width = newCanvas.style.width; + if (useRender) { + textLayerDiv.replaceWith(newTextLayerDiv); + textLayerDiv = newTextLayerDiv; + } + newTextLayerDiv.appendChild(textLayerFrag); + + if (cache.length === maxCached) { + cache.shift() + } + cache.push({ + pageNumber: pageNumber, + zoomLevel: newZoomLevel, + canvas: newCanvas, + textLayerDiv: newTextLayerDiv + }); + + pageRendering = false; + doPrerender(pageNumber, prerenderTrigger); + }).catch(handleRenderingError); + }).catch(handleRenderingError); + }); +} + +function onRenderPage(lazy) { + if (pageRendering) { + if (newPageNumber === channel.getPage() && newZoomLevel === zoomLevels[channel.getZoomLevel()]) { + useRender = true; + return; + } + + renderPending = true; + renderPendingLazy = lazy; + if (task !== null) { + task.cancel(); + task = null; + } + } else { + renderPage(channel.getPage(), lazy, false); + } +} + +PDFJS.getDocument("https://localhost/placeholder.pdf").then(function(newDoc) { + pdfDoc = newDoc; + channel.setNumPages(pdfDoc.numPages); + pdfDoc.getMetadata().then(function(data) { + channel.setDocumentProperties(JSON.stringify(data.info)); + }).catch(function(error) { + console.log("getMetadata error: " + error); + }); + renderPage(channel.getPage(), false, false); +}).catch(function(error) { + console.log("getDocument error: " + error); +}); diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..a407aa1 Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/org/grapheneos/pdfviewer/PdfViewer.java b/app/src/main/java/org/grapheneos/pdfviewer/PdfViewer.java new file mode 100644 index 0000000..3127fb3 --- /dev/null +++ b/app/src/main/java/org/grapheneos/pdfviewer/PdfViewer.java @@ -0,0 +1,442 @@ +package org.grapheneos.pdfviewer; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.webkit.CookieManager; +import android.webkit.JavascriptInterface; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment; +import org.grapheneos.pdfviewer.fragment.JumpToPageFragment; +import org.grapheneos.pdfviewer.loader.DocumentPropertiesLoader; + +public class PdfViewer extends AppCompatActivity implements LoaderManager.LoaderCallbacks> { + public static final String TAG = "PdfViewer"; + + private static final String STATE_URI = "uri"; + private static final String STATE_PAGE = "page"; + private static final String STATE_ZOOM_LEVEL = "zoomLevel"; + private static final String KEY_PROPERTIES = "properties"; + + private static final String CONTENT_SECURITY_POLICY = + "default-src 'none'; " + + "form-action 'none'; " + + "connect-src https://localhost/placeholder.pdf; " + + "img-src blob: 'self'; " + + "script-src 'self'; " + + "style-src 'self'; " + + "frame-ancestors 'none'; " + + "base-uri 'none'"; + + private static final String FEATURE_POLICY = + "accelerometer 'none'; " + + "ambient-light-sensor 'none'; " + + "autoplay 'none'; " + + "camera 'none'; " + + "encrypted-media 'none'; " + + "fullscreen 'none'; " + + "geolocation 'none'; " + + "gyroscope 'none'; " + + "magnetometer 'none'; " + + "microphone 'none'; " + + "midi 'none'; " + + "payment 'none'; " + + "picture-in-picture 'none'; " + + "speaker 'none'; " + + "sync-xhr 'none'; " + + "usb 'none'; " + + "vr 'none'"; + + private static final int MIN_ZOOM_LEVEL = 0; + private static final int MAX_ZOOM_LEVEL = 4; + private static final int ALPHA_LOW = 130; + private static final int ALPHA_HIGH = 255; + private static final int ACTION_OPEN_DOCUMENT_REQUEST_CODE = 1; + private static final int STATE_LOADED = 1; + private static final int STATE_END = 2; + private static final int PADDING = 10; + + private Uri mUri; + public int mPage; + public int mNumPages; + private int mZoomLevel = 2; + private int mDocumentState; + private List mDocumentProperties; + private InputStream mInputStream; + + private WebView mWebView; + private TextView mTextView; + private Toast mToast; + + private class Channel { + @JavascriptInterface + public int getPage() { + return mPage; + } + + @JavascriptInterface + public int getZoomLevel() { + return mZoomLevel; + } + + @JavascriptInterface + public void setNumPages(int numPages) { + mNumPages = numPages; + } + + @JavascriptInterface + public void setDocumentProperties(final String properties) { + if (mDocumentProperties != null) { + throw new SecurityException("mDocumentProperties not null"); + } + + final Bundle args = new Bundle(); + args.putString(KEY_PROPERTIES, properties); + runOnUiThread(() -> { + LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this); + }); + } + } + + // Can be removed once minSdkVersion >= 26 + @SuppressWarnings("deprecation") + private void disableSaveFormData(final WebSettings settings) { + settings.setSaveFormData(false); + } + + @Override + @SuppressLint("SetJavaScriptEnabled") + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.webview); + + mWebView = findViewById(R.id.webview); + final WebSettings settings = mWebView.getSettings(); + settings.setAllowContentAccess(false); + settings.setAllowFileAccess(false); + settings.setCacheMode(WebSettings.LOAD_NO_CACHE); + settings.setJavaScriptEnabled(true); + disableSaveFormData(settings); + + CookieManager.getInstance().setAcceptCookie(false); + + mWebView.addJavascriptInterface(new Channel(), "channel"); + + mWebView.setWebViewClient(new WebViewClient() { + private WebResourceResponse fromAsset(final String mime, final String path) { + try { + InputStream inputStream = getAssets().open(path.substring(1)); + return new WebResourceResponse(mime, null, inputStream); + } catch (IOException e) { + return null; + } + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + if (!"GET".equals(request.getMethod())) { + return null; + } + + final Uri url = request.getUrl(); + if (!"localhost".equals(url.getHost())) { + return null; + } + + final String path = url.getPath(); + Log.d(TAG, "path " + path); + + if ("/placeholder.pdf".equals(path)) { + return new WebResourceResponse("application/pdf", null, mInputStream); + } + + if ("/viewer.html".equals(path)) { + final WebResourceResponse response = fromAsset("text/html", path); + HashMap headers = new HashMap(); + headers.put("Content-Security-Policy", CONTENT_SECURITY_POLICY); + headers.put("Feature-Policy", FEATURE_POLICY); + response.setResponseHeaders(headers); + return response; + } + + if ("/viewer.css".equals(path)) { + return fromAsset("text/css", path); + } + + if ("/viewer.js".equals(path)) { + return fromAsset("application/javascript", path); + } + + if ("/pdf.js".equals(path)) { + return fromAsset("application/javascript", path); + } + + if ("/pdf.worker.js".equals(path)) { + return fromAsset("application/javascript", path); + } + + return null; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + return true; + } + + @Override + public void onPageFinished(WebView view, String url) { + mDocumentState = STATE_LOADED; + invalidateOptionsMenu(); + } + }); + + mTextView = new TextView(this); + mTextView.setBackgroundColor(Color.DKGRAY); + mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE)); + mTextView.setTextSize(18); + mTextView.setPadding(PADDING, 0, PADDING, 0); + + // If loaders are not being initialized in onCreate(), the result will not be delivered + // after orientation change (See FragmentHostCallback), thus initialize the + // loader manager impl so that the result will be delivered. + LoaderManager.getInstance(this); + + final Intent intent = getIntent(); + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + if (!"application/pdf".equals(intent.getType())) { + Log.e(TAG, "invalid mime type"); + finish(); + return; + } + mUri = intent.getData(); + mPage = 1; + } + + if (savedInstanceState != null) { + mUri = savedInstanceState.getParcelable(STATE_URI); + mPage = savedInstanceState.getInt(STATE_PAGE); + mZoomLevel = savedInstanceState.getInt(STATE_ZOOM_LEVEL); + } + + if (mUri != null) { + loadPdf(); + } + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new DocumentPropertiesLoader(this, args.getString(KEY_PROPERTIES), mNumPages, mUri); + } + + @Override + public void onLoadFinished(Loader> loader, List data) { + mDocumentProperties = data; + LoaderManager.getInstance(this).destroyLoader(DocumentPropertiesLoader.ID); + } + + @Override + public void onLoaderReset(Loader> loader) { + mDocumentProperties = null; + } + + private void loadPdf() { + try { + if (mInputStream != null) { + mInputStream.close(); + } + mInputStream = getContentResolver().openInputStream(mUri); + } catch (IOException e) { + return; + } + mWebView.loadUrl("https://localhost/viewer.html"); + } + + private void renderPage(final boolean lazy) { + mWebView.evaluateJavascript(lazy ? "onRenderPage(true)" : "onRenderPage(false)", null); + } + + private void openDocument() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/pdf"); + startActivityForResult(intent, ACTION_OPEN_DOCUMENT_REQUEST_CODE); + } + + private static void enableDisableMenuItem(MenuItem item, boolean enable) { + if (enable) { + if (!item.isEnabled()) { + item.setEnabled(true); + item.getIcon().setAlpha(ALPHA_HIGH); + } + } else if (item.isEnabled()) { + item.setEnabled(false); + item.getIcon().setAlpha(ALPHA_LOW); + } + } + + public void onJumpToPageInDocument(int selected_page) { + if (selected_page >= 1 && selected_page <= mNumPages) { + mPage = selected_page; + renderPage(false); + showPageNumber(); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + savedInstanceState.putParcelable(STATE_URI, mUri); + savedInstanceState.putInt(STATE_PAGE, mPage); + savedInstanceState.putInt(STATE_ZOOM_LEVEL, mZoomLevel); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent resultData) { + if (requestCode == ACTION_OPEN_DOCUMENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + if (resultData != null) { + mUri = resultData.getData(); + mPage = 1; + mDocumentProperties = null; + loadPdf(); + invalidateOptionsMenu(); + } + } + } + + private void showPageNumber() { + if (mToast != null) { + mToast.cancel(); + } + mTextView.setText(String.format("%s/%s", mPage, mNumPages)); + mToast = new Toast(getApplicationContext()); + mToast.setGravity(Gravity.BOTTOM | Gravity.END, PADDING, PADDING); + mToast.setDuration(Toast.LENGTH_SHORT); + mToast.setView(mTextView); + mToast.show(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.pdf_viewer, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final int ids[] = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page, + R.id.action_next, R.id.action_previous, R.id.action_view_document_properties }; + if (mDocumentState < STATE_LOADED) { + for (final int id : ids) { + final MenuItem item = menu.findItem(id); + if (item.isVisible()) { + item.setVisible(false); + } + } + } else if (mDocumentState == STATE_LOADED) { + for (final int id : ids) { + final MenuItem item = menu.findItem(id); + if (!item.isVisible()) { + item.setVisible(true); + } + } + mDocumentState = STATE_END; + } + + switch (mZoomLevel) { + case MAX_ZOOM_LEVEL: + enableDisableMenuItem(menu.findItem(R.id.action_zoom_in), false); + return true; + case MIN_ZOOM_LEVEL: + enableDisableMenuItem(menu.findItem(R.id.action_zoom_out), false); + return true; + default: + enableDisableMenuItem(menu.findItem(R.id.action_zoom_in), true); + enableDisableMenuItem(menu.findItem(R.id.action_zoom_out), true); + return true; + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_previous: + if (mPage > 1) { + mPage--; + renderPage(false); + showPageNumber(); + } + return true; + + case R.id.action_next: + if (mPage < mNumPages) { + mPage++; + renderPage(false); + showPageNumber(); + } + return true; + + case R.id.action_open: + openDocument(); + return super.onOptionsItemSelected(item); + + case R.id.action_zoom_out: + if (mZoomLevel > 0) { + mZoomLevel--; + renderPage(true); + invalidateOptionsMenu(); + } + return true; + + case R.id.action_zoom_in: + if (mZoomLevel < MAX_ZOOM_LEVEL) { + mZoomLevel++; + renderPage(true); + invalidateOptionsMenu(); + } + return true; + + case R.id.action_view_document_properties: + DocumentPropertiesFragment + .getInstance((ArrayList) mDocumentProperties) + .show(getSupportFragmentManager(), DocumentPropertiesFragment.TAG); + return true; + + case R.id.action_jump_to_page: + new JumpToPageFragment() + .show(getSupportFragmentManager(), JumpToPageFragment.TAG); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/app/src/main/java/org/grapheneos/pdfviewer/Utils.java b/app/src/main/java/org/grapheneos/pdfviewer/Utils.java new file mode 100644 index 0000000..9cb0183 --- /dev/null +++ b/app/src/main/java/org/grapheneos/pdfviewer/Utils.java @@ -0,0 +1,196 @@ +package org.grapheneos.pdfviewer; + +import android.text.TextUtils; + +import java.math.RoundingMode; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.Calendar; + +public class Utils { + public static String parseFileSize(long fileSize) { + final double kb = fileSize / 1000; + + if (kb == 0) { + return fileSize + " Bytes"; + } + + final DecimalFormat format = new DecimalFormat("#.##"); + format.setRoundingMode(RoundingMode.CEILING); + + if (kb < 1000) { + return format.format(kb) + " kB (" + fileSize + " Bytes)"; + } + return format.format(kb / 1000) + " MB (" + fileSize + " Bytes)"; + } + + // Parse date as per PDF spec (complies with PDF v1.4 to v1.7) + public static String parseDate(String date) throws ParseException { + int position = 0; + + // D: prefix is optional for PDF < v1.7; required for PDF v1.7 + if (!date.startsWith("D:")) { + date = "D:" + date; + } + if (date.length() < 6 || date.length() > 23) { + throw new ParseException("Invalid datetime length", position); + } + + final Calendar calendar = Calendar.getInstance(); + final int currentYear = calendar.get(Calendar.YEAR); + int year; + + // Year is required + String field = date.substring(position += 2, 6); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid year", position); + } + year = Integer.valueOf(field); + if (year > currentYear) { + year = currentYear; + } + + position += 4; + + // Default value for month and day shall be 1 (calendar month starts at 0 in Java 7), + // all others default to 0 + int month = 0; + int day = 1; + int hours = 0; + int minutes = 0; + int seconds = 0; + + // All succeeding fields are optional, but each preceding field must be present + if (date.length() > 8) { + field = date.substring(position, 8); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid month", position); + } + month = Integer.valueOf(field) - 1; + if (month > 11) { + throw new ParseException("Invalid month", position); + } + position += 2; + } + if (date.length() > 10) { + field = date.substring(8, 10); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid day", position); + } + day = Integer.valueOf(field); + if (day > 31) { + throw new ParseException("Invalid day", position); + } + position += 2; + } + if (date.length() > 12) { + field = date.substring(10, 12); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid hours", position); + } + hours = Integer.valueOf(field); + if (hours > 23) { + throw new ParseException("Invalid hours", position); + } + position += 2; + } + if (date.length() > 14) { + field = date.substring(12, 14); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid minutes", position); + } + minutes = Integer.valueOf(field); + if (minutes > 59) { + throw new ParseException("Invalid minutes", position); + } + position += 2; + } + if (date.length() > 16) { + field = date.substring(14, 16); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid seconds", position); + } + seconds = Integer.valueOf(field); + if (seconds > 59) { + throw new ParseException("Invalid seconds", position); + } + position += 2; + } + + + if (date.length() > position) { + int offsetHours = 0; + int offsetMinutes = 0; + + final char utRel = date.charAt(position); + if (utRel != '\u002D' && utRel != '\u002B' && utRel != '\u005A') { + throw new ParseException("Invalid UT relation", position); + } + + position++; + + if (date.length() > position + 2) { + field = date.substring(position, position + 2); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid UTC offset hours", position); + } + offsetHours = Integer.parseInt(field); + final int offsetHoursMinutes = offsetHours * 100 + offsetMinutes; + + // Validate UTC offset (UTC-12:00 to UTC+14:00) + if ((utRel == '\u002D' && offsetHoursMinutes > 1200) || + (utRel == '\u002B' && offsetHoursMinutes > 1400)) { + throw new ParseException("Invalid UTC offset hours", position); + } + + position += 2; + + // Apostrophe shall succeed HH and precede mm + if (date.charAt(position) != '\'') { + throw new ParseException("Expected apostrophe", position); + } + + position++; + + if (date.length() > position + 2) { + field = date.substring(position, position + 2); + if (!TextUtils.isDigitsOnly(field)) { + throw new ParseException("Invalid UTC offset minutes", position); + } + offsetMinutes = Integer.parseInt(field); + if (offsetMinutes > 59) { + throw new ParseException("Invalid UTC offset minutes", position); + } + position += 2; + } + + // Apostrophe shall succeed mm + if (date.charAt(position) != '\'') { + throw new ParseException("Expected apostrophe", position); + } + } + + + switch (utRel) { + case '\u002D': + hours -= offsetHours; + minutes -= offsetMinutes; + break; + case '\u002B': + hours += offsetHours; + minutes += offsetMinutes; + break; + default: + // "Z" means equal to UTC + break; + } + } + + calendar.set(year, month, day, hours, minutes, seconds); + + return DateFormat + .getDateTimeInstance(DateFormat.DEFAULT, DateFormat.LONG) + .format(calendar.getTime()); + } +} diff --git a/app/src/main/java/org/grapheneos/pdfviewer/fragment/DocumentPropertiesFragment.java b/app/src/main/java/org/grapheneos/pdfviewer/fragment/DocumentPropertiesFragment.java new file mode 100644 index 0000000..0f50198 --- /dev/null +++ b/app/src/main/java/org/grapheneos/pdfviewer/fragment/DocumentPropertiesFragment.java @@ -0,0 +1,60 @@ +package org.grapheneos.pdfviewer.fragment; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Bundle; +import android.widget.ArrayAdapter; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import java.util.ArrayList; +import java.util.List; + +import org.grapheneos.pdfviewer.R; + +public class DocumentPropertiesFragment extends DialogFragment { + public static final String TAG = "DocumentPropertiesFragment"; + + private static final String KEY_DOCUMENT_PROPERTIES = "key_document_properties"; + + private static DocumentPropertiesFragment sDocumentPropertiesFragment; + + private List mDocumentProperties; + + public static DocumentPropertiesFragment getInstance(final ArrayList metaData) { + if (sDocumentPropertiesFragment == null) { + sDocumentPropertiesFragment = new DocumentPropertiesFragment(); + final Bundle args = new Bundle(); + args.putCharSequenceArrayList(KEY_DOCUMENT_PROPERTIES, metaData); + sDocumentPropertiesFragment.setArguments(args); + } else { + final Bundle args = sDocumentPropertiesFragment.getArguments(); + args.clear(); + args.putCharSequenceArrayList(KEY_DOCUMENT_PROPERTIES, metaData); + } + return sDocumentPropertiesFragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDocumentProperties = getArguments().getStringArrayList(KEY_DOCUMENT_PROPERTIES); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + final AlertDialog.Builder dialog = new AlertDialog.Builder(activity) + .setPositiveButton(android.R.string.ok, null); + + if (mDocumentProperties != null) { + dialog.setTitle(getString(R.string.action_view_document_properties)); + dialog.setAdapter(new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, + mDocumentProperties), null); + } else { + dialog.setTitle(R.string.document_properties_retrieval_failed); + } + return dialog.create(); + } +} diff --git a/app/src/main/java/org/grapheneos/pdfviewer/fragment/JumpToPageFragment.java b/app/src/main/java/org/grapheneos/pdfviewer/fragment/JumpToPageFragment.java new file mode 100644 index 0000000..53c236f --- /dev/null +++ b/app/src/main/java/org/grapheneos/pdfviewer/fragment/JumpToPageFragment.java @@ -0,0 +1,66 @@ +package org.grapheneos.pdfviewer.fragment; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import org.grapheneos.pdfviewer.PdfViewer; + +public class JumpToPageFragment extends DialogFragment { + public static final String TAG = "JumpToPageFragment"; + + private final static String STATE_PICKER_CUR = "picker_cur"; + private final static String STATE_PICKER_MIN = "picker_min"; + private final static String STATE_PICKER_MAX = "picker_max"; + + private NumberPicker mPicker; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState != null) { + mPicker.setMinValue(savedInstanceState.getInt(STATE_PICKER_MIN)); + mPicker.setMaxValue(savedInstanceState.getInt(STATE_PICKER_MAX)); + mPicker.setValue(savedInstanceState.getInt(STATE_PICKER_CUR)); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + mPicker = new NumberPicker(getActivity()); + mPicker.setMinValue(1); + mPicker.setMaxValue(((PdfViewer)getActivity()).mNumPages); + mPicker.setValue(((PdfViewer)getActivity()).mPage); + + final FrameLayout layout = new FrameLayout(getActivity()); + layout.addView(mPicker, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.CENTER)); + + return new AlertDialog.Builder(getActivity()) + .setView(layout) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + mPicker.clearFocus(); + ((PdfViewer)getActivity()).onJumpToPageInDocument(mPicker.getValue()); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(STATE_PICKER_MIN, mPicker.getMinValue()); + outState.putInt(STATE_PICKER_MAX, mPicker.getMaxValue()); + outState.putInt(STATE_PICKER_CUR, mPicker.getValue()); + } +} diff --git a/app/src/main/java/org/grapheneos/pdfviewer/loader/DocumentPropertiesLoader.java b/app/src/main/java/org/grapheneos/pdfviewer/loader/DocumentPropertiesLoader.java new file mode 100644 index 0000000..f271c05 --- /dev/null +++ b/app/src/main/java/org/grapheneos/pdfviewer/loader/DocumentPropertiesLoader.java @@ -0,0 +1,149 @@ +package org.grapheneos.pdfviewer.loader; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.Typeface; +import android.net.Uri; +import android.provider.OpenableColumns; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.util.Log; + +import androidx.loader.content.AsyncTaskLoader; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import org.grapheneos.pdfviewer.R; +import org.grapheneos.pdfviewer.Utils; + +public class DocumentPropertiesLoader extends AsyncTaskLoader> { + public static final String TAG = "DocumentPropertiesLoader"; + + public static final int ID = 1; + + private final String mProperties; + private final int mNumPages; + private final Uri mUri; + + private Cursor mCursor; + + public DocumentPropertiesLoader(Context context, String properties, int numPages, Uri uri) { + super(context); + + mProperties = properties; + mNumPages = numPages; + mUri = uri; + } + + @Override + public List loadInBackground() { + final Context context = getContext(); + + final String[] names = context.getResources().getStringArray(R.array.property_names); + final List properties = new ArrayList<>(names.length); + + mCursor = context.getContentResolver().query(mUri, null, null, null, null); + if (mCursor != null) { + mCursor.moveToFirst(); + + final int indexName = mCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (indexName >= 0) { + properties.add(getProperty(null, names[0], mCursor.getString(indexName))); + } + + final int indexSize = mCursor.getColumnIndex(OpenableColumns.SIZE); + if (indexSize >= 0) { + final long fileSize = Long.valueOf(mCursor.getString(indexSize)); + properties.add(getProperty(null, names[1], Utils.parseFileSize(fileSize))); + } + + mCursor.close(); + } + + try { + final JSONObject json = new JSONObject(mProperties); + + properties.add(getProperty(json, names[2], "Title")); + properties.add(getProperty(json, names[3], "Author")); + properties.add(getProperty(json, names[4], "Subject")); + properties.add(getProperty(json, names[5], "Keywords")); + properties.add(getProperty(json, names[6], "CreationDate")); + properties.add(getProperty(json, names[7], "ModDate")); + properties.add(getProperty(json, names[8], "Producer")); + properties.add(getProperty(json, names[9], "Creator")); + properties.add(getProperty(json, names[10], "PDFFormatVersion")); + properties.add(getProperty(null, names[11], String.valueOf(mNumPages))); + + return properties; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void deliverResult(List properties) { + if (isReset()) { + onReleaseResources(); + } else if (isStarted()) { + super.deliverResult(properties); + } + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(List properties) { + super.onCanceled(properties); + + onReleaseResources(); + } + + @Override + protected void onReset() { + super.onReset(); + + onStopLoading(); + onReleaseResources(); + } + + private void onReleaseResources() { + if (mCursor != null) { + mCursor.close(); + mCursor = null; + } + } + + private CharSequence getProperty(final JSONObject json, String name, String specName) { + final SpannableStringBuilder property = new SpannableStringBuilder(name).append(":\n"); + final String value = json != null ? json.optString(specName, "-") : specName; + + if (specName.endsWith("Date")) { + final Context context = getContext(); + try { + property.append(value.equals("-") ? value : Utils.parseDate(value)); + } catch (ParseException e) { + Log.w(TAG, e.getMessage() + " for " + value + " at offset: " + e.getErrorOffset()); + property.append(context.getString(R.string.document_properties_invalid_date)); + } + } else { + property.append(value); + } + property.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return property; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_insert_drive_file_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_insert_drive_file_white_24dp.png new file mode 100644 index 0000000..84755e4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_insert_drive_file_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_navigate_before_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_navigate_before_white_24dp.png new file mode 100644 index 0000000..bd23a06 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_navigate_before_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_navigate_next_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_navigate_next_white_24dp.png new file mode 100644 index 0000000..1f10ee4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_navigate_next_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pageview_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_pageview_white_24dp.png new file mode 100644 index 0000000..e0adb37 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pageview_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_zoom_in_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_zoom_in_white_24dp.png new file mode 100644 index 0000000..6373294 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_zoom_in_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_zoom_out_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_zoom_out_white_24dp.png new file mode 100644 index 0000000..7772cee Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_zoom_out_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_insert_drive_file_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_insert_drive_file_white_24dp.png new file mode 100644 index 0000000..b51ce3e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_insert_drive_file_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_navigate_before_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_navigate_before_white_24dp.png new file mode 100644 index 0000000..4d7869d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_navigate_before_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_navigate_next_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_navigate_next_white_24dp.png new file mode 100644 index 0000000..b4f3c6d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_navigate_next_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_pageview_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_pageview_white_24dp.png new file mode 100644 index 0000000..2e1a810 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pageview_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_zoom_in_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_zoom_in_white_24dp.png new file mode 100644 index 0000000..36a659d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_zoom_in_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_zoom_out_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_zoom_out_white_24dp.png new file mode 100644 index 0000000..e69da0b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_zoom_out_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_insert_drive_file_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_insert_drive_file_white_24dp.png new file mode 100644 index 0000000..798ebd4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_insert_drive_file_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_navigate_before_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_navigate_before_white_24dp.png new file mode 100644 index 0000000..62f3590 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_navigate_before_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_navigate_next_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_navigate_next_white_24dp.png new file mode 100644 index 0000000..93dec39 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_navigate_next_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pageview_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_pageview_white_24dp.png new file mode 100644 index 0000000..e33356a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pageview_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_zoom_in_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_zoom_in_white_24dp.png new file mode 100644 index 0000000..ce88ebd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_zoom_in_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_zoom_out_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_zoom_out_white_24dp.png new file mode 100644 index 0000000..8c6ccea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_zoom_out_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_insert_drive_file_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_insert_drive_file_white_24dp.png new file mode 100644 index 0000000..f3e153b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_insert_drive_file_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_navigate_before_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_navigate_before_white_24dp.png new file mode 100644 index 0000000..87a5f51 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_navigate_before_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_navigate_next_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_navigate_next_white_24dp.png new file mode 100644 index 0000000..b2812e9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_navigate_next_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pageview_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_pageview_white_24dp.png new file mode 100644 index 0000000..d292eff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pageview_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_zoom_in_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_zoom_in_white_24dp.png new file mode 100644 index 0000000..30b8470 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_zoom_in_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_zoom_out_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_zoom_out_white_24dp.png new file mode 100644 index 0000000..68aaec6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_zoom_out_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_insert_drive_file_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_insert_drive_file_white_24dp.png new file mode 100644 index 0000000..5bd5690 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_insert_drive_file_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_navigate_before_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_navigate_before_white_24dp.png new file mode 100644 index 0000000..a68bc5f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_navigate_before_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_white_24dp.png new file mode 100644 index 0000000..6858f02 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_navigate_next_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pageview_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_pageview_white_24dp.png new file mode 100644 index 0000000..9df7fdd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pageview_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_zoom_in_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_zoom_in_white_24dp.png new file mode 100644 index 0000000..b636f6e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_zoom_in_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_zoom_out_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_zoom_out_white_24dp.png new file mode 100644 index 0000000..d85e242 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_zoom_out_white_24dp.png differ diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..cfcabea --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/layout/webview.xml b/app/src/main/res/layout/webview.xml new file mode 100644 index 0000000..36674fc --- /dev/null +++ b/app/src/main/res/layout/webview.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/menu/pdf_viewer.xml b/app/src/main/res/menu/pdf_viewer.xml new file mode 100644 index 0000000..516fb98 --- /dev/null +++ b/app/src/main/res/menu/pdf_viewer.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..e2c7ff3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..8b1922d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..477b692 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..9223cf6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..9d72112 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..96e577f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..960f52b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1427b07 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..85eed32 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3526c51 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..d9d1b85 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,17 @@ + + + + File name + File size + Title + Author + Subject + Keywords + Creation date + Modify date + Producer + Creator + PDF version + Pages + + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..beab31f --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..9fd3196 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + + PDF Viewer + + Previous page + Next page + Open document + Zoom out + Zoom in + Jump to page + Properties + + Invalid date + Failed to obtain document metadata + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..766ab99 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..33d9a98 --- /dev/null +++ b/build.gradle @@ -0,0 +1,31 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } + + gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..dbb7bf7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f4d7b2b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..b0d6d0a --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..15e1ee3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'