SkyEpub for iOS Tutorial

SkyEpub for iOS is the general SDK to build Epub Reader.
It is a component based SDK that can be easily insert the ViewController into the iOS project.
Various features can be implemented by numerous APIs.

Most of APIs are compatible with SkyEpub for Android.
Developers can easily build and freely modify the powerful epub reader using SkyEpub SDK.

The features of SkyEpub for iOS.

  • Component Oriented Libary.
  • Various and Powerful functions.
  • The book looks natural and real.
  • Pagination for each chapter or entire chapters.
  • Supports for Dynamic Contents based on HTML5, CSS3 and Javascript.
  • Built-in Content Server which can deal with DRM and encrypted contents.
  • Fixed Layout supported.

Delegate to communicate with ReflowableViewController

ReflowableViewControllerDelegate provides various call-back functions to retrieve events from engine.
Delegate can be used as followings.

BookViewController.h

@interface BookViewController : UIViewController <ReflowableViewControllerDataSource,ReflowableViewControllerDelegate>{
    ...
}

In ReflowableViewController.h Delegate Protocol to deliver events is defined.

/**
 ReflowableViewControllerDelegate is the protocol containing functions which should be implemented to handle the events from ReflowableViewController.
*/
@protocol ReflowableViewControllerDelegate <NSObject>
    @optional
    /** called when text selection is finished. @param highlight Highlight object @param positon CGPoint at the end of selection area. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didSelectRange:(Highlight*)highlight AtPosition:(CGPoint)position;
    /** called when single tap is detected @param position CGPoint object at tap position */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didDetectTapAtPosition:(CGPoint)position;
    /** called when double tap is detected @param position CGPoint object at double tap position */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didDetectDoubleTapAtPosition:(CGPoint)position;
    /** called when highlight is hit by tap gesture. @param highlight Highlight object hit by tap gesture. @param position CGPoint at tap position */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didHitHighlight:(Highlight*)highlight atPosition:(CGPoint)position;
    /** called when page is moved to or chapter is loaded at first time. @param pageInformation PageInformation object of current page. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc pageMoved:(PageInformation*)pageInformation;
    /** called when the key is found. @param searchResult SearchResult object. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didSearchKey:(SearchResult*)searchResult;
    /** called when search process for one chapter is finished @param searchResult SearchResult object. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didFinishSearchForChapter:(SearchResult*)searchResult;
    /** called when all search process is finihsed @param searchResult SearchResult object. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc didFinishSearchAll:(SearchResult*)searchResult;
@end

DataSource to communicate with ReflowableViewController

Sometimes engine can demand some proper information from outside.
In this case, developer should implement call-back function in BookViewController.

BookViewController.h

/**
 ReflowableViewControllerDataSource is the protocol containing methods to be implemented to respond to the request from ReflowableViewController.
*/
@protocol ReflowableViewControllerDataSource <NSObject>
    @optional
    /** should return NSMutableArray holding highlight objects for the given chapter index. */
    -(NSMutableArray*)reflowableViewController:(ReflowableViewController*)rvc highlightsForChapter:(NSInteger)chapterIndex;
    /** called when new highlight object must be inserted. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc insertHighlight:(Highlight*)highlight;
    /** called when certain highlight should be deleted in the case like merging highlights. */
    -(void)reflowableViewController:(ReflowableViewController*)rvc deleteHighlight:(Highlight*)highlight;
    /** Javascript source for chapterIndex can be passed to the engine if you like to implement some custom behaviors.  */
    -(NSString*)reflowableViewController:(ReflowableViewController*)rvc scriptForChapter:(NSInteger)chapterIndex;
@end

Call-back function called when text is selected.

When user select text in content, this function will be called.

-(void)reflowableViewController:(ReflowableViewController*)rvc 
    didSelectRange:(Highlight*)highlight AtPosition:(CGPoint)position;

Through the Highlight object the information on the certain area will be deliverd and the user’s final touch spot will be transfer to the AtPosition.

Mostly popup menu can be showed up around x,y of AtPostion parameter.

In this sample project, ActionSheet will be showed instead of customized popup menu.
If user clicks the button on ActionSheet

[rvc makeSelectionHighlight];

May be used to highlight the selected text.

// this function will be called when customer select some text.  
-(void)reflowableViewController:(ReflowableViewController*)rvc 
    didSelectRange:(Highlight*)highlight AtPosition:(CGPoint)position{
        NSLog(@"Selection(%@) Detected at %d %d %d %d %f ","highlight.text,highlight.startIndex,highlight.startOffset,highlight.endIndex,highlight.endOffset,position.x,position.y);
        // In this sample project, ActionSheet will be called. 
        [as showInView:self.view];
}

// In ActionSheet,
-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
    NSLog(@"Button %d", buttonIndex);
    if (buttonIndex==0) {
        // highlight the selected text. 
        [rvc makeSelectionHighlight];
    }else if (buttonIndex==1) {
        [rvc makeSelectionNote];
    }
}

Function for highlighting.

The structure of Highlight class

Highlight.h

@interface Highlight : NSObject {
    int code;
    int bookCode;
    int chapterIndex;       // Chapter Index
    float pagePercent;      
    int startIndex;         // the start element index.
    int startOffset;        // the start offset in the start element.
    int endIndex;           // the end element index.
    int endOffset;          // the end offset in the end element. 
    int highlightColor;
    bool isNote;
    NSString *text;         // the text which is highlighted.
    NSString *note;
    int backgroundColor;
    int top;
    int left;
}

If user marks highlight on certain area in the text, engine marks the area with specific color and generates Highlight Insert event.
In this case, the following callback function is called.
The correct information in the highlighted area is conveyed to highlighted object.

These information should be stored in persistent memory in device.

-(void)reflowableViewController:(ReflowableViewController*)rvc 
    insertHighlight:(Highlight*)highlight {
    NSLog(@"insertHighlight(%@) Detected at %d %d %d %d",highlight.text,highlight.startIndex,highlight.startOffset,highlight.endIndex,highlight.endOffset);
    [ad insertHighlight:highlight];
}

If new highlight is inserted into existing highlights, engine generates event to require the deletion of old highlights.
In this case, developer has to remove the existing highlights denoted by event.


-(void)reflowableViewController:(ReflowableViewController*)rvc deleteHighlight:(Highlight*)highlight {
    NSLog(@"deleteHighlight(%@) Detected at %d %d %d %d",highlight.text,highlight.startIndex,highlight.startOffset,highlight.endIndex,highlight.endOffset);
    [ad deleteHighlight:highlight];    
}

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.
In this case, “highlightsForChapter:(NSInteger)chapterIndex” in dataSource should be used.

-(NSMutableArray*)reflowableViewController:(ReflowableViewController*)rvc highlightsForChapter:(NSInteger)chapterIndex {
    NSMutableArray* highlights;
    highlights = [ad fetchHighlights:self.bookCode chapterIndex:chapterIndex];
    return highlights;
}

The data structure to be delivered to the engine is NSMutableArray type.
After inserting all highlights for specific chapter into NSMutalbeArray, return to the engine.

Processing user’s touch actions.

If user taps on screen, this callback function will be called.

-(void)reflowableViewController:(ReflowableViewController*)rvc didDetectTapAtPosition:(CGPoint)position{
    NSLog(@"tap Detected in BookView at (%f,%f)",position.x, position.y);
}

In positon, there’re x,y coodinates where customer touched.

If customer touches on existing highlight, this function will be called.


-(void)reflowableViewController:(ReflowableViewController*)rvc didHitHighlight:(Highlight*)highlight atPosition:(CGPoint)position{
    NSLog(@"Highlight(%@ %d %d %d %d) Detected at     (%f %f)",highlight.text,highlight.startIndex,highlight.startOffset,highlight.endIndex,highlight.endOffset,position.x,position.y);
}

In this case, Highlight class holds the highlight information touched.
Also the coodination touched can be check by position .
Usually as developer provides context-sensitive popup menu, customers can make a proper choice for highlight processing.

The information when the page has moved.

The information about page is stored into PageInformation class defined in ReflowableViewController.h.

/**
 PageInfomation class contains information about a specific page of epub.
*/
@interface PageInformation :NSObject{
    /** the index of the chapter that this page belongs to */
    NSInteger chapterIndex;
    /** the number of chapter that epub has. */
    NSInteger numberOfChaptersInBook;
    /** the page index from the start of this chapter. */
    NSInteger pageIndex;
    /** the total page number of this chapter. */
    NSInteger numberOfPagesInChapter;
    /** the title of this chapter. */
    NSString* chapterTitle;
    /** all Highlights in this page. */
    NSMutableArray *highlightsInPage;
    /** the position from the start of this chapter. */
    double pagePositionInChapter;
    /** the global postion from the start of this book. */
    double pagePositionInBook;
    /** the description on this page. */
    NSString *pageDescription;
    bool isLoadedChapter;
    /** the index of the first element in this page. */
    NSInteger startIndex;
    /** the index of the end element in this page. */
    NSInteger endIndex;
}

If customer jumps to previous page or next page,

-(void)reflowableViewController:(ReflowableViewController*)rvc pageMoved:(PageInformation*)pageInformation{
    info = pageInformation;
//    NSLog(@"CCI:%d CPI:%d NCB:%d NPC:%d PPB:%f PPC:%f SI:%d EI:%d",pageInformation.chapterIndex,pageInformation.pageIndex,pageInformation.numberOfChaptersInBook,pageInformation.numberOfPagesInChapter,pageInformation.pagePositionInBook,pageInformation.pagePositionInChapter,pageInformation.startIndex,pageInformation.endIndex);
//    NSLog(@"pageIndex:%d pageCount:%d ",pageInformation.pageIndex,pageInformation.numberOfPagesInChapter);
    for (int i=0; i<[pageInformation.highlightsInPage count]; i++) {
        Highlight* highlight = [pageInformation.highlightsInPage objectAtIndex:i];
//        NSLog(@"%@ at (%d:%d) so:%d si:%d eo:%d ei:%d ",highlight.text,highlight.left,highlight.top,highlight.startIndex,highlight.startOffset,highlight.endIndex,highlight.endOffset);
    }
    [rvc unselect];

}

these function are callled.

In this function, using PageInformation object, developer can check new position where page has moved to and provide the proper UI to the user.

Jumping to the certain position in Epub.

When jump to the certain area in epub is need these function can be used.
Because of the uniqueness of the Reflowable Llayout Epub, there is no fixed page numbers even if there are changes on fonts or lining.

Therefore 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 used, 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 global position in book. */
-(void)gotoPageByPagePositionInBook:(double)pagePositionInBook;

/**  goes to the page by the position(by pagePositionInChapter) in the chapter(by chapterIndex) */
-(void)gotoPageByPagePosition:(double)pagePositionInChapter inChapter:(int)chapterIndex;

/**  goes to the page by the element index in the chapter by chapterIndex */
-(void)gotoPageByUniqueIndex:(int)index inChapter:(int)chapterIndex;

/**  goes to the page by NavPoint index */
-(void)gotoPageByNavMapIndex:(int)index;

/**  goes to the page by Highlight object, highlight must contain chapterIndex. */
-(void)gotoPageByHighlight:(Highlight*)highlight;

Global Pagination.

General epub file is composed of multiple xhtml files.

Specially in Reflowable Layout, one xhtml file is used for one chapter.
When chapter is loaded, following factors may affect the total number of pages.

Factors affect the total number of pages.

  • verticalGapRatio – the margins in top and bottom sides.
  • horizontalGapRatio – the margins in left and right sides.
  • fontName – font name.
  • fontSize – font size.
  • lineSpacing – line space.
  • isPortrait – whether device was portrait or landscape mode.
  • isDoublePagedForLandscape – double pages view when landscape mode is chosen.

In general it is more important to find the absolute position in the book(pagePositionInBook) rather than page numbers in the Reflowable Layout.
However if page numbers in the book is necessary, SkyEpub provides few functions

    // enable global pagination. 
    [rvc setGlobalPaging:YES];

If this option is turned on, the one of factors metioned above is changed, engine will load all chapters and recalculate the number of pages for each chapter.

Whenever the pagination for one chapter is finished, PagingInformation object will be dispatched from engine.

/**
 PagingInformation class contains the information about paging for one chapter.
 */
@interface PagingInformation :NSObject {
    /** the code of book which is loaded now */
    NSInteger bookCode;
    /** the index of chapter which is paginated now */
    NSInteger chapterIndex;
    /** the number of pages in this chapter */
    NSInteger numberOfPagesInChapter;
    /** the font name that is used for this paging. */
    NSString *fontName;
    /** the font size that is used for this paging. */
    NSInteger fontSize;
    /** the line space that is used for this paging. */
    NSInteger lineSpacing;
    /** the vertical gap ratio that is used for this paging. */
    double verticalGapRatio;
    /** the horizontal gap ratio that is used for this paging. */
    double horizontalGapRatio;
    /** denote the device was portrait or not */
    BOOL isPortrait;
    /** double paged in landscape mode */
    BOOL isDoublePagedForLandscape;
}

And these callback functions will be called.

// called when global pagination is started. 
-(void)reflowableViewController:(ReflowableViewController*)rvc didStartPaging:(int)code {
    NSLog(@"Paging for bookCode %d Started.",code);
}

// this function will be called whenever the pagination for one chapter is finished.
// To avoid the recalculation for the same factors, PagingInformation object must be stored into persistent memory of device. 

-(void)reflowableViewController:(ReflowableViewController*)rvc didPaging:(PagingInformation *)pagingInformation {
    NSLog(@"Paging:%d for chapterIndex:%d is finished.",pagingInformation.numberOfPagesInChapter,pagingInformation.chapterIndex);
    [pagings addObject:pagingInformation];
}

// Before loading one chapter for pagination, the engine always asked to the outside about the existing information for the same factors.
// In this case, developer has to chcek on sustainable storage space  for PagingInformation class which controlls the specific book (bookCode), a specific chapter and other.
// If there is PagingInformation class, the number of the pages for that chapter is passed to the engine. 

-(NSInteger)reflowableViewController:(ReflowableViewController*)rvc numberOfPagesForPagingInformation:(PagingInformation *)pagingInformation{
    int np = 0;
    PagingInformation* pg;
    for (int i=0; i<[pagings count]; i++) {
        pg = [pagings objectAtIndex:i];
        if ([pg isEqualTo:pagingInformation]) {
            np =  pg.numberOfPagesInChapter;
            break;
        }
    }
    if (np!=0) NSLog(@"numberOfPages:%d for chapterIndex:%d is returned to engine.",np,pagingInformation.chapterIndex);
    return np;
}

// called when global pagination is finished. 
-(void)reflowableViewController:(ReflowableViewController*)rvc didFinishPaging:(int)code {
    NSLog(@"Paging for bookCode %d Finished.",code);
}

Building the ViewController for Fixed Layout.

FixedViewController inherits ViewController 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 is fixed.
This 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 can 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 FixedViewController and Settings.

-(void)makeBookViewer {
    fvc = [[FixedViewController alloc]initWithStartPageIndex:3];
    Book *book = [[Book alloc]init];
    book.bookCode = self.bookCode;
    book.fileName = self.bookName;
    book.isFixedLayout = YES;
    fvc.book = book;
    fvc.transitionType = [ad getTransitionType];
    fvc.dataSource = self;
    fvc.delegate =self;
    fvc.baseDirectory = [self getBooksDirectory];
    fvc.view.frame = self.view.bounds;
    fvc.contentProviderClass = [FileProvider self];
    fvc.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
    [self addChildViewController:fvc];
    [self.view addSubview:fvc.view];
    self.view.autoresizesSubviews = YES;   
}

-(void)makeXIB {
    [self makeBookViewer];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    ad =  (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [self makeXIB];
    [self makeUI];

}

About Page Caching in Fixed Layout.

As mentioned above, each page has its own xhtml file in Fixed Layout Epub.
For a variety of visual effects that consists of very large files, it is a common characteristics.
So to implement turning page effect like Curling page naturally, SkyEpub engine caches each page into image file in background.
To support this, several callback functions are provided.

// It will be called when the caching operation is started.
// index is the page number to start. 
-(void)fixedViewController:(FixedViewController*)fvc cachingStarted:(int)index {
    isCaching = YES;
}

// It will be called the caching for a specific page has been completed
// index is the number of pages. 
// path is the absolute path which image file is stored. 

-(void)fixedViewController:(FixedViewController*)fvc cached:(int)index path:(NSString *)path {
    NSLog(@"PageIndex %d is cached to %@",index,path);
}

//  it will be called caching task  operation is finished. 
-(void)fixedViewController:(FixedViewController*)fvc cachingFinished:(int)index {
    isCaching = NO;
}

Developer can show the customized user inferface or about caching pages or thumbnail using this callback function.

-(void)fixedViewController:(FixedViewController*)fvc cached:(int)index path:(NSString *)path; 

Processing for ContentProvider

Internal engine in SkyEpub for iOS does not read directly from the content of the file inside the epub.
Engine always accesses epub indirectly through the ContentProvider.
The reason for such case is 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 using ContentProvider class.

‘ContentProvider.h’

@protocol ContentProvider
-(void)setContentPath:(NSString*)path;           //  path will be set by engine.
-(long long)lengthOfContent;                     //  you should return the length of content(file)
-(long long)offsetOfContent;                     //  should return the offset of content
-(void)setOffsetOfContent:(long long)offset;     //  offset will be set by skyepub engine.
-(NSData*)dataForContent:(long long)length;      //  should return the NSData for the content with the size of given length.
-(BOOL)isFinished;                               //  should return whether reading content is finished or not.
@end

Through the setContextPath the engine deliver the necessary content to the relative path.
Through the lengthOfContent, the developer should send out the length of its content to the engine.
If the content is very long, the engine tells you where to read in the same content through setOffsetOfContent.
After reading the certain length the engine will deliver the offset of certain spot through offsetOf Content.
In case of very large files, this process may be repeated many times.
The specific part of content should be delived as NSData type to engine using dataForContent function.
it is passed to the engine wheather or not it’s being read its content through is Finished

Following example is designed to explain how to implement ContentProvider object for file based contents that are stored in local device.

//
//  FileProvider.h
//  SktreeEPub
//

#import <Foundation/Foundation.h>
#import "ContentProvider.h"

@interface FileProvider : NSObject <ContentProvider> {
    long long contentLength;
    NSFileHandle *fileHandle;    
}

@end
 //
//  FileProvider.m
//  SktreeEPub
//

#import "FileProvider.h"

@implementation FileProvider

-(void)setContentPath:(NSString *)path {
    fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
    contentLength = [fileHandle seekToEndOfFile];
    [fileHandle seekToFileOffset:0];
}

//  you should return the length of content(file)
-(long long)lengthOfContent {
    return contentLength;
}

//  should return the offset of content
-(long long)offsetOfContent {
    return [fileHandle offsetInFile];
}

//  offset will be set by skyepub engine
-(void)setOffsetOfContent:(long long)offset {
    [fileHandle seekToFileOffset:offset];
}

// should return the NSData for the content of given path with the size of given length.
// this can be invoked times depends the size of content and the size of buffer. 
-(NSData*)dataForContent:(long long)length {
    long lengthLeft = contentLength - [fileHandle offsetInFile];
    long lengthToRead = MIN(length,lengthLeft);
    NSData *data = [fileHandle readDataOfLength:lengthToRead];
    if ([data length]==0 || data==nil) {
        return nil;
    }
    else {
        return data;
    }
}

//  should return whether reading content is finished or not.
-(BOOL)isFinished {
    if ([fileHandle offsetInFile]>=contentLength) {
        return YES;
    }else {
        return NO;
    }
}

@end

 

Conclusion

SkyEpub for iOS is the component which contains the most of functions to be needed to handle epub format.
SkyEpub helps developer to focus on the core features without any effort to implement the complicated engine.
SkyEpub for Android is compatible with this SDK in the most parts.
So SkyEpub libraries will be very 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 iOS Reference Guide”.
Latest builds and documentations are also available in the offcial website. http://www.skyepub.net
You can use this SDK from iOS version 4.0.
Also this SDK is optimized for iOS version 5.0 or above.

Download the Source code for this tutorial.

Leave a Reply