Compare commits

...

69 Commits

Author SHA1 Message Date
Matéo Duparc 59973a6b42
libpdfviewer: custom README 2023-09-08 19:48:14 +02:00
Matéo Duparc 476f4aa685
libpdfviewer: update to PdfViewer 17 2023-09-08 19:47:30 +02:00
Daniel Micay c7241646fe increment version to 17 2023-07-30 13:33:19 -04:00
Daniel Micay 516d99bb13 update Android Gradle plugin to 8.0.2 2023-07-30 13:33:15 -04:00
Daniel Micay 5afd670354 update Gradle to 8.2 2023-07-03 19:28:04 -04:00
Hanouta 20d6c4ea9f decrease minimum zoom ratio to 0.2 2023-07-03 19:21:54 -04:00
Hanouta 0060e2cb73 set zoomRatio to fit document to screen size 2023-07-03 19:21:29 -04:00
Daniel Micay a820314362 update Material library to 1.9.0 2023-07-03 19:12:00 -04:00
Daniel Micay e064995a1b update kotlin to 1.8.22 2023-07-03 19:09:20 -04:00
Daniel Micay 0f520dd517 update npm dependencies for build 2023-07-03 19:07:09 -04:00
dependabot[bot] e9c5deb95d Bump pdfjs-dist from 3.7.107 to 3.8.162
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.7.107 to 3.8.162.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 19:01:17 -04:00
octocorvus 457cbc9e98 update MIN_WEBVIEW_RELEASE to 92
This is the minimum supported Chromium version for PDF.js v3.8.162.
2023-07-03 19:01:17 -04:00
dependabot[bot] efc1a27203 Bump eslint from 8.43.0 to 8.44.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.43.0 to 8.44.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.43.0...v8.44.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 17:41:56 -04:00
octocorvus 123bcf30a8 make DocumentPropertiesLoader#load private 2023-06-28 00:12:28 -04:00
octocorvus bfb5e4a538 fix file size parsing issue causing crash
Opening a PDF using Storage Access Framework (SAF) causes the app to
crash in some cases, when the file is not present on the device's
internal storage (for example, on Proton Drive).
2023-06-28 00:12:28 -04:00
dependabot[bot] 145fd9896c Bump eslint from 8.42.0 to 8.43.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.42.0 to 8.43.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.42.0...v8.43.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 18:23:52 -04:00
octocorvus 6f40b25b31 add eslint config and missing semicolons 2023-06-09 11:33:53 -04:00
dependabot[bot] f6dd0b2a87 Bump pdfjs-dist from 3.5.141 to 3.7.107
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.5.141 to 3.7.107.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 08:50:52 +03:00
Daniel Micay 4de602be3a drop unnecessary com.android.library plugin 2023-05-03 12:59:30 -04:00
Patryk Mis 5d6c6aa4e5 Update AGP and Kotlin
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Patryk Mis 3119c94d71 Update Gradle to v8.1.1
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Patryk Mis 115ddcb164 Update Gradle build scripts
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Daniel Micay 29a004091e minor whitespace fixes 2023-04-22 09:33:16 -04:00
Pratyush 195bba7891 replace custom file size parser with AOSP implementation 2023-04-20 11:18:14 -04:00
Pratyush 17c7c84296 rewrite DocumentPropertiesLoader in kotlin 2023-04-20 11:18:14 -04:00
Pratyush 61607858ef convert remaining fragments to kotlin 2023-04-20 11:18:11 -04:00
octocorvus fb59568765 update Android Gradle Plugin to 8.0.0
fix: update JDK to 17 for AGP 8.0
fix: enable buildConfig build feature
refactor: remove unnecessary android.enableR8.fullMode
2023-04-14 08:37:42 -04:00
Patryk Mis 4d1807718e Update Gradle to 8.1 2023-04-12 12:57:17 -04:00
Daniel Micay a6b4144a08 drop legacy GitHub Actions submodule checkout 2023-04-10 01:33:57 -04:00
Daniel Micay 5c8c4d7d83 update GitHub Actions JDK to 20 2023-04-10 01:33:27 -04:00
dependabot[bot] e161b71d22 Bump pdfjs-dist from 3.4.120 to 3.5.141
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.4.120 to 3.5.141.
- [Release notes](https://github.com/mozilla/pdfjs-dist/releases)
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-09 10:40:16 -04:00
Daniel Micay 759417f4da update Kotlin to 1.8.20 2023-04-09 09:34:51 -04:00
octocorvus bb14ba1a25 bugfix: make text layer position absolute and use CSS transform to al...
...ign it to canvas

Fixes a bug where there was empty space above pdf page, when page
rotation is 90 or 270 degrees.

Testing: open pdf and try all rotations (0, 90, 180 and 270 degrees)
with various zoom levels (especially, max and min zoom levels), and make
sure that text layer is aligned properly to the canvas and there aren't
any unwanted blank spaces. Finally, repeat the same testing procedure
for landscape mode.

To check if text layer is aligned properly on debug builds, toggle text
layer visibility from menu or use chrome dev tools.
2023-04-09 09:32:58 -04:00
Daniel Micay a59e72d9e0 add setup script 2023-03-24 19:46:52 -04:00
Daniel Micay f2b0162630 remove empty .gitmodules 2023-03-24 19:46:11 -04:00
octocorvus ae1c0874ce add option to toggle text layer visibility on debug builds
This is to ease debugging text layer on debug builds.
2023-03-24 19:45:49 -04:00
octocorvus d445c48f3c code style changes
- js: use double quotes consistently
- js: use strict equality operator everywhere
- js: end all statements with semi colon
- css: remove redundant unit of measures
2023-03-24 19:45:49 -04:00
octocorvus 69696ae2a9 use CSS transform to rotate text layer
Newer pdf.js versions don't automatically rotate text layer based on
viewport's rotation, instead they now set the value of the attribute
"data-main-rotation" to value of rotation in degrees. So, we now apply
CSS transformation to the text layer depending on the value of that
attribute.
2023-03-24 19:45:49 -04:00
octocorvus 2dea11799c use --scale-factor variable to set text layer font size
Newer pdf.js versions requires this variable to be set equal to the
actual scale (zoom ratio), to properly set font size in text layer div.
2023-03-24 19:45:49 -04:00
octocorvus f5a1452a2d migrate away from deprecated APIs
- renderTextLayer: use textContentSource parameter instead of textContentStream
- renderTextLayer: pass a div for the container argument
2023-03-24 19:45:49 -04:00
octocorvus f87941ea22 update pdf.js to v3.4.120 2023-03-24 19:45:49 -04:00
octocorvus 371c9509f6 update GitHub workflow to install npm dependencies 2023-03-24 19:45:49 -04:00
octocorvus d01131d4c6 use dependabot for npm dependencies 2023-03-24 19:45:49 -04:00
octocorvus 2935bd4b27 migrate to pdfjs-dist npm package 2023-03-24 19:45:49 -04:00
Daniel Micay 30b8769ed2 update Gradle to 8.0.2 2023-03-10 13:40:06 -05:00
Daniel Micay f6127ca0e0 update Android Gradle plugin to 7.4.2 2023-03-10 13:40:02 -05:00
octocorvus 0c6dcd35a7 Revert "Update pdf.js to v3.2.146"
This reverts commit e34c727ff7.

Temporarily revert pdf.js update as versions equal to or greater
than this breaks our text layer rendering.
2023-02-27 10:56:06 -05:00
Daniel Micay 1e5988346c update Gradle to 8.0.1 2023-02-24 15:37:12 -05:00
amalgame21 8d44658c80 Always shows previous and next page button in any Display size 2023-02-19 10:01:40 -05:00
Daniel Micay 6db24ef23d update build tools version to 33.0.2 2023-02-11 05:36:29 -05:00
Daniel Micay deba479a69 update AndroidX appcompat library to 1.6.1 2023-02-08 14:45:45 -05:00
Daniel Micay 7703b1a175 update Kotlin to 1.8.10 2023-02-04 07:06:08 -05:00
Daniel Micay d0c95f5478 update copyright notice 2023-02-01 23:37:39 -05:00
Daniel Micay f60cd27e65 update Kotlin Gradle plugin to 1.8.0 2023-02-01 22:00:06 -05:00
Daniel Micay a25d0a4405 update Kotlin Gradle plugin to 1.8.0 2023-02-01 21:58:53 -05:00
Daniel Micay 424010af1c use Gradle toolchain support 2023-02-01 21:43:25 -05:00
Daniel Micay 284fc306fc update Material library to 1.8.0 2023-02-01 20:47:56 -05:00
Daniel Micay d78e15b1b1 update Android Gradle plugin to 7.4.1 2023-02-01 20:32:07 -05:00
Xsims c6fb2aceda Add document name in toolbar 2023-01-27 02:08:50 -05:00
Daniel Micay 7f59118453 update AndroidX appcompat library to 1.6.0 2023-01-13 19:54:54 -05:00
Daniel Micay 5b46b4c80e update Android Gradle plugin to 7.4.0 2023-01-13 19:54:29 -05:00
r3g_5z e34c727ff7 Update pdf.js to v3.2.146
Signed-off-by: r3g_5z <june@girlboss.ceo>
2023-01-09 01:20:41 -05:00
Daniel Micay 3e60cca098 add Gradle verification metadata 2023-01-09 01:11:30 -05:00
Daniel Micay 48910db7c6 silence spurious getParcelable deprecation warning 2022-12-28 12:15:34 -05:00
Daniel Micay fd510a3c19 avoid deprecated DialogFragment onActivityCreated 2022-12-28 12:08:22 -05:00
Daniel Micay b8dcc33696 remove legacy roundIcon attribute 2022-12-25 10:38:50 -05:00
Daniel Micay 45a0952296 drop non-working dependabot workaround 2022-12-25 10:14:16 -05:00
Patryk Mis aaa93a6f88 update build tools to 33.0.1 2022-12-20 04:31:08 -05:00
Daniel Micay 414d7fe3d1 update Gradle to 7.6 2022-12-09 08:48:17 -05:00
32 changed files with 2742 additions and 388 deletions

View File

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
target-branch: main
- package-ecosystem: gradle
directory: "/"
schedule:
interval: daily
target-branch: main

View File

@ -1,20 +0,0 @@
name: Build application
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Set up JDK 19
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 19
cache: gradle
- name: Build with Gradle
run: ./gradlew build --no-daemon

View File

@ -1,11 +0,0 @@
name: Validate Gradle Wrapper
on: [pull_request, push]
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "pdfjs-dist"]
path = app/pdfjs-dist
url = https://github.com/mozilla/pdfjs-dist.git

View File

@ -1,4 +1,4 @@
Copyright © 2017-2022 GrapheneOS
Copyright © 2017-2023 GrapheneOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,11 +1,6 @@
Simple Android PDF viewer based on pdf.js and content providers. The app
doesn't require any permissions. The PDF stream is fed into the sandboxed
WebView without giving it access to content or files. Content-Security-Policy
is used to enforce that the JavaScript and styling properties within the
WebView are entirely static content from the apk assets. It reuses the hardened
Chromium rendering stack while only exposing a tiny subset of the attack
surface compared to actual web content. The PDF rendering code itself is memory
safe with dynamic code evaluation disabled, and even if an attacker did gain
code execution by exploiting the underlying web rendering engine, they're
within the Chromium renderer sandbox with no access to the network (unlike a
browser), files, or other content.
Fork of GrapheneOS' [PdfViewer](https://github.com/GrapheneOS/PdfViewer) to work as a library.
## Warning !
The only goal of this library is to be integrated in [DroidFS](https://forge.chapril.org/hardcoresushi/DroidFS). Use it at your own risk !
The npm dependency has been removed. Instead, `pdf.min.js` and `pdf.worker.min.js` must be manually placed in `app/pdfjs-dist/build/`.

View File

@ -13,6 +13,12 @@ plugins {
id("kotlin-android")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
android {
if (useKeystoreProperties) {
signingConfigs {
@ -33,7 +39,7 @@ android {
}
compileSdk = 33
buildToolsVersion = "33.0.0"
buildToolsVersion = "33.0.2"
namespace = "app.grapheneos.pdfviewer"
@ -61,20 +67,21 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
}
}
compileOptions {
sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_17)
targetCompatibility(JavaVersion.VERSION_17)
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
}

View File

@ -1,3 +1,8 @@
:root {
--text-layer-opacity: 0.2;
--text-layer-foreground: transparent;
}
html, body {
height: 100%;
}
@ -12,6 +17,8 @@ body {
}
#container {
--scale-factor: 1;
width: 100%;
height: 100%;
display: grid;
@ -24,25 +31,26 @@ body {
grid-column-start: 1;
}
canvas, .textLayer {
canvas {
display: inline-block;
position: relative;
}
.textLayer {
text-align: initial;
position: absolute;
overflow: hidden;
opacity: 0.2;
opacity: var(--text-layer-opacity);
line-height: 1;
}
.textLayer span,
.textLayer br {
color: transparent;
color: var(--text-layer-foreground);
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
transform-origin: 0 0;
}
.textLayer .highlight {
@ -91,3 +99,13 @@ canvas, .textLayer {
.textLayer .endOfContent.active {
top: 0;
}
[data-main-rotation="90"] {
transform: rotate(90deg);
}
[data-main-rotation="180"] {
transform: rotate(180deg);
}
[data-main-rotation="270"] {
transform: rotate(270deg);
}

View File

@ -6,7 +6,8 @@ let pdfDoc = null;
let pageRendering = false;
let renderPending = false;
let renderPendingZoom = 0;
const canvas = document.getElementById('content');
const canvas = document.getElementById("content");
const container = document.getElementById("container");
let orientationDegrees = 0;
let zoomRatio = 1;
let textLayerDiv = document.getElementById("text");
@ -19,6 +20,8 @@ let useRender;
const cache = [];
const maxCached = 6;
let isTextLayerVisible = false;
function maybeRenderNextPage() {
if (renderPending) {
pageRendering = false;
@ -61,6 +64,21 @@ function display(newCanvas, zoom) {
}
}
function setLayerTransform(pageWidth, pageHeight, layerDiv) {
const translate = {
X: Math.max(0, pageWidth - document.body.clientWidth) / 2,
Y: Math.max(0, pageHeight - document.body.clientHeight) / 2
};
layerDiv.style.translate = `${translate.X}px ${translate.Y}px`;
}
function getDefaultZoomRatio(page, orientationDegrees) {
const viewport = page.getViewport({scale: 1, rotation: orientationDegrees});
const widthZoomRatio = document.body.clientWidth / viewport.width;
const heightZoomRatio = document.body.clientHeight / viewport.height;
return Math.max(Math.min(widthZoomRatio, heightZoomRatio, channel.getMaxZoomRatio()), channel.getMinZoomRatio());
}
function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
pageRendering = true;
useRender = !prerender;
@ -82,6 +100,8 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
textLayerDiv.replaceWith(cached.textLayerDiv);
textLayerDiv = cached.textLayerDiv;
setLayerTransform(cached.pageWidth, cached.pageHeight, textLayerDiv);
container.style.setProperty("--scale-factor", newZoomRatio.toString());
}
pageRendering = false;
@ -95,7 +115,15 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
return;
}
const viewport = page.getViewport({scale: newZoomRatio, rotation: orientationDegrees})
const defaultZoomRatio = getDefaultZoomRatio(page, orientationDegrees);
if (cache.length === 0) {
zoomRatio = defaultZoomRatio;
newZoomRatio = defaultZoomRatio;
channel.setZoomRatio(defaultZoomRatio);
}
const viewport = page.getViewport({scale: newZoomRatio, rotation: orientationDegrees});
if (useRender) {
if (newZoomRatio !== zoomRatio) {
@ -105,7 +133,7 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
zoomRatio = newZoomRatio;
}
if (zoom == 2) {
if (zoom === 2) {
pageRendering = false;
return;
}
@ -137,10 +165,10 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
}
render();
const textLayerFrag = document.createDocumentFragment();
const newTextLayerDiv = textLayerDiv.cloneNode();
task = pdfjsLib.renderTextLayer({
textContentStream: page.streamTextContent(),
container: textLayerFrag,
textContentSource: page.streamTextContent(),
container: newTextLayerDiv,
viewport: viewport
});
task.promise.then(function() {
@ -148,24 +176,36 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
render();
const newTextLayerDiv = textLayerDiv.cloneNode();
newTextLayerDiv.style.height = newCanvas.style.height;
newTextLayerDiv.style.width = newCanvas.style.width;
// We use CSS transform to rotate a text layer div of zero
// degrees rotation. So, when the rotation is 90 or 270
// degrees, set width and height of the text layer div to the
// height and width of the canvas, respectively, to prevent
// text layer misalignment.
if (orientationDegrees % 180 === 0) {
newTextLayerDiv.style.height = newCanvas.style.height;
newTextLayerDiv.style.width = newCanvas.style.width;
} else {
newTextLayerDiv.style.height = newCanvas.style.width;
newTextLayerDiv.style.width = newCanvas.style.height;
}
setLayerTransform(viewport.width, viewport.height, newTextLayerDiv);
if (useRender) {
textLayerDiv.replaceWith(newTextLayerDiv);
textLayerDiv = newTextLayerDiv;
container.style.setProperty("--scale-factor", newZoomRatio.toString());
}
newTextLayerDiv.appendChild(textLayerFrag);
if (cache.length === maxCached) {
cache.shift()
cache.shift();
}
cache.push({
pageNumber: pageNumber,
zoomRatio: newZoomRatio,
orientationDegrees: orientationDegrees,
canvas: newCanvas,
textLayerDiv: newTextLayerDiv
textLayerDiv: newTextLayerDiv,
pageWidth: viewport.width,
pageHeight: viewport.height
});
pageRendering = false;
@ -198,6 +238,18 @@ function isTextSelected() {
return window.getSelection().toString() !== "";
}
function toggleTextLayerVisibility() {
let textLayerForeground = "red";
let textLayerOpacity = 1;
if (isTextLayerVisible) {
textLayerForeground = "transparent";
textLayerOpacity = 0.2;
}
document.documentElement.style.setProperty("--text-layer-foreground", textLayerForeground);
document.documentElement.style.setProperty("--text-layer-opacity", textLayerOpacity.toString());
isTextLayerVisible = !isTextLayerVisible;
}
function loadDocument() {
const pdfPassword = channel.getPassword();
const loadingTask = pdfjsLib.getDocument({ url: "https://localhost/placeholder.pdf", password: pdfPassword });
@ -207,7 +259,7 @@ function loadDocument() {
} else if (error === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
channel.invalidPassword();
}
}
};
loadingTask.promise.then(function (newDoc) {
channel.onLoaded();
@ -223,3 +275,7 @@ function loadDocument() {
console.error(reason.name + ": " + reason.message);
});
}
window.onresize = () => {
setLayerTransform(canvas.clientWidth, canvas.clientHeight, textLayerDiv);
};

View File

@ -42,12 +42,14 @@ import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import app.grapheneos.pdfviewer.ktx.ViewKt;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesAsyncTaskLoader;
import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@ -55,7 +57,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
public static final String TAG = "PdfViewer";
private static final String KEY_PROPERTIES = "properties";
private static final int MIN_WEBVIEW_RELEASE = 89;
private static final int MIN_WEBVIEW_RELEASE = 92;
private static final String CONTENT_SECURITY_POLICY =
"default-src 'none'; " +
@ -98,7 +100,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
"usb=(), " +
"xr-spatial-tracking=()";
private static final float MIN_ZOOM_RATIO = 0.5f;
private static final float MIN_ZOOM_RATIO = 0.2f;
private static final float MAX_ZOOM_RATIO = 1.5f;
private static final int ALPHA_LOW = 130;
private static final int ALPHA_HIGH = 255;
@ -136,6 +138,21 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
return mZoomRatio;
}
@JavascriptInterface
public void setZoomRatio(final float ratio) {
mZoomRatio = Math.max(Math.min(ratio, MAX_ZOOM_RATIO), MIN_ZOOM_RATIO);
}
@JavascriptInterface
public float getMinZoomRatio() {
return MIN_ZOOM_RATIO;
}
@JavascriptInterface
public float getMaxZoomRatio() {
return MAX_ZOOM_RATIO;
}
@JavascriptInterface
public int getDocumentOrientationDegrees() {
return mDocumentOrientationDegrees;
@ -155,7 +172,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
final Bundle args = new Bundle();
args.putString(KEY_PROPERTIES, properties);
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesAsyncTaskLoader.ID, args, PdfViewer.this));
}
@JavascriptInterface
@ -354,6 +371,15 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
return mPasswordPromptFragment;
}
private void setToolbarTitleWithDocumentName() {
String documentName = getCurrentDocumentName();
if (documentName != null && !documentName.isEmpty()) {
activity.getSupportActionBar().setTitle(documentName);
} else {
activity.getSupportActionBar().setTitle(R.string.app_name);
}
}
public void onResume() {
// The user could have left the activity to update the WebView
activity.invalidateOptionsMenu();
@ -379,13 +405,14 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
@NonNull
@Override
public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) {
return new DocumentPropertiesLoader(activity, args.getString(KEY_PROPERTIES), mNumPages, fileName, fileSize);
return new DocumentPropertiesAsyncTaskLoader(activity, args.getString(KEY_PROPERTIES), mNumPages, fileName, fileSize);
}
@Override
public void onLoadFinished(@NonNull Loader<List<CharSequence>> loader, List<CharSequence> data) {
mDocumentProperties = data;
LoaderManager.getInstance(activity).destroyLoader(DocumentPropertiesLoader.ID);
setToolbarTitleWithDocumentName();
LoaderManager.getInstance(activity).destroyLoader(DocumentPropertiesAsyncTaskLoader.ID);
}
@Override
@ -485,12 +512,19 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
public void onCreateOptionMenu(@NonNull Menu menu) {
MenuInflater inflater = activity.getMenuInflater();
inflater.inflate(R.menu.pdf_viewer, menu);
if (BuildConfig.DEBUG) {
inflater.inflate(R.menu.pdf_viewer_debug, menu);
}
}
public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
final int[] ids = {R.id.action_jump_to_page, R.id.action_next, R.id.action_previous,
R.id.action_first, R.id.action_last, R.id.action_rotate_clockwise,
R.id.action_rotate_counterclockwise, R.id.action_view_document_properties};
final ArrayList<Integer> ids = new ArrayList<>(Arrays.asList(R.id.action_jump_to_page,
R.id.action_next, R.id.action_previous, R.id.action_first, R.id.action_last,
R.id.action_rotate_clockwise, R.id.action_rotate_counterclockwise,
R.id.action_view_document_properties));
if (BuildConfig.DEBUG) {
ids.add(R.id.debug_action_toggle_text_layer_visibility);
}
if (mDocumentState < STATE_LOADED) {
for (final int id : ids) {
final MenuItem item = menu.findItem(id);
@ -540,10 +574,29 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
.show(activity.getSupportFragmentManager(), DocumentPropertiesFragment.TAG);
return true;
} else if (itemId == R.id.action_jump_to_page) {
JumpToPageFragment.newInstance(this)
new JumpToPageFragment(this)
.show(activity.getSupportFragmentManager(), JumpToPageFragment.TAG);
return true;
} else if (itemId == R.id.debug_action_toggle_text_layer_visibility) {
binding.webview.evaluateJavascript("toggleTextLayerVisibility()", null);
return true;
}
return false;
}
private String getCurrentDocumentName() {
if (mDocumentProperties == null || mDocumentProperties.isEmpty()) return "";
String fileName = "";
String title = "";
for (CharSequence property : mDocumentProperties) {
if (property.toString().startsWith("File name:")) {
fileName = property.toString().replace("File name:", "");
}
if (property.toString().startsWith("Title:-")) {
title = property.toString().replace("Title:-", "");
}
}
return fileName.length() > 2 ? fileName : title;
}
}

View File

@ -9,21 +9,6 @@ import java.text.ParseException;
import java.util.Calendar;
public class Utils {
public static String parseFileSize(long fileSize) {
final double kb = fileSize / 1000d;
if (kb == 0d) {
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)";
}
private static int parseIntSafely(String field) throws ParseException {
try {

View File

@ -1,60 +0,0 @@
package app.grapheneos.pdfviewer.fragment;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import app.grapheneos.pdfviewer.R;
import java.util.ArrayList;
import java.util.List;
public class DocumentPropertiesFragment extends DialogFragment {
public static final String TAG = "DocumentPropertiesFragment";
private static final String KEY_DOCUMENT_PROPERTIES = "document_properties";
private List<String> mDocumentProperties;
public static DocumentPropertiesFragment newInstance(final List<CharSequence> metaData) {
final DocumentPropertiesFragment fragment = new DocumentPropertiesFragment();
final Bundle args = new Bundle();
args.putCharSequenceArrayList(KEY_DOCUMENT_PROPERTIES, (ArrayList<CharSequence>) metaData);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mDocumentProperties = getArguments().getStringArrayList(KEY_DOCUMENT_PROPERTIES);
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = requireActivity();
final MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(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();
}
}

View File

@ -0,0 +1,53 @@
package app.grapheneos.pdfviewer.fragment
import android.app.Dialog
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DocumentPropertiesFragment : DialogFragment() {
// TODO replace with nav args once the `PdfViewer` activity is converted to kotlin
private val mDocumentProperties: List<String> by lazy {
requireArguments().getStringArrayList(KEY_DOCUMENT_PROPERTIES)?.toList() ?: emptyList()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireActivity())
.setPositiveButton(android.R.string.ok, null).apply {
if (mDocumentProperties.isNotEmpty()) {
setTitle(getString(R.string.action_view_document_properties))
setAdapter(
ArrayAdapter(
requireActivity(),
android.R.layout.simple_list_item_1,
mDocumentProperties
), null
)
} else {
setTitle(R.string.document_properties_retrieval_failed)
}
}
.create()
}
companion object {
const val TAG = "DocumentPropertiesFragment"
private const val KEY_DOCUMENT_PROPERTIES = "document_properties"
@JvmStatic
fun newInstance(metaData: List<CharSequence>): DocumentPropertiesFragment {
val fragment = DocumentPropertiesFragment()
val args = Bundle()
args.putCharSequenceArrayList(
KEY_DOCUMENT_PROPERTIES,
metaData as ArrayList<CharSequence>
)
fragment.arguments = args
return fragment
}
}
}

View File

@ -1,62 +0,0 @@
package app.grapheneos.pdfviewer.fragment;
import android.app.Dialog;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import app.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;
PdfViewer pdfViewer;
public static JumpToPageFragment newInstance(PdfViewer pdfViewer) {
JumpToPageFragment f = new JumpToPageFragment();
f.pdfViewer = pdfViewer;
return f;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mPicker = new NumberPicker(getActivity());
mPicker.setMinValue(1);
mPicker.setMaxValue(pdfViewer.mNumPages);
mPicker.setValue(pdfViewer.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 MaterialAlertDialogBuilder(requireActivity())
.setView(layout)
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
mPicker.clearFocus();
pdfViewer.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());
}
}

View File

@ -0,0 +1,58 @@
package app.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.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.PdfViewer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class JumpToPageFragment(private val pdfViewer: PdfViewer) : DialogFragment() {
companion object {
const val TAG = "JumpToPageFragment"
private const val STATE_PICKER_CUR = "picker_cur"
private const val STATE_PICKER_MIN = "picker_min"
private const val STATE_PICKER_MAX = "picker_max"
}
private val mPicker: NumberPicker by lazy { NumberPicker(requireActivity()) }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (savedInstanceState != null) {
mPicker.minValue = savedInstanceState.getInt(STATE_PICKER_MIN)
mPicker.maxValue = savedInstanceState.getInt(STATE_PICKER_MAX)
mPicker.value = savedInstanceState.getInt(STATE_PICKER_CUR)
} else {
mPicker.minValue = 1
mPicker.maxValue = pdfViewer.mNumPages
mPicker.value = pdfViewer.mPage
}
val layout = FrameLayout(requireActivity())
layout.addView(
mPicker, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER
)
)
return MaterialAlertDialogBuilder(requireActivity())
.setView(layout)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
mPicker.clearFocus()
pdfViewer.onJumpToPageInDocument(mPicker.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(STATE_PICKER_MIN, mPicker.minValue)
outState.putInt(STATE_PICKER_MAX, mPicker.maxValue)
outState.putInt(STATE_PICKER_CUR, mPicker.value)
}
}

View File

@ -0,0 +1,51 @@
package app.grapheneos.pdfviewer.loader;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.loader.content.AsyncTaskLoader;
import java.util.List;
public class DocumentPropertiesAsyncTaskLoader extends AsyncTaskLoader<List<CharSequence>> {
public static final String TAG = "DocumentPropertiesLoader";
public static final int ID = 1;
private final String mProperties;
private final int mNumPages;
String fileName;
Long fileSize;
public DocumentPropertiesAsyncTaskLoader(Context context, String properties, int numPages, String fileName, Long fileSize) {
super(context);
mProperties = properties;
mNumPages = numPages;
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Nullable
@Override
public List<CharSequence> loadInBackground() {
DocumentPropertiesLoader loader = new DocumentPropertiesLoader(
getContext(),
mProperties,
mNumPages,
fileName,
fileSize
);
return loader.loadAsList();
}
}

View File

@ -1,115 +0,0 @@
package app.grapheneos.pdfviewer.loader;
import android.content.Context;
import android.graphics.Typeface;
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 app.grapheneos.pdfviewer.R;
import app.grapheneos.pdfviewer.Utils;
public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>> {
public static final String TAG = "DocumentPropertiesLoader";
public static final int ID = 1;
private final String mProperties;
private final int mNumPages;
String fileName;
Long fileSize;
public DocumentPropertiesLoader(Context context, String properties, int numPages, String fileName, Long fileSize) {
super(context);
mProperties = properties;
mNumPages = numPages;
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
public List<CharSequence> loadInBackground() {
final Context context = getContext();
final String[] names = context.getResources().getStringArray(R.array.property_names);
final List<CharSequence> properties = new ArrayList<>(names.length);
properties.add(getProperty(null, names[0], fileName));
properties.add(getProperty(null, names[1], Utils.parseFileSize(fileSize)));
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<CharSequence> properties) {
if (!isReset() && isStarted()) {
super.deliverResult(properties);
}
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
}
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 != null && 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;
}
}

View File

@ -0,0 +1,88 @@
package app.grapheneos.pdfviewer.loader
import android.content.Context
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.format.Formatter
import android.text.style.StyleSpan
import android.util.Log
import app.grapheneos.pdfviewer.R
import org.json.JSONException
class DocumentPropertiesLoader(
private val context: Context,
private val properties: String,
private val numPages: Int,
private val fileName: String,
private val fileSize: Long,
) {
fun loadAsList(): List<CharSequence> {
return load().map { item ->
val name = context.getString(item.key.nameResource)
val value = item.value
SpannableStringBuilder()
.append(name)
.append(":\n")
.append(value)
.apply {
setSpan(
StyleSpan(Typeface.BOLD),
0,
name.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
private fun load(): Map<DocumentProperty, String> {
val result = mutableMapOf<DocumentProperty, String>()
result.addFileProperties()
result.addPageSizeProperty()
result.addPDFJsProperties()
return result
}
private fun MutableMap<DocumentProperty, String>.addPageSizeProperty() {
this[DocumentProperty.Pages] = java.lang.String.valueOf(numPages)
}
private fun MutableMap<DocumentProperty, String>.addFileProperties() {
putAll(getFileProperties())
}
private fun MutableMap<DocumentProperty, String>.addPDFJsProperties() {
putAll(getPDFJsProperties())
}
private fun getPDFJsProperties(): Map<DocumentProperty, String> {
return try {
PDFJsPropertiesToDocumentPropertyConverter(
properties,
context.getString(R.string.document_properties_invalid_date),
parseExceptionListener = { parseException, value ->
Log.w(
DocumentPropertiesAsyncTaskLoader.TAG,
"${parseException.message} for $value at offset: ${parseException.errorOffset}"
)
}
).convert()
} catch (e: JSONException) {
Log.w(
DocumentPropertiesAsyncTaskLoader.TAG,
"invalid properties"
)
emptyMap()
}
}
private fun getFileProperties(): Map<DocumentProperty, String> {
val collections = mutableMapOf<DocumentProperty, String>()
collections[DocumentProperty.FileName] = fileName
collections[DocumentProperty.FileSize] = Formatter.formatFileSize(context, fileSize)
return collections
}
}

View File

@ -0,0 +1,35 @@
package app.grapheneos.pdfviewer.loader
import androidx.annotation.StringRes
import app.grapheneos.pdfviewer.R
private const val TITLE_KEY = "Title"
private const val AUTHOR_KEY = "Author"
private const val SUBJECT_KEY = "Subject"
private const val KEYWORDS_KEY = "Keywords"
private const val CREATION_DATE_KEY = "CreationDate"
private const val MODIFY_DATE_KEY = "ModDate"
private const val PRODUCER_KEY = "Producer"
private const val CREATOR_KEY = "Creator"
private const val PDF_VERSION_KEY = "PDFFormatVersion"
const val DEFAULT_VALUE = "-"
enum class DocumentProperty(
val key: String = "",
@StringRes val nameResource: Int,
val isDate: Boolean = false
) {
FileName(key = "", nameResource = R.string.file_name),
FileSize(key = "", nameResource = R.string.file_size),
Pages(key = "", nameResource = R.string.pages),
Title(key = TITLE_KEY, nameResource = R.string.title),
Author(key = AUTHOR_KEY, nameResource = R.string.author),
Subject(key = SUBJECT_KEY, nameResource = R.string.subject),
Keywords(key = KEYWORDS_KEY, nameResource = R.string.keywords),
CreationDate(key = CREATION_DATE_KEY, nameResource = R.string.creation_date, isDate = true),
ModifyDate(key = MODIFY_DATE_KEY, nameResource = R.string.modify_date, isDate = true),
Producer(key = PRODUCER_KEY, nameResource = R.string.producer),
Creator(key = CREATOR_KEY, nameResource = R.string.creator),
PDFVersion(key = PDF_VERSION_KEY, nameResource = R.string.pdf_version);
}

View File

@ -0,0 +1,47 @@
package app.grapheneos.pdfviewer.loader
import app.grapheneos.pdfviewer.Utils
import org.json.JSONException
import org.json.JSONObject
import java.text.ParseException
import kotlin.jvm.Throws
class PDFJsPropertiesToDocumentPropertyConverter(
private val properties: String,
private val propertyInvalidDate: String,
private val parseExceptionListener: (e: ParseException, value: String) -> Unit
) {
@Throws(JSONException::class)
fun convert(): Map<DocumentProperty, String> {
val result = mutableMapOf<DocumentProperty, String>()
val json = JSONObject(properties)
addJsonProperties(json, result)
return result
}
private fun addJsonProperties(
json: JSONObject,
collections: MutableMap<DocumentProperty, String>
) {
for (documentProperty in DocumentProperty.values()) {
val key = documentProperty.key
if (key.isEmpty()) continue
val value = json.optString(key, DEFAULT_VALUE)
collections[documentProperty] = prettify(documentProperty, value)
}
}
private fun prettify(property: DocumentProperty, value: String): String {
if (value != DEFAULT_VALUE && property.isDate) {
return try {
Utils.parseDate(value)
} catch (parseException: ParseException) {
parseExceptionListener.invoke(parseException, value)
propertyInvalidDate
}
}
return value
}
}

View File

@ -11,13 +11,13 @@
android:id="@+id/action_previous"
android:icon="@drawable/ic_navigate_before_24dp"
android:title="@string/action_previous"
app:showAsAction="ifRoom" />
app:showAsAction="always" />
<item
android:id="@+id/action_next"
android:icon="@drawable/ic_navigate_next_24dp"
android:title="@string/action_next"
app:showAsAction="ifRoom" />
app:showAsAction="always" />
<item
android:id="@+id/action_first"

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/debug_action_toggle_text_layer_visibility"
android:title="@string/debug_action_toggle_text_layer_visibility"
app:showAsAction="never" />
</menu>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="property_names">
<item>File name</item>
<item>File size</item>
<item>Title</item>
<item>Author</item>
<item>Subject</item>
<item>Keywords</item>
<item>Creation date</item>
<item>Modify date</item>
<item>Producer</item>
<item>Creator</item>
<item>PDF version</item>
<item>Pages</item>
</string-array>
</resources>

View File

@ -11,6 +11,8 @@
<string name="action_rotate_counterclockwise">Rotate counterclockwise</string>
<string name="action_view_document_properties">Properties</string>
<string name="debug_action_toggle_text_layer_visibility">Toggle text layer visibility</string>
<string name="document_properties_invalid_date">Invalid date</string>
<string name="document_properties_retrieval_failed">Failed to obtain document metadata</string>
@ -21,4 +23,17 @@
<string name="password_prompt_description">Enter the password to decrypt this PDF file</string>
<string name="open">Open</string>
<string name="cancel">Cancel</string>
<string name="file_name">File name</string>
<string name="file_size">File size</string>
<string name="title">Title</string>
<string name="author">Author</string>
<string name="subject">Subject</string>
<string name="keywords">Keywords</string>
<string name="creation_date">Creation date</string>
<string name="modify_date">Modify date</string>
<string name="producer">Producer</string>
<string name="creator">Creator</string>
<string name="pdf_version">PDF version</string>
<string name="pages">Pages</string>
</resources>

View File

@ -1,18 +1,6 @@
buildscript {
repositories {
// dependabot cannot handle google()
maven {
url = uri("https://dl.google.com/dl/android/maven2")
}
// dependabot cannot handle mavenCentral()
maven {
url = uri("https://repo.maven.apache.org/maven2")
}
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22")
}
plugins {
id("com.android.application") apply false
id("org.jetbrains.kotlin.android") apply false
}
allprojects {

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableR8.fullMode=true
kotlin.code.style=official

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,6 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

24
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${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"'
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,22 +130,29 @@ 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.
if ! command -v java >/dev/null 2>&1
then
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
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# 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"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

1
gradlew.bat vendored
View File

@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

View File

@ -1,3 +1,9 @@
pluginManagement {
repositories {
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {