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
13 changes: 0 additions & 13 deletions readium/shared/src/main/java/org/readium/r2/shared/OptIn.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ public annotation class ExperimentalReadiumApi
)
public annotation class DelicateReadiumApi

@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Support for PDF is still experimental. The API may be changed in the future without notice."
)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.PROPERTY
)
public annotation class PdfSupport

@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Support for SearchService is still experimental. The API may be changed in the future without notice."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,29 @@

package org.readium.r2.streamer

import android.content.Context
import org.readium.r2.shared.PdfSupport
import org.readium.r2.shared.publication.Publication
import org.readium.r2.shared.publication.protection.ContentProtection
import org.readium.r2.shared.publication.protection.FallbackContentProtection
import org.readium.r2.shared.util.DebugError
import org.readium.r2.shared.util.Error
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.asset.Asset
import org.readium.r2.shared.util.asset.AssetOpener
import org.readium.r2.shared.util.data.ReadError
import org.readium.r2.shared.util.getOrElse
import org.readium.r2.shared.util.http.HttpClient
import org.readium.r2.shared.util.logging.WarningLogger
import org.readium.r2.shared.util.pdf.PdfDocumentFactory
import org.readium.r2.streamer.parser.PublicationParser
import org.readium.r2.streamer.parser.audio.AudioParser
import org.readium.r2.streamer.parser.epub.EpubParser
import org.readium.r2.streamer.parser.image.ImageParser
import org.readium.r2.streamer.parser.pdf.PdfParser
import org.readium.r2.streamer.parser.readium.ReadiumWebPubParser

/**
* Opens a Publication using a list of parsers.
* Opens a [Publication] from an [Asset].
*
* The [PublicationOpener] is configured to use Readium's default parsers, which you can bypass
* using ignoreDefaultParsers. However, you can provide additional [parsers] which will take
* precedence over the default ones. This can also be used to provide an alternative configuration
* of a default parser.
*
* @param context Application context.
* @param parsers Parsers used to open a publication, in addition to the default parsers.
* @param ignoreDefaultParsers When true, only parsers provided in parsers will be used.
* @param parser Parses the content of a publication [Asset].
* @param contentProtections Opens DRM-protected publications.
* @param httpClient Service performing HTTP requests.
* @param pdfFactory Parses a PDF document, optionally protected by password.
* @param assetOpener Opens assets in case of indirection.
* @param onCreatePublication Called on every parsed [Publication.Builder]. It can be used to modify
* the manifest, the root container or the list of service factories of a [Publication].
*/
@OptIn(PdfSupport::class)
public class PublicationOpener(
context: Context,
parsers: List<PublicationParser> = emptyList(),
ignoreDefaultParsers: Boolean = false,
private val parser: PublicationParser,
contentProtections: List<ContentProtection>,
private val httpClient: HttpClient,
pdfFactory: PdfDocumentFactory<*>?,
assetOpener: AssetOpener,
private val onCreatePublication: Publication.Builder.() -> Unit = {}
) {
public sealed class OpenError(
Expand All @@ -74,18 +48,6 @@ public class PublicationOpener(
private val contentProtections: List<ContentProtection> =
contentProtections + FallbackContentProtection()

private val defaultParsers: List<PublicationParser> =
listOfNotNull(
EpubParser(),
pdfFactory?.let { PdfParser(context, it) },
ReadiumWebPubParser(context, httpClient, pdfFactory),
ImageParser(assetOpener.assetSniffer),
AudioParser(assetOpener.assetSniffer)
)

private val parsers: List<PublicationParser> = parsers +
if (!ignoreDefaultParsers) defaultParsers else emptyList()

/**
* Opens a [Publication] from the given asset.
*
Expand Down Expand Up @@ -139,7 +101,7 @@ public class PublicationOpener(
}
}

val builder = parse(transformedAsset, warnings)
val builder = parser.parse(transformedAsset, warnings)
.getOrElse { return Try.failure(wrapParserException(it)) }

builder.apply {
Expand All @@ -152,22 +114,6 @@ public class PublicationOpener(
return Try.success(publication)
}

private suspend fun parse(
publicationAsset: Asset,
warnings: WarningLogger?
): Try<Publication.Builder, PublicationParser.ParseError> {
for (parser in parsers) {
val result = parser.parse(publicationAsset, warnings)
if (
result is Try.Success ||
result is Try.Failure && result.value !is PublicationParser.ParseError.FormatNotSupported
) {
return result
}
}
return Try.failure(PublicationParser.ParseError.FormatNotSupported())
}

private fun wrapParserException(e: PublicationParser.ParseError): OpenError =
when (e) {
is PublicationParser.ParseError.FormatNotSupported ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@

package org.readium.r2.streamer.parser

import android.content.Context
import org.readium.r2.shared.publication.Publication
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.asset.Asset
import org.readium.r2.shared.util.asset.AssetOpener
import org.readium.r2.shared.util.http.HttpClient
import org.readium.r2.shared.util.logging.WarningLogger
import org.readium.r2.shared.util.pdf.PdfDocumentFactory
import org.readium.r2.streamer.parser.audio.AudioParser
import org.readium.r2.streamer.parser.epub.EpubParser
import org.readium.r2.streamer.parser.image.ImageParser
import org.readium.r2.streamer.parser.pdf.PdfParser
import org.readium.r2.streamer.parser.readium.ReadiumWebPubParser

/**
* Parses a Publication from an asset.
* Parses a [Publication] from an [Asset].
*/
public interface PublicationParser {

Expand Down Expand Up @@ -41,3 +50,56 @@ public interface PublicationParser {
ParseError("An error occurred while trying to read asset.", cause)
}
}

/**
* Default implementation of [PublicationParser] handling all the publication formats supported by
* Readium.
*
* @param additionalParsers Parsers used to open a publication, in addition to the default parsers. They take precedence over the default ones.
* @param httpClient Service performing HTTP requests.
* @param pdfFactory Parses a PDF document, optionally protected by password.
* @param assetOpener Opens assets in case of indirection.
*/
public class DefaultPublicationParser(
context: Context,
additionalParsers: List<PublicationParser> = emptyList(),
private val httpClient: HttpClient,
pdfFactory: PdfDocumentFactory<*>?,
assetOpener: AssetOpener
) : PublicationParser by CompositePublicationParser(
additionalParsers + listOfNotNull(
EpubParser(),
pdfFactory?.let { PdfParser(context, it) },
ReadiumWebPubParser(context, httpClient, pdfFactory),
ImageParser(assetOpener.assetSniffer),
AudioParser(assetOpener.assetSniffer)
)
)

/**
* A composite [PublicationParser] which tries several parsers until it finds one which supports
* the asset.
*/
public class CompositePublicationParser(
private val parsers: List<PublicationParser>
) : PublicationParser {

public constructor(vararg parsers: PublicationParser) :
this(parsers.toList())

override suspend fun parse(
asset: Asset,
warnings: WarningLogger?
): Try<Publication.Builder, PublicationParser.ParseError> {
for (parser in parsers) {
val result = parser.parse(asset, warnings)
if (
result is Try.Success ||
result is Try.Failure && result.value !is PublicationParser.ParseError.FormatNotSupported
) {
return result
}
}
return Try.failure(PublicationParser.ParseError.FormatNotSupported())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package org.readium.r2.streamer.parser.pdf

import android.content.Context
import org.readium.r2.shared.ExperimentalReadiumApi
import org.readium.r2.shared.PdfSupport
import org.readium.r2.shared.publication.*
import org.readium.r2.shared.publication.services.InMemoryCacheService
import org.readium.r2.shared.publication.services.InMemoryCoverService
Expand All @@ -27,7 +26,6 @@ import org.readium.r2.streamer.parser.PublicationParser
/**
* Parses a PDF file into a Readium [Publication].
*/
@PdfSupport
@OptIn(ExperimentalReadiumApi::class)
public class PdfParser(
context: Context,
Expand Down
17 changes: 10 additions & 7 deletions test-app/src/main/java/org/readium/r2/testapp/Readium.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.readium.r2.shared.util.http.HttpResourceFactory
import org.readium.r2.shared.util.resource.CompositeResourceFactory
import org.readium.r2.shared.util.zip.ZipArchiveOpener
import org.readium.r2.streamer.PublicationOpener
import org.readium.r2.streamer.parser.DefaultPublicationParser

/**
* Holds the shared Readium objects and services used by the app.
Expand Down Expand Up @@ -70,15 +71,17 @@ class Readium(context: Context) {
)

/**
* The PublicationFactory is used to parse and open publications.
* The PublicationFactory is used to open publications.
*/
val publicationOpener = PublicationOpener(
context,
contentProtections = contentProtections,
assetOpener = assetOpener,
httpClient = httpClient,
// Only required if you want to support PDF files using the PDFium adapter.
pdfFactory = PdfiumDocumentFactory(context)
parser = DefaultPublicationParser(
context,
assetOpener = assetOpener,
httpClient = httpClient,
// Only required if you want to support PDF files using the PDFium adapter.
pdfFactory = PdfiumDocumentFactory(context)
),
contentProtections = contentProtections
)

fun onLcpDialogAuthenticationParentAttached(view: View) {
Expand Down