好久好久不写代码了,也好久没更新博客了,这次就和大家分享一个电子书阅读器分页的算法吧。
像一些主流的阅读器,如QQ阅读、iReader等,都实现了txt文档分页显示的功能,打开一个txt文档可以快速把文档分割成若干页,每页文字正好铺满屏幕,点击翻屏显示下一页,这样不用操作滚动条,阅读体验更好。那么如何实现txt文件的快速分页呢?如果计算屏幕大小和字体大小算出一屏可以有多少字,这样肯定不行,因为有英文和其他字符比如中文和标点,还有换行回车,所以肯定要先设置一块和显示屏一样大的容器(可变高度),首先根据屏幕大小和字体大小算出一屏可以容纳的字数最大值,从文件读出这些字数,将这段文字写入设置好的那个容器,如果容器高度大于屏幕高度,则减少文字读取的长度,再次试探容器高度是否大于屏幕高度,直到容器高度和屏幕高度一致,则当前屏幕应该显示的内容就是容器内的内容。要是一个字一个字试探效率太低,我改进了算法,每次减去20字,当减到容器高度小于屏幕高度时,在反向微调,添加一个字,这样分页的效率大大提高,点击显示下一页看不出延迟。核心代码如下图
前些日子写了个阅读器demo,只是实现了文件浏览,打开文件和文件分页功能,阅读器主题设置,阅读历史存档等还没做,因为这些都比较简单了,懒得去做了,大家有兴趣可以在此基础上修改成成品。下面先上效果图再上完整代码,这个是用xml做的视图层,JScript做的逻辑层,可以运行于windows7及以上系统。
首先是效果图:
主题设置目前只做了字体和背景颜色的设置,所有的设置都存在一个对象之中,可以通过读写json文件存储,读写我已经封装到IOMod这个模块了,可以直接拿来用getSetting和setSetting两个方法。
最后编译完成的文件如下:
下面上代码:首先是视图模块viewMod.js
/*应用程序界面模块*/
this.viewMod={
root:"F:\\电子书",totalNum:0,ramPoint:0,textData:"",S:{},screen:"",
init:function(screen){
this.S=ioMod.getSetting();
/*读取配置,fontSize,color,background,lastRead*/
var SC=document.querySelector(screen);
SC.style.fontSize=this.S.fontSize+"px";
SC.style.color=this.S.color;
document.body.style.background=this.S.background;
var SC_BAR=document.querySelector("readerViewBar");
SC_BAR.style.borderBottom="1px solid "+this.S.color;
SC_BAR.style.color=this.S.color;
this.screenX=SC.offsetWidth;
this.screenY=SC.offsetHeight;
this.screen=screen;
},
print:function(item,data){
document.querySelector(item).innerHTML=data;
},
printT:function(item,data){
document.querySelector(item).innerText=data;
},
fileList:function(folder){
folder=(folder==undefined)?this.root:folder;
/*文件夹和电子书列表的显示*/
var lastFolder;
if(folder.indexOf("\\")<0){
lastFolder=folder;
}else{
lastFolder=folder.split("\\");
lastFolder.splice(lastFolder.length-1,1);
lastFolder=lastFolder.join("\\");
}
var view="<<返回上级 ";
var data=ioMod.getFolderList(folder);
for(var i in data){
view+="
";
view+=data[i].name;
view+=" ";
}
data=ioMod.getBookList(folder);
for(var i in data){
view+="
";
view+=data[i].name;
view+=" ";
}
this.print("bookFileList",view);
},
openBook:function(url,name){
/*打开文件*/
this.ramPoint=0;
this.textData=ioMod.openFile(url);
var data=this.getTextData("next");
this.printT(this.screen,data);
this.printT("titleBar","文件->"+name);
},
getTextData:function(direction){
/*分页*/
if(document.querySelector("SYS_TEXT_SCREEN")==null){
var SYS_TEXT_SC=document.createElement("SYS_TEXT_SCREEN");
SYS_TEXT_SC.style.position="absolute";
SYS_TEXT_SC.style.top="0px";
SYS_TEXT_SC.style.left="0px";
SYS_TEXT_SC.style.visibility="hidden";
document.body.appendChild(SYS_TEXT_SC);
}//创建文本读取容器
var SYS_TEXT=document.querySelector("SYS_TEXT_SCREEN");
SYS_TEXT.style.width=this.screenX+"px";
SYS_TEXT.style.fontSize=this.S.fontSize+"px";
var ramLength=Math.round((this.screenX/this.S.fontSize)*(this.screenY/this.S.fontSize));
ramLength=ramLength>1500?1500:ramLength;
var data=this.textData;
var ramLength=data.length>ramLength?ramLength:data.length;//每页大约的文字长度
if(direction=="next"){
SYS_TEXT.innerText=data.substr(this.ramPoint,ramLength);
while(SYS_TEXT.offsetHeight>this.screenY){
ramLength-=20;
SYS_TEXT.innerText=data.substr(this.ramPoint,ramLength);
}
while(SYS_TEXT.offsetHeightthis.screenY){
IS_LONG=true;
ramLength-=20;
SYS_TEXT.innerText=data.substr(this.ramPoint-ramLength,ramLength);
}
if(IS_LONG){
while(SYS_TEXT.offsetHeightthis.textData.length){
}else{
var data=this.getTextData("next");
this.printT(this.screen,data);
}
},
lastPage:function(){
/*上一页*/
if(this.ramPoint<=0){
}else{
var data=this.getTextData("last");
this.printT(this.screen,data);
}
},
diaLog:function(view){
return showModalDialog(view+".view");
},
setColors:function(type){
var color=this.diaLog("colors");
if(color==undefined){
return;
}
if(type=="color"){
this.S.color=color;
document.querySelector(this.screen).style.color=this.S.color;
document.querySelector("readerViewBar").style.color=this.S.color;
document.querySelector("readerViewBar").style.borderBottom="1px solid "+this.S.color;
document.querySelector("fontColor").style.color=this.S.color;
}else{
this.S.background=color;
document.body.style.background=this.S.background;
document.querySelector("backgroundColor").style.color=this.S.background;
}
}
};
其次是文件读写模块ioMod.js
/*文件读写模块*/
this.ioMod={
getBookList:function(folder){
var data=[];
/*取得书籍列表*/
var fso=new ActiveXObject("Scripting.FileSystemObject");
var f=fso.GetFolder(folder);
var fc=new Enumerator(f.files);
var books=/(.txt|.pdf)/i;
for (; !fc.atEnd(); fc.moveNext())
{
if(books.test(fc.item())==true){
data.push({url:""+fc.item(),name:fc.item().name});
}
}
return data;
},
getFolderList:function(folder){
/*取得文件夹列表*/
var data=[];
var fso=new ActiveXObject("Scripting.FileSystemObject");
var f=fso.GetFolder(folder);
var fc=new Enumerator(f.SubFolders);
var str="\r\n";
for (; !fc.atEnd(); fc.moveNext())
{
data.push({url:""+fc.item(),name:fc.item().name});
}
return data;
},
openFile:function(url){
/*读取文件流*/
var fso=new ActiveXObject("Scripting.FileSystemObject");
var fl=fso.OpenTextFile(url,1);
doc=fl.ReadAll();
fl.Close();
return doc;
},
getSetting:function(){
/*读取配置*/
var fso=new ActiveXObject("Scripting.FileSystemObject");
var fl=fso.OpenTextFile("setting.json",1);
data=fl.ReadAll();
data=JSON.parse(data);
fl.Close();
return data;
},
setSetting:function(data){
/*写入配置*/
data=JSON.stringify(data);
var fso=new ActiveXObject("Scripting.FileSystemObject");
var tf=fso.CreateTextFile("setting.json",true);
tf.Write(data);
tf.Close();
},
write:function(data,fileName){
/*写入配置*/
var fso=new ActiveXObject("Scripting.FileSystemObject");
var tf=fso.CreateTextFile(fileName,true);
tf.Write(data);
tf.Close();
},
}
然后是主视图main.hta
文本阅读器v1.0
最近阅读
测试文件.pdf
浏览文件
进度:
文件->
主题设置
字体颜色:■
背景颜色:■
字体大小:24px
字体设置:宋体
系统设置
添加书签
书签管理
然后是颜色选择器视图colors.view
文本阅读器【颜色选择控件】
基础的功能已完成,剩下的只是添加功能了。暂时没添加对pdf的支持,可以使用“xpdf-chinese-simplified”库将pdf转换为txt在进行读取,还可以添加对SAPI的支持,使软件具有朗读电子书的功能。