Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,14 @@ already had one and both write the same preference; the other two never get one.

### Fitting the page to the screen is the WebView's job

`PageView` sets `useWideViewPort` and `loadWithOverviewMode`, so a page wider than the screen -
a slide, a pdf, a text document with its margins - opens scaled down to fit, and is scaled again
every time the phone is turned.

odrcore's `HtmlConfig.viewportWidth` writes the same fit into the page's own css. That is for an
embedder rendering into a frame, where the viewport meta tag is inert; do not set it here. It is
decided while translating, so it freezes the fit at the width the document was opened at, and a
deck opened in portrait then keeps a portrait-sized slide in the middle of a landscape screen -
measured, on a deck and on a text document. `initialZoom` and the `odr.setZoom` api behind it are
for a host with a zoom control of its own, which this app does not have: the pinch is the
WebView's.
`PageView` sets `useWideViewPort` and `loadWithOverviewMode`, so a page wider than the screen
opens scaled down to fit and is scaled again every time the phone is turned.

odrcore's `HtmlConfig.viewportWidth` writes the same fit into the page's css, for an embedder
rendering into a frame where the viewport meta tag is inert. Do not set it here: it is decided
while translating, so it freezes the fit at the width the document was opened at - measured, a
deck opened in portrait keeps a portrait-sized slide in a landscape screen. `initialZoom` and
`odr.setZoom` are for a host with a zoom control of its own; here the pinch is the WebView's.

### Editability comes from the core, never from a mime type

Expand All @@ -280,11 +277,9 @@ editable formats in the UI.
second parse, so a format declaring no `edit`/`save` is never opened to be told no. It is an
upper bound - the document still answers.

Decryption is the same question in the same shape. `capabilities().decrypt` says whether a
password is worth asking for, already narrowed to a file that really is encrypted, and
`CoreLoader.host` refuses an encrypted `.doc`, `.ppt` or `.xls` on it rather than raising a
dialog no password can close - the core knows it cannot decrypt those, and the app must not
learn the list for itself.
Decryption is the same shape. `capabilities().decrypt` says whether a password is worth asking
for, and `CoreLoader.host` refuses an encrypted `.doc`, `.ppt` or `.xls` on it rather than
raising a dialog no password can close. The app must not learn that list for itself.

### Storage access

Expand Down
14 changes: 3 additions & 11 deletions app/src/androidTest/java/app/opendocument/droid/test/CoreTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,8 @@ class CoreTest {
}

/**
* The legacy binary formats keep what says they are encrypted in the clear, so an encrypted
* `.doc` is known to be one. It used to be parsed as if it were not, and failed with whatever
* the ciphertext happened to mean - a locked document looked like a broken one.
*
* odrcore has no way into any of the three, and says so through `capabilities().decrypt`, so
* [CoreLoader.host] refuses it up front rather than raising the password prompt: a dialog no
* password can close is worse than being told the document is locked.
* An encrypted `.doc` says so rather than failing as a parse error, and odrcore has no way into
* it, so [CoreLoader.host] refuses it instead of raising a prompt no password can close.
*/
@Test
fun testEncryptedLegacyDocument() {
Expand All @@ -209,10 +204,7 @@ class CoreTest {
}
}

/**
* The refusal above is the core's answer for the format, not a rule of ours: an encrypted odf
* document is one odrcore can open, so it still asks for the password.
*/
/** The refusal is per format, so an odf document odrcore can decrypt still prompts. */
@Test
fun testEncryptedOdfDocumentStillPrompts() {
Assert.assertThrows(OdrException.FileEncrypted::class.java) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ class CoreLoader(private val context: Context) {
var file = Odr.open(inputPath)

if (file.passwordEncrypted()) {
// the format's own answer rather than a list of ours, and already narrowed to a file
// that really is encrypted: a legacy .doc, .ppt or .xls says so and has no way in
// whatever the password, so asking for one would be a dialog that can never close
// the core's answer, not a list of ours: a legacy .doc, .ppt or .xls has no way in
// whatever the password, so the prompt would be a dialog that can never close
if (!file.capabilities().decrypt) {
throw UndecryptableFile(inputPath)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,8 @@ class DocumentLoader(application: Application) : AndroidViewModel(application) {
fun onLoadSuccess(document: LoadedDocument)

/**
* The document needs a password, or the one it was given was wrong.
*
* [canDecrypt] is false where odrcore cannot decrypt the format at all - asking for a
* password would only ask again, so there is nothing to prompt for.
* The document needs a password, or the one it was given was wrong. [canDecrypt] is false
* where odrcore cannot decrypt the format at all, so there is nothing to prompt for.
*/
fun onEncrypted(request: DocumentRequest, file: IdentifiedFile, canDecrypt: Boolean)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,8 +792,7 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener {
dismissProgress()

if (!canDecrypt) {
// no password opens one of these, so the dialog could only ask again. another app
// might have a way in, which is what the bar offers
// no password opens one of these; another app might, which is what the bar offers
offerReopen(activity, R.string.toast_error_password_protected, true)
giveUp(activity)

Expand Down
Loading