Skip to content

Commit

Permalink
Merge branch 'develop' into rewrite-swiftui
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenzeck authored Sep 2, 2024
2 parents 3700357 + 6e432e8 commit 7c53197
Show file tree
Hide file tree
Showing 332 changed files with 23,698 additions and 8,676 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ env:
platform: ${{ 'iOS Simulator' }}
device: ${{ 'iPhone 15' }}
commit_sha: ${{ github.sha }}
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer

jobs:
build:
name: Build
runs-on: macos-13
runs-on: macos-14
if: ${{ !github.event.pull_request.draft }}
env:
scheme: ${{ 'Readium-Package' }}
Expand Down Expand Up @@ -42,7 +42,7 @@ jobs:
lint:
name: Lint
runs-on: macos-13
runs-on: macos-14
if: ${{ !github.event.pull_request.draft }}
env:
scripts: ${{ 'Sources/Navigator/EPUB/Scripts' }}
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:

int-dev:
name: Integration (Local)
runs-on: macos-13
runs-on: macos-14
if: ${{ !github.event.pull_request.draft }}
defaults:
run:
Expand All @@ -98,7 +98,7 @@ jobs:
int-spm:
name: Integration (Swift Package Manager)
runs-on: macos-13
runs-on: macos-14
if: ${{ !github.event.pull_request.draft }}
defaults:
run:
Expand Down Expand Up @@ -126,7 +126,7 @@ jobs:
int-carthage:
name: Integration (Carthage)
runs-on: macos-13
runs-on: macos-14
if: ${{ !github.event.pull_request.draft }}
defaults:
run:
Expand Down Expand Up @@ -157,7 +157,7 @@ jobs:
int-cocoapods:
name: Integration (CocoaPods)
if: github.event_name == 'push'
runs-on: macos-13
runs-on: macos-14
defaults:
run:
working-directory: TestApp
Expand Down
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file. Take a look

**Warning:** Features marked as *alpha* may change or be removed in a future release without notice. Use with caution.

## [Unreleased]
<!-- ## [Unreleased] -->

## [3.0.0-alpha.2]

### Added

Expand All @@ -19,9 +21,17 @@ All notable changes to this project will be documented in this file. Take a look

#### Shared

* A new `Format` type was introduced to augment `MediaType` with more precise information about the format specifications of an `Asset`.
* `Fetcher` was replaced with a simpler `Container` type.
* `PublicationAsset` was replaced by `Asset`, which contains a `Format` and access to the underlying `Container` or `Resource`.
* The `ResourceError` hierarchy was revamped and simplified (see `ReadError`). Now it is your responsibility to provide a localized user message for each error case.
* The `Link` property key for archive-based publication assets (e.g. an EPUB/ZIP) is now `https://readium.org/webpub-manifest/properties#archive` instead of `archive`.
* The API of `HTTPServer` slightly changed to be more future-proof.

#### Streamer

* The `Streamer` object was deprecated in favor of smaller segregated APIs: `AssetRetriever` and `PublicationOpener`.

#### Navigator

* EPUB: The `scroll` preference is now forced to `true` when rendering vertical text (e.g. CJK vertical). [See this discussion for the rationale](https://github.com/readium/swift-toolkit/discussions/370).
Expand All @@ -36,6 +46,7 @@ All notable changes to this project will be documented in this file. Take a look

* Optimized scrolling to an EPUB text-based locator if it contains a CSS selector.
* The first resource of a fixed-layout EPUB is now displayed on its own when spreads are enabled and the author has not set a `page-spread-*` property. This is the default behavior in major reading apps like Apple Books.
* [#471](https://github.com/readium/swift-toolkit/issues/471) EPUB: Fixed reporting the current location when submitting new preferences.


## [3.0.0-alpha.1]
Expand Down Expand Up @@ -737,3 +748,4 @@ progression. Now if no reading progression is set, the `effectiveReadingProgress
[2.7.1]: https://github.com/readium/swift-toolkit/compare/2.7.0...2.7.1
[2.7.2]: https://github.com/readium/swift-toolkit/compare/2.7.1...2.7.2
[3.0.0-alpha.1]: https://github.com/readium/swift-toolkit/compare/2.7.1...3.0.0-alpha.1
[3.0.0-alpha.2]: https://github.com/readium/swift-toolkit/compare/3.0.0-alpha.1...3.0.0-alpha.2
3 changes: 2 additions & 1 deletion Cartfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ github "dexman/Minizip" ~> 1.4.0
github "krzyzanowskim/CryptoSwift" ~> 1.8.0
github "ra1028/DifferenceKit" ~> 1.3.0
github "readium/GCDWebServer" ~> 4.0.0
github "scinfu/SwiftSoup" ~> 2.7.0
# There's a regression with 2.7.4 in SwiftSoup, because they used iOS 13 APIs without bumping the deployment target.
github "scinfu/SwiftSoup" == 2.7.1
github "stephencelis/SQLite.swift" ~> 0.15.0
github "weichsel/ZIPFoundation" ~> 0.9.0
94 changes: 56 additions & 38 deletions Documentation/Guides/Getting Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ The toolkit has been designed following these core tenets:

### 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).
* `ReadiumShared` contains shared `Publication` models and utilities.
* `ReadiumStreamer` parses publication files (e.g. an EPUB) into a `Publication` object.
* [`ReadiumNavigator` renders the content of a publication](Navigator/Navigator.md).

### Specialized packages

Expand All @@ -30,7 +30,7 @@ The toolkit has been designed following these core tenets:
* `ReadiumAdapterGCDWebServer` provides an HTTP server built with [GCDWebServer](https://github.com/swisspol/GCDWebServer).
* `ReadiumAdapterLCPSQLite` provides implementations of the `ReadiumLCP` license and passphrase repositories using [SQLite.swift](https://github.com/stephencelis/SQLite.swift).

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

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

Expand All @@ -48,7 +48,6 @@ A `Publication` instance:

#### 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:
Expand All @@ -70,68 +69,87 @@ A [`Locator` object](https://readium.org/architecture/models/locators/) represen

### Data models

#### Publication Asset
#### 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.
An `Asset` represents a single file or package and provides access to its content. There are two types of `Asset`:

#### Resource
* `ContainerAsset` for packages which contains several resources, such as a ZIP archive.
* `ResourceAsset` for accessing a single resource, such as a JSON or PDF file.

A `Resource` provides read access to a single resource of a publication, such as a file or an entry in an archive.
`Asset` instances are obtained through an `AssetRetriever`.

`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.
You can use the `asset.format` to identify the media type and capabilities of the asset.

#### Fetcher
```swift
if asset.format.conformsTo(.lcp) {
// The asset is protected with LCP.
}
if asset.format.conformsTo(.epub) {
// The asset represents an EPUB publication.
}
```

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.
#### Resource

`Publication` objects internally use a `Fetcher` to expose their content.
A `Resource` provides read access to a single resource, such as a file or an entry in an archive.

## Opening a publication (`R2Streamer`)
`Resource` instances are usually created by a `ResourceFactory`. The toolkit ships with various implementations supporting different data access protocols such as local files or HTTP.

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.
#### Container

```swift
let file = URL(fileURLWithPath: "path/to/book.epub")
let asset = FileAsset(file: file)
```
A `Container` provides read access to a collection of resources. `Container` instances representing an archive are usually created by an `ArchiveOpener`. The toolkit ships with a `ZIPArchiveOpener` supporting local ZIP files.

`Publication` objects internally use a `Container` to expose its content.

## Opening a publication (`ReadiumStreamer`)

Then, use a `Streamer` instance to parse the asset and create a `Publication` object.
To retrieve a `Publication` object from a publication file like an EPUB or audiobook, you can use an `AssetRetriever` and `PublicationOpener`.

```swift
let streamer = Streamer()
// Instantiate the required components.
let httpClient = DefaultHTTPClient()
let assetRetriever = AssetRetriever(
httpClient: httpClient
)
let publicationOpener = PublicationOpener(
publicationParser: DefaultPublicationParser(
httpClient: httpClient,
assetRetriever: assetRetriever,
pdfFactory: DefaultPDFDocumentFactory()
)
)

let url: URL = URL(...)

streamer.open(asset: asset, allowUserInteraction: false) { result in
switch result {
// Retrieve an `Asset` to access the file content.
switch await assetRetriever.retrieve(url: url.anyURL.absoluteURL!) {
case .success(let asset):
// Open a `Publication` from the `Asset`.
switch await publicationOpener.open(asset: asset, allowUserInteraction: true, sender: view) {
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
// Failed to access or parse the publication
}

case .failure(let error):
// Failed to retrieve the asset
}
```

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.

[See the dedicated user guide for more information](Open%20Publication.md).

## 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.
You can retrieve the publication cover using `await publication.cover()`.

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

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.
92 changes: 92 additions & 0 deletions Documentation/Guides/Open Publication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Opening a publication

To open a publication with Readium, you need to instantiate a couple of components: an `AssetRetriever` and a `PublicationOpener`.

## `AssetRetriever`

The `AssetRetriever` grants access to the content of an asset located at a given URL, such as a publication package, manifest, or LCP license.

### Constructing an `AssetRetriever`

You can create an instance of `AssetRetriever` with:

* An `HTTPClient` to enable the toolkit to perform HTTP requests and support the `http` and `https` URL schemes. You can use `DefaultHTTPClient` which provides callbacks for handling authentication when needed.

```swift
let assetRetriever = AssetRetriever(httpClient: DefaultHTTPClient())
```

### Retrieving an `Asset`

With your fresh instance of `AssetRetriever`, you can open an `Asset` from any `AbsoluteURL`.

```swift
// From a local file.
let url = FileURL(string: "file:///path/to/book.epub")
// or from an HTTP URL.
let url = HTTPURL(string: "https://domain/book.epub")

switch await assetRetriever.retrieve(url: url) {
case .success(let asset):
...
case .failure(let error):
// Failed to retrieve the asset.
}
```

The `AssetRetriever` will sniff the media type of the asset, which you can store in your bookshelf database to speed up the process next time you retrieve the `Asset`. This will improve performance, especially with HTTP URL schemes.

```swift
let mediaType = asset.format.mediaType

// Speed up the retrieval with a known media type.
let result = await assetRetriever.retrieve(url: url, mediaType: mediaType)
```

## `PublicationOpener`

`PublicationOpener` builds a `Publication` object from an `Asset` using:

* A `PublicationParser` to parse the asset structure and publication metadata.
* The `DefaultPublicationParser` handles all the formats supported by Readium out of the box.
* An optional list of `ContentProtection` to decrypt DRM-protected publications.
* If you support Readium LCP, you can get one from the `LCPService`.

```swift
let publicationOpener = PublicationOpener(
parser: DefaultPublicationParser(
httpClient: httpClient,
assetRetriever: assetRetriever
),
contentProtections: [
lcpService.contentProtection(with: LCPDialogAuthentication()),
]
)
```

### Opening a `Publication`

Now that you have a `PublicationOpener` ready, you can use it to create a `Publication` from an `Asset` that was previously obtained using the `AssetRetriever`.

The `allowUserInteraction` parameter is useful when supporting Readium LCP. When enabled and using a `LCPDialogAuthentication`, the toolkit will prompt the user if the passphrase is missing.

```swift
let result = await readium.publicationOpener.open(
asset: asset,
allowUserInteraction: true,
sender: sender
)
```

## Supporting additional formats or URL schemes

`DefaultPublicationParser` accepts additional parsers. You also have the option to use your own parser list by using `CompositePublicationParser` or create your own `PublicationParser` for a fully customized parsing resolution strategy.

The `AssetRetriever` offers an additional constructor that provides greater extensibility options, using:

* `ResourceFactory` which handles the URL schemes through which you can access content.
* `ArchiveOpener` which determines the types of archives (ZIP, RAR, etc.) that can be opened by the `AssetRetriever`.
* `FormatSniffer` which identifies the file formats that `AssetRetriever` can recognize.

You can use either the default implementations or implement your own for each of these components using the composite pattern. The toolkit's `CompositeResourceFactory`, `CompositeArchiveOpener`, and `CompositeFormatSniffer` provide a simple resolution strategy.

1 change: 1 addition & 0 deletions Documentation/Guides/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# User guides

* [Getting Started](Getting%20Started.md)
* [Opening a publication](Open%20Publication.md)
* [Extracting the content of a publication](Content.md)
* [Text-to-speech](TTS.md)
* [Supporting Readium LCP](Readium%20LCP.md)
Expand Down
Loading

0 comments on commit 7c53197

Please sign in to comment.