diff --git a/jni/NativeFormats/JavaNativeFormatPlugin.cpp b/jni/NativeFormats/JavaNativeFormatPlugin.cpp index 82ebfd461..0378ca893 100644 --- a/jni/NativeFormats/JavaNativeFormatPlugin.cpp +++ b/jni/NativeFormats/JavaNativeFormatPlugin.cpp @@ -21,7 +21,9 @@ #include +#include "fbreader/src/bookmodel/BookModel.h" #include "fbreader/src/formats/FormatPlugin.h" +#include "fbreader/src/library/Library.h" #include "fbreader/src/library/Author.h" #include "fbreader/src/library/Book.h" #include "fbreader/src/library/Tag.h" @@ -101,9 +103,177 @@ JNIEXPORT jboolean JNICALL Java_org_geometerplus_fbreader_formats_NativeFormatPl return JNI_FALSE; } +static bool initBookModel(JNIEnv *env, jobject javaModel, BookModel &model) { + shared_ptr imageMapWriter = model.imageMapWriter(); + + env->PushLocalFrame(16); + + jobjectArray ids = AndroidUtil::createStringArray(env, imageMapWriter->identifiers()); + jintArray indices = AndroidUtil::createIntArray(env, imageMapWriter->indices()); + jintArray offsets = AndroidUtil::createIntArray(env, imageMapWriter->offsets()); + jstring imageDirectoryName = env->NewStringUTF(imageMapWriter->allocator().directoryName().c_str()); + jstring imageFileExtension = env->NewStringUTF(imageMapWriter->allocator().fileExtension().c_str()); + jint imageBlocksNumber = imageMapWriter->allocator().blocksNumber(); + env->CallVoidMethod(javaModel, AndroidUtil::MID_NativeBookModel_initImageMap, + ids, indices, offsets, imageDirectoryName, imageFileExtension, imageBlocksNumber); + env->PopLocalFrame(0); + return !env->ExceptionCheck(); +} + +static bool initInternalHyperlinks(JNIEnv *env, jobject javaModel, BookModel &model) { + ZLCachedMemoryAllocator allocator(131072, Library::Instance().cacheDirectory(), "nlinks"); + + ZLUnicodeUtil::Ucs2String ucs2id; + ZLUnicodeUtil::Ucs2String ucs2modelId; + + const std::map &links = model.internalHyperlinks(); + std::map::const_iterator it = links.begin(); + for (; it != links.end(); ++it) { + const std::string &id = it->first; + const BookModel::Label &label = it->second; + if (label.Model.isNull()) { + continue; + } + ZLUnicodeUtil::utf8ToUcs2(ucs2id, id); + ZLUnicodeUtil::utf8ToUcs2(ucs2modelId, label.Model->id()); + const size_t idLen = ucs2id.size() * 2; + const size_t modelIdLen = ucs2modelId.size() * 2; + + char *ptr = allocator.allocate(idLen + modelIdLen + 8); + ZLCachedMemoryAllocator::writeUInt16(ptr, ucs2id.size()); + ptr += 2; + memcpy(ptr, &ucs2id.front(), idLen); + ptr += idLen; + ZLCachedMemoryAllocator::writeUInt16(ptr, ucs2modelId.size()); + ptr += 2; + memcpy(ptr, &ucs2modelId.front(), modelIdLen); + ptr += modelIdLen; + ZLCachedMemoryAllocator::writeUInt32(ptr, label.ParagraphNumber); + } + allocator.flush(); + + jstring linksDirectoryName = env->NewStringUTF(allocator.directoryName().c_str()); + jstring linksFileExtension = env->NewStringUTF(allocator.fileExtension().c_str()); + jint linksBlocksNumber = allocator.blocksNumber(); + env->CallVoidMethod(javaModel, AndroidUtil::MID_NativeBookModel_initInternalHyperlinks, + linksDirectoryName, linksFileExtension, linksBlocksNumber); + env->DeleteLocalRef(linksDirectoryName); + env->DeleteLocalRef(linksFileExtension); + return !env->ExceptionCheck(); +} + +static jobject createTextModel(JNIEnv *env, jobject javaModel, ZLTextModel &model) { + env->PushLocalFrame(16); + + jstring id = AndroidUtil::createJavaString(env, model.id()); + jstring language = AndroidUtil::createJavaString(env, model.language()); + jint paragraphsNumber = model.paragraphsNumber(); + + const size_t arraysSize = model.startEntryIndices().size(); + jintArray entryIndices = env->NewIntArray(arraysSize); + jintArray entryOffsets = env->NewIntArray(arraysSize); + jintArray paragraphLenghts = env->NewIntArray(arraysSize); + jintArray textSizes = env->NewIntArray(arraysSize); + jbyteArray paragraphKinds = env->NewByteArray(arraysSize); + env->SetIntArrayRegion(entryIndices, 0, arraysSize, &model.startEntryIndices().front()); + env->SetIntArrayRegion(entryOffsets, 0, arraysSize, &model.startEntryOffsets().front()); + env->SetIntArrayRegion(paragraphLenghts, 0, arraysSize, &model.paragraphLengths().front()); + env->SetIntArrayRegion(textSizes, 0, arraysSize, &model.textSizes().front()); + env->SetByteArrayRegion(paragraphKinds, 0, arraysSize, &model.paragraphKinds().front()); + + jstring directoryName = env->NewStringUTF(model.allocator().directoryName().c_str()); + jstring fileExtension = env->NewStringUTF(model.allocator().fileExtension().c_str()); + jint blocksNumber = (jint) model.allocator().blocksNumber(); + + jobject textModel = env->CallObjectMethod(javaModel, AndroidUtil::MID_NativeBookModel_createTextModel, + id, language, + paragraphsNumber, entryIndices, entryOffsets, + paragraphLenghts, textSizes, paragraphKinds, + directoryName, fileExtension, blocksNumber); + + if (env->ExceptionCheck()) { + textModel = 0; + } + return env->PopLocalFrame(textModel); +} + +static bool initTOC(JNIEnv *env, jobject javaModel, BookModel &model) { + ContentsModel &contentsModel = (ContentsModel&)*model.contentsModel(); + + jobject javaTextModel = createTextModel(env, javaModel, contentsModel); + if (javaTextModel == 0) { + return false; + } + + std::vector childrenNumbers; + std::vector referenceNumbers; + const size_t size = contentsModel.paragraphsNumber(); + childrenNumbers.reserve(size); + referenceNumbers.reserve(size); + for (size_t pos = 0; pos < size; ++pos) { + ZLTextTreeParagraph *par = (ZLTextTreeParagraph*)contentsModel[pos]; + childrenNumbers.push_back(par->children().size()); + referenceNumbers.push_back(contentsModel.reference(par)); + } + jintArray javaChildrenNumbers = AndroidUtil::createIntArray(env, childrenNumbers); + jintArray javaReferenceNumbers = AndroidUtil::createIntArray(env, referenceNumbers); + + env->CallVoidMethod(javaModel, AndroidUtil::MID_NativeBookModel_initTOC, + javaTextModel, javaChildrenNumbers, javaReferenceNumbers); + + env->DeleteLocalRef(javaTextModel); + env->DeleteLocalRef(javaChildrenNumbers); + env->DeleteLocalRef(javaReferenceNumbers); + return !env->ExceptionCheck(); +} + extern "C" JNIEXPORT jboolean JNICALL Java_org_geometerplus_fbreader_formats_NativeFormatPlugin_readModel(JNIEnv* env, jobject thiz, jobject javaModel) { - return JNI_FALSE; + shared_ptr plugin = findCppPlugin(env, thiz); + if (plugin.isNull()) { + return JNI_FALSE; + } + + jobject javaBook = env->GetObjectField(javaModel, AndroidUtil::FID_NativeBookModel_Book); + + shared_ptr book = Book::loadFromJavaBook(env, javaBook); + shared_ptr model = new BookModel(book); + if (!plugin->readModel(*model)) { + return JNI_FALSE; + } + model->flush(); + + if (!initBookModel(env, javaModel, *model) || + !initInternalHyperlinks(env, javaModel, *model) || + !initTOC(env, javaModel, *model)) { + return JNI_FALSE; + } + + shared_ptr textModel = model->bookTextModel(); + jobject javaTextModel = createTextModel(env, javaModel, *textModel); + if (javaTextModel == 0) { + return JNI_FALSE; + } + env->CallVoidMethod(javaModel, AndroidUtil::MID_NativeBookModel_setBookTextModel, javaTextModel); + if (env->ExceptionCheck()) { + return JNI_FALSE; + } + env->DeleteLocalRef(javaTextModel); + + const std::map > &footnotes = model->footnotes(); + std::map >::const_iterator it = footnotes.begin(); + for (; it != footnotes.end(); ++it) { + jobject javaFootnoteModel = createTextModel(env, javaModel, *it->second); + if (javaFootnoteModel == 0) { + return JNI_FALSE; + } + env->CallVoidMethod(javaModel, AndroidUtil::MID_NativeBookModel_setFootnoteModel, javaFootnoteModel); + if (env->ExceptionCheck()) { + return JNI_FALSE; + } + env->DeleteLocalRef(javaFootnoteModel); + } + return JNI_TRUE; } extern "C" diff --git a/jni/NativeFormats/util/AndroidUtil.cpp b/jni/NativeFormats/util/AndroidUtil.cpp index 705f4960b..ba6fd0ebd 100644 --- a/jni/NativeFormats/util/AndroidUtil.cpp +++ b/jni/NativeFormats/util/AndroidUtil.cpp @@ -33,6 +33,7 @@ const char * const AndroidUtil::Class_Paths = "org/geometerplus/fbreader/Paths"; const char * const AndroidUtil::Class_ZLFile = "org/geometerplus/zlibrary/core/filesystem/ZLFile"; const char * const AndroidUtil::Class_Book = "org/geometerplus/fbreader/library/Book"; const char * const AndroidUtil::Class_Tag = "org/geometerplus/fbreader/library/Tag"; +const char * const AndroidUtil::Class_NativeBookModel = "org/geometerplus/fbreader/bookmodel/NativeBookModel"; jobject AndroidUtil::OBJECT_java_lang_System_err; @@ -78,6 +79,14 @@ jmethodID AndroidUtil::MID_Book_addTag; jmethodID AndroidUtil::SMID_Tag_getTag; +jfieldID AndroidUtil::FID_NativeBookModel_Book; +jmethodID AndroidUtil::MID_NativeBookModel_initImageMap; +jmethodID AndroidUtil::MID_NativeBookModel_initInternalHyperlinks; +jmethodID AndroidUtil::MID_NativeBookModel_initTOC; +jmethodID AndroidUtil::MID_NativeBookModel_createTextModel; +jmethodID AndroidUtil::MID_NativeBookModel_setBookTextModel; +jmethodID AndroidUtil::MID_NativeBookModel_setFootnoteModel; + JNIEnv *AndroidUtil::getEnv() { JNIEnv *env; ourJavaVM->GetEnv((void **)&env, JNI_VERSION_1_2); @@ -164,6 +173,16 @@ bool AndroidUtil::init(JavaVM* jvm) { CHECK_NULL( SMID_Tag_getTag = env->GetStaticMethodID(cls, "getTag", "(Lorg/geometerplus/fbreader/library/Tag;Ljava/lang/String;)Lorg/geometerplus/fbreader/library/Tag;") ); env->DeleteLocalRef(cls); + CHECK_NULL( cls = env->FindClass(Class_NativeBookModel) ); + CHECK_NULL( FID_NativeBookModel_Book = env->GetFieldID(cls, "Book", "Lorg/geometerplus/fbreader/library/Book;") ); + CHECK_NULL( MID_NativeBookModel_initImageMap = env->GetMethodID(cls, "initImageMap", "([Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;I)V") ); + CHECK_NULL( MID_NativeBookModel_initInternalHyperlinks = env->GetMethodID(cls, "initInternalHyperlinks", "(Ljava/lang/String;Ljava/lang/String;I)V") ); + CHECK_NULL( MID_NativeBookModel_initTOC = env->GetMethodID(cls, "initTOC", "(Lorg/geometerplus/zlibrary/text/model/ZLTextModel;[I[I)V") ); + CHECK_NULL( MID_NativeBookModel_createTextModel = env->GetMethodID(cls, "createTextModel", "(Ljava/lang/String;Ljava/lang/String;I[I[I[I[I[BLjava/lang/String;Ljava/lang/String;I)Lorg/geometerplus/zlibrary/text/model/ZLTextModel;") ); + CHECK_NULL( MID_NativeBookModel_setBookTextModel = env->GetMethodID(cls, "setBookTextModel", "(Lorg/geometerplus/zlibrary/text/model/ZLTextModel;)V") ); + CHECK_NULL( MID_NativeBookModel_setFootnoteModel = env->GetMethodID(cls, "setFootnoteModel", "(Lorg/geometerplus/zlibrary/text/model/ZLTextModel;)V") ); + env->DeleteLocalRef(cls); + return true; } @@ -214,6 +233,35 @@ std::string AndroidUtil::convertNonUtfString(const std::string &str) { return result; } +jintArray AndroidUtil::createIntArray(JNIEnv *env, const std::vector &data) { + size_t size = data.size(); + jintArray array = env->NewIntArray(size); + env->SetIntArrayRegion(array, 0, size, &data.front()); + return array; +} + +jbyteArray AndroidUtil::createByteArray(JNIEnv *env, const std::vector &data) { + size_t size = data.size(); + jbyteArray array = env->NewByteArray(size); + env->SetByteArrayRegion(array, 0, size, &data.front()); + return array; +} + +jobjectArray AndroidUtil::createStringArray(JNIEnv *env, const std::vector &data) { + size_t size = data.size(); + jclass cls = env->FindClass("java/lang/String"); + jobjectArray array = env->NewObjectArray(size, cls, 0); + for (size_t i = 0; i < size; ++i) { + const std::string &str = data[i]; + if (str.length() > 0) { + jstring javaStr = env->NewStringUTF(str.c_str()); + env->SetObjectArrayElement(array, i, javaStr); + env->DeleteLocalRef(javaStr); + } + } + return array; +} + void AndroidUtil::throwRuntimeException(JNIEnv *env, const std::string &message) { jclass cls = env->FindClass("java/lang/RuntimeException"); env->ThrowNew(cls, message.c_str()); diff --git a/jni/NativeFormats/util/AndroidUtil.h b/jni/NativeFormats/util/AndroidUtil.h index 7abb7bacd..0dab2ef6c 100644 --- a/jni/NativeFormats/util/AndroidUtil.h +++ b/jni/NativeFormats/util/AndroidUtil.h @@ -23,6 +23,7 @@ #include #include +#include class AndroidUtil { @@ -42,6 +43,7 @@ public: static const char * const Class_Paths; static const char * const Class_Book; static const char * const Class_Tag; + static const char * const Class_NativeBookModel; static jobject OBJECT_java_lang_System_err; @@ -87,6 +89,14 @@ public: static jmethodID SMID_Tag_getTag; + static jfieldID FID_NativeBookModel_Book; + static jmethodID MID_NativeBookModel_initImageMap; + static jmethodID MID_NativeBookModel_initInternalHyperlinks; + static jmethodID MID_NativeBookModel_initTOC; + static jmethodID MID_NativeBookModel_createTextModel; + static jmethodID MID_NativeBookModel_setBookTextModel; + static jmethodID MID_NativeBookModel_setFootnoteModel; + public: static bool init(JavaVM* jvm); static JNIEnv *getEnv(); @@ -96,6 +106,10 @@ public: static jstring createJavaString(JNIEnv* env, const std::string &str); static std::string convertNonUtfString(const std::string &str); + static jintArray createIntArray(JNIEnv *env, const std::vector &data); + static jbyteArray createByteArray(JNIEnv *env, const std::vector &data); + static jobjectArray createStringArray(JNIEnv *env, const std::vector &data); + static void throwRuntimeException(JNIEnv *env, const std::string &message); }; diff --git a/proguard.cfg b/proguard.cfg index 2e458c548..52c34322b 100755 --- a/proguard.cfg +++ b/proguard.cfg @@ -27,6 +27,7 @@ public ** getPath(); public long size(); } +-keep class org.geometerplus.zlibrary.text.model.ZLTextModel -keep class org.geometerplus.fbreader.formats.PluginCollection -keepclassmembers class org.geometerplus.fbreader.formats.PluginCollection { public static ** Instance(); @@ -55,6 +56,16 @@ -keepclassmembers class org.geometerplus.fbreader.library.Tag { public static ** getTag(**,**); } +-keep class org.geometerplus.fbreader.bookmodel.NativeBookModel +-keepclassmembers class org.geometerplus.fbreader.bookmodel.NativeBookModel { + public ** Book; + public void initImageMap(**[],int[],int[],**,**,int); + public void initInternalHyperlinks(**,**,int); + public void initTOC(**,int[],int[]); + public ** createTextModel(**,**,int,int[],int[],int[],int[],byte[],**,**,int); + public void setBookTextModel(**); + public void setFootnoteModel(**); +} -keepclasseswithmembernames class * { native ; diff --git a/src/org/geometerplus/fbreader/bookmodel/NativeBookModel.java b/src/org/geometerplus/fbreader/bookmodel/NativeBookModel.java index 827720db1..f5471e814 100644 --- a/src/org/geometerplus/fbreader/bookmodel/NativeBookModel.java +++ b/src/org/geometerplus/fbreader/bookmodel/NativeBookModel.java @@ -19,6 +19,8 @@ package org.geometerplus.fbreader.bookmodel; +import java.util.ArrayList; + import org.geometerplus.zlibrary.text.model.*; import org.geometerplus.fbreader.library.Book; @@ -43,6 +45,48 @@ public class NativeBookModel extends BookModelImpl { myInternalHyperlinks = new CachedCharStorageRO(directoryName, fileExtension, blocksNumber); } + public void initTOC(ZLTextModel contentsModel, int[] childrenNumbers, int[] referenceNumbers) { + final StringBuilder buffer = new StringBuilder(); + + final ArrayList positions = new ArrayList(); + TOCTree tree = TOCTree; + + final int size = contentsModel.getParagraphsNumber(); + for (int pos = 0; pos < size; ++pos) { + positions.add(pos); + ZLTextParagraph par = contentsModel.getParagraph(pos); + + buffer.delete(0, buffer.length()); + ZLTextParagraph.EntryIterator it = par.iterator(); + while (it.hasNext()) { + it.next(); + if (it.getType() == ZLTextParagraph.Entry.TEXT) { + buffer.append(it.getTextData(), it.getTextOffset(), it.getTextLength()); + } + } + + tree = new TOCTree(tree); + tree.setText(buffer.toString()); + tree.setReference(myBookTextModel, referenceNumbers[pos]); + + while (positions.size() > 0 && tree != TOCTree) { + final int lastIndex = positions.size() - 1; + final int treePos = positions.get(lastIndex); + if (tree.subTrees().size() < childrenNumbers[treePos]) { + break; + } + tree = tree.Parent; + positions.remove(lastIndex); + } + } + + if (tree != TOCTree || positions.size() > 0) { + throw new RuntimeException("Invalid state after TOC building:\n" + + "tree.Level = " + tree.Level + "\n" + + "positions.size() = " + positions.size()); + } + } + public ZLTextModel createTextModel( String id, String language, int paragraphsNumber, int[] entryIndices, int[] entryOffsets,