Pdf reflow upgrade (#63)

* Replaced PDF-to-Markdown reflow with an enhanced PDF-to-HTML generator.

Specific changes include:
- Replaced `PdfToMarkdownGenerator` and `PdfReflowGenerator` with `PdfToHtmlGenerator`.
- Added native bridge methods in `NativePdfiumBridge.kt` and `pdfium_bridge.cpp` to extract character bounding boxes, page objects, and image pixels.
- Implemented vertical merging of text and images in `PdfToHtmlGenerator` to maintain document layout.
- Added logic to detect and filter repeating headers and footers across PDF pages.
- Updated `ReflowWorker` to generate `.html` files instead of `.md` files and updated `FileType` handling.
- Simplified `SingleFileImporter` by removing dependencies on the legacy Markdown generator.

* Updated PDF to HTML generation to include page breaks and split HTML files into individual chapters based on page markers.

* Added junk character filtering and normalization to PDF text extraction

* - Added `allRecentFiles` to `ReaderScreenState` to track all files, including reflowed versions.
- Updated `recentFiles` in `MainViewModel` to filter out reflowed files (`_reflow`) from the main library view.
- Implemented `deleteBookPermanently` in `MainViewModel` to handle book deletion and cache cleanup.
- Added a "Delete Text View" option to the `EpubReader` controls for reflowed files.
- Improved reflow file detection in `PdfViewerScreen` by checking against `allRecentFiles`.

* Implemented a centralized data-saving mechanism in `PdfViewerScreen` using a debounced `saveAllData` function. This refactor consolidates the saving of annotations, text boxes, highlights, bookmarks, and scroll positions, adding lifecycle-aware triggers and a mutex to ensure data integrity during pauses or document navigation.

* Optimized PDF selection and annotation performance by offloading heavy operations to background threads and improving UI responsiveness.

- Moved PDF page/text opening, character range calculations, and text extraction to `Dispatchers.IO`.
- Wrapped UI updates in `updateSelectionVisuals` and selection logic with `withContext(Dispatchers.Main)`.
- Implemented a more efficient `Popup`-based magnifier to replace manual offsets and transformations.
- Updated `PdfSelectionMenuPopup` properties to prevent focus and click-outside dismissal conflicts.
This commit is contained in:
Aryan 2026-03-13 17:02:09 +05:30 committed by GitHub
parent 9a95b4afcd
commit 843a77d0ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1270 additions and 895 deletions

View file

@ -12,13 +12,34 @@
typedef double (*FPDFText_GetFontSize_t)(void* text_page, int index);
typedef int (*FPDFText_GetFontWeight_t)(void* text_page, int index);
typedef int (*FPDFText_GetFontInfo_t)(void* text_page, int index, void* buffer, unsigned long buflen, int* flags);
typedef int (*FPDFText_GetCharBox_t)(void* text_page, int index, double* left, double* right, double* bottom, double* top);
typedef int (*FPDFPage_GetAnnotCount_t)(void* page);
typedef void* (*FPDFPage_GetAnnot_t)(void* page, int index);
typedef int (*FPDFAnnot_GetSubtype_t)(void* annot);
typedef int (*FPDFAnnot_GetRect_t)(void* annot, void* rect);
typedef unsigned long (*FPDFAnnot_GetStringValue_t)(void* annot, const char* key, void* buffer, unsigned long buflen);
typedef int (*FPDFAnnot_GetColor_t)(void* annot, int type, unsigned int* R, unsigned int* G, unsigned int* B, unsigned int* A);
typedef int (*FPDFPage_CountObjects_t)(void* page);
typedef void* (*FPDFPage_GetObject_t)(void* page, int index);
typedef int (*FPDFPageObj_GetType_t)(void* page_object);
typedef void* (*FPDFImageObj_GetBitmap_t)(void* image_object);
typedef int (*FPDFBitmap_GetWidth_t)(void* bitmap);
typedef int (*FPDFBitmap_GetHeight_t)(void* bitmap);
typedef int (*FPDFBitmap_GetStride_t)(void* bitmap);
typedef void* (*FPDFBitmap_GetBuffer_t)(void* bitmap);
typedef void (*FPDFBitmap_Destroy_t)(void* bitmap);
typedef int (*FPDFPageObj_GetBounds_t)(void* page_object, float* left, float* bottom, float* right, float* top);
static FPDFPage_CountObjects_t count_objects_func = nullptr;
static FPDFPage_GetObject_t get_object_func = nullptr;
static FPDFPageObj_GetType_t get_object_type_func = nullptr;
static FPDFImageObj_GetBitmap_t get_image_bitmap_func = nullptr;
static FPDFBitmap_GetWidth_t bitmap_get_width_func = nullptr;
static FPDFBitmap_GetHeight_t bitmap_get_height_func = nullptr;
static FPDFBitmap_GetStride_t bitmap_get_stride_func = nullptr;
static FPDFBitmap_GetBuffer_t bitmap_get_buffer_func = nullptr;
static FPDFBitmap_Destroy_t bitmap_destroy_func = nullptr;
static FPDFPageObj_GetBounds_t get_object_bounds_func = nullptr;
static FPDFPage_GetAnnotCount_t get_annot_count_func = nullptr;
static FPDFPage_GetAnnot_t get_annot_func = nullptr;
static FPDFAnnot_GetSubtype_t get_annot_subtype_func = nullptr;
@ -29,6 +50,7 @@ static void* pdfium_handle = nullptr;
static FPDFText_GetFontSize_t get_font_size_func = nullptr;
static FPDFText_GetFontWeight_t get_font_weight_func = nullptr;
static FPDFText_GetFontInfo_t get_font_info_func = nullptr;
static FPDFText_GetCharBox_t get_char_box_func = nullptr;
typedef void* (*FPDFAnnot_GetLinkedAnnot_t)(void* annot, const char* key);
typedef void (*FPDFPage_CloseAnnot_t)(void* annot);
@ -48,6 +70,7 @@ static bool init_pdfium() {
get_font_size_func = (FPDFText_GetFontSize_t) dlsym(pdfium_handle, "FPDFText_GetFontSize");
get_font_weight_func = (FPDFText_GetFontWeight_t) dlsym(pdfium_handle, "FPDFText_GetFontWeight");
get_font_info_func = (FPDFText_GetFontInfo_t) dlsym(pdfium_handle, "FPDFText_GetFontInfo");
get_char_box_func = (FPDFText_GetCharBox_t) dlsym(pdfium_handle, "FPDFText_GetCharBox");
get_annot_count_func = (FPDFPage_GetAnnotCount_t) dlsym(pdfium_handle, "FPDFPage_GetAnnotCount");
get_annot_func = (FPDFPage_GetAnnot_t) dlsym(pdfium_handle, "FPDFPage_GetAnnot");
@ -58,6 +81,17 @@ static bool init_pdfium() {
get_linked_annot_func = (FPDFAnnot_GetLinkedAnnot_t) dlsym(pdfium_handle, "FPDFAnnot_GetLinkedAnnot");
close_annot_func = (FPDFPage_CloseAnnot_t) dlsym(pdfium_handle, "FPDFPage_CloseAnnot");
count_objects_func = (FPDFPage_CountObjects_t) dlsym(pdfium_handle, "FPDFPage_CountObjects");
get_object_func = (FPDFPage_GetObject_t) dlsym(pdfium_handle, "FPDFPage_GetObject");
get_object_type_func = (FPDFPageObj_GetType_t) dlsym(pdfium_handle, "FPDFPageObj_GetType");
get_image_bitmap_func = (FPDFImageObj_GetBitmap_t) dlsym(pdfium_handle, "FPDFImageObj_GetBitmap");
bitmap_get_width_func = (FPDFBitmap_GetWidth_t) dlsym(pdfium_handle, "FPDFBitmap_GetWidth");
bitmap_get_height_func = (FPDFBitmap_GetHeight_t) dlsym(pdfium_handle, "FPDFBitmap_GetHeight");
bitmap_get_stride_func = (FPDFBitmap_GetStride_t) dlsym(pdfium_handle, "FPDFBitmap_GetStride");
bitmap_get_buffer_func = (FPDFBitmap_GetBuffer_t) dlsym(pdfium_handle, "FPDFBitmap_GetBuffer");
bitmap_destroy_func = (FPDFBitmap_Destroy_t) dlsym(pdfium_handle, "FPDFBitmap_Destroy");
get_object_bounds_func = (FPDFPageObj_GetBounds_t) dlsym(pdfium_handle, "FPDFPageObj_GetBounds");
bool success = get_annot_count_func && get_annot_func && get_annot_subtype_func &&
get_annot_rect_func && get_annot_string_func;
@ -127,6 +161,27 @@ Java_com_aryan_reader_pdf_NativePdfiumBridge_getPageFontFlags(JNIEnv *env, jclas
return result;
}
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_getPageCharBoxes(JNIEnv *env, jclass clazz, jlong textPagePtr, jint count) {
if (!init_pdfium() || !get_char_box_func || count <= 0) return nullptr;
const int stride = 4;
jfloatArray result = env->NewFloatArray(count * stride);
jfloat *fill = new jfloat[count * stride];
void* tp = reinterpret_cast<void*>(textPagePtr);
for (int i = 0; i < count; i++) {
double left = 0, right = 0, bottom = 0, top = 0;
get_char_box_func(tp, i, &left, &right, &bottom, &top);
fill[i * stride + 0] = (jfloat)left;
fill[i * stride + 1] = (jfloat)bottom;
fill[i * stride + 2] = (jfloat)right;
fill[i * stride + 3] = (jfloat)top;
}
env->SetFloatArrayRegion(result, 0, count * stride, fill);
delete[] fill;
return result;
}
extern "C" JNIEXPORT jint JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_getAnnotCount(JNIEnv *env, jclass clazz, jlong pagePtr) {
if (!init_pdfium() || !get_annot_count_func) return 0;
@ -191,5 +246,84 @@ Java_com_aryan_reader_pdf_NativePdfiumBridge_getAnnotString(JNIEnv *env, jclass
jstring result = env->NewString(reinterpret_cast<const jchar*>(buffer.data()), (jsize)(buffer.size() - 1));
env->ReleaseStringUTFChars(key, nativeKey);
return result;
}
extern "C" JNIEXPORT jint JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_getPageObjectCount(JNIEnv *env, jclass clazz, jlong pagePtr) {
if (!init_pdfium() || !count_objects_func) return 0;
return count_objects_func(reinterpret_cast<void*>(pagePtr));
}
extern "C" JNIEXPORT jint JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_getPageObjectType(JNIEnv *env, jclass clazz, jlong pagePtr, jint index) {
if (!init_pdfium() || !get_object_func || !get_object_type_func) return 0;
void* obj = get_object_func(reinterpret_cast<void*>(pagePtr), index);
return obj ? get_object_type_func(obj) : 0;
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_getPageObjectBoundingBox(JNIEnv *env, jclass clazz, jlong pagePtr, jint index, jfloatArray outRect) {
if (!init_pdfium() || !get_object_func || !get_object_bounds_func) return JNI_FALSE;
void* obj = get_object_func(reinterpret_cast<void*>(pagePtr), index);
if (!obj) return JNI_FALSE;
float left = 0, bottom = 0, right = 0, top = 0;
if (get_object_bounds_func(obj, &left, &bottom, &right, &top)) {
jfloat rect[4] = {left, bottom, right, top};
env->SetFloatArrayRegion(outRect, 0, 4, rect);
return JNI_TRUE;
}
return JNI_FALSE;
}
extern "C" JNIEXPORT jintArray JNICALL
Java_com_aryan_reader_pdf_NativePdfiumBridge_extractImagePixels(JNIEnv *env, jclass clazz, jlong pagePtr, jint index, jintArray dimens) {
if (!init_pdfium() || !get_object_func || !get_image_bitmap_func || !bitmap_get_buffer_func) return nullptr;
void* obj = get_object_func(reinterpret_cast<void*>(pagePtr), index);
if (!obj || get_object_type_func(obj) != 3) return nullptr; // 3 = FPDF_PAGEOBJ_IMAGE
void* bmp = get_image_bitmap_func(obj);
if (!bmp) return nullptr;
int w = bitmap_get_width_func(bmp);
int h = bitmap_get_height_func(bmp);
int stride = bitmap_get_stride_func(bmp);
uint8_t* buffer = (uint8_t*)bitmap_get_buffer_func(bmp);
if (!buffer || w <= 0 || h <= 0) {
bitmap_destroy_func(bmp);
return nullptr;
}
jintArray result = env->NewIntArray(w * h);
jint* pixels = new jint[w * h];
int bpp = stride / w;
for (int y = 0; y < h; y++) {
uint8_t* row = buffer + y * stride;
for (int x = 0; x < w; x++) {
uint8_t r = 0, g = 0, b = 0, a = 255;
if (bpp >= 3) {
b = row[x * bpp + 0];
g = row[x * bpp + 1];
r = row[x * bpp + 2];
if (bpp >= 4) a = row[x * bpp + 3];
} else if (bpp == 1) {
r = g = b = row[x];
}
// Pack pixels for Android ARGB_8888
pixels[y * w + x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
env->SetIntArrayRegion(result, 0, w * h, pixels);
delete[] pixels;
bitmap_destroy_func(bmp);
jint dims[2] = {w, h};
env->SetIntArrayRegion(dimens, 0, 2, dims);
return result;
}