Skip to content

Commit 5c7a8a0

Browse files
authored
Support HTML IDs and CSS selectors with EPUB decorations (#310)
1 parent a311140 commit 5c7a8a0

File tree

6 files changed

+127
-23
lines changed

6 files changed

+127
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ All notable changes to this project will be documented in this file. Take a look
5757
* `hyphens` - Enable hyphenation.
5858
* `ligatures` - Enable ligatures in Arabic.
5959
* Fixed scroll inertia when scrolling an EPUB.
60+
* EPUB decorations can now be attached to `Locator` objects containing only an HTML ID (`locations.fragments`) or a CSS selector (`locations.cssSelector`).
6061

6162
### Changed
6263

readium/navigator/src/main/assets/_scripts/src/utils.js

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -228,29 +228,53 @@ export function snapCurrentOffset() {
228228
}
229229

230230
export function rangeFromLocator(locator) {
231-
let text = locator.text;
232-
if (!text || !text.highlight) {
233-
return null;
234-
}
235231
try {
236-
var root;
237232
let locations = locator.locations;
238-
if (locations && locations.cssSelector) {
239-
root = document.querySelector(locations.cssSelector);
240-
}
241-
if (!root) {
242-
root = document.body;
233+
let text = locator.text;
234+
if (text && text.highlight) {
235+
var root;
236+
if (locations && locations.cssSelector) {
237+
root = document.querySelector(locations.cssSelector);
238+
}
239+
if (!root) {
240+
root = document.body;
241+
}
242+
243+
let anchor = new TextQuoteAnchor(root, text.highlight, {
244+
prefix: text.before,
245+
suffix: text.after,
246+
});
247+
return anchor.toRange();
243248
}
244249

245-
let anchor = new TextQuoteAnchor(root, text.highlight, {
246-
prefix: text.before,
247-
suffix: text.after,
248-
});
249-
return anchor.toRange();
250+
if (locations) {
251+
var element = null;
252+
253+
if (!element && locations.cssSelector) {
254+
element = document.querySelector(locations.cssSelector);
255+
}
256+
257+
if (!element && locations.fragments) {
258+
for (const htmlId of locations.fragments) {
259+
element = document.getElementById(htmlId);
260+
if (element) {
261+
break;
262+
}
263+
}
264+
}
265+
266+
if (element) {
267+
let range = document.createRange();
268+
range.setStartBefore(element);
269+
range.setEndAfter(element);
270+
return range;
271+
}
272+
}
250273
} catch (e) {
251274
logError(e);
252-
return null;
253275
}
276+
277+
return null;
254278
}
255279

256280
/// User Settings.

readium/navigator/src/main/assets/readium/scripts/readium-fixed.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readium/navigator/src/main/assets/readium/scripts/readium-reflowable.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import androidx.appcompat.widget.SearchView
1717
import androidx.fragment.app.FragmentResultListener
1818
import androidx.fragment.app.commit
1919
import androidx.fragment.app.commitNow
20+
import androidx.lifecycle.lifecycleScope
21+
import org.readium.r2.navigator.DecorableNavigator
22+
import org.readium.r2.navigator.Decoration
2023
import org.readium.r2.navigator.ExperimentalDecorator
2124
import org.readium.r2.navigator.epub.*
2225
import org.readium.r2.navigator.epub.css.FontStyle
@@ -25,6 +28,7 @@ import org.readium.r2.navigator.html.toCss
2528
import org.readium.r2.navigator.preferences.FontFamily
2629
import org.readium.r2.shared.ExperimentalReadiumApi
2730
import org.readium.r2.shared.publication.Locator
31+
import org.readium.r2.shared.publication.epub.pageList
2832
import org.readium.r2.testapp.LITERATA
2933
import org.readium.r2.testapp.R
3034
import org.readium.r2.testapp.reader.preferences.UserPreferencesViewModel
@@ -66,8 +70,9 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene
6670
"annotation-icon.svg"
6771
)
6872

69-
// Register the HTML template for our custom [DecorationStyleAnnotationMark].
73+
// Register the HTML templates for our custom decoration styles.
7074
decorationTemplates[DecorationStyleAnnotationMark::class] = annotationMarkTemplate()
75+
decorationTemplates[DecorationStylePageNumber::class] = pageNumberTemplate()
7176

7277
// Declare a custom font family for reflowable EPUBs.
7378
addFontFamilyDeclaration(FontFamily.LITERATA) {
@@ -126,6 +131,33 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene
126131
@Suppress("Unchecked_cast")
127132
(model.settings as UserPreferencesViewModel<EpubSettings, EpubPreferences>)
128133
.bind(navigator, viewLifecycleOwner)
134+
135+
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
136+
// Display page number labels if the book contains a `page-list` navigation document.
137+
(navigator as? DecorableNavigator)?.applyPageNumberDecorations()
138+
}
139+
}
140+
141+
/**
142+
* Will display margin labels next to page numbers in an EPUB publication with a `page-list`
143+
* navigation document.
144+
*
145+
* See http://kb.daisy.org/publishing/docs/navigation/pagelist.html
146+
*/
147+
private suspend fun DecorableNavigator.applyPageNumberDecorations() {
148+
val decorations = publication.pageList
149+
.mapIndexedNotNull { index, link ->
150+
val label = link.title ?: return@mapIndexedNotNull null
151+
val locator = publication.locatorFromLink(link) ?: return@mapIndexedNotNull null
152+
153+
Decoration(
154+
id = "page-$index",
155+
locator = locator,
156+
style = DecorationStylePageNumber(label = label),
157+
)
158+
}
159+
160+
applyDecorations(decorations, "pageNumbers")
129161
}
130162

131163
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
@@ -218,11 +250,11 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene
218250
}
219251
}
220252

253+
// Examples of HTML templates for custom Decoration Styles.
254+
221255
/**
222-
* Example of an HTML template for a custom Decoration Style.
223-
*
224-
* This one will display a tinted "pen" icon in the page margin to show that a highlight has an
225-
* associated note.
256+
* This Decorator Style will display a tinted "pen" icon in the page margin to show that a highlight
257+
* has an associated note.
226258
*
227259
* Note that the icon is served from the app assets folder.
228260
*/
@@ -256,3 +288,39 @@ private fun annotationMarkTemplate(@ColorInt defaultTint: Int = Color.YELLOW): H
256288
"""
257289
)
258290
}
291+
292+
/**
293+
* This Decoration Style is used to display the page number labels in the margins, when a book
294+
* provides a `page-list`. The label is stored in the [DecorationStylePageNumber] itself.
295+
*
296+
* See http://kb.daisy.org/publishing/docs/navigation/pagelist.html
297+
*/
298+
@OptIn(ExperimentalDecorator::class)
299+
private fun pageNumberTemplate(): HtmlDecorationTemplate {
300+
val className = "testapp-page-number"
301+
return HtmlDecorationTemplate(
302+
layout = HtmlDecorationTemplate.Layout.BOUNDS,
303+
width = HtmlDecorationTemplate.Width.PAGE,
304+
element = { decoration ->
305+
val style = decoration.style as? DecorationStylePageNumber
306+
307+
// Using `var(--RS__backgroundColor)` is a trick to use the same background color as
308+
// the Readium theme. If we don't set it directly inline in the HTML, it might be
309+
// forced transparent by Readium CSS.
310+
"""
311+
<div><span class="$className" style="background-color: var(--RS__backgroundColor) !important">${style?.label}</span></div>"
312+
"""
313+
},
314+
stylesheet = """
315+
.$className {
316+
float: left;
317+
margin-left: 8px;
318+
padding: 0px 4px 0px 4px;
319+
border: 1px solid;
320+
border-radius: 20%;
321+
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
322+
opacity: 0.8;
323+
}
324+
"""
325+
)
326+
}

test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,14 @@ abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.List
536536
@Parcelize
537537
@OptIn(ExperimentalDecorator::class)
538538
data class DecorationStyleAnnotationMark(@ColorInt val tint: Int) : Decoration.Style
539+
540+
/**
541+
* Decoration Style for a page number label.
542+
*
543+
* This is an example of a custom Decoration Style declaration.
544+
*
545+
* @param label Page number label as declared in the `page-list` link object.
546+
*/
547+
@Parcelize
548+
@OptIn(ExperimentalDecorator::class)
549+
data class DecorationStylePageNumber(val label: String) : Decoration.Style

0 commit comments

Comments
 (0)