Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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. You can still use the EPUB extensibility to access the original value.")
var clipped: Bool? {
otherProperties["clipped"] as? Bool
}

/// Suggested method for constraining a resource inside the viewport.
@available(*, unavailable, message: "This was removed from RWPM. You can still use the EPUB extensibility to access the original value.")
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