Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3ab1381
Refactor the `LocatorService` implementations and add `locate(Link)`
mickael-menu Apr 27, 2022
6964075
Add tests and deprecate `Locator(link: Link)`
mickael-menu Apr 27, 2022
935a51c
Update changelog
mickael-menu Apr 27, 2022
d6d3dea
Update Carthage project
mickael-menu Apr 27, 2022
0cb62a2
Add the `PublicationContentIterator`
mickael-menu Apr 28, 2022
08e88ec
Simplify the ContentIterator by making it synchronous
mickael-menu Apr 28, 2022
6ed6a7e
Add the `HTMLResourceContentIterator` and `ContentIterationService`
mickael-menu Apr 28, 2022
c5de09b
Add findFirstVisibleElement() JS API
mickael-menu May 2, 2022
9b047ec
Add the `TTSController`
mickael-menu May 2, 2022
0ff9ad2
Improve `TTSController`, add the `TTSViewModel`
mickael-menu May 3, 2022
f346158
Add the TTS settings
mickael-menu May 5, 2022
f14d613
Extract a `TTSEngine` protocol from the `TTSController`
mickael-menu May 5, 2022
1be7114
Refactor `TTSController`
mickael-menu May 5, 2022
bfc92a5
Implement previous()
mickael-menu May 9, 2022
53e7d1c
Add various text tokenizers
mickael-menu May 9, 2022
966b9c1
Tokenize the TTS text spans
mickael-menu May 9, 2022
bfd06f7
Refactor the text tokanizers into functions and introduce generic `To…
mickael-menu May 9, 2022
9ae1b7d
Add the `ContentTokenizer`
mickael-menu May 9, 2022
3474cad
Deprecate `Locator(link: Link)` in favor of `Publication.locate(Link)…
mickael-menu May 10, 2022
230e577
Merge branch 'develop' into feature/tts
mickael-menu May 10, 2022
15b9546
Go to currently playing word
mickael-menu May 10, 2022
6fdc0a5
Refactor TTS engine and add `Language`
mickael-menu May 11, 2022
6d85d81
Change the TTS voice
mickael-menu May 11, 2022
2e5fbd1
Fix setting voice and language
mickael-menu May 11, 2022
d9d27b7
Merge branch 'develop' into feature/tts
mickael-menu May 16, 2022
43580fc
Add `TTSEngine.voiceWithIdentifier()`
mickael-menu May 18, 2022
108b2bf
Fix flashing pages when following the speech
mickael-menu May 18, 2022
acc3eae
Fix deadlock with AVTTSEngine
mickael-menu May 19, 2022
e682b7b
Setup last position when jumping to a previous resource
mickael-menu May 19, 2022
c9642fb
Improve settings screen
mickael-menu May 19, 2022
d84c066
Merge branch 'develop' of github.com:readium/swift-toolkit into featu…
mickael-menu May 23, 2022
f074a93
Support for AudioSession to play while in the background
mickael-menu May 23, 2022
7b609f3
Fix Xcode warnings
mickael-menu Jul 22, 2022
3f23cd2
Merge branch 'develop' into feature/tts
mickael-menu Aug 6, 2022
883a02c
Refactor content models
mickael-menu Aug 6, 2022
4d31aef
Refactor content iterators
mickael-menu Aug 7, 2022
c3c67c2
Refactor `firstVisibleElementLocator`
mickael-menu Aug 7, 2022
862d89d
Refactor TTSEngine
mickael-menu Aug 7, 2022
e855e54
Add `PublicationSpeechSynthesizer`
mickael-menu Aug 7, 2022
5202707
Refactor the `TTSViewModel`
mickael-menu Aug 8, 2022
71ac66b
Auto-cancel tasks
mickael-menu Aug 8, 2022
7a9879b
Fix pause
mickael-menu Aug 8, 2022
d182ec7
Fix pitch
mickael-menu Aug 8, 2022
eb597ab
Remove rate and pitch settings for now (will be available with the Se…
mickael-menu Aug 13, 2022
b46bbe7
Add the TTS user guide
mickael-menu Aug 13, 2022
be2225d
Add `Content` helpers
mickael-menu Aug 13, 2022
31dc9f8
Add the content user guide
mickael-menu Aug 13, 2022
bc4cbea
Update Carthage project
mickael-menu Aug 13, 2022
68c8996
Fix build on older Xcode versions
mickael-menu Aug 13, 2022
2e2cba5
Merge branch 'develop' into feature/tts
mickael-menu Aug 26, 2022
669a3df
Fix Carthage project
mickael-menu Aug 26, 2022
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. Take a look

## [Unreleased]

### Added

#### Shared

* [Extract the raw content (text, images, etc.) of a publication](Documentation/Guides/Content.md).

#### Navigator

* [A brand new text-to-speech implementation](Documentation/Guides/TTS.md).

### Deprecated

#### Shared
Expand Down
202 changes: 202 additions & 0 deletions Documentation/Guides/Content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Extracting the content of a publication

:warning: The described feature is still experimental and the implementation incomplete.

Many high-level features require access to the raw content (text, media, etc.) of a publication, such as:

* Text-to-speech
* Accessibility reader
* Basic search
* Full-text search indexing
* Image or audio indexes

The `ContentService` provides a way to iterate through a publication's content, extracted as semantic elements.

First, request the publication's `Content`, starting from a given `Locator`. If the locator is missing, the `Content` will be extracted from the beginning of the publication.

```swift
guard let content = publication.content(from: startLocator) else {
// Abort as the content cannot be extracted
return
}
```

## Extracting the raw text content

Getting the whole raw text of a publication is such a common use case that a helper is available on `Content`:

```swift
let wholeText = content.text()
```

This is an expensive operation, proceed with caution and cache the result if you need to reuse it.

## Iterating through the content

The individual `Content` elements can be iterated through with a regular `for` loop by converting it to a sequence:

```swift
for (element in content.sequence()) {
// Process element
}
```

Alternatively, you can get the whole list of elements with `content.elements()`, or use the lower level APIs to iterate the content manually:

```swift
let iterator = content.iterator()
while let element = try iterator.next() {
print(element)
}
```

Some `Content` implementations support bidirectional iterations. To iterate backwards, use:

```swift
let iterator = content.iterator()
while let element = try iterator.previous() {
print(element)
}
```

## Processing the elements

The `Content` iterator yields `ContentElement` objects representing a single semantic portion of the publication, such as a heading, a paragraph or an embedded image.

Every element has a `locator` property targeting it in the publication. You can use the locator, for example, to navigate to the element or to draw a `Decoration` on top of it.

```swift
navigator.go(to: element.locator)
```

### Types of elements

Depending on the actual implementation of `ContentElement`, more properties are available to access the actual data. The toolkit ships with a number of default implementations for common types of elements.

#### Embedded media

The `EmbeddedContentElement` protocol is implemented by any element referencing an external resource. It contains an `embeddedLink` property you can use to get the actual content of the resource.

```swift
if let element = element as? EmbeddedContentElement {
let bytes = try publication
.get(element.embeddedLink)
.read().get()
}
```

Here are the default available implementations:

* `AudioContentElement` - audio clips
* `VideoContentElement` - video clips
* `ImageContentElement` - bitmap images, with the additional property:
* `caption: String?` - figure caption, when available

#### Text

##### Textual elements

The `TextualContentElement` protocol is implemented by any element which can be represented as human-readable text. This is useful when you want to extract the text content of a publication without caring for each individual type of elements.

```swift
let wholeText = publication.content()
.elements()
.compactMap { ($0 as? TextualContentElement)?.text.takeIf { !$0.isEmpty } }
.joined(separator: "\n")
```

##### Text elements

Actual text elements are instances of `TextContentElement`, which represent a single block of text such as a heading, a paragraph or a list item. It is comprised of a `role` and a list of `segments`.

The `role` is the nature of the text element in the document. For example a heading, body, footnote or a quote. It can be used to reconstruct part of the structure of the original document.

A text element is composed of individual segments with their own `locator` and `attributes`. They are useful to associate attributes with a portion of a text element. For example, given the HTML paragraph:

```html
<p>It is pronounced <span lang="fr">croissant</span>.</p>
```

The following `TextContentElement` will be produced:

```swift
TextContentElement(
role: .body,
segments: [
TextContentElement.Segment(text: "It is pronounced "),
TextContentElement.Segment(text: "croissant", attributes: [ContentAttribute(key: .language, value: "fr")]),
TextContentElement.Segment(text: ".")
]
)
```

If you are not interested in the segment attributes, you can also use `element.text` to get the concatenated raw text.

### Element attributes

All types of `ContentElement` can have associated attributes. Custom `ContentService` implementations can use this as an extensibility point.

## Use cases

### An index of all images embedded in the publication

This example extracts all the embedded images in the publication and displays them in a SwiftUI list. Clicking on an image jumps to its location in the publication.

```swift
struct ImageIndex: View {
struct Item: Hashable {
let locator: Locator
let text: String?
let image: UIImage
}

let publication: Publication
let navigator: Navigator
@State private var items: [Item] = []

init(publication: Publication, navigator: Navigator) {
self.publication = publication
self.navigator = navigator
}

var body: some View {
ScrollView {
LazyVStack {
ForEach(items, id: \.self) { item in
VStack() {
Image(uiImage: item.image)
Text(item.text ?? "No caption")
}
.onTapGesture {
navigator.go(to: item.locator)
}
}
}
}
.onAppear {
items = publication.content()?
.elements()
.compactMap { element in
guard
let element = element as? ImageContentElement,
let image = try? publication.get(element.embeddedLink)
.read().map(UIImage.init).get()
else {
return nil
}

return Item(
locator: element.locator,
text: element.caption ?? element.accessibilityLabel,
image: image
)
}
?? []
}
}
}
```

## References

* [Content Iterator proposal](https://github.com/readium/architecture/pull/177)
Loading