render the epub book in android? - android

I am try to show epub book in android pad. I can parse the html and css, in order to show the book's content and format, perhaps the book include pictures, It seems that I have two option:
use Webview.
Write a customer view, so that it can render html/css --- it seems a very complicated task.
Which is the good way? If I have to use WebView, how about the page break logic, since webview parse one html file in one page, I can not find the page break in webview.

I have developed a native epub player for android and ios
Code I shared here is part of my product source code, copying and pasting of it will not work for you. Consider it as reference.
I have used webview in android and uiwebview in ios making custom view and parsing html/css is almost like developing a new rendering engine (i.e browser).Its a tedious and complex.
Briefly I give you the steps I have followed for android
Create a custom webview
load url and write call back clients (WebViewClient,WebChromeClient)
after webview load do pagination using below method
Code:
private class MyWebClient extends WebViewClient
{
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url)
{
super.onPageFinished(view, url);
final MyWebView myWebView = (MyWebView) view;
String varMySheet = "var mySheet = document.styleSheets[0];";
String addCSSRule = "function addCSSRule(selector, newRule) {"
+ "ruleIndex = mySheet.cssRules.length;"
+ "mySheet.insertRule(selector + '{' + newRule + ';}', ruleIndex);"
+ "}";
String insertRule1 = "addCSSRule('html', 'padding: 0px; height: "
+ (myWebView.getMeasuredHeight()/getContext().getResources().getDisplayMetrics().density )
+ "px; -webkit-column-gap: 0px; -webkit-column-width: "
+ myWebView.getMeasuredWidth() + "px;')";
myWebView.loadUrl("javascript:" + varMySheet);
myWebView.loadUrl("javascript:" + addCSSRule);
myWebView.loadUrl("javascript:" + insertRule1);
}
}
private class MyWebChromeClient extends WebChromeClient
{
#Override
public void onProgressChanged(WebView view, int newProgress)
{
super.onProgressChanged(view, newProgress);
// GlobalConstants.ENABLE_WEB_VIEW_TOUCH = false;
if(newProgress == 100)
{
postDelayed(new Runnable()
{
#Override
public void run()
{
calculateNoOfPages();
}
},300);
}
}
}
private void calculateNoOfPages()
{
if(getMeasuredWidth() != 0)
{
int newPageCount = computeHorizontalScrollRange()/getMeasuredWidth();
}
}
Inject jquery.js into webview:
private void addJQueryJS()
{
String path = "file:///android_asset/JSLibraries/jquery.min.js";
String data = "{\"MethodName\":\"onJQueryJSLoaded\",\"MethodArguments\":{}}";
String callBackToNative = " jsInterface.callNativeMethod('jstoobjc:"+data+"');";
String script = "function includeJSFile()"
+"{"
+"function loadScript(url, callback)"
+"{"
+"var script = document.createElement('script');"
+"script.type = 'text/javascript';"
+"script.onload = function () {"
+"callback();"
+"};"
+"script.src = url;"
+"if(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0])"
+"{"
+"(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);"
+"}"
+"else { callback(); }"
+"}"
+"loadScript('"+path+"', function ()"
+"{"
+callBackToNative
+"});"
+"} ; includeJSFile();";
loadUrl("javascript: "+script);
}
wrap all words into spans - used for highlighting text and navigating to a page.
there would be 3 webviews - current page ,next page and previous page.You should set offset to webview scroll according to page count of that chapter.
lets say one .html file has content of 3 pages - previous webview is first page,current webview is second page,next webview is third page but all webviews loaded the same url.But their content offset is different.
write you own page swiping logic instead of using viewpager.Just pass the current page to the adapter then adapter will return you the next page and previous page.by some calculations.
Code:
#Override
public PageView getPreviousView(PageView oldPage)
{
MyWebView oldWebView = ((PageView)oldPage).getWebView();
int chapterIndex = oldWebView.getData().getIndexOfChapter();
int pageIndex = oldWebView.getData().getIndexOfPage();
int pageCount = oldWebView.getData().getChapterVO().getPageCount();
pageIndex--;
if(pageIndex < 0)
{
pageIndex = 0;
chapterIndex--;
if(chapterIndex<0)
{
//return the same page
chapterIndex = 0;
return null;
}
else
{
//previous chapter last page
PageView pageView = new PageView(oldPage.getContext(),_mViewPager);
MyWebView webView= pageView.getWebView();
PageVO data = new PageVO();
data.setChapterVO(_chaptersColl.get(chapterIndex));
data.setIndexOfChapter(chapterIndex);
data.setIndexOfPage(-2);
webView.setData(data);
return pageView;
}
}
else if(pageIndex <= pageCount-1)
{
//same chapter previous page
PageView pageView = new PageView(oldPage.getContext(),_mViewPager);
MyWebView webView= pageView.getWebView();
PageVO data = new PageVO();
data.setChapterVO(_chaptersColl.get(chapterIndex));
data.setIndexOfChapter(chapterIndex);
data.setIndexOfPage(pageIndex);
webView.setData(data);
return pageView;
}
return oldPage;
}
#Override
public PageView getNextView(PageView oldPage)
{
MyWebView oldWebView = ((PageView)oldPage).getWebView();
int chapterIndex = oldWebView.getData().getIndexOfChapter();
int pageIndex = oldWebView.getData().getIndexOfPage();
int pageCount = oldWebView.getData().getChapterVO().getPageCount();
pageIndex++;
if(pageIndex>=pageCount)
{
pageIndex=0;
chapterIndex++;
if(chapterIndex>=_chaptersColl.size())
{
//end of the chapters and pages so return the same page
chapterIndex--;
return null;
}
else
{
//next chapter first page
PageView pageView = new PageView(oldPage.getContext(),_mViewPager);
MyWebView webView= pageView.getWebView();
PageVO data = new PageVO();
data.setChapterVO(_chaptersColl.get(chapterIndex));
data.setIndexOfChapter(chapterIndex);
data.setIndexOfPage(pageIndex);
webView.setData(data);
return pageView;
}
}
else
{
//next page in same chapter
PageView pageView = new PageView(oldPage.getContext(),_mViewPager);
MyWebView webView= pageView.getWebView();
PageVO data = new PageVO();
data.setChapterVO(_chaptersColl.get(chapterIndex));
data.setIndexOfChapter(chapterIndex);
data.setIndexOfPage(pageIndex);
//data.setPageCount(pageCount);
webView.setData(data);
return pageView;
}
}
No need to use any third party libs .Just need to spend good amount of time to write every thing your own.

Nice One, But in Question... :-)
I don't think any Page Break logic for android webview is available, As per your concern WebView is the good choice to display .epub file (You can add many functionality like, highlight, search, bookmark etc..). And If you found that one then what about if device size is changed. What I am doing is, I just display WebPage in webview and disable scroll, Then I can find the max height of webview, and device screen size (Height and width), Now I have put two buttons for next and previous pages, which just scroll page according to height of device size..
Something easy.. Try this if you want to... (This is my personal opinion may be I am wrong on this)

There's this javascript library that takes care of the pagination issue
http://monocle.inventivelabs.com.au/
This project uses it in android
https://github.com/sharathpuranik/chaek-android
Grab the sourcecode and take a look.

Related

How to load only html(not load css,png,ico etc) in android webview?

I'm trying to develop android app using webview and JavaScriptInterface like this.
private static final String ENTRY_URL = "https://www.google.com";
mWebView.setWebViewClient(new WebViewClient() {
...
#Override
public void onPageFinished(WebView view, String url) {
if (url.equals(ENTRY_URL)) {
String keyword = "tistory";
String script = "javascript:function afterLoad() {"
+ "document.getElementById('keyword').value = '" + keyword + "';"
+ "document.forms[0].setAttribute('onsubmit', 'window.Zeany.justDoIt(elements[0].value); return true;');"
+ "};"
+ "afterLoad();";
view.loadUrl(script);
}
}
}
when I call method (mwebview.load("https://www.google.com")), I want to load only html file. Beacuse of Download Speed
I need only html, not css,png. when download all resources, webview load speed is too slow.
I really want to get only html. How to I achieve this goal?
Using this code you should be able to do that.
WebSettings settings = webview.getSettings();
settings.setBlockNetworkLoads(true);
settings.setBlockNetworkImage(true);
settings.setJavaScriptEnabled(false);

Android:Getting meta name="description" in Webview

I am making an application which uses Android WebView. This application displays page title and page description of the URL that is loaded. As a sample I am using following url
http://in.news.yahoo.com/ca-chief-slams-racist-comments-over-fawad-ahmeds-071749766.html?.tsrc=yahoo
Getting page title is easy
public void onPageFinished (WebView view, String url) {
String title = view.getTitle();
}
If you do a view source of the above url in Chrome, this title is fetched from
<title>CA chief slams `racist` comments over Fawad Ahmed's `beer-branded kit` refusal - Yahoo! News India</title>
Now, I need to get meta name "description", so as to show page description, which is as given below
<meta name="description" lang="en-IN" content="'CA chief slams `racist` comments over Fawad Ahmed's `beer-branded kit` refusal' on Yahoo! News India. Islamabad, Sept 5 (ANI): Cricket Australia (CA) chief James Sutherland has slammed 'racist comments' aimed at Pakistan-born Australian leg-spinner Fawad Ahmed following his refusal to wear a kit displaying the logo of beer brand VB due to 'religious reasons'."/>
Android web view doesn't have an API to get description from meta tag "description".
How is it possible to get meta tags from document element?
You can solve the problem by this way:
private class JsInterface {
#JavascriptInterface
#SuppressWarnings("unused")
public void processHTML(String content) {
//handle content
}
}
mWebView.addJavascriptInterface(new JsInterface(), "CC_FUND");
mWebView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
mWebView.loadUrl("javascript:window.CC_FUND.processHTML( (function (){var metas = document.getElementsByTagName('meta'); \n" +
"\n" +
" for (var i=0; i<metas.length; i++) { \n" +
" if (metas[i].getAttribute(\"name\") == \"description\") { \n" +
" return metas[i].getAttribute(\"content\"); \n" +
" } \n" +
" } \n" +
"\n" +
" return \"\";})() );");
super.onPageFinished(view, url);
}
}
I think what you are looking for is String.contains("meta name=\"description\"").
That would be the easiest method anyway. You could probably piece together a result from the code in how to get html content from a webview?
Another way would be to use the WebView.addJavascriptInterface() to insert the code from murala's answer, but I'm not sure how you would retrieve the result. Use my first idea.
Kotlin code:
if you use evaluateJavascript then not need to define javascriptInterface.
call evaluateJavascript then use ValueCallback<String> resultCallback callback value.
webview.evaluateJavascript(
"""
function getOGPImageUrl(){
var metas = document.getElementsByTagName('meta');
for (var i=0; i<metas.length; i++) {
if (metas[i].getAttribute("property") == "og:image") {
return metas[i].getAttribute("content");
}
}
return "";
};
getOGPImageUrl();
""".trimIndent()) {
// this is ValueCallback block
Logger.d("getOGPImageUrl: $it")
}
document.getElementsByName("title"); returns a set of elements not a single element so within a cycle you could use element.tagName to get the tag
basicly
document.getElementsByName("title")[0].tagName should work
(or)
Just use something like :
var author = $('meta[name=author]').attr("content");

Application Error on Android Cordova/Phonegap Application

I have a cordova (2.7.0) android app that is crashing with an Application Error when it tries to load an iframe where the source has a protocol relative (network-path reference) src.
For instance, if the iframe is:
<iframe src="//instagram.com/p/beGdCuhQYl/embed/?wmode=opaque&wmode=opaque" width="800" height="928" style="border:0;" frameborder="0"></iframe>
Then the app tries to load the source from
file://instagram.com/p/beGdCuhQYl/embed/?wmode=opaque&wmode=opaque
Since the html page that loads this iframe is loaded from the file system, it makes sense that it is doing this. However, is there a way to stop the app from crashing? The same cordova app on iOS just doesn't load anything, and has a blank iframe. I would be nice if the android app behaved the same way.
It would be even nicer if there was a way to tell the cordova app to load these types of urls from http:// and not file:// but I think that is asking too much.
Ok, so I ended up doing this in two parts. First part, try to fix as many protocol relative urls as possible in javascript, and the second part was to provide some java code to ignore any that I missed.
First part (uses jQuery)
/**
* Takes text, looks for elements with src attributes that are
* protocol relative (//) and converts them to http (http://)
* #param {String} text the text that you want to fix urls in
* #returns {String} the updated text with corrected urls
*/
fixProtocolRelativeUrlsInText: function(text) {
var $html, $elements;
try {
$html = $('<div>' + text + '</div>');
$elements = $html.find('[src^="//"]');
if ($elements.length) {
$elements.each(function() {
var $this = $(this);
$this.attr('src', 'http:' + $this.attr('src'));
});
return $html.html();
} else {
return text;
}
} catch(ex) {
return text;
}
},
Second part:
/**
* Override the default makeWebViewClient and provide a custom handler for protocol
* relative urls.
*/
#Override
public CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
//
// We already try to fix protocol relative urls in the javascript. But this is a safety net in case anything
// gets through. So, in order to not crash the app, lets handle these types ourself and just swallow them up
// for now. The url won't load but at least it won't crash the app either. By the time the protocol relative
// url gets in here, it has the file: appended to it already. If it was a true file:// path to something on the
// device, then it will have file:///some/path, and if it was a protocol relative url that was converted to a
// file:// then it will have file://some.domain, so we look for urls that don't have the three /'s
//
final Pattern pattern = Pattern.compile("^file://[^/].*$");
CordovaWebViewClient webViewClient;
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
webViewClient = new CordovaWebViewClient(this, webView) {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
Log.i(LOG_TAG, "swallowing url '" + url + "'");
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
};
} else {
webViewClient = new IceCreamCordovaWebViewClient(this, webView) {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
Log.i(LOG_TAG, "swallowing url '" + url + "'");
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
};
}
return webViewClient;
}
Cordova doesn't support protocol relative src, it expects you to specify either file, or http.

Display a part of the webpage on the webview android

I want to make an app which loads the content from the webpage into webview. I want to show only a particular thing in the entire webview, not the whole content of the webpage.
Here is an example. If I use: http://us.m.yahoo.com/w/search%3B_ylt=A2KL8xs0vUBQMg0AwAkp89w4?submit=oneSearch&.intl=us&.lang=en&.tsrc=yahoo&.sep=fp&p=digital+cameras&x=0&y=0 as the URL for the webview, it loads all the contents of the page on the webview. But I want to remove the banner of the page and show it on the webview of my application.
I have tried using adblocker using CSS tags, but that is not helping me. Please give me some idea for overcoming this problem.
Thanks.
Thank You for the answer Zyber. I have solved it using injection of JavaScript in the code for WebView in android.
final WebView webview = (WebView)findViewById(R.id.browser);
webview.getSettings().setJavaScriptEnabled(true);
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url)
{
webview.loadUrl("javascript:(function() { " +
"document.getElementsByTagName('header')[0].style.display="none"; " +
"})()");
}
});
webview.loadUrl("http://code.google.com/android");
This solved my purpose and it is easy to use to.
I got the solution to add this:
view.getSettings().setJavaScriptEnabled(true);
view.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url)
{
view.loadUrl("javascript:(function() { " +
"var head = document.getElementsByClassName('header')[0].style.display='none'; " +
"var head = document.getElementsByClassName('blog-sidebar')[0].style.display='none'; " +
"var head = document.getElementsByClassName('footer-container')[0].style.display='none'; " +
"})()");
}
});
view.loadUrl("your url");
Adding (var head =) looks like to hide my class in webview.
I hope this will be helpful for someone.
check Jsoup it provides an library which gives an easy way of extracting Html elements from a webpage
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(url.toURI());
HttpResponse resp = client.execute(get);
String content = EntityUtils.toString(resp.getEntity());
Document doc = Jsoup.parse(content);
Elements ele = doc.select("div.classname");
This example executes an Http GET and then extracts an Div element with the class "classname" which you can then load in your webview

WebView and Assets problems

My current monodroid project is having two problems - one in loading from Assets and the other is a generated webpage being passed to be displayed and their inter-related which makes things more annoying!
The assets problem is simple enough, the file can't be found. My current code looks like this
Android.App.Application.SynchronizationContext.Post(delegate
{
string uri = "file:///android_asset/StyleSheet.css";
string settings = string.Empty;
using (var input = c.Assets.Open(uri))
using (StreamReader sr = new System.IO.StreamReader(input))
{
settings = sr.ReadToEnd();
}
string CSS = "<html><head><style>" + settings + "</style></head><body style='height:600px;background: url(Back-AQHA.jpg)' >";
retStr = CSS + tableData.Content + "</body></html>";
callback(retStr);
}, null);
This dies at the sr.ReadToEnd() line as the file can't be found. All the permissions are correctly set for me to be able to access the assets directory and read in.
c is just a Context passed to the method
Now, assuming that the file had be read in and passed back to the caller, it now fails to be displayed (tested this by generating a page within the app and passing it back)... The webviewer code is bog standard
public class webservice_webview : Activity
{
WebView web_view;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.webview);
string res = base.Intent.GetStringExtra("content");
if (res.Length > 0)
{
web_view = FindViewById<WebView>(Resource.Id.webviewer);
web_view.Settings.JavaScriptEnabled = true;
web_view.LoadData(res, "text/html", null);
web_view.SetWebViewClient(new websiteviewClient());
}
else
return;
}
private class websiteviewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
view.LoadData(url, "text/html", null);
return true;
}
}
public override bool OnKeyDown(Android.Views.Keycode keyCode, Android.Views.KeyEvent e)
{
if (keyCode == Keycode.Back && web_view.CanGoBack())
{
web_view.GoBack();
return true;
}
return base.OnKeyDown(keyCode, e);
}
}
}
When the content is passed over and rendered, all that results is a dead webpage saying that the url data;text/html;null,[generated html] may have moved or has gone. How do I get the generated page to display?
Thanks
Paul
It might be worth check your assets are in the assets folder and are marked in the properties pane as being "AndroidAsset" - also please make sure capitalisation is correct.
Assuming they are then you can just pass the assets direct to the webview using code like:
var web = FindViewById<WebView>(Resource.Id.AboutWebView);
web.LoadUrl("file:///android_asset/About/index.html");
from https://github.com/slodge/mobile-samples/blob/master/MWC/MWC.Droid/Views/About/AboutXamarinView.cs
If you do want to read in and manipulate the html text first, then try code like: Assets.Open("About/index.html") - i.e. without the file: scheme attached.
In your second sample, the code for web_view.LoadData(res, "text/html", null); looks correct - but then I'm not sure what your logic inside ShouldOverrideUrlLoading does - that looks like it might just prevent things from loading?

Categories

Resources