Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ All notable changes to this project will be documented in this file. Take a look

* You can now access the `viewport` property of an `EPUBNavigatorViewController` to obtain information about the visible portion of the publication, including the visible positions and reading order indices.

### Deprecated

#### Shared

* The Presentation Hints properties are deprecated from the Readium Web Publication Manifest models. [See the official documentation](https://readium.org/webpub-manifest/profiles/epub.html#appendix-b---deprecated-properties).

### Fixed

#### Navigator
Expand Down
2 changes: 1 addition & 1 deletion Sources/Navigator/EPUB/EPUBNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ extension EPUBNavigatorViewController: EditingActionsControllerDelegate {
extension EPUBNavigatorViewController: PaginationViewDelegate {
func paginationView(_ paginationView: PaginationView, pageViewAtIndex index: Int) -> (UIView & PageView)? {
let spread = spreads[index]
let spreadViewType = (spread.layout == .fixed) ? EPUBFixedSpreadView.self : EPUBReflowableSpreadView.self
let spreadViewType = (publication.metadata.layout == .fixed) ? EPUBFixedSpreadView.self : EPUBReflowableSpreadView.self
let spreadView = spreadViewType.init(
viewModel: viewModel,
spread: spread,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ final class EPUBNavigatorViewModel: Loggable {
guard
let link = publication.linkWithHREF(href),
link.mediaType?.isHTML == true,
publication.metadata.presentation.layout(of: link) == .reflowable
publication.metadata.layout == .reflowable
else {
return resource
}
Expand Down
25 changes: 9 additions & 16 deletions Sources/Navigator/EPUB/EPUBSpread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,13 @@ struct EPUBSpread: Loggable {
/// Spread reading progression direction.
var readingProgression: ReadingProgression

/// Rendition layout of the links in the spread.
var layout: EPUBLayout

init(spread: Bool, readingOrderIndices: ReadingOrderIndices, readingProgression: ReadingProgression, layout: EPUBLayout) {
init(spread: Bool, readingOrderIndices: ReadingOrderIndices, readingProgression: ReadingProgression) {
precondition(!readingOrderIndices.isEmpty, "A spread must have at least one page")
precondition(spread || readingOrderIndices.count == 1, "A one-page spread must have only one page")
precondition(!spread || 1 ... 2 ~= readingOrderIndices.count, "A two-pages spread must have one or two pages max")
self.spread = spread
self.readingOrderIndices = readingOrderIndices
self.readingProgression = readingProgression
self.layout = layout
}

/// Returns the left-most reading order index in the spread.
Expand Down Expand Up @@ -83,7 +79,7 @@ struct EPUBSpread: Loggable {
/// - url: Full URL to the resource.
/// - page [left|center|right]: (optional) Page position of the linked resource in the spread.
func json(forBaseURL baseURL: HTTPURL, readingOrder: ReadingOrder) -> [[String: Any]] {
func makeLinkJSON(_ index: ReadingOrder.Index, page: Presentation.Page? = nil) -> [String: Any]? {
func makeLinkJSON(_ index: ReadingOrder.Index, page: Properties.Page? = nil) -> [String: Any]? {
guard let link = readingOrder.getOrNil(index) else {
return nil
}
Expand Down Expand Up @@ -136,12 +132,11 @@ struct EPUBSpread: Loggable {
readingOrder: [Link],
readingProgression: ReadingProgression
) -> [EPUBSpread] {
readingOrder.enumerated().map { index, link in
readingOrder.enumerated().map { index, _ in
EPUBSpread(
spread: false,
readingOrderIndices: index ... index,
readingProgression: readingProgression,
layout: publication.metadata.presentation.layout(of: link)
readingProgression: readingProgression
)
}
}
Expand All @@ -157,22 +152,20 @@ struct EPUBSpread: Loggable {
var index = 0
while index < readingOrder.count {
let first = readingOrder[index]
let layout = publication.metadata.presentation.layout(of: first)

var spread = EPUBSpread(
spread: true,
readingOrderIndices: index ... index,
readingProgression: readingProgression,
layout: layout
readingProgression: readingProgression
)

let nextIndex = index + 1
// To be displayed together, the two pages must have a fixed layout,
// and have consecutive position hints (Properties.Page).
// To be displayed together, two pages must be part of a fixed
// layout publication and have consecutive position hints
// (Properties.Page).
if
let second = readingOrder.getOrNil(nextIndex),
layout == .fixed,
layout == publication.metadata.presentation.layout(of: second),
publication.metadata.layout == .fixed,
publication.areConsecutive(first, second, index: index)
{
spread.readingOrderIndices = index ... nextIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ public final class EPUBPreferencesEditor: StatefulPreferencesEditor<EPUBPreferen
metadata: Metadata,
defaults: EPUBDefaults
) {
layout = metadata.presentation.layout ?? .reflowable
switch metadata.layout {
case .fixed:
layout = .fixed
default:
layout = .reflowable
}
self.defaults = defaults

super.init(
Expand Down
1 change: 0 additions & 1 deletion Sources/Navigator/EPUB/Preferences/EPUBSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ public struct EPUBSettings: ConfigurableSettings {
readingProgression: readingProgression,
scroll: scroll,
spread: preferences.spread
?? Spread(metadata.presentation.spread)
?? defaults.spread
?? .auto,
textAlign: preferences.textAlign
Expand Down
16 changes: 1 addition & 15 deletions Sources/Navigator/Preferences/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,6 @@ public enum Spread: String, Codable, Hashable {
case never
/// The publication should always be displayed in a spread.
case always

init?(_ spread: ReadiumShared.Presentation.Spread?) {
guard let spread = spread else {
return nil
}
switch spread {
case .both:
self = .always
case .none:
self = .never
case .auto, .landscape:
self = .auto
}
}
}

/// Direction of the reading progression across resources.
Expand All @@ -53,7 +39,7 @@ public enum ReadingProgression: String, Codable, Hashable {
}

/// Returns the starting page for the reading progression.
var startingPage: Presentation.Page {
var startingPage: Properties.Page {
switch self {
case .ltr:
return .right
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public struct AccessibilityMetadataDisplayGuide: Sendable, Equatable {
}

public init(publication: Publication) {
let isFixedLayout = publication.metadata.presentation.layout == .fixed
let isFixedLayout = publication.metadata.layout == .fixed
let a11y = publication.metadata.accessibility ?? Accessibility()

visualAdjustments = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public extension Properties {
}

/// Hint about the nature of the layout for the linked resources.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original value.")
var layout: EPUBLayout? {
parseRaw(otherProperties["layout"])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Foundation

/// Presentation extensions for `Metadata`.
public extension Metadata {
var presentation: Presentation {
(try? Presentation(json: otherMetadata["presentation"], warnings: self)) ?? Presentation()
}
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original values.")
var presentation: Presentation { fatalError() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ReadiumInternal
/// These properties are nullable to avoid having default values when it doesn't make sense for a
/// given `Publication`. If a navigator needs a default value when not specified,
/// `Presentation.defaultX` and `Presentation.X.default` can be used.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original values.")
public struct Presentation: Equatable {
/// Specifies whether or not the parts of a linked resource that flow out of the viewport are
/// clipped.
Expand Down Expand Up @@ -85,14 +86,6 @@ public struct Presentation: Equatable {
])
}

/// Determines the layout of the given resource in this publication.
/// Default layout is reflowable.
public func layout(of link: Link) -> EPUBLayout {
link.properties.layout
?? layout
?? .reflowable
}

/// Suggested method for constraining a resource inside the viewport.
public enum Fit: String {
/// The content is centered and scaled to fit both dimensions into the viewport.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,33 @@ import ReadiumInternal
public extension Properties {
/// Specifies whether or not the parts of a linked resource that flow out of the viewport are
/// clipped.
@available(*, unavailable, message: "This was removed from RWPM.")
var clipped: Bool? {
otherProperties["clipped"] as? Bool
}

/// Suggested method for constraining a resource inside the viewport.
@available(*, unavailable, message: "This was removed from RWPM.")
var fit: Presentation.Fit? {
parseRaw(otherProperties["fit"])
}

/// Suggested orientation for the device when displaying the linked resource.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original value.")
var orientation: Presentation.Orientation? {
parseRaw(otherProperties["orientation"])
}

/// Indicates if the overflow of linked resources from the `readingOrder` or `resources` should
/// be handled using dynamic pagination or scrolling.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original value.")
var overflow: Presentation.Overflow? {
parseRaw(otherProperties["overflow"])
}

/// Indicates how the linked resource should be displayed in a reading environment that
/// displays synthetic spreads.
var page: Presentation.Page? {
parseRaw(otherProperties["page"])
}

/// Indicates the condition to be met for the linked resource to be rendered within a synthetic
/// spread.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original value.")
var spread: Presentation.Spread? {
parseRaw(otherProperties["spread"])
}
Expand Down
31 changes: 31 additions & 0 deletions Sources/Shared/Publication/Layout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright 2025 Readium Foundation. All rights reserved.
// Use of this source code is governed by the BSD-style license
// available in the top-level LICENSE file of the project.
//

import Foundation

/// Hint about the nature of the layout for the publication.
///
/// https://readium.org/webpub-manifest/contexts/default/#layout-and-reading-progression
public enum Layout: String, Sendable {
/// Reading systems are free to adapt text and layout entirely based on user
/// preferences.
///
/// Formats: Reflowable EPUB
case reflowable

/// Each resource is a “page” where both dimensions are usually contained in
/// the device’s viewport. Based on user preferences, the reading system may
/// also display two resources side by side in a spread.
///
/// Formats: Divina, FXL EPUB or PDF
case fixed

/// Resources are displayed in a continuous scroll, usually by filling the
/// width of the viewport, without any visible gap between between spine items.
///
/// Formats: Scrolled Divina
case scrolled
}
9 changes: 9 additions & 0 deletions Sources/Shared/Publication/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public struct Metadata: Hashable, Loggable, WarningLogger, Sendable {
/// as defined in a [W3C Community Group Report](https://www.w3.org/community/reports/tdmrep/CG-FINAL-tdmrep-20240510/).
public var tdm: TDM?

/// Hint about the nature of the layout for the publication.
///
/// https://readium.org/webpub-manifest/contexts/default/#layout-and-reading-progression
public var layout: Layout?

public var readingProgression: ReadingProgression

/// Additional properties for extensions.
Expand Down Expand Up @@ -90,6 +95,7 @@ public struct Metadata: Hashable, Loggable, WarningLogger, Sendable {
contributors: [Contributor] = [],
publishers: [Contributor] = [],
imprints: [Contributor] = [],
layout: Layout? = nil,
readingProgression: ReadingProgression = .auto,
description: String? = nil,
duration: Double? = nil,
Expand Down Expand Up @@ -125,6 +131,7 @@ public struct Metadata: Hashable, Loggable, WarningLogger, Sendable {
self.contributors = contributors
self.publishers = publishers
self.imprints = imprints
self.layout = layout
self.readingProgression = readingProgression
self.description = description
self.duration = duration
Expand Down Expand Up @@ -180,6 +187,7 @@ public struct Metadata: Hashable, Loggable, WarningLogger, Sendable {
contributors = [Contributor](json: json.pop("contributor"), warnings: warnings)
publishers = [Contributor](json: json.pop("publisher"), warnings: warnings)
imprints = [Contributor](json: json.pop("imprint"), warnings: warnings)
layout = parseRaw(json.pop("layout"))
readingProgression = parseRaw(json.pop("readingProgression")) ?? .auto
description = json.pop("description") as? String
duration = parsePositiveDouble(json.pop("duration"))
Expand Down Expand Up @@ -217,6 +225,7 @@ public struct Metadata: Hashable, Loggable, WarningLogger, Sendable {
"contributor": encodeIfNotEmpty(contributors.json),
"publisher": encodeIfNotEmpty(publishers.json),
"imprint": encodeIfNotEmpty(imprints.json),
"layout": encodeIfNotNil(layout?.rawValue),
"readingProgression": readingProgression.rawValue,
"description": encodeIfNotNil(description),
"duration": encodeIfNotNil(duration),
Expand Down
17 changes: 17 additions & 0 deletions Sources/Shared/Publication/Properties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,20 @@ public struct Properties: Hashable, Loggable, WarningLogger, Sendable {
otherPropertiesJSON.json.merge(properties, uniquingKeysWith: { _, second in second })
}
}

/// Core properties
///
/// https://github.com/readium/webpub-manifest/blob/master/properties.md#core-properties
public extension Properties {
/// Indicates how the linked resource should be displayed in a reading
/// environment that displays synthetic spreads.
var page: Page? {
parseRaw(otherProperties["page"])
}

/// Indicates how the linked resource should be displayed in a reading
/// environment that displays synthetic spreads.
enum Page: String {
case left, right, center
}
}
1 change: 1 addition & 0 deletions Sources/Shared/Publication/ReadingProgression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum ReadingProgression: String, Sendable {
case auto

/// Returns the leading Page for the reading progression.
@available(*, unavailable)
public var leadingPage: Presentation.Page {
switch self {
case .ltr, .ttb, .auto:
Expand Down
Loading