diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ec88c45..ed7b81585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift b/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift index ae87bd7a2..add8c7e1b 100644 --- a/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift +++ b/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift @@ -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, diff --git a/Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift b/Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift index 021e09773..6e93fecf8 100644 --- a/Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift +++ b/Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift @@ -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 } diff --git a/Sources/Navigator/EPUB/EPUBSpread.swift b/Sources/Navigator/EPUB/EPUBSpread.swift index a79885ef4..573c96371 100644 --- a/Sources/Navigator/EPUB/EPUBSpread.swift +++ b/Sources/Navigator/EPUB/EPUBSpread.swift @@ -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. @@ -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 } @@ -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 ) } } @@ -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 diff --git a/Sources/Navigator/EPUB/Preferences/EPUBPreferencesEditor.swift b/Sources/Navigator/EPUB/Preferences/EPUBPreferencesEditor.swift index 28180192a..98843dc8f 100644 --- a/Sources/Navigator/EPUB/Preferences/EPUBPreferencesEditor.swift +++ b/Sources/Navigator/EPUB/Preferences/EPUBPreferencesEditor.swift @@ -21,7 +21,12 @@ public final class EPUBPreferencesEditor: StatefulPreferencesEditor 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. diff --git a/Sources/Shared/Publication/Extensions/Presentation/Properties+Presentation.swift b/Sources/Shared/Publication/Extensions/Presentation/Properties+Presentation.swift index ad737249c..2c5d6dd50 100644 --- a/Sources/Shared/Publication/Extensions/Presentation/Properties+Presentation.swift +++ b/Sources/Shared/Publication/Extensions/Presentation/Properties+Presentation.swift @@ -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"]) } diff --git a/Sources/Shared/Publication/Layout.swift b/Sources/Shared/Publication/Layout.swift new file mode 100644 index 000000000..971e945f3 --- /dev/null +++ b/Sources/Shared/Publication/Layout.swift @@ -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 +} diff --git a/Sources/Shared/Publication/Metadata.swift b/Sources/Shared/Publication/Metadata.swift index 3c0aaecff..15f14a76e 100644 --- a/Sources/Shared/Publication/Metadata.swift +++ b/Sources/Shared/Publication/Metadata.swift @@ -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. @@ -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, @@ -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 @@ -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")) @@ -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), diff --git a/Sources/Shared/Publication/Properties.swift b/Sources/Shared/Publication/Properties.swift index 44d5eb898..9a909602b 100644 --- a/Sources/Shared/Publication/Properties.swift +++ b/Sources/Shared/Publication/Properties.swift @@ -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 + } +} diff --git a/Sources/Shared/Publication/ReadingProgression.swift b/Sources/Shared/Publication/ReadingProgression.swift index 2f0aa0587..bf6ea5129 100644 --- a/Sources/Shared/Publication/ReadingProgression.swift +++ b/Sources/Shared/Publication/ReadingProgression.swift @@ -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: diff --git a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift index e132ad2d5..2de261b04 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift @@ -29,11 +29,6 @@ final class EPUBMetadataParser: Loggable { /// Parses the Metadata in the XML element. func parse() throws -> Metadata { - var otherMetadata = metas.otherMetadata - if !presentation.json.isEmpty { - otherMetadata["presentation"] = presentation.json - } - let contributorsWithRoles = findContributorElements() .compactMap { createContributor(from: $0) } @@ -64,13 +59,14 @@ final class EPUBMetadataParser: Loggable { narrators: contributorsForRole(role: "nrt"), contributors: contributorsForRole(role: nil), publishers: contributorsForRole(role: "pbl"), + layout: layout(), readingProgression: readingProgression, description: description, numberOfPages: numberOfPages, belongsToCollections: belongsToCollections, belongsToSeries: belongsToSeries, tdm: tdm(), - otherMetadata: otherMetadata + otherMetadata: metas.otherMetadata ) } @@ -103,13 +99,7 @@ final class EPUBMetadataParser: Loggable { private lazy var numberOfPages: Int? = metas["numberOfPages", in: .schema] .first.flatMap { Int($0.content) } - /// Extracts the Presentation properties from the XML element metadata and fill - /// them into the Metadata object instance. - private lazy var presentation: Presentation = { - func renditionMetadata(_ property: String) -> String { - metas[property, in: .rendition].last?.content ?? "" - } - + private func layout() -> Layout { func displayOption(_ name: String) -> String? { // https://readium.org/architecture/streamer/parser/metadata#epub-2x-10 guard let platform = displayOptions?.firstChild(xpath: "platform[@name='*']") @@ -122,32 +112,11 @@ final class EPUBMetadataParser: Loggable { return platform.firstChild(xpath: "option[@name='\(name)']")?.stringValue } - return Presentation( - continuous: renditionMetadata("flow") == "scrolled-continuous", - orientation: .init( - epub: renditionMetadata("orientation"), - fallback: { - let orientationLock = displayOption("orientation-lock") ?? "" - switch orientationLock { - case "none": - return .auto - case "landscape-only": - return .landscape - case "portrait-only": - return .portrait - default: - return nil - } - }() - ), - overflow: .init(epub: renditionMetadata("flow")), - spread: .init(epub: renditionMetadata("spread")), - layout: .init( - epub: renditionMetadata("layout"), - fallback: (displayOption("fixed-layout") == "true") ? .fixed : nil - ) - ) - }() + let layoutMetadata = metas["layout", in: .rendition].last?.content ?? "" + + return Layout(epub: layoutMetadata) + ?? ((displayOption("fixed-layout") == "true") ? .fixed : .reflowable) + } /// Finds all the ` element matching the given `title-type`. /// The elements are then sorted by the `display-seq` refines, when available. diff --git a/Sources/Streamer/Parser/EPUB/Extensions/Layout+EPUB.swift b/Sources/Streamer/Parser/EPUB/Extensions/Layout+EPUB.swift new file mode 100644 index 000000000..a06ab03a1 --- /dev/null +++ b/Sources/Streamer/Parser/EPUB/Extensions/Layout+EPUB.swift @@ -0,0 +1,22 @@ +// +// 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 +import ReadiumShared + +extension Layout { + /// Creates from an EPUB rendition:layout property. + init?(epub: String) { + switch epub { + case "reflowable": + self = .reflowable + case "pre-paginated": + self = .fixed + default: + return nil + } + } +} diff --git a/Sources/Streamer/Parser/EPUB/Extensions/Presentation+EPUB.swift b/Sources/Streamer/Parser/EPUB/Extensions/Presentation+EPUB.swift deleted file mode 100644 index 8aff98bba..000000000 --- a/Sources/Streamer/Parser/EPUB/Extensions/Presentation+EPUB.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// 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 -import ReadiumShared - -public extension Presentation.Orientation { - /// Creates from an EPUB rendition:orientation property. - init(epub: String, fallback: Presentation.Orientation? = nil) { - switch epub { - case "landscape": - self = .landscape - case "portrait": - self = .portrait - case "auto": - self = .auto - default: - self = fallback ?? .auto - } - } -} - -public extension Presentation.Overflow { - /// Creates from an EPUB rendition:flow property. - init(epub: String, fallback: Presentation.Overflow? = nil) { - switch epub { - case "auto": - self = .auto - case "paginated": - self = .paginated - case "scrolled-doc", "scrolled-continuous": - self = .scrolled - default: - self = fallback ?? .auto - } - } -} - -public extension Presentation.Spread { - /// Creates from an EPUB rendition:spread property. - init(epub: String, fallback: Presentation.Spread? = nil) { - switch epub { - case "none": - self = .none - case "auto": - self = .auto - case "landscape": - self = .landscape - // `portrait` is deprecated and should fallback to `both`. - // See. https://readium.org/architecture/streamer/parser/metadata#epub-3x-11 - case "both", "portrait": - self = .both - default: - self = fallback ?? .auto - } - } -} - -public extension EPUBLayout { - /// Creates from an EPUB rendition:layout property. - init(epub: String, fallback: EPUBLayout? = nil) { - switch epub { - case "reflowable": - self = .reflowable - case "pre-paginated": - self = .fixed - default: - self = fallback ?? .reflowable - } - } -} diff --git a/Sources/Streamer/Parser/EPUB/OPFMeta.swift b/Sources/Streamer/Parser/EPUB/OPFMeta.swift index 4d73ea503..2418eeda1 100644 --- a/Sources/Streamer/Parser/EPUB/OPFMeta.swift +++ b/Sources/Streamer/Parser/EPUB/OPFMeta.swift @@ -310,7 +310,7 @@ struct OPFMetaList { "conformsTo", ], .media: ["duration"], - .rendition: ["flow", "layout", "orientation", "spread"], + .rendition: ["layout"], .schema: [ "numberOfPages", "accessMode", "accessModeSufficient", "accessibilitySummary", "accessibilityFeature", "accessibilityHazard", diff --git a/Sources/Streamer/Parser/EPUB/OPFParser.swift b/Sources/Streamer/Parser/EPUB/OPFParser.swift index 00c2d2f8f..7cedd3163 100644 --- a/Sources/Streamer/Parser/EPUB/OPFParser.swift +++ b/Sources/Streamer/Parser/EPUB/OPFParser.swift @@ -233,11 +233,7 @@ final class OPFParser: Loggable { /// Parse string properties into an `otherProperties` dictionary. private func parseStringProperties(_ properties: [String]) -> [String: Any] { var contains: [String] = [] - var layout: EPUBLayout? - var orientation: Presentation.Orientation? - var overflow: Presentation.Overflow? - var page: Presentation.Page? - var spread: Presentation.Spread? + var page: Properties.Page? for property in properties { switch property { @@ -261,37 +257,6 @@ final class OPFParser: Loggable { page = .right case "page-spread-center", "rendition:page-spread-center": page = .center - // Spread - case "rendition:spread-none", "rendition:spread-auto": - // If we don't qualify `.none` here it sets it to `nil`. - spread = Presentation.Spread.none - case "rendition:spread-landscape": - spread = .landscape - case "rendition:spread-portrait": - // `portrait` is deprecated and should fallback to `both`. - // See. https://readium.org/architecture/streamer/parser/metadata#epub-3x-11 - spread = .both - case "rendition:spread-both": - spread = .both - // Layout - case "rendition:layout-reflowable": - layout = .reflowable - case "rendition:layout-pre-paginated": - layout = .fixed - // Orientation - case "rendition:orientation-auto": - orientation = .auto - case "rendition:orientation-landscape": - orientation = .landscape - case "rendition:orientation-portrait": - orientation = .portrait - // Rendition - case "rendition:flow-auto": - overflow = .auto - case "rendition:flow-paginated": - overflow = .paginated - case "rendition:flow-scrolled-continuous", "rendition:flow-scrolled-doc": - overflow = .scrolled default: continue } @@ -301,21 +266,9 @@ final class OPFParser: Loggable { if !contains.isEmpty { otherProperties["contains"] = contains } - if let layout = layout { - otherProperties["layout"] = layout.rawValue - } - if let orientation = orientation { - otherProperties["orientation"] = orientation.rawValue - } - if let overflow = overflow { - otherProperties["overflow"] = overflow.rawValue - } if let page = page { otherProperties["page"] = page.rawValue } - if let spread = spread { - otherProperties["spread"] = spread.rawValue - } return otherProperties } diff --git a/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift b/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift index a1b1c4e30..da3759a2b 100644 --- a/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift +++ b/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift @@ -20,7 +20,7 @@ public actor EPUBPositionsService: PositionsService { { context in EPUBPositionsService( readingOrder: context.manifest.readingOrder, - presentation: context.manifest.metadata.presentation, + layout: context.manifest.metadata.layout, container: context.container, reflowableStrategy: reflowableStrategy ) @@ -59,18 +59,18 @@ public actor EPUBPositionsService: PositionsService { } private let readingOrder: [Link] - private let presentation: Presentation + private let layout: Layout? private let container: Container private let reflowableStrategy: ReflowableStrategy init( readingOrder: [Link], - presentation: Presentation, + layout: Layout?, container: Container, reflowableStrategy: ReflowableStrategy ) { self.readingOrder = readingOrder - self.presentation = presentation + self.layout = layout self.container = container self.reflowableStrategy = reflowableStrategy } @@ -88,9 +88,10 @@ public actor EPUBPositionsService: PositionsService { var lastPositionOfPreviousResource = 0 var positions = await readingOrder.asyncMap { link -> [Locator] in let (lastPosition, positions): (Int, [Locator]) = await { - if presentation.layout(of: link) == .fixed { + switch layout { + case .fixed: return makePositions(ofFixedResource: link, from: lastPositionOfPreviousResource) - } else { + case nil, .reflowable, .scrolled: return await makePositions(ofReflowableResource: link, from: lastPositionOfPreviousResource) } }() diff --git a/Support/Carthage/.xcodegen b/Support/Carthage/.xcodegen index 475bca9de..8bc695f3b 100644 --- a/Support/Carthage/.xcodegen +++ b/Support/Carthage/.xcodegen @@ -408,9 +408,11 @@ ../../Sources/Navigator/EditingAction.swift ../../Sources/Navigator/EPUB ../../Sources/Navigator/EPUB/Assets +../../Sources/Navigator/EPUB/Assets/.DS_Store ../../Sources/Navigator/EPUB/Assets/fxl-spread-one.html ../../Sources/Navigator/EPUB/Assets/fxl-spread-two.html ../../Sources/Navigator/EPUB/Assets/Static +../../Sources/Navigator/EPUB/Assets/Static/.DS_Store ../../Sources/Navigator/EPUB/Assets/Static/fonts ../../Sources/Navigator/EPUB/Assets/Static/fonts/OpenDyslexic-Bold.otf ../../Sources/Navigator/EPUB/Assets/Static/fonts/OpenDyslexic-BoldItalic.otf @@ -628,6 +630,7 @@ ../../Sources/Shared/Publication/Extensions/Presentation/Presentation.swift ../../Sources/Shared/Publication/Extensions/Presentation/Properties+Presentation.swift ../../Sources/Shared/Publication/HREFNormalizer.swift +../../Sources/Shared/Publication/Layout.swift ../../Sources/Shared/Publication/Link.swift ../../Sources/Shared/Publication/LinkRelation.swift ../../Sources/Shared/Publication/LocalizedString.swift @@ -823,8 +826,8 @@ ../../Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift ../../Sources/Streamer/Parser/EPUB/EPUBParser.swift ../../Sources/Streamer/Parser/EPUB/Extensions +../../Sources/Streamer/Parser/EPUB/Extensions/Layout+EPUB.swift ../../Sources/Streamer/Parser/EPUB/Extensions/LinkRelation+EPUB.swift -../../Sources/Streamer/Parser/EPUB/Extensions/Presentation+EPUB.swift ../../Sources/Streamer/Parser/EPUB/NavigationDocumentParser.swift ../../Sources/Streamer/Parser/EPUB/NCXParser.swift ../../Sources/Streamer/Parser/EPUB/OPFMeta.swift diff --git a/Support/Carthage/Readium.xcodeproj/project.pbxproj b/Support/Carthage/Readium.xcodeproj/project.pbxproj index 483ae8a38..89acb4254 100644 --- a/Support/Carthage/Readium.xcodeproj/project.pbxproj +++ b/Support/Carthage/Readium.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 1004CE1C72C85CC3702C09C0 /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC811653B33761089E270C4A /* Asset.swift */; }; 10C07601930485DFA27CD231 /* PDFParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13272E03B63E96D4246F79D /* PDFParser.swift */; }; 10FE85C02C2782FCD957346F /* PublicationMediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C489452239BF85F4D14E95 /* PublicationMediaLoader.swift */; }; + 1107597E99B703B53FDE89AB /* Layout+EPUB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 211A98219D7D1583E829DEC2 /* Layout+EPUB.swift */; }; 1112E4B2F49A39383D4D48F9 /* UInt64.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF63FC52733A417765146AE2 /* UInt64.swift */; }; 1136DC853876D905FC33597F /* GCDHTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1597E4216CF16380AC2811 /* GCDHTTPServer.swift */; }; 1221E200A377D294050B8F00 /* LicenseValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDEFB3D1218817F835A3C5F4 /* LicenseValidation.swift */; }; @@ -115,7 +116,6 @@ 422FF6643B32F9BCE8043757 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 44152DBECE34F063AD0E93BC /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3E6442F0C7FE2098D71F27 /* Link.swift */; }; 448374F2605586249A6CB4C8 /* FailureResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78FFDF8CF77437EDB41E4547 /* FailureResource.swift */; }; - 46D29739FB017C62767CBE63 /* Presentation+EPUB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EFF9139B59D763CE254F92 /* Presentation+EPUB.swift */; }; 47125BFFEC67DEB2C3D1B48C /* AudioNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE34D74E282834684E1C999 /* AudioNavigator.swift */; }; 4977279900B4BA602D92B5C4 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 4A5F53CCC083D3E348379963 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF64D7C05A790D9CA5DD442 /* Types.swift */; }; @@ -394,6 +394,7 @@ FBC62A9EC21695FF47572E04 /* URITemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE1142A6C038A35C527CE84 /* URITemplate.swift */; }; FCFFE5305127D9FC72549EAA /* LCPDFPositionsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B9196192A22B8AB80E6B2F /* LCPDFPositionsService.swift */; }; FED01A1734FAAD72683CC79E /* CompletionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C8719E9CC8EF0D2430AD85 /* CompletionList.swift */; }; + FFA39E29D60BE8D527192CF0 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325DC872B215C040B8123980 /* Layout.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -532,6 +533,7 @@ 1EBC685D4A0E07997088DD2D /* DataCompression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCompression.swift; sourceTree = ""; }; 1F89BC365BDD19BE84F4D3B5 /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; 1FBFAC2D57DE7EBB4E2F31BE /* ReadiumNavigatorLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadiumNavigatorLocalizedString.swift; sourceTree = ""; }; + 211A98219D7D1583E829DEC2 /* Layout+EPUB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Layout+EPUB.swift"; sourceTree = ""; }; 212E89D9F2CC639C3E1F81C3 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; 218BE3110D2886B252A769A2 /* UTI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTI.swift; sourceTree = ""; }; 21944E1DABB61C2CF2EA89C5 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; @@ -555,6 +557,7 @@ 31C1E0FDB5373E672D5FF80F /* ZIPFoundationArchiveFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZIPFoundationArchiveFactory.swift; sourceTree = ""; }; 3230FB63D7ADDD514D74F7E6 /* LCPLicenseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCPLicenseRepository.swift; sourceTree = ""; }; 3231F989F7D7E560DD5364B9 /* Range.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Range.swift; sourceTree = ""; }; + 325DC872B215C040B8123980 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = ""; }; 339637CCF01E665F4CB78B01 /* EPUBLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBLayout.swift; sourceTree = ""; }; 342D5C0FEE79A2ABEE24A43E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 34AB954525AC159166C96A36 /* HTMLResourceContentIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLResourceContentIterator.swift; sourceTree = ""; }; @@ -583,7 +586,6 @@ 41EB258B894B86A0DA1D00D4 /* ArchiveOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveOpener.swift; sourceTree = ""; }; 421AE163B6A0248637504A07 /* InputObserving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputObserving.swift; sourceTree = ""; }; 42BAA4F11B7355F1962FEAD9 /* HTTPResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResource.swift; sourceTree = ""; }; - 42EFF9139B59D763CE254F92 /* Presentation+EPUB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Presentation+EPUB.swift"; sourceTree = ""; }; 42FD63C2720614E558522675 /* ReadiumInternal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReadiumInternal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4363E8A92B1EA9AF2561DCE9 /* NCXParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCXParser.swift; sourceTree = ""; }; 44D0B1BEF4825550464B9F62 /* PDFNavigatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFNavigatorViewController.swift; sourceTree = ""; }; @@ -953,8 +955,8 @@ 00753735EE68A5BCEF35D787 /* Extensions */ = { isa = PBXGroup; children = ( + 211A98219D7D1583E829DEC2 /* Layout+EPUB.swift */, FB91391C899304A1DC051C6E /* LinkRelation+EPUB.swift */, - 42EFF9139B59D763CE254F92 /* Presentation+EPUB.swift */, ); path = Extensions; sourceTree = ""; @@ -2005,6 +2007,7 @@ children = ( 456192DBCB3A29ADA9C3CCB9 /* Contributor.swift */, AC8639886BD43362741AADD0 /* HREFNormalizer.swift */, + 325DC872B215C040B8123980 /* Layout.swift */, DF92954C8C8C3EC50C835CBA /* Link.swift */, AB0EF21FADD12D51D0619C0D /* LinkRelation.swift */, 600D8714B762FE37DE405C2E /* LocalizedString.swift */, @@ -2467,6 +2470,7 @@ EF15E9163EBC82672B22F6E0 /* ImageParser.swift in Sources */, FCFFE5305127D9FC72549EAA /* LCPDFPositionsService.swift in Sources */, 0BFCDAEC82CFF09AFC53A5D0 /* LCPDFTableOfContentsService.swift in Sources */, + 1107597E99B703B53FDE89AB /* Layout+EPUB.swift in Sources */, BFC777DBBFBDFDD54876BB59 /* LinkRelation+EPUB.swift in Sources */, C1A94B2A9C446CB03650DC47 /* NCXParser.swift in Sources */, 01AD628D6DE82E1C1C4C281D /* NavigationDocumentParser.swift in Sources */, @@ -2474,7 +2478,6 @@ 2C5436091DD72FDBF6FF136D /* OPFParser.swift in Sources */, 10C07601930485DFA27CD231 /* PDFParser.swift in Sources */, E5D440B49453AC615946E7FB /* PDFPositionsService.swift in Sources */, - 46D29739FB017C62767CBE63 /* Presentation+EPUB.swift in Sources */, 2F8F8B6A05F8E124BA9D6B22 /* PublicationOpener.swift in Sources */, 67F1C7C3D434D2AA542376E3 /* PublicationParser.swift in Sources */, C9FBD23E459FB395377E149E /* ReadiumWebPubParser.swift in Sources */, @@ -2667,6 +2670,7 @@ 4D4915BB3847EF285362CF50 /* LCPLicenseFormatSniffer.swift in Sources */, 56CB87DACCA10F737710BFF6 /* Language.swift in Sources */, AD572C6A7AD031FEC40A0BD7 /* LanguageFormatSniffer.swift in Sources */, + FFA39E29D60BE8D527192CF0 /* Layout.swift in Sources */, DD8E2E0D394399A51F295380 /* Link.swift in Sources */, C8A94F023B6C0F96875D5D62 /* LinkRelation.swift in Sources */, 8CCDF77696A0F2C7BF3171CC /* LocalizedString.swift in Sources */, diff --git a/Tests/SharedTests/Publication/Accessibility/AccessibilityMetadataDisplayGuideTests.swift b/Tests/SharedTests/Publication/Accessibility/AccessibilityMetadataDisplayGuideTests.swift index bda91f223..b3ffb7783 100644 --- a/Tests/SharedTests/Publication/Accessibility/AccessibilityMetadataDisplayGuideTests.swift +++ b/Tests/SharedTests/Publication/Accessibility/AccessibilityMetadataDisplayGuideTests.swift @@ -48,7 +48,7 @@ class AccessibilityMetadataDisplayGuideTests: XCTestCase { } func testWaysOfReadingInitVisualAdjustments() { - func test(layout: EPUBLayout, a11y: Accessibility?, expected: WaysOfReading.VisualAdjustments) { + func test(layout: Layout, a11y: Accessibility?, expected: WaysOfReading.VisualAdjustments) { let sut = WaysOfReading(publication: publication( layout: layout, accessibility: a11y @@ -1337,18 +1337,14 @@ class AccessibilityMetadataDisplayGuideTests: XCTestCase { } private func publication( - layout: EPUBLayout? = nil, + layout: Layout? = nil, accessibility: Accessibility? ) -> Publication { Publication( manifest: Manifest( metadata: Metadata( accessibility: accessibility, - otherMetadata: [ - "presentation": Presentation( - layout: layout - ).json, - ] + layout: layout ) ) ) diff --git a/Tests/SharedTests/Publication/Extensions/EPUB/Properties+EPUBTests.swift b/Tests/SharedTests/Publication/Extensions/EPUB/Properties+EPUBTests.swift index 81dac1d48..e43b3db63 100644 --- a/Tests/SharedTests/Publication/Extensions/EPUB/Properties+EPUBTests.swift +++ b/Tests/SharedTests/Publication/Extensions/EPUB/Properties+EPUBTests.swift @@ -17,14 +17,4 @@ class PropertiesEPUBTests: XCTestCase { let sut = Properties(["contains": ["mathml", "onix"]]) XCTAssertEqual(sut.contains, ["mathml", "onix"]) } - - func testNoLayout() { - let sut = Properties() - XCTAssertNil(sut.layout) - } - - func testLayout() { - let sut = Properties(["layout": "fixed"]) - XCTAssertEqual(sut.layout, .fixed) - } } diff --git a/Tests/SharedTests/Publication/Extensions/Presentation/Metadata+PresentationTests.swift b/Tests/SharedTests/Publication/Extensions/Presentation/Metadata+PresentationTests.swift deleted file mode 100644 index 719fe393a..000000000 --- a/Tests/SharedTests/Publication/Extensions/Presentation/Metadata+PresentationTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// 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. -// - -@testable import ReadiumShared -import XCTest - -class MetadataPresentationTests: XCTestCase { - func testGetPresentationWhenAvailable() { - XCTAssertEqual( - Metadata( - title: "Title", - otherMetadata: [ - "presentation": [ - "continuous": false, - "orientation": "landscape", - ] as [String: Any], - ] - ).presentation, - Presentation(continuous: false, orientation: .landscape) - ) - } - - func testGetPresentationWhenMissing() { - XCTAssertEqual( - Metadata(title: "Title").presentation, - Presentation() - ) - } -} diff --git a/Tests/SharedTests/Publication/Extensions/Presentation/PresentationTests.swift b/Tests/SharedTests/Publication/Extensions/Presentation/PresentationTests.swift deleted file mode 100644 index 80c1bf64f..000000000 --- a/Tests/SharedTests/Publication/Extensions/Presentation/PresentationTests.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// 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. -// - -@testable import ReadiumShared -import XCTest - -class PresentationTests: XCTestCase { - func testParseMinimalJSON() { - XCTAssertEqual( - try? Presentation(json: [:] as [String: Any]), - Presentation() - ) - } - - func testParseFullJSON() { - XCTAssertEqual( - try? Presentation(json: [ - "clipped": true, - "continuous": false, - "fit": "cover", - "orientation": "landscape", - "overflow": "paginated", - "spread": "both", - "layout": "fixed", - ] as [String: Any]), - Presentation( - clipped: true, - continuous: false, - fit: .cover, - orientation: .landscape, - overflow: .paginated, - spread: .both, - layout: .fixed - ) - ) - } - - func testParseInvalidJSON() { - XCTAssertThrowsError(try Presentation(json: "")) - } - - func testParseAllowsNil() { - XCTAssertEqual(try Presentation(json: nil), Presentation()) - } - - func testGetMinimalJSON() { - AssertJSONEqual(Presentation().json, [:] as [String: Any]) - } - - func testGetFullJSON() { - AssertJSONEqual( - Presentation( - clipped: true, - continuous: false, - fit: .cover, - orientation: .landscape, - overflow: .paginated, - spread: .both, - layout: .fixed - ).json as Any, - [ - "clipped": true, - "continuous": false, - "fit": "cover", - "orientation": "landscape", - "overflow": "paginated", - "spread": "both", - "layout": "fixed", - ] as [String: Any] - ) - } - - func testParseFitFromJSONValue() { - XCTAssertEqual(Presentation.Fit(rawValue: "contain"), .contain) - XCTAssertEqual(Presentation.Fit(rawValue: "cover"), .cover) - XCTAssertEqual(Presentation.Fit(rawValue: "width"), .width) - XCTAssertEqual(Presentation.Fit(rawValue: "height"), .height) - XCTAssertNil(Presentation.Fit(rawValue: "foobar")) - } - - func testGetFitJSONValue() { - XCTAssertEqual("contain", Presentation.Fit.contain.rawValue) - XCTAssertEqual("cover", Presentation.Fit.cover.rawValue) - XCTAssertEqual("width", Presentation.Fit.width.rawValue) - XCTAssertEqual("height", Presentation.Fit.height.rawValue) - } - - func testParseOrientationFromJSONValue() { - XCTAssertEqual(Presentation.Orientation(rawValue: "landscape"), .landscape) - XCTAssertEqual(Presentation.Orientation(rawValue: "portrait"), .portrait) - XCTAssertEqual(Presentation.Orientation(rawValue: "auto"), .auto) - XCTAssertNil(Presentation.Orientation(rawValue: "foobar")) - } - - func testGetOrientationJSONValue() { - XCTAssertEqual("landscape", Presentation.Orientation.landscape.rawValue) - XCTAssertEqual("portrait", Presentation.Orientation.portrait.rawValue) - XCTAssertEqual("auto", Presentation.Orientation.auto.rawValue) - } - - func testParseOverflowFromJSONValue() { - XCTAssertEqual(Presentation.Overflow(rawValue: "paginated"), .paginated) - XCTAssertEqual(Presentation.Overflow(rawValue: "scrolled"), .scrolled) - XCTAssertEqual(Presentation.Overflow(rawValue: "auto"), .auto) - XCTAssertNil(Presentation.Overflow(rawValue: "foobar")) - } - - func testGetOverflowJSONValue() { - XCTAssertEqual("paginated", Presentation.Overflow.paginated.rawValue) - XCTAssertEqual("scrolled", Presentation.Overflow.scrolled.rawValue) - XCTAssertEqual("auto", Presentation.Overflow.auto.rawValue) - } - - func testParsePageFromJSONValue() { - XCTAssertEqual(Presentation.Page(rawValue: "left"), .left) - XCTAssertEqual(Presentation.Page(rawValue: "right"), .right) - XCTAssertEqual(Presentation.Page(rawValue: "center"), .center) - XCTAssertNil(Presentation.Page(rawValue: "foobar")) - } - - func testGetPageJSONValue() { - XCTAssertEqual("left", Presentation.Page.left.rawValue) - XCTAssertEqual("right", Presentation.Page.right.rawValue) - XCTAssertEqual("center", Presentation.Page.center.rawValue) - } - - func testParseSpreadFromJSONValue() { - XCTAssertEqual(Presentation.Spread(rawValue: "landscape"), .landscape) - XCTAssertEqual(Presentation.Spread(rawValue: "both"), .both) - XCTAssertEqual(Presentation.Spread(rawValue: "none"), Presentation.Spread.none) // For some reason this fails if we don't fully qualify .none - XCTAssertEqual(Presentation.Spread(rawValue: "auto"), .auto) - XCTAssertNil(Presentation.Spread(rawValue: "foobar")) - } - - func testGetSpreadJSONValue() { - XCTAssertEqual("landscape", Presentation.Spread.landscape.rawValue) - XCTAssertEqual("both", Presentation.Spread.both.rawValue) - XCTAssertEqual("none", Presentation.Spread.none.rawValue) - XCTAssertEqual("auto", Presentation.Spread.auto.rawValue) - } -} diff --git a/Tests/SharedTests/Publication/Extensions/Presentation/Properties+PresentationTests.swift b/Tests/SharedTests/Publication/Extensions/Presentation/Properties+PresentationTests.swift deleted file mode 100644 index cb6d7b889..000000000 --- a/Tests/SharedTests/Publication/Extensions/Presentation/Properties+PresentationTests.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// 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. -// - -@testable import ReadiumShared -import XCTest - -class PropertiesPresentationTests: XCTestCase { - func testGetClippedWhenAvailable() { - XCTAssertEqual(Properties(["clipped": true]).clipped, true) - } - - func testGetClippedWhenMissing() { - XCTAssertNil(Properties().clipped) - } - - func testGetFitWhenAvailable() { - XCTAssertEqual(Properties(["fit": "cover"]).fit, .cover) - } - - func testGetFitWhenMissing() { - XCTAssertNil(Properties().fit) - } - - func testGetOrientationWhenAvailable() { - XCTAssertEqual(Properties(["orientation": "landscape"]).orientation, .landscape) - } - - func testGetOrientationWhenMissing() { - XCTAssertNil(Properties().orientation) - } - - func testGetOverflowWhenAvailable() { - XCTAssertEqual(Properties(["overflow": "scrolled"]).overflow, .scrolled) - } - - func testGetOverflowWhenMissing() { - XCTAssertNil(Properties().overflow) - } - - func testGetPageWhenAvailable() { - XCTAssertEqual(Properties(["page": "right"]).page, .right) - } - - func testGetPageWhenMissing() { - XCTAssertNil(Properties().page) - } - - func testGetSpreadWhenAvailable() { - XCTAssertEqual(Properties(["spread": "both"]).spread, .both) - } - - func testGetSpreadWhenMissing() { - XCTAssertNil(Properties().spread) - } -} diff --git a/Tests/SharedTests/Publication/PropertiesTests.swift b/Tests/SharedTests/Publication/PropertiesTests.swift index da8f1463c..c83d10324 100644 --- a/Tests/SharedTests/Publication/PropertiesTests.swift +++ b/Tests/SharedTests/Publication/PropertiesTests.swift @@ -72,4 +72,12 @@ class PropertiesTests: XCTestCase { ] as [String: Any] ) } + + func testGetPageWhenMissing() { + XCTAssertNil(Properties().page) + } + + func testGetPageWhenAvailable() { + XCTAssertEqual(Properties(["page": "center"]).page, .center) + } } diff --git a/Tests/StreamerTests/Parser/EPUB/EPUBManifestParserTests.swift b/Tests/StreamerTests/Parser/EPUB/EPUBManifestParserTests.swift index cd4e1ad7a..3ad62840e 100644 --- a/Tests/StreamerTests/Parser/EPUB/EPUBManifestParserTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/EPUBManifestParserTests.swift @@ -41,6 +41,7 @@ class EPUBManifestParserTests: XCTestCase { ], authors: [Contributor(name: "Lewis Carroll")], publishers: [Contributor(name: "D. Appleton and Co")], + layout: .fixed, readingProgression: .rtl, description: "The book description.", numberOfPages: 42, @@ -61,13 +62,9 @@ class EPUBManifestParserTests: XCTestCase { "http://my.url/#refine2": "Refine 2", ], "http://purl.org/dc/terms/format": "application/epub+zip", - "presentation": [ - "continuous": false, - "spread": "both", - "overflow": "scrolled", - "orientation": "landscape", - "layout": "fixed", - ] as [String: Any], + "http://www.idpf.org/vocab/rendition/#flow": "scrolled-doc", + "http://www.idpf.org/vocab/rendition/#orientation": "landscape", + "http://www.idpf.org/vocab/rendition/#spread": "both", ] ), readingOrder: [ diff --git a/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift b/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift index 3c97e7f46..14920fa89 100644 --- a/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift @@ -34,6 +34,7 @@ class EPUBMetadataParserTests: XCTestCase { ], authors: [Contributor(name: "Lewis Carroll")], publishers: [Contributor(name: "D. Appleton and Co")], + layout: .fixed, readingProgression: .rtl, description: "The book description.", numberOfPages: 42, @@ -54,13 +55,9 @@ class EPUBMetadataParserTests: XCTestCase { "http://my.url/#refine2": "Refine 2", ], "http://purl.org/dc/terms/format": "application/epub+zip", - "presentation": [ - "continuous": false, - "spread": "both", - "overflow": "scrolled", - "orientation": "landscape", - "layout": "fixed", - ] as [String: Any], + "http://www.idpf.org/vocab/rendition/#flow": "scrolled-doc", + "http://www.idpf.org/vocab/rendition/#orientation": "landscape", + "http://www.idpf.org/vocab/rendition/#spread": "both", ] )) } @@ -71,15 +68,7 @@ class EPUBMetadataParserTests: XCTestCase { XCTAssertEqual(sut, Metadata( conformsTo: [.epub], title: "Alice's Adventures in Wonderland", - otherMetadata: [ - "presentation": [ - "continuous": false, - "spread": "auto", - "overflow": "auto", - "orientation": "auto", - "layout": "reflowable", - ] as [String: Any], - ] + layout: .reflowable )) } @@ -89,15 +78,7 @@ class EPUBMetadataParserTests: XCTestCase { XCTAssertEqual(sut, Metadata( conformsTo: [.epub], title: "Alice's Adventures in Wonderland", - otherMetadata: [ - "presentation": [ - "continuous": false, - "spread": "auto", - "overflow": "auto", - "orientation": "auto", - "layout": "reflowable", - ] as [String: Any], - ] + layout: .reflowable )) } @@ -201,15 +182,7 @@ class EPUBMetadataParserTests: XCTestCase { Contributor(name: "Publisher 2"), ], imprints: [], - otherMetadata: [ - "presentation": [ - "continuous": false, - "spread": "auto", - "overflow": "auto", - "orientation": "auto", - "layout": "reflowable", - ] as [String: Any], - ] + layout: .reflowable )) } @@ -295,16 +268,7 @@ class EPUBMetadataParserTests: XCTestCase { func testParseRenditionFallbackWithDisplayOptions() throws { let sut = try parseMetadata("minimal", displayOptions: "displayOptions") - AssertJSONEqual( - sut.otherMetadata["presentation"], - [ - "continuous": false, - "spread": "auto", - "overflow": "auto", - "orientation": "landscape", - "layout": "fixed", - ] as [String: Any] - ) + XCTAssertEqual(sut.layout, .fixed) } func testParseEPUB2Accessibility() throws { @@ -327,7 +291,7 @@ class EPUBMetadataParserTests: XCTestCase { ) ) // Checks that the a11y metadata are not added to otherMetadata. - XCTAssertEqual(Array(sut.otherMetadata.keys), ["presentation"]) + XCTAssertTrue(sut.otherMetadata.isEmpty) } func testParseEPUB3Accessibility() throws { @@ -350,7 +314,7 @@ class EPUBMetadataParserTests: XCTestCase { ) ) // Checks that the a11y metadata are not added to otherMetadata. - XCTAssertEqual(Array(sut.otherMetadata.keys), ["presentation"]) + XCTAssertTrue(sut.otherMetadata.isEmpty) } func testParseEPUB2TDM() throws { diff --git a/Tests/StreamerTests/Parser/EPUB/OPFParserTests.swift b/Tests/StreamerTests/Parser/EPUB/OPFParserTests.swift index f0b39a209..42193b482 100644 --- a/Tests/StreamerTests/Parser/EPUB/OPFParserTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/OPFParserTests.swift @@ -18,15 +18,7 @@ class OPFParserTests: XCTestCase { metadata: Metadata( conformsTo: [.epub], title: "Alice's Adventures in Wonderland", - otherMetadata: [ - "presentation": [ - "continuous": false, - "spread": "auto", - "overflow": "auto", - "orientation": "auto", - "layout": "reflowable", - ] as [String: Any], - ] + layout: .reflowable ), readingOrder: [ link(id: "titlepage", href: "EPUB/titlepage.xhtml"), @@ -84,41 +76,23 @@ class OPFParserTests: XCTestCase { XCTAssertEqual(sut.readingOrder.count, 8) XCTAssertEqual(sut.readingOrder[0], link(id: "chapter01", href: "EPUB/chapter01.xhtml", rels: [.contents], properties: Properties([ "contains": ["mathml"], - "orientation": "auto", - "overflow": "auto", "page": "right", - "layout": "fixed", ]))) XCTAssertEqual(sut.readingOrder[1], link(id: "chapter02", href: "EPUB/chapter02.xhtml", properties: Properties([ "contains": ["remote-resources"], - "orientation": "landscape", - "overflow": "paginated", "page": "left", - "layout": "reflowable", ]))) XCTAssertEqual(sut.readingOrder[2], link(id: "chapter03", href: "EPUB/chapter03.xhtml", properties: Properties([ "contains": ["js", "svg"], - "orientation": "portrait", - "overflow": "scrolled", "page": "center", ]))) XCTAssertEqual(sut.readingOrder[3], link(id: "chapter04", href: "EPUB/chapter04.xhtml", properties: Properties([ "contains": ["onix", "xmp"], - "overflow": "scrolled", - "spread": "none", - ]))) - XCTAssertEqual(sut.readingOrder[4], link(id: "chapter05", href: "EPUB/chapter05.xhtml", properties: Properties([ - "spread": "both", - ]))) - XCTAssertEqual(sut.readingOrder[5], link(id: "chapter06", href: "EPUB/chapter06.xhtml", properties: Properties([ - "spread": "landscape", - ]))) - XCTAssertEqual(sut.readingOrder[6], link(id: "chapter07", href: "EPUB/chapter07.xhtml", properties: Properties([ - "spread": "none", - ]))) - XCTAssertEqual(sut.readingOrder[7], link(id: "chapter08", href: "EPUB/chapter08.xhtml", properties: Properties([ - "spread": "both", ]))) + XCTAssertEqual(sut.readingOrder[4], link(id: "chapter05", href: "EPUB/chapter05.xhtml", properties: Properties())) + XCTAssertEqual(sut.readingOrder[5], link(id: "chapter06", href: "EPUB/chapter06.xhtml", properties: Properties())) + XCTAssertEqual(sut.readingOrder[6], link(id: "chapter07", href: "EPUB/chapter07.xhtml", properties: Properties())) + XCTAssertEqual(sut.readingOrder[7], link(id: "chapter08", href: "EPUB/chapter08.xhtml", properties: Properties())) } func testParseEPUB2Cover() throws { diff --git a/Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift b/Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift index 9e8b88d8b..9f81852dd 100644 --- a/Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift @@ -278,64 +278,6 @@ class EPUBPositionsServiceTests: XCTestCase { ]])) } - func testFromMixedLayouts() async { - let service = makeService( - layout: .fixed, - readingOrder: [ - (20000, Link(href: "chap1"), nil), - (60, Link(href: "chap2", properties: makeProperties(layout: .reflowable)), nil), - (20000, Link(href: "chap3", properties: makeProperties(layout: .fixed)), nil), - ], - reflowableStrategy: .archiveEntryLength(pageLength: 50) - ) - - let result = await service.positionsByReadingOrder() - XCTAssertEqual(result, .success([ - [ - Locator( - href: "chap1", - mediaType: .html, - locations: Locator.Locations( - progression: 0.0, - totalProgression: 0.0, - position: 1 - ) - ), - ], - [ - Locator( - href: "chap2", - mediaType: .html, - locations: Locator.Locations( - progression: 0.0, - totalProgression: 1.0 / 4.0, - position: 2 - ) - ), - Locator( - href: "chap2", - mediaType: .html, - locations: Locator.Locations( - progression: 0.5, - totalProgression: 2.0 / 4.0, - position: 3 - ) - ), - ], - [ - Locator( - href: "chap3", - mediaType: .html, - locations: Locator.Locations( - progression: 0.0, - totalProgression: 3.0 / 4.0, - position: 4 - ) - ), - ], - ])) - } - func testArchiveEntryLengthStrategy() async { let service = makeService( layout: .reflowable, @@ -383,10 +325,10 @@ class EPUBPositionsServiceTests: XCTestCase { } } -func makeService(layout: EPUBLayout? = nil, readingOrder: [(UInt64, Link, ArchiveProperties?)], reflowableStrategy: EPUBPositionsService.ReflowableStrategy = .archiveEntryLength(pageLength: 50)) -> EPUBPositionsService { +func makeService(layout: Layout? = nil, readingOrder: [(UInt64, Link, ArchiveProperties?)], reflowableStrategy: EPUBPositionsService.ReflowableStrategy = .archiveEntryLength(pageLength: 50)) -> EPUBPositionsService { EPUBPositionsService( readingOrder: readingOrder.map { _, l, _ in l }, - presentation: Presentation(layout: layout), + layout: layout, container: MockContainer(readingOrder: readingOrder), reflowableStrategy: reflowableStrategy )