/* logic/reader/page_source.c — 页源契约实现(纯 C):目录册 + 图片头探测。 * * 契约与两种消费方式的说明见 page_source.h。本文件不含 SDK 依赖:解码/释放由 * UI 层注回回调,因此可在宿主测试里用假回调覆盖「空册/越界/重复 open-close/首末页」。 */ #include "logic/reader/page_source.h" #include #include #include #include #include /* ============================================================ */ /* 契约包装 */ /* ============================================================ */ int mr_book_valid(const mr_book *b) { return b && b->ops && b->ops->count && b->ops->open; } int mr_book_count(const mr_book *b) { if (!mr_book_valid(b)) return 0; int n = b->ops->count(b->ctx); return n > 0 ? n : 0; } int mr_book_meta(const mr_book *b, int index, int *w, int *h) { if (w) *w = 0; if (h) *h = 0; if (!mr_book_valid(b) || !b->ops->meta) return 0; if (index < 0 || index >= mr_book_count(b)) return 0; return b->ops->meta(b->ctx, index, w, h) ? 1 : 0; } meow_img *mr_book_open(mr_book *b, int index) { if (!mr_book_valid(b)) return NULL; if (index < 0 || index >= mr_book_count(b)) return NULL; /* 越界:契约边界 */ return b->ops->open(b->ctx, index); } void mr_book_close(mr_book *b, meow_img *img) { if (!img) return; if (!mr_book_valid(b) || !b->ops->close) return; b->ops->close(b->ctx, img); } void mr_book_destroy(mr_book *b) { if (!b) return; if (b->ops && b->ops->destroy && b->ctx) b->ops->destroy(b->ctx); b->ops = NULL; b->ctx = NULL; } /* ============================================================ */ /* 目录册 */ /* ============================================================ */ static int name_cmp_qsort(const void *pa, const void *pb) { const char *a = *(const char *const *)pa; const char *b = *(const char *const *)pb; return mr_natural_cmp(a, b); } void mr_dir_book_path(const mr_dir_book *db, int index, char *out, int outsz) { if (!out || outsz <= 0) return; out[0] = '\0'; if (!db || index < 0 || index >= db->count || !db->names[index]) return; size_t dl = strlen(db->dir); if (dl > 0 && db->dir[dl - 1] == '/') snprintf(out, (size_t)outsz, "%s%s", db->dir, db->names[index]); else snprintf(out, (size_t)outsz, "%s/%s", db->dir, db->names[index]); } void mr_dir_book_free(mr_dir_book *db) { if (!db) return; for (int i = 0; i < db->count; i++) free(db->names[i]); free(db->names); db->names = NULL; db->count = 0; } int mr_dir_book_init(mr_dir_book *db, const char *dir, int maxdim, mr_book_load_fn load, mr_book_unload_fn unload, void *ud) { if (!db) return -1; memset(db, 0, sizeof *db); if (!dir || !dir[0] || !load || !unload) return -1; if (maxdim <= 0) maxdim = 720; if (strlen(dir) >= sizeof db->dir) return -1; DIR *d = opendir(dir); if (!d) return -1; snprintf(db->dir, sizeof db->dir, "%s", dir); db->maxdim = maxdim; db->load = load; db->unload = unload; db->ud = ud; int cap = 0; struct dirent *de; while ((de = readdir(d)) != NULL) { const char *name = de->d_name; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; if (name[0] == '.') continue; /* 隐藏文件不参与成册 */ if (!mr_path_is_image(name)) continue; char full[MR_PATH_MAX]; size_t dl = strlen(db->dir); int n = (dl > 0 && db->dir[dl - 1] == '/') ? snprintf(full, sizeof full, "%s%s", db->dir, name) : snprintf(full, sizeof full, "%s/%s", db->dir, name); if (n <= 0 || n >= (int)sizeof full) continue; struct stat st; if (lstat(full, &st) != 0 || !S_ISREG(st.st_mode)) continue; /* 跳过符号链接/目录 */ if (db->count >= MR_BOOK_MAX_PAGES) break; if (db->count >= cap) { int nc = cap ? cap * 2 : 32; char **nn = (char **)realloc(db->names, (size_t)nc * sizeof *nn); if (!nn) { mr_dir_book_free(db); closedir(d); return -1; } db->names = nn; cap = nc; } size_t len = strlen(name); db->names[db->count] = (char *)malloc(len + 1); if (!db->names[db->count]) { mr_dir_book_free(db); closedir(d); return -1; } memcpy(db->names[db->count], name, len + 1); db->count++; } closedir(d); if (db->count > 1) qsort(db->names, (size_t)db->count, sizeof *db->names, name_cmp_qsort); return db->count; } /* ============================================================ */ /* 图片头探测(不解码像素) */ /* ============================================================ */ static unsigned probe_be32(const unsigned char *p) { return ((unsigned)p[0] << 24) | ((unsigned)p[1] << 16) | ((unsigned)p[2] << 8) | p[3]; } static int probe_png(FILE *f, int *w, int *h) { unsigned char hdr[24]; if (fread(hdr, 1, sizeof hdr, f) != sizeof hdr) return 0; static const unsigned char sig[8] = { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A }; if (memcmp(hdr, sig, 8) != 0 || memcmp(hdr + 12, "IHDR", 4) != 0) return 0; unsigned ww = probe_be32(hdr + 16), hh = probe_be32(hdr + 20); if (ww == 0 || hh == 0) return 0; *w = (int)ww; *h = (int)hh; return 1; } static int probe_jpeg(FILE *f, int *w, int *h) { unsigned char b[2]; if (fread(b, 1, 2, f) != 2 || b[0] != 0xFF || b[1] != 0xD8) return 0; for (;;) { int c = fgetc(f); if (c == EOF) return 0; if (c != 0xFF) continue; int m = fgetc(f); while (m == 0xFF) m = fgetc(f); /* 填充字节 */ if (m == EOF) return 0; if (m == 0xD8 || m == 0x01 || (m >= 0xD0 && m <= 0xD7)) continue; /* 无长度段 */ if (m == 0xD9 || m == 0xDA) return 0; /* EOI/SOS 前没等到 SOF */ unsigned char lb[2]; if (fread(lb, 1, 2, f) != 2) return 0; int len = ((int)lb[0] << 8) | lb[1]; if (len < 2) return 0; int is_sof = (m >= 0xC0 && m <= 0xCF) && m != 0xC4 && m != 0xC8 && m != 0xCC; if (is_sof) { unsigned char so[5]; if (len < 7 || fread(so, 1, 5, f) != 5) return 0; int hh = ((int)so[1] << 8) | so[2]; int ww = ((int)so[3] << 8) | so[4]; if (ww <= 0 || hh <= 0) return 0; *w = ww; *h = hh; return 1; } if (fseek(f, len - 2, SEEK_CUR) != 0) return 0; } } static int probe_bmp(FILE *f, int *w, int *h) { unsigned char hdr[26]; if (fread(hdr, 1, sizeof hdr, f) != sizeof hdr) return 0; if (hdr[0] != 'B' || hdr[1] != 'M') return 0; int ww = (int)((unsigned)hdr[18] | ((unsigned)hdr[19] << 8) | ((unsigned)hdr[20] << 16) | ((unsigned)hdr[21] << 24)); int hh = (int)((unsigned)hdr[22] | ((unsigned)hdr[23] << 8) | ((unsigned)hdr[24] << 16) | ((unsigned)hdr[25] << 24)); if (hh < 0) hh = -hh; /* 自底向上 BMP */ if (ww <= 0 || hh <= 0) return 0; *w = ww; *h = hh; return 1; } int mr_probe_size(const char *path, int *w, int *h) { if (w) *w = 0; if (h) *h = 0; if (!path || !path[0] || !w || !h) return 0; FILE *f = fopen(path, "rb"); if (!f) return 0; int ok = probe_png(f, w, h); if (!ok) { rewind(f); ok = probe_jpeg(f, w, h); } if (!ok) { rewind(f); ok = probe_bmp(f, w, h); } fclose(f); if (ok && (*w <= 0 || *h <= 0)) { *w = *h = 0; return 0; } return ok; } /* ============================================================ */ /* mr_book_ops 适配器(目录册) */ /* ============================================================ */ static int dir_ops_count(void *ctx) { mr_dir_book *db = (mr_dir_book *)ctx; return db ? db->count : 0; } static int dir_ops_meta(void *ctx, int index, int *w, int *h) { mr_dir_book *db = (mr_dir_book *)ctx; if (w) *w = 0; if (h) *h = 0; if (!db || index < 0 || index >= db->count) return 0; char path[MR_PATH_MAX]; mr_dir_book_path(db, index, path, (int)sizeof path); return mr_probe_size(path, w, h); } static meow_img *dir_ops_open(void *ctx, int index) { mr_dir_book *db = (mr_dir_book *)ctx; if (!db || index < 0 || index >= db->count) return NULL; char path[MR_PATH_MAX]; mr_dir_book_path(db, index, path, (int)sizeof path); if (!path[0]) return NULL; return db->load(path, db->ud, db->maxdim); } static void dir_ops_close(void *ctx, meow_img *img) { mr_dir_book *db = (mr_dir_book *)ctx; if (db && db->unload && img) db->unload(img); } static void dir_ops_destroy(void *ctx) { mr_dir_book *db = (mr_dir_book *)ctx; if (db) mr_dir_book_free(db); } static const mr_book_ops g_dir_ops = { dir_ops_count, dir_ops_meta, dir_ops_open, dir_ops_close, dir_ops_destroy, }; void mr_dir_book_bind(mr_book *b, mr_dir_book *db) { if (!b) return; b->ops = db ? &g_dir_ops : NULL; b->ctx = db; }