package com.codingbingo.fastreader.view.readview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.Log; import com.codingbingo.fastreader.Constants; import com.codingbingo.fastreader.FRApplication; import com.codingbingo.fastreader.dao.Book; import com.codingbingo.fastreader.dao.BookDao; import com.codingbingo.fastreader.dao.Chapter; import com.codingbingo.fastreader.dao.ChapterDao; import com.codingbingo.fastreader.dao.DaoSession; import com.codingbingo.fastreader.manager.SettingManager; import com.codingbingo.fastreader.model.eventbus.BookStatusChangeEvent; import com.codingbingo.fastreader.model.eventbus.RefreshBookListEvent; import com.codingbingo.fastreader.utils.FileUtils; import com.codingbingo.fastreader.utils.ScreenUtils; import com.codingbingo.fastreader.utils.StringUtils; import com.codingbingo.fastreader.utils.ThreadPool; import org.greenrobot.eventbus.EventBus; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Author: bingo * Email: codingbingo@gmail.com * By 2017/3/11. */ public class PageFactory { public static final String TAG = "PageFactory"; private Context mContext; private SettingManager settingManager; //数据库DAO private DaoSession mDaoSession; private BookDao mBookDao; private ChapterDao mChapterDao; private Book mBook; private List<Chapter> mChapterList; private List<String> mLines = new ArrayList<>(); private MappedByteBuffer mMappedByteBuffer; private long mByteBufferLength; private Paint mTitlePaint; private Paint mPaint; private Paint mBottomPaint; //上一页或者下一页 private int tempStartPos; //当前的章节以及位置 private int totalWidth; private int totalHeight; private int currentChapter = 0; private int currentStartPosition = 0; private int currentEndPosition = 0; private int mLineCount; private int mVisibleHeight; private int mVisibleWidth; private int mLineSpace; private int mFontSize; private int mTitleFontSize; private int mBottomFontSize; private int marginWidth; private int marginHeight; private String charSet = "UTF-8"; private String backgroundColor = "#FFFFFF"; public PageFactory(Context mContext) { this.mContext = mContext; settingManager = SettingManager.getInstance(); mDaoSession = ((FRApplication) mContext.getApplicationContext()).getDaoSession(); mBookDao = mDaoSession.getBookDao(); mChapterDao = mDaoSession.getChapterDao(); init(); } public void init() { mTitleFontSize = ScreenUtils.dp2px(mContext, 17); mBottomFontSize = ScreenUtils.dp2px(mContext, 15); marginWidth = ScreenUtils.dp2px(mContext, 15); marginHeight = ScreenUtils.dp2px(mContext, 15); totalWidth = ScreenUtils.getScreenWidth(mContext); totalHeight = ScreenUtils.getScreenHeight(mContext); mVisibleWidth = totalWidth - 2 * marginWidth; mVisibleHeight = totalHeight - marginHeight - mTitleFontSize - mBottomFontSize; mFontSize = SettingManager.getInstance().getReadFontSize(); mLineSpace = mFontSize / 5 * 2; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setTextSize(mFontSize); if (settingManager.getReadMode()) { mPaint.setColor(Color.WHITE); } else{ mPaint.setColor(Color.BLACK); } mTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTitlePaint.setTextSize(mTitleFontSize); mTitlePaint.setColor(Color.GRAY); mBottomPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBottomPaint.setTextSize(mBottomFontSize); mBottomPaint.setColor(Color.GRAY); } public void clearParams(){ mLines.clear(); //更新一下book对象 mBook = mBookDao.load(mBook.getId()); currentChapter = mBook.getCurrentChapter(); currentStartPosition = mBook.getCurrentPosition(); currentEndPosition = 0; } /** * 打开已经加入数据库的书籍 * * @param bookId */ public void openBook(long bookId) { //先检查bookId是否存在 mBook = mBookDao.load(bookId); if (mBook == null) { //书籍不存在 //// TODO: 2017/3/11 书籍不存在,后续需要加载错误页 return; } //处理状态 int processStatus = mBook.getProcessStatus(); switch (processStatus) { case Constants.BOOK_UNPROCESS: //书籍还没有处理,应该交给上面的函数处理 openBook(mBook.getBookPath()); break; case Constants.BOOK_PROCESSING: //书籍正在处理中,但是还没有处理完成,应该继续下一步处理 List<Chapter> chapterList = mChapterDao.queryBuilder().where(ChapterDao.Properties.BookId.eq(mBook.getId())).list(); try { File file = new File(mBook.getBookPath()); mByteBufferLength = file.length(); mMappedByteBuffer = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, mByteBufferLength); //此处应该根据整个页面的情况计算当前页面能显示多少字符 if (chapterList.size() > 0){ Chapter chapter = chapterList.get(chapterList.size() - 1); //接着上一章节处理 ThreadPool.getInstance().submitTask(new OpenBookTask(chapter.getPosition() + chapter.getTitle().getBytes().length)); } else{ //一点都没处理 //开始处理章节 ThreadPool.getInstance().submitTask(new OpenBookTask(0)); } } catch (IOException e) { //基本没可能了,上面已经确保了文件存在 Log.e(TAG, e.getMessage()); } break; case Constants.BOOK_PROCESSED: //处理完成,可以开始阅读啦 if (mBook.getCurrentChapter() != null) { currentChapter = mBook.getCurrentChapter(); } else { currentChapter = 0; } if (mBook.getCurrentPosition() != null) { currentStartPosition = mBook.getCurrentPosition(); } else { currentStartPosition = 0; } File bookFile = new File(mBook.getBookPath()); mByteBufferLength = bookFile.length(); charSet = mBook.getCharSet(); if (bookFile.exists() == false) { //文件可能已经删除 Log.e(TAG, "File doesn't exits"); return; } //获取所有章节 mChapterList = mChapterDao.queryBuilder().where(ChapterDao.Properties.BookId.eq(mBook.getId())).orderAsc(ChapterDao.Properties.Id).list(); try { mMappedByteBuffer = new RandomAccessFile(bookFile, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, mByteBufferLength); //此处应该根据整个页面的情况计算当前页面能显示多少字符 } catch (IOException e) { //基本没可能了,上面已经确保了文件存在 Log.e(TAG, e.getMessage()); } break; } } public synchronized void onDraw(Canvas canvas) { if (mLines.size() == 0) { if (currentEndPosition > currentStartPosition) { currentStartPosition = currentEndPosition; } else { currentEndPosition = currentStartPosition; } mLines = pageDown(); } if (mLines.size() > 0) { int y = marginHeight + mTitleFontSize / 2; //绘制背景,后面添加换背景功能 if (settingManager.getReadMode()) { canvas.drawColor(Color.BLACK); }else{ canvas.drawColor(Color.parseColor(SettingManager.getInstance().getReadBackground())); } //绘制章节名称 canvas.drawText(mChapterList.get(currentChapter).getTitle(), marginWidth, y, mTitlePaint); y += mTitleFontSize; int bottomPositionY = totalHeight - mBottomFontSize / 2 - mLineSpace; //绘制内容 for (String line : mLines) { y += mLineSpace; if (line.endsWith("@")) { line = line.substring(0, line.length() - 1); canvas.drawText(line, marginWidth, y, mPaint); y += mLineSpace; } else { canvas.drawText(line, marginWidth, y, mPaint); } y += mFontSize; } //绘制底部 float progress = getReadProgress(); String progressPercent = String.format("%.2f", progress * 100) + "%"; int bottomPositionX = (int) (totalWidth / 2 - mBottomPaint.measureText(progressPercent) / 2); canvas.drawText(progressPercent, bottomPositionX, bottomPositionY, mBottomPaint); } } private float getReadProgress() { float progress = 0; if (mByteBufferLength != 0) { progress = currentStartPosition * 1.0f / mByteBufferLength; return progress; } return progress; } private List<String> pageUp() { String paragraphStr = ""; List<String> lines = new ArrayList<>(); mLineCount = mVisibleHeight / (mFontSize + mLineSpace); int paraSpace = 0;//段落中间会添加的空白 while ((lines.size() < mLineCount) && currentStartPosition > 0) { List<String> paragraphLines = new ArrayList<>(); byte[] paragraphBuffer = readParagraphBack(currentStartPosition); currentStartPosition -= paragraphBuffer.length; try { paragraphStr = new String(paragraphBuffer, charSet); } catch (UnsupportedEncodingException e) { Log.e(TAG, e.getMessage()); } paragraphStr = paragraphStr.replaceAll("\r", " ").replaceAll("\n", " "); // 段落中的换行符去掉,绘制的时候再换行 if (paragraphStr.replaceAll(" ", "").length() == 0){ //说明当前段落无内容 continue; } while (paragraphStr.length() > 0) { int paintSize = mPaint.breakText(paragraphStr, true, mVisibleWidth, null); paragraphLines.add(paragraphStr.substring(0, paintSize)); paragraphStr = paragraphStr.substring(paintSize); } if (paragraphLines.size() > 0) { paragraphLines.set(paragraphLines.size() - 1, paragraphLines.get(paragraphLines.size() - 1) + "@"); lines.addAll(0, paragraphLines); } while (lines.size() > mLineCount) { // 4.如果段落添加完,但是超出一页,则超出部分需删减 try { currentStartPosition += lines.get(0).getBytes(charSet).length; // 5.删减行数同时起始位置指针也要跟着偏移 lines.remove(0); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } paraSpace += mLineSpace; mLineCount = (mVisibleHeight - paraSpace) / (mFontSize + mLineSpace); // 添加段落间距,实时更新容纳行数 } return lines; } private List<String> pageDown() { String paragraphStr = ""; List<String> lines = new ArrayList<>(); int paraSpace = 0; //下一章节 Chapter nextChapter = null; if (currentChapter < mChapterList.size() - 1) { nextChapter = mChapterList.get(currentChapter + 1); } mLineCount = mVisibleHeight / (mFontSize + mLineSpace); while ((lines.size() < mLineCount) && currentEndPosition < mByteBufferLength) { byte[] paragraphBuffer = readParagraphForward(currentEndPosition); currentEndPosition += paragraphBuffer.length; if (nextChapter != null && currentEndPosition >= nextChapter.getPosition()) { //已经到下一个章节了 currentEndPosition = nextChapter.getPosition(); return lines; } try { paragraphStr = new String(paragraphBuffer, charSet); } catch (Exception e) { Log.e(TAG, e.getLocalizedMessage()); } paragraphStr = paragraphStr.replaceAll("\r", " ").replaceAll("\n", " "); // 段落中的换行符去掉,绘制的时候再换行 if (paragraphStr.replaceAll(" ", "").length() == 0){ //说明当前段落无内容 continue; } while (paragraphStr.length() > 0) { int paintSize = mPaint.breakText(paragraphStr, true, mVisibleWidth, null); lines.add(paragraphStr.substring(0, paintSize)); paragraphStr = paragraphStr.substring(paintSize); if (lines.size() >= mLineCount) { break; } } lines.set(lines.size() - 1, lines.get(lines.size() - 1) + "@"); if (paragraphStr.length() != 0) { try { currentEndPosition -= paragraphStr.getBytes(charSet).length; } catch (UnsupportedEncodingException e) { Log.e(TAG, e.getLocalizedMessage()); } } paraSpace += mLineSpace; mLineCount = (mVisibleHeight - paraSpace) / (mFontSize + mLineSpace); } return lines; } /** * 打开没有处理过的文件 * * @param filePath */ public void openBook(String filePath) { if (StringUtils.isBlank(filePath)) { //书籍路径为空 Log.e(TAG, "FilePath is empty!"); return; } //判断文件是否存在 File bookFile = new File(filePath); if (bookFile.exists() == false) { Log.e(TAG, "File is not exits"); return; } //文件存在,开始读取文件 try { mByteBufferLength = bookFile.length(); mMappedByteBuffer = new RandomAccessFile(bookFile, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, mByteBufferLength); if (mBook == null) { mBook = new Book(); } mBook.setBookName(bookFile.getName()); mBook.setBookPath(filePath); mBook.setCharSet("");//先插入数据库中,chatset只在后续需要处理 mBook.setDescription(""); mBook.setBookImagePath(""); mBook.setProcessStatus(Constants.BOOK_UNPROCESS); mBook.setCurrentChapter(0); //章节从0开始 mBook.setCurrentPosition(0); long bookId = mBookDao.insert(mBook); mBook.setId(bookId); //开始处理章节 ThreadPool.getInstance().submitTask(new OpenBookTask(0)); } catch (Exception e) { //文件已经判断是否存在了,理论上只有文件保存到数据库失败会出现问题 Log.e(TAG, e.getMessage()); } } public boolean hasNextPage() { return currentChapter + 1 < mChapterList.size() || currentEndPosition < mByteBufferLength; } public boolean hasPrePage() { return currentChapter > 0 || (currentChapter == 0 && currentStartPosition > 0); } /** * 根据新的样式刷新页面 * * @param mCurrentPageCanvas */ public void refreshAccordingToStyle(Canvas mCurrentPageCanvas) { tempStartPos = currentStartPosition; currentEndPosition = currentStartPosition; //刷新init中初始化得样式 init(); Chapter chapter = mChapterList.get(currentChapter); if (currentStartPosition == 0 || chapter.getPosition() == currentEndPosition) { //说明是章节的开始 //直接读一遍就好 mLines.clear(); mLines.addAll(pageDown()); } else { //这个时候需要从章节开头开始读,然后读到包含当前位置为止 currentStartPosition = chapter.getPosition(); currentEndPosition = currentStartPosition; while (!(currentStartPosition <= tempStartPos && currentEndPosition >= tempStartPos)) { mLines.clear(); mLines.addAll(pageDown()); } } onDraw(mCurrentPageCanvas); } public class OpenBookTask implements Runnable { private int processStartPosition; private String filePath; private boolean isFinished; public OpenBookTask(int processStartPosition) { this.processStartPosition = processStartPosition; filePath = mBook.getBookPath(); isFinished = false; } public boolean isFinished() { return isFinished; } public String getFilePath() { return filePath; } @Override public void run() { if (mBook == null || StringUtils.isBlank(mBook.getBookPath())) { Log.e(TAG, "Error filePath"); isFinished = true; return; } String charSet = FileUtils.getJavaEncode(mBook.getBookPath());//这个步骤需要消耗不少的时间 mBook.setProcessStatus(Constants.BOOK_PROCESSING); mBook.setCharSet(charSet); mBookDao.update(mBook); processChapters(processStartPosition);//刷新页面 mChapterList = mChapterDao.queryBuilder().where(ChapterDao.Properties.BookId.eq(mBook.getId())).list(); if (mChapterList == null || mChapterList.size() == 0){ Chapter chapter = new Chapter(); chapter.setTitle(mBook.getBookName()); chapter.setBook(mBook); chapter.setPosition(0); mChapterDao.insert(chapter); } mBook.setProcessStatus(Constants.BOOK_PROCESSED); mBookDao.update(mBook); //本次书籍处理完毕 EventBus.getDefault().post(new RefreshBookListEvent()); EventBus.getDefault().post(new BookStatusChangeEvent(Constants.BOOK_PROCESSED, 100, mBook.getId())); isFinished = true; } } /** * 读书籍目录,断章 * 这个需要放到一个线程里面运行 */ public void processChapters(int startPosition) { int currentPosition = startPosition; int lastPosition = 0; Pattern pattern = Pattern.compile("第.{1,7}章.*\r\n"); while (currentPosition < mByteBufferLength) { byte[] bytes = readParagraphForward(currentPosition); try { String paragraph = new String(bytes, mBook.getCharSet()); Matcher matcher = pattern.matcher(paragraph); if (matcher.find()) { //修正章节错 if (currentPosition - lastPosition > 200 && bytes.length < 50) { if (mBook != null) { Chapter chapter = new Chapter(); chapter.setTitle(matcher.group()); if (lastPosition == 0) { chapter.setPosition(0); } else { chapter.setPosition(currentPosition); } chapter.setIsRead(false); chapter.setBook(mBook); //插入数据库 mChapterDao.insert(chapter); lastPosition = currentPosition; } } } } catch (UnsupportedEncodingException e) { //不支持编码 Log.e(TAG, "Book encoding is not supported"); } currentPosition += bytes.length; BookStatusChangeEvent bookStatusChangeEvent = new BookStatusChangeEvent(); bookStatusChangeEvent.setProgress((int)(currentPosition * 100.0 / mByteBufferLength)); bookStatusChangeEvent.setStatus(Constants.BOOK_PROCESSING); bookStatusChangeEvent.setBookId(mBook.getId()); EventBus.getDefault().post(bookStatusChangeEvent); } } /** * 读取下一段落 * * @param curEndPos 当前页结束位置指针 * @return */ private byte[] readParagraphForward(int curEndPos) { byte b0; int i = curEndPos; while (i < mByteBufferLength) { b0 = mMappedByteBuffer.get(i++); if (b0 == 0x0a) { break; } } int nParaSize = i - curEndPos; byte[] buf = new byte[nParaSize]; for (i = 0; i < nParaSize; i++) { buf[i] = mMappedByteBuffer.get(curEndPos + i); } return buf; } /** * 读取上一段落 * * @param curBeginPos 当前页起始位置指针 * @return */ private byte[] readParagraphBack(int curBeginPos) { byte b0; int i = curBeginPos - 1; while (i > 0) { b0 = mMappedByteBuffer.get(i); if (b0 == 0x0a && i != curBeginPos - 1) { i++; break; } i--; } int nParaSize = curBeginPos - i; byte[] buf = new byte[nParaSize]; for (int j = 0; j < nParaSize; j++) { buf[j] = mMappedByteBuffer.get(i + j); } return buf; } /** * 跳转下一页 */ public BookStatus nextPage() { if (!hasNextPage()) { // 最后一章的结束页 return BookStatus.NO_NEXT_PAGE; } else { tempStartPos = currentStartPosition; //向下移 currentStartPosition = currentEndPosition; if (currentChapter + 1 < mChapterList.size()) { Chapter nextChapter = mChapterList.get(currentChapter + 1); if (currentEndPosition == nextChapter.getPosition()) { currentChapter++; } } mLines.clear(); mLines.addAll(pageDown()); //更新书籍 updateBookInfo(); } return BookStatus.LOAD_SUCCESS; } /** * 跳转上一页 */ public BookStatus prePage() { if (!hasPrePage()) { // 第一章第一页 return BookStatus.NO_PRE_PAGE; } else { //向前移动 currentEndPosition = currentStartPosition; Chapter chapter = mChapterList.get(currentChapter); if (chapter.getPosition() >= currentStartPosition) { //说明这是一章的开头,上一页就要到上一个章节了,这里要直接对上一章节的部分分页 currentChapter--; Chapter preChapter = mChapterList.get(currentChapter); //上一章节开始的地方 currentStartPosition = preChapter.getPosition(); currentEndPosition = currentStartPosition; while (currentEndPosition != chapter.getPosition()) { currentStartPosition = currentEndPosition;//调整开始位置 mLines.clear(); mLines.addAll(pageDown()); } } else { //不是章节开头,直接往前读取 mLines.clear(); // 起始指针移到上一页开始处 mLines.addAll(pageUp()); // 读取一页内容 } //更新书籍 updateBookInfo(); } return BookStatus.LOAD_SUCCESS; } /** * 当前阅读章节,当前位置 */ private void updateBookInfo(){ mBook.setCurrentChapter(currentChapter); mBook.setCurrentPosition(currentStartPosition); mBookDao.update(mBook); } }