What is SkyEpub for Android?
Sky Epub for Android is the general SDK to build epub reader.
It is component based SDK that can be easily insert the Activity into the Android project.
Most of APIs are compatible with SkyEpub for iOS
Developers can easily build and freely modify the powerful epub reader using SkyEpub SDK.
The features of SkyEpub for Android
- Provide the various SDK for creating text reader of Epub 2.0/3.0 (ePub, ePUB, EPub, epub).
- Auto pagenation is avaiable for single page and double page mode in Reflowable Layaut Type of Epub.
- Provide caching funtion and unlimited zoom in/out for Fixed Layout Type of Epub.
- Very light but powerful engine.
- Provide the information listener and text structure for flexible UI component.
- User friendly.
Library Import
This is for the developers using Eclipse to build Android applications.
To use SkyEpub for Android, skyepub.jar must be imported.
In order to import skyepub.jar into your project, following procedures must be executed.
- [Right] Button click on SRC in Package Explorer.
- Choose import in menu and then Select FileSystem.
- Click [browse] button, choose the folder which contains skyepub.jar
- Select skyepub.jar and press [Finish] button.
- In Package Explorer, Choose Top most project name and [Right] Click.
- In the menu choose [properties].
- Choose [add jars] in java build path.
- Select skyepub.jar in SRC folder.
Implementing the Listener for text selection.
When the text is selected or selected area has been changed, call-back functions are called.
Generally when the user performs LongTouch on text, the text will be reversed and selectors are displayed.
By moving selectors, text can be selected.
When text selection is started, selectionStarted is called.
When text selection is changed by moving selectors, selectionChanged is called.
When user touch off the screen, finally selectionEnded is called.
In this slectionEnded function, you may perform text related processes.
Mainly the case sensitive menu for text processing can be presented to the user.
class SelectionDelegate implements SelectionListener {
// startX, startY are the coordinate of start selection area.
// endX,endY are the coodinate for end selection area.
// selectedText is the text which user select now.
// in case user touches down selection bar, normally hide markButton
public void selectionStarted(int startX, int startY, int endX,int endY, String selectedText) {
Log.w("EPub", "selectionStarted");
hideButton();
};
// this may happen when user dragging selection.
public void selectionChanged(int startX, int startY, int endX,int endY, String selectedText) {
Log.w("EPub", "selectionChanged :"+selectedText);
hideButton();
};
// in case user touches up selection bar,custom menu view has to be
// shown near endX,endY.
public void selectionEnded(int startX, int startY, int endX, int endY,String selectedText) {
Log.w("EPub", "selectionEnded");
if ((endY + 30 + markButton.getHeight()) < ePubView.getHeight())
moveButton(endX, endY + 30);
else
moveButton(startX, startY - 30 - markButton.getHeight());
showButton();
};
// selection cancelled by user.
public void selectionCancelled() {
Log.w("EPub", "selectionCancelled");
hideButton();
}
}
The sample codes for text processing menu.
While the user handles text related action, various menu is available in UI.
In this sample project, simple button is provided.
After the user performs LongTouch on screen, button will be show up. (showButton)
While the user changes the selected area, button should be hidden (hideButton)
Finally when the user touches off the screen, you shoud move the button at that position.
And show the button again.
rv.markSelection(0x66FFFF00,"");
If the user presses the button, call markSelection function to highlight selected area in specific color.
In this case, you can set the color with alpha, red, green and blue.
If alpha is opaque, text will be overwritten by highlighted color.
// move the button to desired spot.
private void moveButton(int x, int y) {
RelativeLayout.LayoutParams markButtonParam = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT); // width,height
markButtonParam.leftMargin = x;
markButtonParam.topMargin = y;
markButton.setLayoutParams(markButtonParam);
}
// show the button on the screen
private void showButton() {
markButton.setVisibility(View.VISIBLE);
}
// hide the button.
private void hideButton() {
markButton.setVisibility(View.INVISIBLE);
markButton.setVisibility(View.GONE);
}
// highlight the selected area with 0x66FFFF00 color.
private void mark() {
rv.markSelection(0x66FFFF00,"");
}
// create the Listener to handle the processing when button is pressed.
private OnClickListener onClickListener = new OnClickListener() {
public void onClick(View arg) {
// if mark button is clicked, ...
if (arg.getId() == 8083) {
hideButton();
mark();
}
}
};
Processing the information about highlight.
HighlightListener is the listener to handle highlight on selected area.
When user highlight the selected area by calling the functon like rv.markSelection(0x66FFFF00,””),
the engine will generate onHighlightInserted call-back function.
Highlight object which is passed in this function contains which all information about hightlight inserted now.
Most of these information should be saved to the persistent storage on the device.
Especially chapterIndex,startIndex,startOffset,endIndex and endOffset must be stored.
These are core information to obtain the uniqueness of highlight.
- chapterIndex is the index for xthml in spine of epub.
- startIndex is the element index where highlight starts.
- startOffset is the start offset in start element.
- endIndex is the element index where highlight ends.
- endOffset is the end offset in end element which highlight covers.
public class Highlight {
public int code;
public int chapterIndex; // the chapter Index in epub
public double pagePositionInBook; // the global position of highlight in book
public double pagePositionInChapter; // the position of highlight in chapter
public int startIndex; // the start index of element which highlight covers
public int endIndex; // the end index of element which highlight covers
public int startOffset; // the start charater offset in start element.
public int endOffset; // the end charater offset in end element
public int color; // highlight color eg. 0x33FFFF00 33:alpha, ff:red, ff:green and 00:blue.
public String text; // highlighed text
public int left; // x coodination where highlight starts
public int top; // y coodination where highlight starts
public String note; // the string for note or memo, this is not displayed. it is used just for holding information.
public boolean isNote; // the flag to denote whether this is note(memo) or not. it is used just for holding information.
}
If new highlight is inserted on existing highlights, the engine generates event to require the deletion of old highlights.
In this case, onHighlightDeleted will be called by the engine.
Then developer has to remove the existing highlights denoted by event from persistent storage.
If the user touches on highlgiht, the engine generates onHighlightHit event.
In this case onHighlightHit will appear.
When the new chapter is loading or has to be loaded engine requires all highlights information from the outside.
Developer should retrieve all highligts included in specific chapter from the persistent memory and deliver them to the engine.
class HighlightDelegate implements HighlightListener {
public void onHighlightDeleted(Highlight highlight) {
for (int index = 0; index < highlights.getSize(); index++) {
Highlight temp = highlights.getHighlight(index);
if (temp.chapterIndex == highlight.chapterIndex
&& temp.startIndex == highlight.startIndex
&& temp.endIndex == highlight.endIndex
&& temp.startOffset == highlight.startOffset
&& temp.endOffset == highlight.endOffset) {
highlights.removeHighlight(index);
}
}
}
public void onHighlightInserted(Highlight highlight) {
highlights.addHighlight(highlight);
}
public void onHighlightHit(Highlight highlight, int x, int y) {
debug(highlight.text);
}
public Highlights getHighlightsForChapter(int chapterIndex) {
Highlights results = new Highlights();
for (int index = 0; index < highlights.getSize(); index++) {
Highlight highlight = highlights.getHighlight(index);
if (highlight.chapterIndex == chapterIndex) {
results.addHighlight(highlight);
}
}
return results;
}
}
The processing when the page has moved.
When the page has moved, the engine passes all informaton through the onPageMoved in PageMovedListener.
PageInformation object passed as parameter, the most of information about new page is included in this object.
public class PageInformation {
/**
* the index of the chapter that this page belongs to
*/
public int chapterIndex;
/**
* the number of chapter that epub has.
*/
public int numberOfChaptersInBook;
/**
* the page index from the start of this chapter.
*/
public int pageIndex;
/**
* the total page number of this chapter.
*/
public int numberOfPagesInChapter;
/**
* the title of this chapter.
*/
public String chapterTitle;
/**
* all Highlights in this page.
*/
public Highlights highlightsInPage;
/**
* the position from the start of this chapter.
*/
public double pagePositionInChapter;
/**
* the global postion from the start of this book.
*/
public double pagePositionInBook;
/**
* the description on this page.
*/
public String pageDescription;
/**
* the index of the first element in this page.
*/
public int startIndex;
/**
* the index of the end element in this page.
*/
public int endIndex;
public int startOffset;
public int endOffset;
}
The following is the example code for onPageMoved.
class PageMovedDelegate implements PageMovedListener {
public void onPageMoved(PageInformation pi) {
String msg = String.format("pn:%d/tn:%d ps:%f si:%d ei:%d ",
pi.pageIndex, pi.numberOfPagesInChapter,
pi.pagePositionInBook, pi.startIndex, pi.endIndex);
for (int i=0; i<pi.highlightsInPage.getSize(); i++) {
Highlight th = pi.highlightsInPage.getHighlight(i);
msg+=String.format(" highlight si:%d so:%d ei:%d eo:%d",th.startIndex,th.startOffset,th.endIndex,th.endOffset);
}
msg+=pi.pageDescription;
debug(msg);
}
}
Processing user’s touch actions.
When the user touches on screen, developer may handle this event if necessary.
In this case onClick even of ClickDelegate is called.
If the user touches on image, onImageClicked is called.
In this case, the path for image source is passed through the src parameter,
Using this, extra image viewer can be shown up.
class ClickDelegate implements ClickListener {
public void onClick(int x,int y) {
Log.w("EPub","Click Detected at"+x+":"+y);
}
public void onImageClicked(int x,int y,String src) {
Log.w("EPub","Click on Image Detected at"+x+":"+y+" src:"+src);
}
}
When the table of contents is needed.
If you need the table of contents, follow the next procedures.
You can get the table of contents for epub using getNavPoints() function.
Using this, this information can be delivered to user after rendering properly.
private void displayNavPoints() {
NavPoints nps = rv.getNavPoints();
for (int i=0; i<nps.getSize(); i++) {
NavPoint np = nps.getNavPoint(i);
debug(""+i+":"+np.text);
}
// modify one NavPoint object at will
NavPoint onp = nps.getNavPoint(1);
onp.text = "preface - it is modified";
for (int i=0; i<nps.getSize(); i++) {
NavPoint np = nps.getNavPoint(i);
debug(""+i+":"+np.text+" :"+np.sourcePath);
}
}
Jumping to the certain position in Epub.
If you need to jump to certain position in epub, following functions can be used.
Because of the uniqueness of the epub Reflowable layout, there is no fixed page numbers even if there are changes on fonts or lining.
So absolute positon in epub is expressed as pagePositionInBook.
This is float value from 0.0f to 1.0f for entile book.
If gotoPageByPagePositionInBook(float pagePositionInBook) is called, you can jump to absolute position of pagePositionInBook.
Also if you need to jump by NaviPoint in NCX, gotoPageByNavPoint(NavPoint navPoint) can be used.
When you have to jump to one of highlights, you can use gotoPageByHighlight(Highlight highlight) function.
In this case, Highlight object is supposed to have core coodinates information such as chapterIndex, startIndex,startOffset, endIndex, endOffset.
/**
* goes to the page by NavPoint index
* @param index NavPoint index in NavMap
*/
public void gotoPageByNavPointIndex(int index);
/**
* goes to the page by Highlight object
* @param highligt Highlight object
*/
public void gotoPageByHighlight(Highlight highlight);
/**
* goes to the page by NavPoint object
* @param navPoint NavPoint object in NavMap
*/
public void gotoPageByNavPoint(NavPoint navPoint);
/**
* goes to the page by global position in book.
* @param pagePositionInBook global position in book.
*/
public void gotoPageByPagePositionInBook(double pagePositionInBook);
/**
* sets the start position in book before loading epub
* @param startPositionInBook global position in book.
*/
public void setStartPositionInBook(float startPositionInBook);
Creating Activity for FixedControl
FixedControl inherits RelativeLayout class for Apple’s Fixed Layout Epub format.
Different from general reflowable layout format, in fixed layout one page is one xthml file and the width and height are fixed.
This control can be used for the contents which need fixed layout such as magazine.
Also customer can freely zoom in/out the content and scroll to anyplace.
Mostly fixed layout format should support highly dynamic contents consisted of Javascript and HTML5 tags.
So this format may cause considerable CPU loads.
FixedViewController in SkyEpub is based on Apple’s format.
Epub 3.0 now contains the Fixed Layout format as the standard.
Creating FixedControl and Settings
RelativeLayout ePubView;
FixedControl fv;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set the file name in type of Fixed Layout.
String fileName = "Blanche.epub";
// Using RelativeLayout of Android, set the properties of FixedControl.
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
params.width = LayoutParams.FILL_PARENT;
params.height = LayoutParams.FILL_PARENT;
// Create FixedControl.
fv = new FixedControl(this);
Bitmap pagesStack = BitmapFactory.decodeFile(getFilesDir().getAbsolutePath()+"/images/PagesStack.png");
Bitmap pagesCenter = BitmapFactory.decodeFile(getFilesDir().getAbsolutePath()+"/images/PagesCenter.png");
fv.setPagesCenterImage(pagesCenter);
fv.setPagesStackImage(pagesStack);
// set the ContentListener to read the content of epub.
fv.setContentListener(new ContentHandler());
// To hanle cashe task, create ContentListener as new instance.
fv.setContentListenerForCache(new ContentHandler());
fv.setBaseDirectory(getFilesDir() + "/books");
fv.setBookName(fileName);
fv.setLayoutParams(params);
// set the Listener to handle the user's touch.
fv.setClickListener(new ClickDelegate());
// set the Listener for page moving.
fv.setPageMovedListener(new PageMovedDelegate());
// set the delay time(1/1000sec) to wait for the finish of rendering page.
// WARNING - if this time is too short many error will be occured.
fv.setTimeForRendering(1000);
// set the navigation area on both sides to move to the previous page or the next.
fv.setNavigationAreaWidthRatio(0.05f);
// set ContentView and put FixedControl created into that.
ePubView = new RelativeLayout(this);
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT,RelativeLayout.LayoutParams.FILL_PARENT);
ePubView.setLayoutParams(rlp);
ePubView.addView(fv);
// set the Contentview of Activity.
setContentView(ePubView);
}
Processing ContentListener
Internal engine in SkyEpub for Android does not read directly from the content of the file inside the epub.
Engine always accesses epub indirectly through the ContentListener.
This is for the case that epub content should not be decompressed and decrypted in local storage because of security or copyrights.
In order to build commercial Epub Reader, according to the policy, Reader has to access the content file within the DRMed, encrypted and compressed format.
In this case, content file can be delivered to the engine by implementing ContentListener interface.
The most of functions requested as call-back have baseDirectory and contentPath.
BaseDirectory is the base Path which developer told the engine already.
The engine ignores this baseDirectory internally. However it can be used just to tell developer if needed.
The followings are typical routines for ContentListener implementation.
Assume the epub is compressed in local storage of smart device and the engine can access the content by simple file processing.
class ContentHandler implements ContentListener {
// you should return the length of file.
public long getLength(String baseDirectory,String contentPath) {
String path = baseDirectory + "/" + contentPath;
File file = new File(path);
if (file.exists()) return file.length();
else return 0;
}
// You should return whether the file exists or not.
public boolean isExists(String baseDirectory,String contentPath) {
String path = baseDirectory + "/" + contentPath;
File file = new File(path);
if (file.exists()) return true;
else return false;
}
// LastModified information should be returned to the engine.
public long getLastModified(String baseDirectory,String contentPath) {
String path = baseDirectory + "/" + contentPath;
File file = new File(path);
if (file.exists()) return file.lastModified();
else return 0;
}
// you should deliver the requested file through the InputStream.
// In this sample, FileInputStream is used.
public InputStream getInputStream(String baseDirectory,String contentPath) {
String path = baseDirectory + "/" + contentPath;
File file = new File(path);
try {
FileInputStream fis = new FileInputStream(file);
return fis;
}catch(Exception e) {
return null;
}
}
}
Conclusion
SkyEpub for Android is the component which contains the most of important functions to be needed to handle epub format.
SkyEpub helps developer to focus on the core features without any effort to implemented complicated engine.
SkyEpub for iOS is compatible with this SDK in the most parts.
So SkyEpub libraries will be really useful when developer has to build large scale projects to cover from phone and tablet on both iOS and Android simultaneously.
If you have to seek more information about this SDK, it is available on “SkyEpub for Android Reference Guide”.
Latest informations and documentations are also available in the offcial website. http://www.skyepub.net
You can use this SDK from Android Version 2.3 GingerBread.
This SDK has been tested on the version 2.3 3.0 4.0 4.1 4.2, 4.3, 4.4 and 5.0
It is tested on Google Nexus, Nexus 4, Nexus 5, Nexus 7 2012, Nexus 7 2013.
It is also tested on Samsung smartphones such as Galaxy 2, Galaxy 3, Galaxy 4, Galaxy 5, Galaxy Note 1, Galaxy Note 2, Galaxy Note 3, Galaxy Note 4 and Samsung tablets like Galaxy Tab 10.1 and Note 10.1