Skip to content

Commit e5f5ad1

Browse files
authored
Fix EPUB vertical text scrolling (#656)
1 parent c1f62fe commit e5f5ad1

File tree

8 files changed

+134
-62
lines changed

8 files changed

+134
-62
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ All notable changes to this project will be documented in this file. Take a look
44

55
**Warning:** Features marked as *experimental* may change or be removed in a future release without notice. Use with caution.
66

7-
<!-- ## [Unreleased] -->
7+
## [Unreleased]
8+
9+
### Fixed
10+
11+
#### Navigator
12+
13+
* Fixed vertical text scrolling in EPUB for right-to-left reading progression (contributed by [@shovel-kun](https://github.com/readium/kotlin-toolkit/pull/656)).
14+
* Fixed notifying the current location when using vertical text scrolling in EPUB (contributed by [@shovel-kun](https://github.com/readium/kotlin-toolkit/pull/656)).
15+
816

917
## [3.1.1]
1018

readium/navigator/src/main/assets/_scripts/src/utils.js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ export function isRTL() {
9797
return document.body.dir.toLowerCase() == "rtl";
9898
}
9999

100+
export function isVerticalWritingMode() {
101+
const writingMode = window
102+
.getComputedStyle(document.documentElement)
103+
.getPropertyValue("writing-mode");
104+
return writingMode.startsWith("vertical");
105+
}
106+
100107
// Scroll to the given TagId in document and snap.
101108
export function scrollToId(id) {
102109
var element = document.getElementById(id);
@@ -109,15 +116,19 @@ export function scrollToId(id) {
109116

110117
// Position must be in the range [0 - 1], 0-100%.
111118
export function scrollToPosition(position) {
112-
// Android.log("scrollToPosition " + position);
113119
if (position < 0 || position > 1) {
114-
throw "scrollToPosition() must be given a position from 0.0 to 1.0";
120+
throw "scrollToPosition() must be given a position from 0.0 to 1.0";
115121
}
116122

117123
let offset;
118124
if (isScrollModeEnabled()) {
119-
offset = document.scrollingElement.scrollHeight * position;
120-
document.scrollingElement.scrollTop = offset;
125+
if (!isVerticalWritingMode()) {
126+
offset = document.scrollingElement.scrollHeight * position;
127+
document.scrollingElement.scrollTop = offset;
128+
} else {
129+
offset = document.scrollingElement.scrollWidth * position;
130+
document.scrollingElement.scrollLeft = -offset;
131+
}
121132
// window.scrollTo(0, offset);
122133
} else {
123134
var documentWidth = document.scrollingElement.scrollWidth;
@@ -156,25 +167,27 @@ function scrollToRect(rect) {
156167
}
157168

158169
export function scrollToStart() {
159-
// Android.log("scrollToStart");
160-
if (!isScrollModeEnabled()) {
161-
document.scrollingElement.scrollLeft = 0;
162-
} else {
170+
if (isScrollModeEnabled() && !isVerticalWritingMode()) {
163171
document.scrollingElement.scrollTop = 0;
164-
window.scrollTo(0, 0);
172+
} else {
173+
document.scrollingElement.scrollLeft = 0;
165174
}
166175
}
167176

168177
export function scrollToEnd() {
169-
// Android.log("scrollToEnd");
170-
if (!isScrollModeEnabled()) {
178+
const scrollingElement = document.scrollingElement;
179+
180+
if (isScrollModeEnabled()) {
181+
if (!isVerticalWritingMode()) {
182+
scrollingElement.scrollTop = document.body.scrollHeight;
183+
} else {
184+
scrollingElement.scrollLeft = -document.scrollingElement.scrollWidth;
185+
}
186+
} else {
171187
var factor = isRTL() ? -1 : 1;
172-
document.scrollingElement.scrollLeft = snapOffset(
173-
document.scrollingElement.scrollWidth * factor
188+
scrollingElement.scrollLeft = snapOffset(
189+
scrollingElement.scrollWidth * factor
174190
);
175-
} else {
176-
document.scrollingElement.scrollTop = document.body.scrollHeight;
177-
window.scrollTo(0, document.body.scrollHeight);
178191
}
179192
}
180193

readium/navigator/src/main/assets/readium/scripts/readium-fixed.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readium/navigator/src/main/assets/readium/scripts/readium-reflowable.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
5555

5656
interface Listener {
5757
val readingProgression: ReadingProgression
58+
val verticalText: Boolean
5859

5960
/** Called when the resource content is loaded in the web view. */
6061
fun onResourceLoaded(webView: R2BasicWebView, link: Link) {}
@@ -127,17 +128,30 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
127128
}
128129

129130
/** Computes the current progression in the resource. */
130-
val progression: Double get() =
131-
if (scrollMode) {
132-
val y = scrollY.toDouble()
133-
val contentHeight = computeVerticalScrollRange()
134-
135-
var progression = 0.0
136-
if (contentHeight > 0) {
137-
progression = (y / contentHeight).coerceIn(0.0, 1.0)
131+
val progression: Double get() {
132+
return if (scrollMode) {
133+
if (listener?.verticalText == true) {
134+
val x = scrollX.toDouble()
135+
val contentWidth = computeHorizontalScrollRange()
136+
val viewportWidth = computeHorizontalScrollExtent()
137+
val maxScrollX = (contentWidth - viewportWidth).coerceAtLeast(0)
138+
139+
var progression = 0.0
140+
if (maxScrollX > 0) {
141+
// Currently, scrollX for page with vertical text starts at maxScrollX
142+
progression = ((maxScrollX - x) / contentWidth).coerceIn(0.0, 1.0)
143+
}
144+
progression
145+
} else {
146+
val y = scrollY.toDouble()
147+
val contentHeight = computeVerticalScrollRange()
148+
149+
var progression = 0.0
150+
if (contentHeight > 0) {
151+
progression = (y / contentHeight).coerceIn(0.0, 1.0)
152+
}
153+
progression
138154
}
139-
140-
progression
141155
} else {
142156
var x = scrollX.toDouble()
143157
val pageWidth = computeHorizontalScrollExtent()
@@ -163,6 +177,7 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
163177

164178
progression
165179
}
180+
}
166181

167182
interface OnOverScrolledCallback {
168183
fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean)

readium/navigator/src/main/java/org/readium/r2/navigator/R2WebView.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ internal class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView
116116
private var mLastMotionX: Float = 0.toFloat()
117117
private var mInitialMotionX: Float = 0.toFloat()
118118
private var mInitialMotionY: Float = 0.toFloat()
119+
private var mInitialOverscroll: OverscrollMode = OverscrollMode.NONE
119120

120121
/**
121122
* Sentinel value for no current active pointer.
@@ -707,6 +708,7 @@ internal class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView
707708
mInitialMotionX = ev.x
708709
mLastMotionX = mInitialMotionX
709710
mInitialMotionY = ev.y
711+
mInitialOverscroll = getOverscrollMode()
710712
mActivePointerId = ev.getPointerId(0)
711713
}
712714
MotionEvent.ACTION_MOVE -> {
@@ -745,9 +747,15 @@ internal class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView
745747
if (scrollMode) {
746748
val totalDelta = (y - mInitialMotionY).toInt()
747749
if (abs(totalDelta) < 200) {
748-
if (mInitialMotionX < x) {
750+
if (mInitialOverscroll == OverscrollMode.BOTH) {
751+
if (mInitialMotionX < x) {
752+
scrollLeft(animated = true)
753+
} else if (mInitialMotionX > x) {
754+
scrollRight(animated = true)
755+
}
756+
} else if (mInitialMotionX < x && mInitialOverscroll == OverscrollMode.LEFT) {
749757
scrollLeft(animated = true)
750-
} else if (mInitialMotionX > x) {
758+
} else if (mInitialMotionX > x && mInitialOverscroll == OverscrollMode.RIGHT) {
751759
scrollRight(animated = true)
752760
}
753761
}
@@ -1054,12 +1062,34 @@ internal class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView
10541062
return false
10551063
}
10561064

1065+
private fun getOverscrollMode(): OverscrollMode {
1066+
val clientWidth = getClientWidth() ?: return OverscrollMode.NONE
1067+
val right = scrollX >= computeHorizontalScrollRange() - clientWidth
1068+
val left = scrollX <= 0
1069+
if (left && right) {
1070+
return OverscrollMode.BOTH
1071+
} else if (left) {
1072+
return OverscrollMode.LEFT
1073+
} else if (right) {
1074+
return OverscrollMode.RIGHT
1075+
} else {
1076+
return OverscrollMode.NONE
1077+
}
1078+
}
1079+
10571080
internal val numPages: Int get() =
10581081
getClientWidth()
10591082
?.let { clientWidth -> (computeHorizontalScrollRange() / clientWidth.toDouble()).roundToInt() }
10601083
?.coerceAtLeast(1)
10611084
?: 1
10621085

1086+
enum class OverscrollMode {
1087+
NONE,
1088+
LEFT,
1089+
RIGHT,
1090+
BOTH,
1091+
}
1092+
10631093
/**
10641094
* Layout parameters that should be supplied for views added to a
10651095
* ViewPager.

readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -412,37 +412,6 @@ public class EpubNavigatorFragment internal constructor(
412412
resourcePager = binding.resourcePager
413413
resetResourcePager()
414414

415-
resourcePager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
416-
417-
override fun onPageSelected(position: Int) {
418-
// if (viewModel.layout == EpubLayout.REFLOWABLE) {
419-
// resourcePager.disableTouchEvents = true
420-
// }
421-
currentReflowablePageFragment?.webView?.let { webView ->
422-
if (viewModel.isScrollEnabled.value) {
423-
if (currentPagerPosition < position) {
424-
// handle swipe LEFT
425-
webView.scrollToStart()
426-
} else if (currentPagerPosition > position) {
427-
// handle swipe RIGHT
428-
webView.scrollToEnd()
429-
}
430-
} else {
431-
if (currentPagerPosition < position) {
432-
// handle swipe LEFT
433-
webView.setCurrentItem(0, false)
434-
} else if (currentPagerPosition > position) {
435-
// handle swipe RIGHT
436-
webView.setCurrentItem(webView.numPages - 1, false)
437-
}
438-
}
439-
}
440-
currentPagerPosition = position // Update current position
441-
442-
notifyCurrentLocation()
443-
}
444-
})
445-
446415
// Fixed layout publications cannot intercept JS events yet.
447416
if (publication.metadata.presentation.layout == EpubLayout.FIXED) {
448417
view = KeyInterceptorView(view, inputListener)
@@ -468,12 +437,40 @@ public class EpubNavigatorFragment internal constructor(
468437
resourcePager.setBackgroundColor(viewModel.settings.value.effectiveBackgroundColor)
469438
// Let the page views handle the keyboard events.
470439
resourcePager.isFocusable = false
440+
resourcePager.addOnPageChangeListener(PageChangeListener())
471441

472442
parent.addView(resourcePager)
473443

474444
resetResourcePagerAdapter()
475445
}
476446

447+
private inner class PageChangeListener : ViewPager.SimpleOnPageChangeListener() {
448+
override fun onPageSelected(position: Int) {
449+
currentReflowablePageFragment?.webView?.let { webView ->
450+
if (viewModel.isScrollEnabled.value) {
451+
if (currentPagerPosition < position) {
452+
// handle swipe LEFT
453+
webView.scrollToStart()
454+
} else if (currentPagerPosition > position) {
455+
// handle swipe RIGHT
456+
webView.scrollToEnd()
457+
}
458+
} else {
459+
if (currentPagerPosition < position) {
460+
// handle swipe LEFT
461+
webView.setCurrentItem(0, false)
462+
} else if (currentPagerPosition > position) {
463+
// handle swipe RIGHT
464+
webView.setCurrentItem(webView.numPages - 1, false)
465+
}
466+
}
467+
}
468+
currentPagerPosition = position // Update current position
469+
470+
notifyCurrentLocation()
471+
}
472+
}
473+
477474
private fun resetResourcePagerAdapter() {
478475
adapter = when (publication.metadata.presentation.layout) {
479476
EpubLayout.REFLOWABLE, null -> {
@@ -770,6 +767,9 @@ public class EpubNavigatorFragment internal constructor(
770767
override val readingProgression: ReadingProgression
771768
get() = viewModel.readingProgression
772769

770+
override val verticalText: Boolean
771+
get() = viewModel.verticalText
772+
773773
override fun onResourceLoaded(webView: R2BasicWebView, link: Link) {
774774
run(viewModel.onResourceLoaded(webView, link))
775775
}

readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ internal class EpubNavigatorViewModel(
253253
val readingProgression: ReadingProgression get() =
254254
settings.value.readingProgression
255255

256+
/**
257+
* Effective vertical text.
258+
*/
259+
val verticalText: Boolean get() =
260+
settings.value.verticalText
261+
256262
/**
257263
* Indicates whether the dual page mode is enabled.
258264
*/

0 commit comments

Comments
 (0)