├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── PhysicsSyllabus.epub │ ├── The Silver Chair.epub │ ├── aayesha.epub │ ├── books │ │ ├── javascript │ │ │ ├── highlight.js │ │ │ └── rangy │ │ │ │ ├── rangy-classapplier.min.js │ │ │ │ ├── rangy-core.min.js │ │ │ │ ├── rangy-highlighter.min.js │ │ │ │ └── rangy-serializer.min.js │ │ └── styles │ │ │ └── night_mode.css │ └── fonts │ │ └── Diplomata_SC │ │ ├── DiplomataSC-Regular.ttf │ │ └── OFL.txt │ ├── java │ └── com │ │ └── smartmobilefactory │ │ └── epubreader │ │ └── sample │ │ ├── ChapterJavaScriptBridge.java │ │ ├── EpubSampleApp.java │ │ ├── MainActivity.java │ │ ├── NightmodePlugin.java │ │ └── TableOfContentsAdapter.java │ └── res │ ├── drawable │ ├── ic_dehaze.xml │ └── ic_settings.xml │ ├── layout │ ├── activity_main.xml │ └── table_of_contents_item.xml │ ├── menu │ └── menu.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── epubreaderandroid │ │ ├── helper_functions.js │ │ ├── script.js │ │ └── style.css │ ├── java │ └── com │ │ └── smartmobilefactory │ │ └── epubreader │ │ ├── EpubScrollDirection.kt │ │ ├── EpubView.kt │ │ ├── EpubViewPlugin.kt │ │ ├── EpubViewSettings.java │ │ ├── InternalEpubViewSettings.kt │ │ ├── SavedState.kt │ │ ├── UrlInterceptor.java │ │ ├── display │ │ ├── EpubDisplayHelper.kt │ │ ├── EpubDisplayStrategy.kt │ │ ├── WebViewHelper.kt │ │ ├── binding │ │ │ ├── EpubHorizontalVerticalContentBinding.kt │ │ │ ├── EpubVerticalVerticalContentBinding.kt │ │ │ ├── ItemEpubVerticalContentBinding.kt │ │ │ └── ItemVerticalVerticalContentBinding.kt │ │ ├── vertical_content │ │ │ ├── SingleChapterVerticalEpubDisplayStrategy.kt │ │ │ ├── VerticalContentBinderHelper.kt │ │ │ ├── VerticalEpubWebView.kt │ │ │ ├── horizontal_chapters │ │ │ │ ├── HorizontalWithVerticalContentEpubDisplayStrategy.kt │ │ │ │ └── PagerAdapter.kt │ │ │ └── vertical_chapters │ │ │ │ ├── ChapterAdapter.kt │ │ │ │ └── VerticalWithVerticalContentEpubDisplayStrategy.kt │ │ └── view │ │ │ ├── BaseViewPagerAdapter.kt │ │ │ ├── EpubWebView.kt │ │ │ ├── InternalEpubBridge.kt │ │ │ └── JsApi.kt │ │ ├── model │ │ ├── Epub.java │ │ ├── EpubFont.java │ │ ├── EpubLocation.java │ │ ├── EpubStorageHelper.java │ │ ├── FileUtils.java │ │ ├── LazyResource.java │ │ ├── UncompressedEpubReader.java │ │ └── Unzipper.java │ │ └── utils │ │ └── BaseDisposableObserver.kt │ └── res │ └── layout │ ├── epub_horizontal_vertical_content.xml │ ├── epub_vertical_vertical_content.xml │ ├── item_epub_vertical_content.xml │ └── item_vertical_vertical_content.xml ├── scripts ├── dumpapp ├── hprof_dump.sh └── stetho_open.py └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .idea/ 11 | app/src/main/assets/private/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Smart Mobile Factory GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EpubReaderAndroid 2 | 3 | ePub-Reader for Android to make it easy integrate and customize epub-Reader functionality in any Android App 4 | 5 | 6 | ## Simple Integrate 7 | 8 | ```xml 9 | 10 | 14 | 15 | ``` 16 | 17 | ```java 18 | 19 | EpubView epubView = findViewById(R.id.epubView); 20 | epubView.setEpub(epub); 21 | 22 | ``` 23 | 24 | 25 | ## Display Modes 26 | 27 | The Epub can be displayed in different modes: 28 | 29 | - horizontal chapters + vertical content 30 | ```java 31 | 32 | epubView.setScrollDirection(EpubScrollDirection.HORIZONTAL_WITH_VERTICAL_CONTENT); 33 | 34 | ``` 35 | - vertical chapters + vertical content 36 | ```java 37 | 38 | epubView.setScrollDirection(EpubScrollDirection.VERTICAL_WITH_VERTICAL_CONTENT); 39 | 40 | ``` 41 | - single chapters + vertical content 42 | ```java 43 | 44 | epubView.setScrollDirection(EpubScrollDirection.SINGLE_CHAPTER_VERTICAL); 45 | 46 | ``` 47 | 48 | More modes may be implemented later. 49 | 50 | ## Customization Settings 51 | 52 | ```java 53 | 54 | EpubViewSettings settings = epubView.getSettings(); 55 | settings.setFont(EpubFont.fromFontFamiliy("Monospace")); 56 | settings.setFontSizeSp(30); 57 | 58 | // inject code into chapters 59 | settings.setJavascriptBridge(bridge); 60 | settings.setCustomChapterScript(...); 61 | settings.setCustomChapterCss(...); 62 | 63 | ``` 64 | 65 | ## Observe current status 66 | 67 | observation is implemented using RxJava2 68 | 69 | - Current Chapter 70 | - Current Location 71 | 72 | 73 | ## Installation 74 | 75 | 76 | ```groovy 77 | 78 | repositories { 79 | // ... 80 | maven { url "https://jitpack.io" } 81 | } 82 | 83 | dependencies { 84 | compile 'com.github.smartmobilefactory:EpubReaderAndroid:XXX' 85 | } 86 | 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | 6 | defaultConfig { 7 | applicationId "com.smartmobilefactory.epubreader.sample" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | dataBinding { 16 | enabled true 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | compile fileTree(include: ['*.jar'], dir: 'libs') 34 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 35 | exclude group: 'com.android.support', module: 'support-annotations' 36 | }) 37 | implementation 'com.android.support:appcompat-v7:25.2.0' 38 | 39 | 40 | implementation 'com.facebook.stetho:stetho:1.4.2' 41 | 42 | testCompile 'junit:junit:4.12' 43 | implementation project(':library') 44 | } 45 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/timfreiheit/ProgrammeOther/android_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/assets/PhysicsSyllabus.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartmobilefactory/EpubReaderAndroid/e89d3523ce3876e0dc44e9a0d5dec8f78909d325/app/src/main/assets/PhysicsSyllabus.epub -------------------------------------------------------------------------------- /app/src/main/assets/The Silver Chair.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartmobilefactory/EpubReaderAndroid/e89d3523ce3876e0dc44e9a0d5dec8f78909d325/app/src/main/assets/The Silver Chair.epub -------------------------------------------------------------------------------- /app/src/main/assets/aayesha.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartmobilefactory/EpubReaderAndroid/e89d3523ce3876e0dc44e9a0d5dec8f78909d325/app/src/main/assets/aayesha.epub -------------------------------------------------------------------------------- /app/src/main/assets/books/javascript/highlight.js: -------------------------------------------------------------------------------- 1 | var highlighter; 2 | var initialDoc; 3 | 4 | var highlighter; 5 | 6 | function init() { 7 | if (!highlighter) { 8 | rangy.init(); 9 | highlighter = rangy.createHighlighter(); 10 | } 11 | 12 | document.addEventListener("selectionchange", function(e) { 13 | bridge.onSelectionChanged(window.getSelection().toString().length); 14 | }, false); 15 | 16 | } 17 | init(); 18 | 19 | /** 20 | * deserialize and format the highlight from database to usable once for rangy 21 | */ 22 | function reloadHighlights() { 23 | var json = bridge.getHighlights(epubChapter.index); 24 | var data = JSON.parse(json); 25 | 26 | // remove all highlights before adding new once 27 | highlighter.removeAllHighlights(); 28 | 29 | var serializedHighlights = ["type:textContent"] 30 | for (var i=0; i < data.length; i++){ 31 | var highlight = data[i]; 32 | 33 | if (highlight.chapterId != epubChapter.index) { 34 | continue; 35 | } 36 | 37 | var parts = [ 38 | highlight.start, 39 | highlight.end, 40 | highlight.id, 41 | getColorClass(highlight.color), 42 | '' 43 | ]; 44 | 45 | serializedHighlights.push( parts.join("$") ); 46 | } 47 | 48 | var serializedData = serializedHighlights.join("|"); 49 | highlighter.deserialize(serializedData); 50 | } 51 | 52 | /** 53 | * highlight the current selected text 54 | * @param color hex formatted rgb color 55 | */ 56 | function highlightSelectedText(color) { 57 | init(); 58 | 59 | var colorClass = getColorClass(color); 60 | var newHighlight = highlighter.highlightSelection(colorClass)[0]; 61 | 62 | var node = newHighlight.getHighlightElements()[0]; 63 | 64 | var x = 0; 65 | var y = 0; 66 | if (node && node.getBoundingClientRect) { 67 | var position = node.getBoundingClientRect(); 68 | x = position.left; 69 | y = position.top; 70 | } 71 | var data = { 72 | x: x, 73 | y: y, 74 | highlights: getAllHighlightsSerialized() 75 | }; 76 | 77 | bridge.onHighlightAdded(epubChapter.index, JSON.stringify(data)); 78 | } 79 | 80 | function getAllHighlightsSerialized() { 81 | 82 | var serializedHighlights = []; 83 | 84 | var highlights = highlighter.highlights; 85 | for (i = 0; i < highlights.length; i++) { 86 | var highlight = highlights[i]; 87 | serializedHighlights.push(serializeHighlight(highlight)); 88 | } 89 | 90 | return serializedHighlights; 91 | } 92 | 93 | function serializeHighlight(highlight) { 94 | var color = "#" + highlight.classApplier.className.replace("highlight_", ""); 95 | 96 | var serialized = { 97 | id : highlight.id, 98 | chapterId: epubChapter.index, 99 | start: highlight.characterRange.start, 100 | end: highlight.characterRange.end, 101 | text: highlight.getText(), 102 | color: color 103 | } 104 | return serialized; 105 | } 106 | 107 | function getColorClass(color) { 108 | var className = "highlight_" + color.replace("#", ""); 109 | 110 | // check if class already exists 111 | for (var i=0; i < document.styleSheets.length; i++){ 112 | var styleSheet = document.styleSheets[i]; 113 | var rules = styleSheet.rules || styleSheet.cssRules; 114 | for(var x in rules) { 115 | if(rules[x].selectorText == className) { 116 | return className; 117 | } 118 | } 119 | } 120 | 121 | // class does not exists 122 | 123 | var style = document.createElement('style'); 124 | style.type = 'text/css'; 125 | style.innerHTML = '.' + className + ' { background-color: ' + color +'; }'; 126 | document.getElementsByTagName('head')[0].appendChild(style); 127 | 128 | highlighter.addClassApplier(rangy.createClassApplier(className, { 129 | ignoreWhiteSpace: true, 130 | tagNames: ["span", "a"], 131 | elementProperties: { 132 | href: "#", 133 | onclick: function(event) { 134 | var highlight = highlighter.getHighlightForElement(this); 135 | var position = event.target.getBoundingClientRect(); 136 | var data = { 137 | x: position.left, 138 | y: position.top, 139 | highlight: serializeHighlight(highlight) 140 | }; 141 | bridge.onHighlightClicked(JSON.stringify(data)); 142 | return false; 143 | } 144 | } 145 | })); 146 | 147 | return className; 148 | } 149 | 150 | reloadHighlights(); 151 | -------------------------------------------------------------------------------- /app/src/main/assets/books/javascript/rangy/rangy-classapplier.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class Applier module for Rangy. 3 | * Adds, removes and toggles classes on Ranges and Selections 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright 2015, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: 1.3.0 13 | * Build date: 10 May 2015 14 | */ 15 | !function(e,t){"function"==typeof define&&define.amd?define(["./rangy-core"],e):"undefined"!=typeof module&&"object"==typeof exports?module.exports=e(require("rangy")):e(t.rangy)}(function(e){return e.createModule("ClassApplier",["WrappedSelection"],function(e,t){function n(e,t){for(var n in e)if(e.hasOwnProperty(n)&&t(n,e[n])===!1)return!1;return!0}function s(e){return e.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}function r(e,t){return!!e&&new RegExp("(?:^|\\s)"+t+"(?:\\s|$)").test(e)}function o(e,t){if("object"==typeof e.classList)return e.classList.contains(t);var n="string"==typeof e.className,s=n?e.className:e.getAttribute("class");return r(s,t)}function a(e,t){if("object"==typeof e.classList)e.classList.add(t);else{var n="string"==typeof e.className,s=n?e.className:e.getAttribute("class");s?r(s,t)||(s+=" "+t):s=t,n?e.className=s:e.setAttribute("class",s)}}function l(e){var t="string"==typeof e.className;return t?e.className:e.getAttribute("class")}function u(e){return e&&e.split(/\s+/).sort().join(" ")}function f(e){return u(l(e))}function c(e,t){return f(e)==f(t)}function p(e,t){for(var n=t.split(/\s+/),r=0,i=n.length;i>r;++r)if(!o(e,s(n[r])))return!1;return!0}function d(e){var t=e.parentNode;return t&&1==t.nodeType&&!/^(textarea|style|script|select|iframe)$/i.test(t.nodeName)}function h(e,t,n,s,r){var i=e.node,o=e.offset,a=i,l=o;i==s&&o>r&&++l,i!=t||o!=n&&o!=n+1||(a=s,l+=r-n),i==t&&o>n+1&&--l,e.node=a,e.offset=l}function m(e,t,n){e.node==t&&e.offset>n&&--e.offset}function g(e,t,n,s){-1==n&&(n=t.childNodes.length);var r=e.parentNode,i=$.getNodeIndex(e);U(s,function(e){h(e,r,i,t,n)}),t.childNodes.length==n?t.appendChild(e):t.insertBefore(e,t.childNodes[n])}function N(e,t){var n=e.parentNode,s=$.getNodeIndex(e);U(t,function(e){m(e,n,s)}),$.removeNode(e)}function v(e,t,n,s,r){for(var i,o=[];i=e.firstChild;)g(i,t,n++,r),o.push(i);return s&&N(e,r),o}function y(e,t){return v(e,e.parentNode,$.getNodeIndex(e),!0,t)}function C(e,t){var n=e.cloneRange();n.selectNodeContents(t);var s=n.intersection(e),r=s?s.toString():"";return""!=r}function T(e){for(var t,n=e.getNodes([3]),s=0;(t=n[s])&&!C(e,t);)++s;for(var r=n.length-1;(t=n[r])&&!C(e,t);)--r;return n.slice(s,r+1)}function E(e,t){if(e.attributes.length!=t.attributes.length)return!1;for(var n,s,r,i=0,o=e.attributes.length;o>i;++i)if(n=e.attributes[i],r=n.name,"class"!=r){if(s=t.attributes.getNamedItem(r),null===n!=(null===s))return!1;if(n.specified!=s.specified)return!1;if(n.specified&&n.nodeValue!==s.nodeValue)return!1}return!0}function b(e,t){for(var n,s=0,r=e.attributes.length;r>s;++s)if(n=e.attributes[s].name,(!t||!B(t,n))&&e.attributes[s].specified&&"class"!=n)return!0;return!1}function A(e){var t;return e&&1==e.nodeType&&((t=e.parentNode)&&9==t.nodeType&&"on"==t.designMode||G(e)&&!G(e.parentNode))}function S(e){return(G(e)||1!=e.nodeType&&G(e.parentNode))&&!A(e)}function x(e){return e&&1==e.nodeType&&!J.test(F(e,"display"))}function R(e){if(0==e.data.length)return!0;if(K.test(e.data))return!1;var t=F(e.parentNode,"whiteSpace");switch(t){case"pre":case"pre-wrap":case"-moz-pre-wrap":return!1;case"pre-line":if(/[\r\n]/.test(e.data))return!1}return x(e.previousSibling)||x(e.nextSibling)}function P(e){var t,n,s=[];for(t=0;n=e[t++];)s.push(new z(n.startContainer,n.startOffset),new z(n.endContainer,n.endOffset));return s}function w(e,t){for(var n,s,r,i=0,o=e.length;o>i;++i)n=e[i],s=t[2*i],r=t[2*i+1],n.setStartAndEnd(s.node,s.offset,r.node,r.offset)}function O(e,t){return $.isCharacterDataNode(e)?0==t?!!e.previousSibling:t==e.length?!!e.nextSibling:!0:t>0&&to;++o)"*"==r[o]?f.applyToAnyTagName=!0:f.tagNames.push(r[o].toLowerCase());else f.tagNames=[f.elementTagName]}function j(e,t,n){return new H(e,t,n)}var $=e.dom,z=$.DomPosition,B=$.arrayContains,D=e.util,U=D.forEach,V="span",k=D.isHostMethod(document,"createElementNS"),q=function(){function e(e,t,n){return t&&n?" ":""}return function(t,n){if("object"==typeof t.classList)t.classList.remove(n);else{var s="string"==typeof t.className,r=s?t.className:t.getAttribute("class");r=r.replace(new RegExp("(^|\\s)"+n+"(\\s|$)"),e),s?t.className=r:t.setAttribute("class",r)}}}(),F=$.getComputedStyleProperty,G=function(){var e=document.createElement("div");return"boolean"==typeof e.isContentEditable?function(e){return e&&1==e.nodeType&&e.isContentEditable}:function(e){return e&&1==e.nodeType&&"false"!=e.contentEditable?"true"==e.contentEditable||G(e.parentNode):!1}}(),J=/^inline(-block|-table)?$/i,K=/[^\r\n\t\f \u200B]/,Q=L(!1),X=L(!0);M.prototype={doMerge:function(e){var t=this.textNodes,n=t[0];if(t.length>1){var s,r=$.getNodeIndex(n),i=[],o=0;U(t,function(t,a){s=t.parentNode,a>0&&(s.removeChild(t),s.hasChildNodes()||$.removeNode(s),e&&U(e,function(e){e.node==t&&(e.node=n,e.offset+=o),e.node==s&&e.offset>r&&(--e.offset,e.offset==r+1&&len-1>a&&(e.node=n,e.offset=o))})),i[a]=t.data,o+=t.data.length}),n.data=i.join("")}return n.data},getLength:function(){for(var e=this.textNodes.length,t=0;e--;)t+=this.textNodes[e].length;return t},toString:function(){var e=[];return U(this.textNodes,function(t,n){e[n]="'"+t.data+"'"}),"[Merge("+e.join(",")+")]"}};var Y=["elementTagName","ignoreWhiteSpace","applyToEditableOnly","useExistingElements","removeEmptyElements","onElementCreate"],Z={};H.prototype={elementTagName:V,elementProperties:{},elementAttributes:{},ignoreWhiteSpace:!0,applyToEditableOnly:!1,useExistingElements:!0,removeEmptyElements:!0,onElementCreate:null,copyPropertiesToElement:function(e,t,n){var s,r,i,o,l,f,c={};for(var p in e)if(e.hasOwnProperty(p))if(o=e[p],l=t[p],"className"==p)a(t,o),a(t,this.className),t[p]=u(t[p]),n&&(c[p]=o);else if("style"==p){r=l,n&&(c[p]=i={});for(s in e[p])e[p].hasOwnProperty(s)&&(r[s]=o[s],n&&(i[s]=r[s]));this.attrExceptions.push(p)}else t[p]=o,n&&(c[p]=t[p],f=Z.hasOwnProperty(p)?Z[p]:p,this.attrExceptions.push(f));return n?c:""},copyAttributesToElement:function(e,t){for(var n in e)e.hasOwnProperty(n)&&!/^class(?:Name)?$/i.test(n)&&t.setAttribute(n,e[n])},appliesToElement:function(e){return B(this.tagNames,e.tagName.toLowerCase())},getEmptyElements:function(e){var t=this;return e.getNodes([1],function(e){return t.appliesToElement(e)&&!e.hasChildNodes()})},hasClass:function(e){return 1==e.nodeType&&(this.applyToAnyTagName||this.appliesToElement(e))&&o(e,this.className)},getSelfOrAncestorWithClass:function(e){for(;e;){if(this.hasClass(e))return e;e=e.parentNode}return null},isModifiable:function(e){return!this.applyToEditableOnly||S(e)},isIgnorableWhiteSpaceNode:function(e){return this.ignoreWhiteSpace&&e&&3==e.nodeType&&R(e)},postApply:function(e,t,n,s){var r,o,a=e[0],l=e[e.length-1],u=[],f=a,c=l,p=0,d=l.length;U(e,function(e){o=Q(e,!s),o?(r||(r=new M(o),u.push(r)),r.textNodes.push(e),e===a&&(f=r.textNodes[0],p=f.length),e===l&&(c=r.textNodes[0],d=r.getLength())):r=null});var h=X(l,!s);if(h&&(r||(r=new M(l),u.push(r)),r.textNodes.push(h)),u.length){for(i=0,len=u.length;len>i;++i)u[i].doMerge(n);t.setStartAndEnd(f,p,c,d)}},createContainer:function(e){var t,n=$.getDocument(e),s=k&&!$.isHtmlNamespace(e)&&(t=e.namespaceURI)?n.createElementNS(e.namespaceURI,this.elementTagName):n.createElement(this.elementTagName);return this.copyPropertiesToElement(this.elementProperties,s,!1),this.copyAttributesToElement(this.elementAttributes,s),a(s,this.className),this.onElementCreate&&this.onElementCreate(s,this),s},elementHasProperties:function(e,t){var s=this;return n(t,function(t,n){if("className"==t)return p(e,n);if("object"==typeof n){if(!s.elementHasProperties(e[t],n))return!1}else if(e[t]!==n)return!1})},elementHasAttributes:function(e,t){return n(t,function(t,n){return e.getAttribute(t)!==n?!1:void 0})},applyToTextNode:function(e){if(d(e)){var t=e.parentNode;if(1==t.childNodes.length&&this.useExistingElements&&this.appliesToElement(t)&&this.elementHasProperties(t,this.elementProperties)&&this.elementHasAttributes(t,this.elementAttributes))a(t,this.className);else{var n=e.parentNode,s=this.createContainer(n);n.insertBefore(s,e),s.appendChild(e)}}},isRemovable:function(e){return e.tagName.toLowerCase()==this.elementTagName&&f(e)==this.elementSortedClassName&&this.elementHasProperties(e,this.elementProperties)&&!b(e,this.attrExceptions)&&this.elementHasAttributes(e,this.elementAttributes)&&this.isModifiable(e)},isEmptyContainer:function(e){var t=e.childNodes.length;return 1==e.nodeType&&this.isRemovable(e)&&(0==t||1==t&&this.isEmptyContainer(e.firstChild))},removeEmptyContainers:function(e){var t=this,n=e.getNodes([1],function(e){return t.isEmptyContainer(e)}),s=[e],r=P(s);U(n,function(e){N(e,r)}),w(s,r)},undoToTextNode:function(e,t,n,s){if(!t.containsNode(n)){var r=t.cloneRange();r.selectNode(n),r.isPointInRange(t.endContainer,t.endOffset)&&(I(n,t.endContainer,t.endOffset,s),t.setEndAfter(n)),r.isPointInRange(t.startContainer,t.startOffset)&&(n=I(n,t.startContainer,t.startOffset,s))}this.isRemovable(n)?y(n,s):q(n,this.className)},splitAncestorWithClass:function(e,t,n){var s=this.getSelfOrAncestorWithClass(e);s&&I(s,e,t,n)},undoToAncestor:function(e,t){this.isRemovable(e)?y(e,t):q(e,this.className)},applyToRange:function(e,t){var n=this;t=t||[];var s=P(t||[]);e.splitBoundariesPreservingPositions(s),n.removeEmptyElements&&n.removeEmptyContainers(e);var r=T(e);if(r.length){U(r,function(e){n.isIgnorableWhiteSpaceNode(e)||n.getSelfOrAncestorWithClass(e)||!n.isModifiable(e)||n.applyToTextNode(e,s)});var i=r[r.length-1];e.setStartAndEnd(r[0],0,i,i.length),n.normalize&&n.postApply(r,e,s,!1),w(t,s)}var o=n.getEmptyElements(e);U(o,function(e){a(e,n.className)})},applyToRanges:function(e){for(var t=e.length;t--;)this.applyToRange(e[t],e);return e},applyToSelection:function(t){var n=e.getSelection(t);n.setRanges(this.applyToRanges(n.getAllRanges()))},undoToRange:function(e,t){var n=this;t=t||[];var s=P(t);e.splitBoundariesPreservingPositions(s),n.removeEmptyElements&&n.removeEmptyContainers(e,s);var r,i,o=T(e),a=o[o.length-1];if(o.length){n.splitAncestorWithClass(e.endContainer,e.endOffset,s),n.splitAncestorWithClass(e.startContainer,e.startOffset,s);for(var l=0,u=o.length;u>l;++l)r=o[l],i=n.getSelfOrAncestorWithClass(r),i&&n.isModifiable(r)&&n.undoToAncestor(i,s);e.setStartAndEnd(o[0],0,a,a.length),n.normalize&&n.postApply(o,e,s,!0),w(t,s)}var f=n.getEmptyElements(e);U(f,function(e){q(e,n.className)})},undoToRanges:function(e){for(var t=e.length;t--;)this.undoToRange(e[t],e);return e},undoToSelection:function(t){var n=e.getSelection(t),s=e.getSelection(t).getAllRanges();this.undoToRanges(s),n.setRanges(s)},isAppliedToRange:function(e){if(e.collapsed||""==e.toString())return!!this.getSelfOrAncestorWithClass(e.commonAncestorContainer);var t=e.getNodes([3]);if(t.length)for(var n,s=0;n=t[s++];)if(!this.isIgnorableWhiteSpaceNode(n)&&C(e,n)&&this.isModifiable(n)&&!this.getSelfOrAncestorWithClass(n))return!1;return!0},isAppliedToRanges:function(e){var t=e.length;if(0==t)return!1;for(;t--;)if(!this.isAppliedToRange(e[t]))return!1;return!0},isAppliedToSelection:function(t){var n=e.getSelection(t);return this.isAppliedToRanges(n.getAllRanges())},toggleRange:function(e){this.isAppliedToRange(e)?this.undoToRange(e):this.applyToRange(e)},toggleSelection:function(e){this.isAppliedToSelection(e)?this.undoToSelection(e):this.applyToSelection(e)},getElementsWithClassIntersectingRange:function(e){var t=[],n=this;return e.getNodes([3],function(e){var s=n.getSelfOrAncestorWithClass(e);s&&!B(t,s)&&t.push(s)}),t},detach:function(){}},H.util={hasClass:o,addClass:a,removeClass:q,getClass:l,hasSameClasses:c,hasAllClasses:p,replaceWithOwnChildren:y,elementsHaveSameNonClassAttributes:E,elementHasNonClassAttributes:b,splitNodeAt:I,isEditableElement:G,isEditingHost:A,isEditable:S},e.CssClassApplier=e.ClassApplier=H,e.createClassApplier=j,D.createAliasForDeprecatedMethod(e,"createCssClassApplier","createClassApplier",t)}),e},this); -------------------------------------------------------------------------------- /app/src/main/assets/books/javascript/rangy/rangy-highlighter.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Highlighter module for Rangy, a cross-browser JavaScript range and selection library 3 | * https://github.com/timdown/rangy 4 | * 5 | * Depends on Rangy core, ClassApplier and optionally TextRange modules. 6 | * 7 | * Copyright 2015, Tim Down 8 | * Licensed under the MIT license. 9 | * Version: 1.3.0 10 | * Build date: 10 May 2015 11 | */ 12 | !function(e,t){"function"==typeof define&&define.amd?define(["./rangy-core"],e):"undefined"!=typeof module&&"object"==typeof exports?module.exports=e(require("rangy")):e(t.rangy)}(function(e){return e.createModule("Highlighter",["ClassApplier"],function(e){function t(e,t){return e.characterRange.start-t.characterRange.start}function n(e,t){return t?e.getElementById(t):l(e)}function r(e,t){this.type=e,this.converterCreator=t}function i(e,t){f[e]=new r(e,t)}function a(e){var t=f[e];if(t instanceof r)return t.create();throw new Error("Highlighter type '"+e+"' is not valid")}function s(e,t){this.start=e,this.end=t}function h(e,t,n,r,i,a){i?(this.id=i,d=Math.max(d,i+1)):this.id=d++,this.characterRange=t,this.doc=e,this.classApplier=n,this.converter=r,this.containerElementId=a||null,this.applied=!1}function o(e,t){t=t||"textContent",this.doc=e||document,this.classAppliers={},this.highlights=[],this.converter=a(t)}var c=e.dom,g=c.arrayContains,l=c.getBody,u=e.util.createOptions,p=e.util.forEach,d=1,f={};r.prototype.create=function(){var e=this.converterCreator();return e.type=this.type,e},e.registerHighlighterType=i,s.prototype={intersects:function(e){return this.starte.start},isContiguousWith:function(e){return this.start==e.end||this.end==e.start},union:function(e){return new s(Math.min(this.start,e.start),Math.max(this.end,e.end))},intersection:function(e){return new s(Math.max(this.start,e.start),Math.min(this.end,e.end))},getComplements:function(e){var t=[];if(this.start>=e.start){if(this.end<=e.end)return[];t.push(new s(e.end,this.end))}else t.push(new s(this.start,Math.min(this.end,e.start))),this.end>e.end&&t.push(new s(e.end,this.end));return t},toString:function(){return"[CharacterRange("+this.start+", "+this.end+")]"}},s.fromCharacterRange=function(e){return new s(e.start,e.end)};var R={rangeToCharacterRange:function(e,t){var n=e.getBookmark(t);return new s(n.start,n.end)},characterRangeToRange:function(t,n,r){var i=e.createRange(t);return i.moveToBookmark({start:n.start,end:n.end,containerNode:r}),i},serializeSelection:function(e,t){for(var n=e.getAllRanges(),r=n.length,i=[],a=1==r&&e.isBackward(),s=0,h=n.length;h>s;++s)i[s]={characterRange:this.rangeToCharacterRange(n[s],t),backward:a};return i},restoreSelection:function(e,t,n){e.removeAllRanges();for(var r,i,a,s=e.win.document,h=0,o=t.length;o>h;++h)i=t[h],a=i.characterRange,r=this.characterRangeToRange(s,i.characterRange,n),e.addRange(r,i.backward)}};i("textContent",function(){return R}),i("TextRange",function(){var t;return function(){if(!t){var n=e.modules.TextRange;if(!n)throw new Error("TextRange module is missing.");if(!n.supported)throw new Error("TextRange module is present but not supported.");t={rangeToCharacterRange:function(e,t){return s.fromCharacterRange(e.toCharacterRange(t))},characterRangeToRange:function(t,n,r){var i=e.createRange(t);return i.selectCharacters(r,n.start,n.end),i},serializeSelection:function(e,t){return e.saveCharacterRanges(t)},restoreSelection:function(e,t,n){e.restoreCharacterRanges(n,t)}}}return t}}()),h.prototype={getContainerElement:function(){return n(this.doc,this.containerElementId)},getRange:function(){return this.converter.characterRangeToRange(this.doc,this.characterRange,this.getContainerElement())},fromRange:function(e){this.characterRange=this.converter.rangeToCharacterRange(e,this.getContainerElement())},getText:function(){return this.getRange().toString()},containsElement:function(e){return this.getRange().containsNodeContents(e.firstChild)},unapply:function(){this.classApplier.undoToRange(this.getRange()),this.applied=!1},apply:function(){this.classApplier.applyToRange(this.getRange()),this.applied=!0},getHighlightElements:function(){return this.classApplier.getElementsWithClassIntersectingRange(this.getRange())},toString:function(){return"[Highlight(ID: "+this.id+", class: "+this.classApplier.className+", character range: "+this.characterRange.start+" - "+this.characterRange.end+")]"}},o.prototype={addClassApplier:function(e){this.classAppliers[e.className]=e},getHighlightForElement:function(e){for(var t=this.highlights,n=0,r=t.length;r>n;++n)if(t[n].containsElement(e))return t[n];return null},removeHighlights:function(e){for(var t,n=0,r=this.highlights.length;r>n;++n)t=this.highlights[n],g(e,t)&&(t.unapply(),this.highlights.splice(n--,1))},removeAllHighlights:function(){this.removeHighlights(this.highlights)},getIntersectingHighlights:function(e){var t=[],n=this.highlights;return p(e,function(e){p(n,function(n){e.intersectsRange(n.getRange())&&!g(t,n)&&t.push(n)})}),t},highlightCharacterRanges:function(t,n,r){var i,a,o,c=this.highlights,g=this.converter,l=this.doc,d=[],f=t?this.classAppliers[t]:null;r=u(r,{containerElementId:null,exclusive:!0});var R,v,m,C=r.containerElementId,w=r.exclusive;C&&(R=this.doc.getElementById(C),R&&(v=e.createRange(this.doc),v.selectNodeContents(R),m=new s(0,v.toString().length)));var y,E,T,x,A,H;for(i=0,a=n.length;a>i;++i)if(y=n[i],A=[],m&&(y=y.intersection(m)),y.start!=y.end){for(o=0;o0},serialize:function(e){var n,r,i,s,h=this,o=h.highlights;return o.sort(t),e=u(e,{serializeHighlightText:!1,type:h.converter.type}),n=e.type,i=n!=h.converter.type,i&&(s=a(n)),r=["type:"+n],p(o,function(t){var n,a=t.characterRange;i&&(n=t.getContainerElement(),a=s.rangeToCharacterRange(h.converter.characterRangeToRange(h.doc,a,n),n));var o=[a.start,a.end,t.id,t.classApplier.className,t.containerElementId];e.serializeHighlightText&&o.push(t.getText()),r.push(o.join("$"))}),r.join("|")},deserialize:function(e){var t,r,i,o=e.split("|"),c=[],g=o[0],l=!1;if(!g||!(t=/^type:(\w+)$/.exec(g)))throw new Error("Serialized highlights are invalid.");r=t[1],r!=this.converter.type&&(i=a(r),l=!0),o.shift();for(var u,p,d,f,R,v,m=o.length;m-->0;){if(v=o[m].split("$"),d=new s(+v[0],+v[1]),f=v[4]||null,l&&(R=n(this.doc,f),d=this.converter.rangeToCharacterRange(i.characterRangeToRange(this.doc,d,R),R)),u=this.classAppliers[v[3]],!u)throw new Error("No class applier found for class '"+v[3]+"'");p=new h(this.doc,d,u,this.converter,parseInt(v[2]),f),p.apply(),c.push(p)}this.highlights=c}},e.Highlighter=o,e.createHighlighter=function(e,t){return new o(e,t)}}),e},this); -------------------------------------------------------------------------------- /app/src/main/assets/books/javascript/rangy/rangy-serializer.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Serializer module for Rangy. 3 | * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a 4 | * cookie or local storage and restore it on the user's next visit to the same page. 5 | * 6 | * Part of Rangy, a cross-browser JavaScript range and selection library 7 | * https://github.com/timdown/rangy 8 | * 9 | * Depends on Rangy core. 10 | * 11 | * Copyright 2015, Tim Down 12 | * Licensed under the MIT license. 13 | * Version: 1.3.0 14 | * Build date: 10 May 2015 15 | */ 16 | !function(e,n){"function"==typeof define&&define.amd?define(["./rangy-core"],e):"undefined"!=typeof module&&"object"==typeof exports?module.exports=e(require("rangy")):e(n.rangy)}(function(e){return e.createModule("Serializer",["WrappedSelection"],function(e,n){function t(e){return e.replace(//g,">")}function o(e,n){n=n||[];var r=e.nodeType,i=e.childNodes,c=i.length,a=[r,e.nodeName,c].join(":"),d="",u="";switch(r){case 3:d=t(e.nodeValue);break;case 8:d="";break;default:d="<"+a+">",u=""}d&&n.push(d);for(var s=0;c>s;++s)o(i[s],n);return u&&n.push(u),n}function r(e){var n=o(e).join("");return w(n).toString(16)}function i(e,n,t){var o=[],r=e;for(t=t||R.getDocument(e).documentElement;r&&r!=t;)o.push(R.getNodeIndex(r,!0)),r=r.parentNode;return o.join("/")+":"+n}function c(e,t,o){t||(t=(o||document).documentElement);for(var r,i=e.split(":"),c=t,a=i[0]?i[0].split("/"):[],d=a.length;d--;){if(r=parseInt(a[d],10),!(rc;++c)i[c]=a(r[c],t,o);return i.join("|")}function l(n,t,o){t?o=o||R.getWindow(t):(o=o||window,t=o.document.documentElement);for(var r=n.split("|"),i=e.getSelection(o),c=[],a=0,u=r.length;u>a;++a)c[a]=d(r[a],t,o.document);return i.setRanges(c),i}function f(e,n,t){var o;n?o=t?t.document:R.getDocument(n):(t=t||window,n=t.document.documentElement);for(var r=e.split("|"),i=0,c=r.length;c>i;++i)if(!u(r[i],n,o))return!1;return!0}function m(e){for(var n,t,o=e.split(/[;,]/),r=0,i=o.length;i>r;++r)if(n=o[r].split("="),n[0].replace(/^\s+/,"")==C&&(t=n[1]))return decodeURIComponent(t.replace(/\s+$/,""));return null}function p(e){e=e||window;var n=m(e.document.cookie);n&&l(n,e.doc)}function g(n,t){n=n||window,t="object"==typeof t?t:{};var o=t.expires?";expires="+t.expires.toUTCString():"",r=t.path?";path="+t.path:"",i=t.domain?";domain="+t.domain:"",c=t.secure?";secure":"",a=s(e.getSelection(n));n.document.cookie=encodeURIComponent(C)+"="+encodeURIComponent(a)+o+r+i+c}var h="undefined",v=e.util;(typeof encodeURIComponent==h||typeof decodeURIComponent==h)&&n.fail("encodeURIComponent and/or decodeURIComponent method is missing");var w=function(){function e(e){for(var n,t=[],o=0,r=e.length;r>o;++o)n=e.charCodeAt(o),128>n?t.push(n):2048>n?t.push(n>>6|192,63&n|128):t.push(n>>12|224,n>>6&63|128,63&n|128);return t}function n(){for(var e,n,t=[],o=0;256>o;++o){for(n=o,e=8;e--;)1==(1&n)?n=n>>>1^3988292384:n>>>=1;t[o]=n>>>0}return t}function t(){return o||(o=n()),o}var o=null;return function(n){for(var o,r=e(n),i=-1,c=t(),a=0,d=r.length;d>a;++a)o=255&(i^r[a]),i=i>>>8^c[o];return(-1^i)>>>0}}(),R=e.dom,S=/^([^,]+),([^,\{]+)(\{([^}]+)\})?$/,C="rangySerializedSelection";v.extend(e,{serializePosition:i,deserializePosition:c,serializeRange:a,deserializeRange:d,canDeserializeRange:u,serializeSelection:s,deserializeSelection:l,canDeserializeSelection:f,restoreSelectionFromCookie:p,saveSelectionCookie:g,getElementChecksum:r,nodeToInfoString:o}),v.crc32=w}),e},this); -------------------------------------------------------------------------------- /app/src/main/assets/books/styles/night_mode.css: -------------------------------------------------------------------------------- 1 | body { zoom: 100%; } * { background: #111111 !important; color: #ABABAB !important; } :link, :link * { color: #DBDBDC !important } :visited, :visited * { color: #5B5B5B !important } 2 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Diplomata_SC/DiplomataSC-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartmobilefactory/EpubReaderAndroid/e89d3523ce3876e0dc44e9a0d5dec8f78909d325/app/src/main/assets/fonts/Diplomata_SC/DiplomataSC-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Diplomata_SC/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Eduardo Tunni (http://www.tipo.net.ar), 2 | with Reserved Font Name "Diplomata" 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/smartmobilefactory/epubreader/sample/ChapterJavaScriptBridge.java: -------------------------------------------------------------------------------- 1 | package com.smartmobilefactory.epubreader.sample; 2 | 3 | import android.util.Log; 4 | import android.util.SparseArray; 5 | import android.webkit.JavascriptInterface; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | public class ChapterJavaScriptBridge { 11 | 12 | private static final String TAG = ChapterJavaScriptBridge.class.getSimpleName(); 13 | 14 | // in memory storage of all highlights 15 | private SparseArray highlights = new SparseArray<>(); 16 | 17 | /** 18 | * @return 19 | * [ 20 | * { 21 | * id : ..., 22 | * chapterId: ..., 23 | * start: ..., 24 | * end: ..., 25 | * color: ... 26 | * } 27 | * ] 28 | */ 29 | @JavascriptInterface 30 | public String getHighlights(int chapter) { 31 | String value = highlights.get(chapter); 32 | if (value == null) { 33 | return "[]"; 34 | } 35 | return value; 36 | } 37 | 38 | @JavascriptInterface 39 | public void onHighlightAdded(int chapter, String data) { 40 | try { 41 | JSONObject object = new JSONObject(data); 42 | highlights.put(chapter, object.getJSONArray("highlights").toString()); 43 | } catch (JSONException e) { 44 | e.printStackTrace(); 45 | } 46 | Log.d(TAG, "onHighlightAdded() called with " + "data = [" + data + "]"); 47 | } 48 | 49 | @JavascriptInterface 50 | public void onHighlightClicked(String json) { 51 | Log.d(TAG, "onHighlightClicked() called with " + "json = [" + json + "]"); 52 | } 53 | 54 | @JavascriptInterface 55 | public void onSelectionChanged(int length) { 56 | Log.d(TAG, "onSelectionChanged() called with " + "length = [" + length + "]"); 57 | } 58 | 59 | public String[] getCustomChapterScripts() { 60 | return new String[]{ 61 | "file:///android_asset/books/javascript/rangy/rangy-core.min.js", 62 | "file:///android_asset/books/javascript/rangy/rangy-classapplier.min.js", 63 | "file:///android_asset/books/javascript/rangy/rangy-highlighter.min.js", 64 | "file:///android_asset/books/javascript/rangy/rangy-serializer.min.js", 65 | "file:///android_asset/books/javascript/highlight.js" 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/smartmobilefactory/epubreader/sample/EpubSampleApp.java: -------------------------------------------------------------------------------- 1 | package com.smartmobilefactory.epubreader.sample; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.stetho.Stetho; 6 | import com.facebook.stetho.dumpapp.plugins.FilesDumperPlugin; 7 | 8 | public class EpubSampleApp extends Application { 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | 14 | Stetho.initialize(Stetho.newInitializerBuilder(this) 15 | .enableDumpapp(() -> new Stetho.DefaultDumperPluginsBuilder(this) 16 | .provide(new FilesDumperPlugin(this)) 17 | .finish()) 18 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this)) 19 | .build()); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/smartmobilefactory/epubreader/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.smartmobilefactory.epubreader.sample; 2 | 3 | import android.app.Application; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.os.StrictMode; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.util.Log; 11 | import android.view.Gravity; 12 | import android.view.View; 13 | import android.webkit.WebView; 14 | import android.widget.SeekBar; 15 | 16 | import com.smartmobilefactory.epubreader.EpubScrollDirection; 17 | import com.smartmobilefactory.epubreader.model.Epub; 18 | import com.smartmobilefactory.epubreader.model.EpubFont; 19 | import com.smartmobilefactory.epubreader.model.EpubLocation; 20 | import com.smartmobilefactory.epubreader.sample.databinding.ActivityMainBinding; 21 | 22 | import io.reactivex.Single; 23 | import io.reactivex.android.schedulers.AndroidSchedulers; 24 | import io.reactivex.schedulers.Schedulers; 25 | 26 | public class MainActivity extends AppCompatActivity { 27 | 28 | private static Single epubSingle; 29 | 30 | private static final String TAG = MainActivity.class.getSimpleName(); 31 | 32 | private NightmodePlugin nightmodePlugin; 33 | private TableOfContentsAdapter tableOfContentsAdapter; 34 | private ActivityMainBinding binding; 35 | 36 | @Override 37 | protected void onCreate(@Nullable Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | enableStrictMode(); 40 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 41 | setContentView(binding.getRoot()); 42 | 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 44 | WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG); 45 | } 46 | 47 | initToolbar(); 48 | initSettingsContainer(); 49 | 50 | ChapterJavaScriptBridge bridge = new ChapterJavaScriptBridge(); 51 | binding.epubView.getSettings().setJavascriptBridge(bridge); 52 | binding.epubView.getSettings().setCustomChapterScript(bridge.getCustomChapterScripts()); 53 | binding.epubView.getSettings().setFont(EpubFont.fromFontFamily("Monospace")); 54 | binding.epubView.setScrollDirection(EpubScrollDirection.HORIZONTAL_WITH_VERTICAL_CONTENT); 55 | 56 | nightmodePlugin = new NightmodePlugin(binding.epubView); 57 | binding.epubView.addPlugin(nightmodePlugin); 58 | 59 | tableOfContentsAdapter = new TableOfContentsAdapter(); 60 | tableOfContentsAdapter.bindToEpubView(binding.epubView); 61 | binding.contentsRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 62 | binding.contentsRecyclerView.setAdapter(tableOfContentsAdapter); 63 | 64 | tableOfContentsAdapter.jumpToChapter() 65 | .doOnNext(chapter -> { 66 | binding.drawerLayout.closeDrawer(Gravity.START); 67 | binding.epubView.gotoLocation(EpubLocation.fromChapter(chapter)); 68 | }) 69 | .subscribe(); 70 | 71 | loadEpub().doOnSuccess(epub -> { 72 | binding.epubView.setEpub(epub); 73 | tableOfContentsAdapter.setEpub(epub); 74 | if (savedInstanceState == null) { 75 | binding.epubView.gotoLocation(EpubLocation.fromChapter(10)); 76 | } 77 | }).subscribe(); 78 | 79 | observeEpub(); 80 | } 81 | 82 | Single loadEpub() { 83 | if (epubSingle == null) { 84 | Application application = getApplication(); 85 | epubSingle = Single.fromCallable(() -> Epub.fromUri(application, "file:///android_asset/The Silver Chair.epub")) 86 | .subscribeOn(Schedulers.io()) 87 | .observeOn(AndroidSchedulers.mainThread()) 88 | .cache(); 89 | } 90 | return epubSingle; 91 | } 92 | 93 | private void initToolbar() { 94 | binding.toolbar.inflateMenu(R.menu.menu); 95 | 96 | binding.toolbar.setOnMenuItemClickListener(item -> { 97 | switch (item.getItemId()) { 98 | case R.id.menu_settings: 99 | if (binding.settings.getVisibility() == View.VISIBLE) { 100 | binding.settings.setVisibility(View.GONE); 101 | } else { 102 | binding.settings.setVisibility(View.VISIBLE); 103 | } 104 | return true; 105 | default: 106 | return false; 107 | } 108 | }); 109 | 110 | binding.toolbar.setNavigationOnClickListener(v -> { 111 | binding.drawerLayout.openDrawer(Gravity.START); 112 | }); 113 | } 114 | 115 | private void initSettingsContainer() { 116 | 117 | // TEXT SIZE 118 | 119 | binding.textSizeSeekbar.setMax(30); 120 | binding.textSizeSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 121 | @Override 122 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 123 | binding.epubView.getSettings().setFontSizeSp(progress + 10); 124 | } 125 | 126 | @Override 127 | public void onStartTrackingTouch(SeekBar seekBar) { 128 | 129 | } 130 | 131 | @Override 132 | public void onStopTrackingTouch(SeekBar seekBar) { 133 | 134 | } 135 | }); 136 | 137 | // DISPLAY FONT 138 | binding.diplomata.setOnClickListener(v -> { 139 | binding.epubView.getSettings().setFont(EpubFont.fromUri("DiplomataSC", "file:///android_asset/fonts/Diplomata_SC/DiplomataSC-Regular.ttf")); 140 | }); 141 | 142 | binding.monospace.setOnClickListener(v -> { 143 | binding.epubView.getSettings().setFont(EpubFont.fromFontFamily("Monospace")); 144 | }); 145 | 146 | binding.serif.setOnClickListener(v -> { 147 | binding.epubView.getSettings().setFont(EpubFont.fromFontFamily("Serif")); 148 | }); 149 | 150 | binding.sanSerif.setOnClickListener(v -> { 151 | binding.epubView.getSettings().setFont(EpubFont.fromFontFamily("Sans Serif")); 152 | }); 153 | 154 | // DISPLAY STRATEGY 155 | 156 | binding.horizontalVerticalContent.setOnClickListener(v -> { 157 | binding.epubView.setScrollDirection(EpubScrollDirection.HORIZONTAL_WITH_VERTICAL_CONTENT); 158 | }); 159 | 160 | binding.verticalVerticalContent.setOnClickListener(v -> { 161 | binding.epubView.setScrollDirection(EpubScrollDirection.VERTICAL_WITH_VERTICAL_CONTENT); 162 | }); 163 | 164 | binding.singleChapterVertical.setOnClickListener(v -> { 165 | binding.epubView.setScrollDirection(EpubScrollDirection.SINGLE_CHAPTER_VERTICAL); 166 | }); 167 | 168 | binding.nightmode.setOnCheckedChangeListener((buttonView, isChecked) -> { 169 | nightmodePlugin.setNightModeEnabled(isChecked); 170 | }); 171 | 172 | } 173 | 174 | private void observeEpub() { 175 | binding.epubView.currentLocation() 176 | .doOnNext(xPathLocation -> { 177 | Log.d(TAG, "CurrentLocation: " + xPathLocation); 178 | }).subscribe(); 179 | 180 | binding.epubView.currentChapter() 181 | .doOnNext(chapter -> { 182 | Log.d(TAG, "CurrentChapter: " + chapter); 183 | }).subscribe(); 184 | } 185 | 186 | private void enableStrictMode() { 187 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 188 | .detectAll() 189 | .penaltyLog() 190 | .build()); 191 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 192 | .detectAll() 193 | .penaltyLog() 194 | .build()); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/smartmobilefactory/epubreader/sample/NightmodePlugin.java: -------------------------------------------------------------------------------- 1 | package com.smartmobilefactory.epubreader.sample; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.smartmobilefactory.epubreader.EpubView; 6 | import com.smartmobilefactory.epubreader.EpubViewPlugin; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class NightmodePlugin extends EpubViewPlugin { 14 | 15 | private EpubView epubView; 16 | 17 | public NightmodePlugin(EpubView epubView) { 18 | this.epubView = epubView; 19 | } 20 | 21 | private boolean nightModeEnabled = false; 22 | 23 | public void setNightModeEnabled(boolean enabled) { 24 | nightModeEnabled = enabled; 25 | if (nightModeEnabled) { 26 | epubView.setBackgroundColor(Color.parseColor("#111111")); 27 | } else { 28 | epubView.setBackgroundColor(Color.WHITE); 29 | } 30 | notifyDataChanged(); 31 | } 32 | 33 | @NotNull 34 | @Override 35 | public List getCustomChapterCss() { 36 | if (nightModeEnabled) { 37 | return Collections.singletonList("file:///android_asset/books/styles/night_mode.css"); 38 | } else { 39 | return Collections.emptyList(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/smartmobilefactory/epubreader/sample/TableOfContentsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.smartmobilefactory.epubreader.sample; 2 | 3 | import android.graphics.Color; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.ViewGroup; 7 | 8 | import com.smartmobilefactory.epubreader.EpubView; 9 | import com.smartmobilefactory.epubreader.model.Epub; 10 | import com.smartmobilefactory.epubreader.sample.databinding.TableOfContentsItemBinding; 11 | import com.smartmobilefactory.epubreader.utils.BaseDisposableObserver; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import io.reactivex.Observable; 17 | import io.reactivex.subjects.PublishSubject; 18 | import nl.siegmann.epublib.domain.TOCReference; 19 | 20 | public class TableOfContentsAdapter extends RecyclerView.Adapter { 21 | 22 | private int currentTocPosition = -1; 23 | 24 | private final List tableOfContents = new ArrayList<>(); 25 | private Epub epub; 26 | 27 | private PublishSubject jumpToChapter = PublishSubject.create(); 28 | 29 | public void bindToEpubView(EpubView epubView) { 30 | epubView.currentChapter() 31 | .filter(integer -> epub != null) 32 | .filter(integer -> epub.equals(epubView.getEpub())) 33 | .doOnNext(chapter -> { 34 | currentTocPosition = epub.getTocPositionForSpinePosition(chapter); 35 | notifyDataSetChanged(); 36 | }) 37 | .subscribe(new BaseDisposableObserver<>()); 38 | } 39 | 40 | public void setEpub(Epub epub) { 41 | this.epub = epub; 42 | tableOfContents.clear(); 43 | fillToc(epub.getBook().getTableOfContents().getTocReferences()); 44 | 45 | notifyDataSetChanged(); 46 | } 47 | 48 | private void fillToc(List tocReferences) { 49 | for (TOCReference tocReference : tocReferences) { 50 | tableOfContents.add(tocReference); 51 | fillToc(tocReference.getChildren()); 52 | } 53 | } 54 | 55 | @Override 56 | public VH onCreateViewHolder(ViewGroup parent, int viewType) { 57 | return new VH(TableOfContentsItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); 58 | } 59 | 60 | @Override 61 | public void onBindViewHolder(VH holder, int position) { 62 | TOCReference tocReference = tableOfContents.get(position); 63 | String title = tocReference.getTitle(); 64 | if (title == null) { 65 | title = "Chapter " + (position + 1); 66 | } 67 | holder.binding.chapterTitle.setText(title); 68 | 69 | if (position == currentTocPosition) { 70 | holder.binding.chapterTitle.setBackgroundColor(Color.LTGRAY); 71 | } else { 72 | holder.binding.chapterTitle.setBackgroundColor(Color.TRANSPARENT); 73 | } 74 | 75 | holder.binding.getRoot().setOnClickListener(v -> { 76 | // search correct chapter (spine position) for toc entry 77 | int spinePosition = epub.getSpinePositionForTocReference(tocReference); 78 | if (spinePosition >= 0) { 79 | jumpToChapter.onNext(spinePosition); 80 | } 81 | }); 82 | 83 | } 84 | 85 | @Override 86 | public int getItemCount() { 87 | if (epub == null) { 88 | return 0; 89 | } 90 | return tableOfContents.size(); 91 | } 92 | 93 | public Observable jumpToChapter() { 94 | return jumpToChapter; 95 | } 96 | 97 | static class VH extends RecyclerView.ViewHolder { 98 | 99 | TableOfContentsItemBinding binding; 100 | 101 | public VH(TableOfContentsItemBinding binding) { 102 | super(binding.getRoot()); 103 | this.binding = binding; 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dehaze.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | 25 | 26 | 30 | 31 | 35 | 36 | 46 | 47 | 53 | 54 | 59 | 60 | 66 | 67 | 70 | 71 | 74 | 75 |