Skip to content

Optimize two-pages spread computation #527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 6, 2025
Merged
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
104 changes: 52 additions & 52 deletions Sources/Navigator/EPUB/EPUBSpread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import ReadiumShared
/// A list of EPUB resources to be displayed together on the screen, as one-page or two-pages spread.
struct EPUBSpread: Loggable {
/// Indicates whether two pages are displayed side by side.
let spread: Bool
var spread: Bool

/// Links for the resources displayed in the spread, in reading order.
/// Note: it's possible to have less links than the amount of `pageCount` available, because a single page might be displayed in a two-page spread (eg. with Properties.Page center, left or right)
let links: [Link]
var links: [Link]

/// Spread reading progression direction.
let readingProgression: ReadingProgression
var readingProgression: ReadingProgression

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

init(spread: Bool, links: [Link], readingProgression: ReadingProgression, layout: EPUBLayout) {
precondition(!links.isEmpty, "A spread must have at least one page")
Expand Down Expand Up @@ -143,64 +143,40 @@ struct EPUBSpread: Loggable {
/// Builds a list of two-page spreads for the given Publication.
private static func makeTwoPagesSpreads(
for publication: Publication,
readingOrder: [Link],
readingOrder links: [Link],
readingProgression: ReadingProgression
) -> [EPUBSpread] {
/// Builds two-pages spreads from a list of links and a spread accumulator.
func makeSpreads(for links: [Link], index: Int, in spreads: [EPUBSpread] = []) -> [EPUBSpread] {
var links = links
var spreads = spreads
guard !links.isEmpty else {
return spreads
}
var spreads: [EPUBSpread] = []

let first = links.removeFirst()
var index = 0
while index < links.count {
let first = links[index]
let layout = publication.metadata.presentation.layout(of: first)
// To be displayed together, the two pages must have a fixed layout, and have consecutive position hints (Properties.Page).
if let second = links.first,
layout == .fixed,
layout == publication.metadata.presentation.layout(of: second),
areConsecutive(first, second, index: index)
{
spreads.append(EPUBSpread(
spread: true,
links: [first, second],
readingProgression: readingProgression, layout: layout
))
links.removeFirst() // Removes the consumed "second" page
} else {
spreads.append(EPUBSpread(
spread: true,
links: [first],
readingProgression: readingProgression, layout: layout
))
}

return makeSpreads(for: links, index: index + 1, in: spreads)
}
var spread = EPUBSpread(
spread: true,
links: [first],
readingProgression: readingProgression,
layout: layout
)

/// Two resources are consecutive if their position hint (Properties.Page) are paired according to the reading progression.
func areConsecutive(_ first: Link, _ second: Link, index: Int) -> Bool {
guard index > 0 || first.properties.page != nil else {
return false
// To be displayed together, the two pages must have a fixed layout,
// and have consecutive position hints (Properties.Page).
if
let second = links.getOrNil(index + 1),
layout == .fixed,
layout == publication.metadata.presentation.layout(of: second),
publication.areConsecutive(first, second, index: index)
{
spread.links.append(second)
index += 1 // Skips the consumed "second" page
}

// Here we use the default publication reading progression instead
// of the custom one provided, otherwise the page position hints
// might be wrong, and we could end up with only one-page spreads.
switch publication.metadata.readingProgression {
case .ltr, .ttb, .auto:
let firstPosition = first.properties.page ?? .left
let secondPosition = second.properties.page ?? .right
return firstPosition == .left && secondPosition == .right
case .rtl, .btt:
let firstPosition = first.properties.page ?? .right
let secondPosition = second.properties.page ?? .left
return firstPosition == .right && secondPosition == .left
}
spreads.append(spread)
index += 1
}

return makeSpreads(for: readingOrder, index: 0)
return spreads
}
}

Expand All @@ -213,3 +189,27 @@ extension Array where Element == EPUBSpread {
}
}
}

private extension Publication {
/// Two resources are consecutive if their position hint (Properties.Page)
/// are paired according to the reading progression.
func areConsecutive(_ first: Link, _ second: Link, index: Int) -> Bool {
guard index > 0 || first.properties.page != nil else {
return false
}

// Here we use the default publication reading progression instead
// of the custom one provided, otherwise the page position hints
// might be wrong, and we could end up with only one-page spreads.
switch metadata.readingProgression {
case .ltr, .ttb, .auto:
let firstPosition = first.properties.page ?? .left
let secondPosition = second.properties.page ?? .right
return firstPosition == .left && secondPosition == .right
case .rtl, .btt:
let firstPosition = first.properties.page ?? .right
let secondPosition = second.properties.page ?? .left
return firstPosition == .right && secondPosition == .left
}
}
}