11/*
2- * Module: r2-streamer-kotlin
3- * Developers: Mickaël Menu
4- *
5- * Copyright (c) 2020. Readium Foundation. All rights reserved.
6- * Use of this source code is governed by a BSD-style license which is detailed in the
7- * LICENSE file present in the project repository where this source code is maintained.
2+ * Copyright 2020 Readium Foundation. All rights reserved.
3+ * Use of this source code is governed by the BSD-style license
4+ * available in the top-level LICENSE file of the project.
85 */
96
107package org.readium.r2.streamer.parser.epub
118
129import org.readium.r2.shared.fetcher.Fetcher
10+ import org.readium.r2.shared.fetcher.Resource
1311import org.readium.r2.shared.publication.Link
1412import org.readium.r2.shared.publication.Locator
1513import org.readium.r2.shared.publication.Publication
14+ import org.readium.r2.shared.publication.archive.archive
1615import org.readium.r2.shared.publication.encryption.encryption
1716import org.readium.r2.shared.publication.epub.EpubLayout
1817import org.readium.r2.shared.publication.epub.layoutOf
@@ -30,17 +29,75 @@ import kotlin.math.ceil
3029 *
3130 * https://github.com/readium/architecture/blob/master/models/locators/best-practices/format.md#epub
3231 * https://github.com/readium/architecture/issues/101
33- *
34- * @param reflowablePositionLength Length in bytes of a position in a reflowable resource. This is
35- * used to split a single reflowable resource into several positions.
3632 */
37- internal class EpubPositionsService (
33+ class EpubPositionsService (
3834 private val readingOrder : List <Link >,
3935 private val presentation : Presentation ,
4036 private val fetcher : Fetcher ,
41- private val reflowablePositionLength : Long
37+ private val reflowableStrategy : ReflowableStrategy
4238) : PositionsService {
4339
40+ companion object {
41+
42+ fun createFactory (reflowableStrategy : ReflowableStrategy = ReflowableStrategy .recommended): (Publication .Service .Context ) -> EpubPositionsService =
43+ { context ->
44+ EpubPositionsService (
45+ readingOrder = context.manifest.readingOrder,
46+ presentation = context.manifest.metadata.presentation,
47+ fetcher = context.fetcher,
48+ reflowableStrategy = reflowableStrategy
49+ )
50+ }
51+ }
52+
53+ /* *
54+ * Strategy used to calculate the number of positions in a reflowable resource.
55+ *
56+ * Note that a fixed-layout resource always has a single position.
57+ */
58+ sealed class ReflowableStrategy {
59+ /* * Returns the number of positions in the given [resource] according to the strategy. */
60+ abstract suspend fun positionCount (resource : Resource ): Int
61+
62+ /* *
63+ * Use the original length of each resource (before compression and encryption) and split it
64+ * by the given [pageLength].
65+ */
66+ data class OriginalLength (val pageLength : Int ) : ReflowableStrategy() {
67+ override suspend fun positionCount (resource : Resource ): Int {
68+ val length = resource.link().properties.encryption?.originalLength
69+ ? : resource.length().getOrNull()
70+ ? : 0
71+ return ceil(length.toDouble() / pageLength.toDouble()).toInt()
72+ .coerceAtLeast(1 )
73+ }
74+ }
75+
76+ /* *
77+ * Use the archive entry length (whether it is compressed or stored) and split it by the
78+ * given [pageLength].
79+ */
80+ data class ArchiveEntryLength (val pageLength : Int ) : ReflowableStrategy() {
81+ override suspend fun positionCount (resource : Resource ): Int {
82+ val length = resource.link().properties.archive?.entryLength
83+ ? : resource.length().getOrNull()
84+ ? : 0
85+ return ceil(length.toDouble() / pageLength.toDouble()).toInt()
86+ .coerceAtLeast(1 )
87+ }
88+ }
89+
90+ companion object {
91+ /* *
92+ * Recommended historical strategy: archive entry length split by 1024 bytes pages.
93+ *
94+ * This strategy is used by Adobe RMSDK as well.
95+ * See https://github.com/readium/architecture/issues/123
96+ */
97+ val recommended = ArchiveEntryLength (pageLength = 1024 )
98+ }
99+ }
100+
44101 override suspend fun positionsByReadingOrder (): List <List <Locator >> {
45102 if (! ::_positions .isInitialized)
46103 _positions = computePositions()
@@ -93,18 +150,13 @@ internal class EpubPositionsService(
93150 )
94151
95152 private suspend fun createReflowable (link : Link , startPosition : Int , fetcher : Fetcher ): List <Locator > {
96- // If the resource is encrypted, we use the `originalLength` declared in `encryption.xml`
97- // instead of the ZIP entry length.
98- val length = link.properties.encryption?.originalLength
99- ? : fetcher.get(link).use { it.length().getOrNull() }
100- ? : return emptyList()
101-
102- val pageCount = ceil(length / reflowablePositionLength.toDouble()).toInt()
103- .coerceAtLeast(1 )
153+ val positionCount = fetcher.get(link).use { resource ->
154+ reflowableStrategy.positionCount(resource)
155+ }
104156
105- return (1 .. pageCount ).map { position ->
157+ return (1 .. positionCount ).map { position ->
106158 createLocator(link,
107- progression = (position - 1 ) / pageCount .toDouble(),
159+ progression = (position - 1 ) / positionCount .toDouble(),
108160 position = startPosition + position
109161 )
110162 }
@@ -119,17 +171,4 @@ internal class EpubPositionsService(
119171 position = position
120172 )
121173 )
122-
123- companion object {
124-
125- fun create (context : Publication .Service .Context ): EpubPositionsService {
126- return EpubPositionsService (
127- readingOrder = context.manifest.readingOrder,
128- presentation = context.manifest.metadata.presentation,
129- fetcher = context.fetcher,
130- reflowablePositionLength = 1024L
131- )
132- }
133-
134- }
135174}
0 commit comments