Skip to content

Documentation #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 6, 2024
Merged
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Contributing to the Readium Swift Toolkit

First and foremost, thanks for your interest! 🙏 We need contributors like you to help bring this project to fruition.

We welcome many kind of contributions such as improving the documentation, submitting bug reports and feature requests, or writing code.

## Writing code

### Coding standard

We use [`SwiftFormat`](https://github.com/nicklockwood/SwiftFormat) to ensure code formatting and avoid bikeshedding.

Before submitting a PR, save yourself some trouble by automatically formatting the code with `make format` from the project's root directory.

### Modifying the EPUB Navigator's JavaScript layer

The EPUB navigator injects a set of JavaScript files into a publication's resources, exposing a JavaScript API to the `WKWebView` under the `readium` global namespace. The JavaScript source code is located under [`Sources/Navigator/EPUB/Scripts`](Sources/Navigator/EPUB/Scripts).

`index-reflowable.js` is the root of the bundle injected in a reflowable EPUB's resources, while `index-fixed.js` is used for a fixed-layout EPUB's resources.

In the case of fixed-layout EPUBs, the publication resources are actually loaded inside an `iframe` in one of [our HTML wrapper pages](Sources/Navigator/EPUB/Assets/) (`fxl-spread-one.html` for single pages, `fxl-spread-two.html` when displaying two pages side-by-side). The matching `index-fixed-wrapper-one.js` and `index-fixed-wrapper-two.js` are injected in the HTML wrappers.

If you make any changes to the JavaScript files, you must regenerate the bundles embedded in the application. First, make sure you have [`corepack` installed](https://pnpm.io/installation#using-corepack). Then, run `make scripts` from the project's root directory.

136 changes: 136 additions & 0 deletions Documentation/Guides/Getting Started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Getting started

The Readium Swift toolkit enables you to develop reading apps for iOS and iPadOS. It provides built-in support for multiple publication formats such as EPUB, PDF, audiobooks, and comics.

:warning: Readium offers only low-level tools. You are responsible for creating a user interface for reading and managing books, as well as a data layer to store the user's publications. The Test App is an example of such integration.

## Design principles

The toolkit has been designed following these core tenets:

* **Modular**: It is divided into separate modules that can be used independently.
* **Extensible**: Integrators should be able to support a custom DRM, publication format or inject their own stylesheets without modifying the toolkit itself.
* **Opiniated**: We adhere to open standards but sometimes interpret them for practicality.

## Packages

### Main packages

* `R2Shared` contains shared `Publication` models and utilities.
* `R2Streamer` parses publication files (e.g. an EPUB) into a `Publication` object.
* [`R2Navigator` renders the content of a publication](Navigator/Navigator.md).

### Specialized packages

* `ReadiumOPDS` parses [OPDS catalog feeds](https://opds.io) (both OPDS 1 and 2).
* [`ReadiumLCP` downloads and decrypts LCP-protected publications](Readium%20LCP.md).

### Adapters to third-party dependencies

* `ReadiumAdapterGCDWebServer` provides an HTTP server built with [GCDWebServer](https://github.com/swisspol/GCDWebServer).

## Overview of the shared models (`R2Shared`)

The Readium toolkit provides models used as exchange types between packages.

### Publication models

#### Publication

`Publication` and its sub-components represent a single publication – ebook, audiobook or comic. It is loosely based on the [Readium Web Publication Manifest](https://readium.org/webpub-manifest/).

A `Publication` instance:

* holds the metadata of a publication, such as its author or table of contents,
* allows to read the contents of a publication, e.g. XHTML or audio resources,
* provides additional services, for example content extraction or text search.

#### Link


A [`Link` object](https://readium.org/webpub-manifest/#24-the-link-object) holds a pointer (URL) to a resource or service along with additional metadata, such as its media type or title.

The `Publication` contains several `Link` collections, for example:

* `readingOrder` lists the publication resources arranged in the order they should be read.
* `resources` contains secondary resources necessary for rendering the `readingOrder`, such as an image or a font file.
* `tableOfContents` represents the table of contents as a tree of `Link` objects.
* `links` exposes additional resources, such as a canonical link to the manifest or a search web service.

#### Locator

A [`Locator` object](https://readium.org/architecture/models/locators/) represents a precise location in a publication resource in a format that can be stored and shared across reading systems. It is more accurate than a `Link` and contains additional information about the location, e.g. progression percentage, position or textual context.

`Locator` objects are used for various features, including:

* reporting the current progression in the publication
* saving bookmarks, highlights and annotations
* navigating search results

### Data models

#### Publication Asset

A `PublicationAsset` is an interface representing a single file or package holding the content of a `Publication`. A default implementation `FileAsset` grants access to a publication stored locally.

#### Resource

A `Resource` provides read access to a single resource of a publication, such as a file or an entry in an archive.

`Resource` instances are usually created by a `Fetcher`. The toolkit ships with various implementations supporting different data access protocols such as local files, HTTP, etc.

#### Fetcher

A `Fetcher` provides read access to a collection of resources. `Fetcher` instances are created by a `PublicationAsset` to provide access to the content of a publication.

`Publication` objects internally use a `Fetcher` to expose their content.

## Opening a publication (`R2Streamer`)

To retrieve a `Publication` object from a publication file like an EPUB or audiobook, begin by creating a `PublicationAsset` object used to read the file. Readium provides a `FileAsset` implementation for reading a publication stored on the local file system.

```swift
let file = URL(fileURLWithPath: "path/to/book.epub")
let asset = FileAsset(file: file)
```

Then, use a `Streamer` instance to parse the asset and create a `Publication` object.

```swift
let streamer = Streamer()

streamer.open(asset: asset, allowUserInteraction: false) { result in
switch result {
case .success(let publication):
print("Opened \(publication.metadata.title)")
case .failure(let error):
alert(error.localizedDescription)
case .cancelled:
// The user cancelled the opening, for example by dismissing a password pop-up.
break
}
}
```

The `allowUserInteraction` parameter is useful when supporting a DRM like Readium LCP. It indicates if the toolkit can prompt the user for credentials when the publication is protected.

## Accessing the metadata of a publication

After opening a publication, you may want to read its metadata to insert a new entity into your bookshelf database, for instance. The `publication.metadata` object contains everything you need, including `title`, `authors` and the `published` date.

You can retrieve the publication cover using `publication.cover`. Avoid calling this from the main thread to prevent blocking the user interface.

## Rendering the publication on the screen (`R2Navigator`)

You can use a Readium navigator to present the publication to the user. The `Navigator` renders resources on the screen and offers APIs and user interactions for navigating the contents.

```swift
let navigator = try EPUBNavigatorViewController(
publication: publication,
initialLocation: lastReadLocation,
httpServer: GCDHTTPServer.shared
)

hostViewController.present(navigator, animated: true)
```
Please refer to the [Navigator guide](Navigator/Navigator.md) for more information.
194 changes: 194 additions & 0 deletions Documentation/Guides/Navigator/Navigator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Navigator

You can use a Readium Navigator to present the publication to the user. The `Navigator` renders resources on the screen and offers APIs and user interactions for navigating the contents.

:warning: Navigators do not have user interfaces besides the view that displays the publication. Applications are responsible for providing a user interface with bookmark buttons, a progress bar, etc.

## Default implementations

The Readium toolkit comes with several `Navigator` implementations for different publication profiles. Some are `UIViewController`s, designed to be added to your view hierarchy, while others are chromeless and can be used in the background.

| Navigator | Profile | Formats |
|-------------------------------|-------------|-----------------------------------------------------------------------|
| `EPUBNavigatorViewController` | `epub` | EPUB (`.epub`), Readium Web Publication (`.webpub`) |
| `PDFNavigatorViewController` | `pdf` | PDF (`.pdf`), LCP-protected PDF (`.lcpdf`) |
| `CBZNavigatorViewController` | `divina` | Zipped Comic Book (`cbz`), Readium Divina (`.divina`) |
| `AudioNavigator` | `audiobook` | Zipped Audio Book (`.zab`), Readium Audiobook (`.audiobook`, `.lcpa`) |

To find out which Navigator is compatible with a publication, refer to its [profile](https://readium.org/webpub-manifest/profiles/). Use `publication.conformsTo()` to identify the publication's profile.

```swift
if publication.conformsTo(.epub) {
let navigator = try EPUBNavigatorViewController(
publication: publication,
initialLocation: lastReadLocation,
httpServer: GCDHTTPServer.shared
)

hostViewController.present(navigator, animated: true)
}
```

## Navigator APIs

Navigators implement a set of shared interfaces to help reuse the reading logic across publication profiles. For example, instead of using specific implementations like `EPUBNavigatorViewController`, use the `Navigator` interface to create a location history manager compatible with all Navigator types.

You can create custom Navigators and easily integrate them into your app with minimal modifications by implementing these interfaces.

### `Navigator` interface

All Navigators implement the `Navigator` interface, which provides the foundation for navigating resources in a `Publication`. You can use it to move through the publication's content or to find the current position.

Note that this interface does not specify how the content is presented to the user.

### `VisualNavigator` interface

Navigators rendering the content visually on the screen implement the `VisualNavigator` interface. This interface offers details about the presentation style (e.g., scrolled, right-to-left, etc.) and allows monitoring input events like taps or keyboard strokes.

### `SelectableNavigator` interface

Navigators enabling users to select parts of the content implement `SelectableNavigator`. You can use it to extract the `Locator` and content of the selected portion.

### `DecorableNavigator` interface

A Decorable Navigator is able to render decorations over a publication, such as highlights or margin icons.

[See the corresponding proposal for more information](https://readium.org/architecture/proposals/008-decorator-api.html).

## Instantiating a Navigator

### Visual Navigators

The Visual Navigators are implemented as `UIViewController` and must be added to your iOS view hierarchy to render the publication contents.

```swift
let navigator = try EPUBNavigatorViewController(
publication: publication,
initialLocation: lastReadLocation,
httpServer: GCDHTTPServer.shared
)

hostViewController.present(navigator, animated: true)
```

:point_up: The HTTP server is used to serve the publication resources to the Navigator. You may use your own implementation, or the recommended `GCDHTTPServer` which is part of the `ReadiumAdapterGCDWebServer` package.

### Audio Navigator

The `AudioNavigator` is chromeless and does not provide any user interface, allowing you to create your own custom UI.

```swift
let navigator = AudioNavigator(
publication: publication,
initialLocation: lastReadLocation
)

navigator.play()
```

## Navigating the contents of the publication

The `Navigator` interface offers various `go` APIs for navigating the publication. For instance:

* to the previous or next pages: `navigator.goForward()` or `navigator.goBackward()`
* to a link from the `publication.tableOfContents` or `publication.readingOrder`: `navigator.go(to: link)`
* to a locator from a search result: `navigator.go(to: locator)`

## Reading progression

### Saving and restoring the last read location

Navigators don't store any data permanently. Therefore, it is your responsibility to save the last read location in your database and restore it when creating a new Navigator.

You can observe the current position in the publication by implementing a `NavigatorDelegate`.

```swift
navigator.delegate = MyNavigatorDelegate()

class MyNavigatorDelegate: NavigatorDelegate {

override func navigator(_ navigator: Navigator, locationDidChange locator: Locator) {
if let position = locator.locations.position {
print("At position \(position) on \(publication.positions.count)")
}
if let progression = locator.locations.progression {
return "Progression in the current resource: \(progression)%"
}
if let totalProgression = locator.locations.totalProgression {
return "Total progression in the publication: \(progression)%"
}

// Save the position in your bookshelf database
database.saveLastReadLocation(locator.jsonString)
}
}
```

The `Locator` object may be serialized to JSON in your database, and deserialized to set the initial location when creating the navigator.

```swift
let lastReadLocation = Locator(jsonString: dabase.lastReadLocation())

let navigator = try EPUBNavigatorViewController(
publication: publication,
initialLocation: lastReadLocation,
httpServer: GCDHTTPServer.shared
)
```

### Bookmarking the current location

Use a Navigator's `currentLocation` property to persists the current position, for instance as a bookmark.

After the user selects a bookmark from your user interface, navigate to it using `navigator.go(bookmark.locator)`.

### Displaying a progression slider

To display a percentage-based progression slider, use the `locations.totalProgression` property of the `currentLocation`. This property holds the total progression across an entire publication.

Given a progression from 0 to 1, you can obtain a `Locator` object from the `Publication`. This can be used to navigate to a specific percentage within the publication.

```swift
if let locator = publication.locate(progression: 0.5) {
navigator.go(to: locator)
}
```

### Displaying the number of positions

:warning: Readium does not have the concept of pages, as they are not useful when dealing with reflowable publications across different screen sizes. Instead, we use [**positions**](https://readium.org/architecture/models/locators/positions/) which remain stable even when the user changes the font size or device.

Not all Navigators provide positions, but most `VisualNavigator` implementations do. Verify if `publication.positions` is not empty to determine if it is supported.

To find the total positions in the publication, use `publication.positions.count`. You can get the current position with `navigator.currentLocation?.locations.position`.

## Navigating with edge taps and keyboard arrows

Readium provides a `DirectionalNavigationAdapter` helper to turn pages using arrow and space keys or screen taps.

You can use it from your `VisualNavigatorDelegate` implementation:

```swift
extension MyReader: VisualNavigatorDelegate {

func navigator(_ navigator: VisualNavigator, didTapAt point: CGPoint) {
// Turn pages when tapping the edge of the screen.
guard !DirectionalNavigationAdapter(navigator: navigator).didTap(at: point) else {
return
}

toggleNavigationBar()
}

func navigator(_ navigator: VisualNavigator, didPressKey event: KeyEvent) {
// Turn pages when pressing the arrow keys.
DirectionalNavigationAdapter(navigator: navigator).didPressKey(event: event)
}
}
```

`DirectionalNavigationAdapter` offers a lot of customization options. Take a look at its API.

## User preferences

Readium Navigators support user preferences, such as font size or background color. Take a look at [the Preferences API guide](Preferences.md) for more information.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Configuring the Navigator

:warning: The Navigator Setting API is brand new and currently only available with `EPUBNavigatorViewController` and `PDFNavigatorViewController`.

Take a look at the [migration guide](../Migration%20Guide.md) if you are already using the legacy EPUB settings.

## Overview
Expand Down
Loading
Loading