A fork of pulp-os for the xteink4 adding custom apps
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

split up reader app struct

hans 37e4b075 0c0a96f7

+502 -456
+98 -89
src/apps/reader/epub_pipeline.rs src/apps/reader/epubs.rs
··· 47 47 "epub_init_zip: too small", 48 48 )); 49 49 } 50 - self.epub_file_size = epub_size; 51 - self.epub_name_hash = cache::fnv1a(name.as_bytes()); 52 - self.cache_dir = cache::dir_name_for_hash(self.epub_name_hash); 50 + self.epub.archive_size = epub_size; 51 + self.epub.name_hash = cache::fnv1a(name.as_bytes()); 52 + self.epub.cache_dir = cache::dir_name_for_hash(self.epub.name_hash); 53 53 54 54 let tail_size = (epub_size as usize).min(EOCD_TAIL); 55 55 let tail_offset = epub_size - tail_size as u32; 56 - let n = k.read_chunk(name, tail_offset, &mut self.buf[..tail_size])?; 57 - let (cd_offset, cd_size) = ZipIndex::parse_eocd(&self.buf[..n], epub_size) 56 + let n = k.read_chunk(name, tail_offset, &mut self.pg.buf[..tail_size])?; 57 + let (cd_offset, cd_size) = ZipIndex::parse_eocd(&self.pg.buf[..n], epub_size) 58 58 .map_err(|_| Error::new(ErrorKind::ParseFailed, "epub_init_zip: EOCD"))?; 59 59 60 60 log::info!( ··· 70 70 .map_err(|_| Error::new(ErrorKind::OutOfMemory, "epub_init_zip: CD alloc"))?; 71 71 cd_buf.resize(cd_size as usize, 0); 72 72 super::read_full(k, name, cd_offset, &mut cd_buf)?; 73 - self.zip.clear(); 74 - self.zip 73 + self.epub.zip.clear(); 74 + self.epub 75 + .zip 75 76 .parse_central_directory(&cd_buf) 76 77 .map_err(|_| Error::new(ErrorKind::ParseFailed, "epub_init_zip: CD parse"))?; 77 78 drop(cd_buf); 78 79 79 - log::info!("epub: {} entries in ZIP", self.zip.count()); 80 + log::info!("epub: {} entries in ZIP", self.epub.zip.count()); 80 81 81 82 Ok(()) 82 83 } ··· 86 87 let name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); 87 88 88 89 let mut opf_path_buf = [0u8; epub::OPF_PATH_CAP]; 89 - let opf_path_len = if let Some(container_idx) = self.zip.find("META-INF/container.xml") { 90 - let container_data = super::extract_zip_entry(k, name, &self.zip, container_idx) 91 - .map_err(|_| Error::new(ErrorKind::ReadFailed, "epub_init_opf: container read"))?; 90 + let opf_path_len = if let Some(container_idx) = self.epub.zip.find("META-INF/container.xml") 91 + { 92 + let container_data = super::extract_zip_entry(k, name, &self.epub.zip, container_idx) 93 + .map_err(|_| { 94 + Error::new(ErrorKind::ReadFailed, "epub_init_opf: container read") 95 + })?; 92 96 let len = epub::parse_container(&container_data, &mut opf_path_buf).map_err(|_| { 93 97 Error::new(ErrorKind::ParseFailed, "epub_init_opf: container parse") 94 98 })?; ··· 96 100 len 97 101 } else { 98 102 log::warn!("epub: no container.xml, scanning for .opf"); 99 - epub::find_opf_in_zip(&self.zip, &mut opf_path_buf) 103 + epub::find_opf_in_zip(&self.epub.zip, &mut opf_path_buf) 100 104 .map_err(|_| Error::new(ErrorKind::NotFound, "epub_init_opf: no .opf in zip"))? 101 105 }; 102 106 ··· 106 110 log::info!("epub: OPF at {}", opf_path); 107 111 108 112 let opf_idx = self 113 + .epub 109 114 .zip 110 115 .find(opf_path) 111 - .or_else(|| self.zip.find_icase(opf_path)) 116 + .or_else(|| self.epub.zip.find_icase(opf_path)) 112 117 .ok_or(Error::new(ErrorKind::NotFound, "epub_init_opf: OPF entry"))?; 113 - let opf_data = super::extract_zip_entry(k, name, &self.zip, opf_idx) 118 + let opf_data = super::extract_zip_entry(k, name, &self.epub.zip, opf_idx) 114 119 .map_err(|_| Error::new(ErrorKind::ReadFailed, "epub_init_opf: OPF read"))?; 115 120 116 121 let opf_dir = opf_path.rsplit_once('/').map(|(d, _)| d).unwrap_or(""); 117 122 epub::parse_opf( 118 123 &opf_data, 119 124 opf_dir, 120 - &self.zip, 121 - &mut self.meta, 122 - &mut self.spine, 125 + &self.epub.zip, 126 + &mut self.epub.meta, 127 + &mut self.epub.spine, 123 128 ) 124 129 .map_err(|_| Error::new(ErrorKind::ParseFailed, "epub_init_opf: OPF parse"))?; 125 130 126 131 // defer TOC to NeedToc to avoid stack overflow while OPF is live 127 - self.toc_source = epub::find_toc_source(&opf_data, opf_dir, &self.zip); 132 + self.epub.toc_source = epub::find_toc_source(&opf_data, opf_dir, &self.epub.zip); 128 133 drop(opf_data); 129 134 130 135 log::info!( 131 136 "epub: \"{}\" by {} -- {} chapters", 132 - self.meta.title_str(), 133 - self.meta.author_str(), 134 - self.spine.len() 137 + self.epub.meta.title_str(), 138 + self.epub.meta.author_str(), 139 + self.epub.spine.len() 135 140 ); 136 141 137 - let tlen = self.meta.title_len as usize; 142 + let tlen = self.epub.meta.title_len as usize; 138 143 if tlen > 0 { 139 144 let n = tlen.min(self.title.len()); 140 - self.title[..n].copy_from_slice(&self.meta.title[..n]); 145 + self.title[..n].copy_from_slice(&self.epub.meta.title[..n]); 141 146 self.title_len = n; 142 147 143 - if let Err(e) = k.save_title(name, self.meta.title_str()) { 148 + if let Err(e) = k.save_title(name, self.epub.meta.title_str()) { 144 149 log::warn!("epub: failed to save title mapping: {}", e); 145 150 } 146 151 } 147 152 148 - self.toc.clear(); 153 + self.epub.toc.clear(); 149 154 150 155 Ok(()) 151 156 } ··· 154 159 &mut self, 155 160 k: &mut KernelHandle<'_>, 156 161 ) -> crate::error::Result<bool> { 157 - let dir_buf = self.cache_dir; 162 + let dir_buf = self.epub.cache_dir; 158 163 let dir = cache::dir_name_str(&dir_buf); 159 164 160 165 // read into self.buf to avoid ~2 KB stack temporaries 161 - let meta_cap = cache::META_MAX_SIZE.min(self.buf.len()); 162 - if let Ok(n) = k.read_app_subdir_chunk(dir, cache::META_FILE, 0, &mut self.buf[..meta_cap]) 166 + let meta_cap = cache::META_MAX_SIZE.min(self.pg.buf.len()); 167 + if let Ok(n) = 168 + k.read_app_subdir_chunk(dir, cache::META_FILE, 0, &mut self.pg.buf[..meta_cap]) 163 169 && let Ok(count) = cache::parse_cache_meta( 164 - &self.buf[..n], 165 - self.epub_file_size, 166 - self.epub_name_hash, 167 - self.spine.len(), 168 - &mut self.chapter_sizes, 170 + &self.pg.buf[..n], 171 + self.epub.archive_size, 172 + self.epub.name_hash, 173 + self.epub.spine.len(), 174 + &mut self.epub.chapter_sizes, 169 175 ) 170 176 { 171 - self.chapters_cached = true; 177 + self.epub.chapters_cached = true; 172 178 for i in 0..count { 173 - self.ch_cached[i] = true; 179 + self.epub.ch_cached[i] = true; 174 180 } 175 181 log::info!("epub: cache hit ({} chapters)", count); 176 182 return Ok(true); 177 183 } 178 184 179 - log::info!("epub: building cache for {} chapters", self.spine.len()); 185 + log::info!( 186 + "epub: building cache for {} chapters", 187 + self.epub.spine.len() 188 + ); 180 189 k.ensure_app_subdir(dir)?; 181 - self.cache_chapter = 0; 190 + self.epub.cache_chapter = 0; 182 191 Ok(false) 183 192 } 184 193 ··· 186 195 &mut self, 187 196 k: &mut KernelHandle<'_>, 188 197 ) -> crate::error::Result<bool> { 189 - let dir_buf = self.cache_dir; 198 + let dir_buf = self.epub.cache_dir; 190 199 let dir = cache::dir_name_str(&dir_buf); 191 - let spine_len = self.spine.len(); 200 + let spine_len = self.epub.spine.len(); 192 201 193 202 let mut meta_buf = [0u8; cache::META_MAX_SIZE]; 194 203 let meta_len = cache::encode_cache_meta( 195 - self.epub_file_size, 196 - self.epub_name_hash, 197 - &self.chapter_sizes[..spine_len], 204 + self.epub.archive_size, 205 + self.epub.name_hash, 206 + &self.epub.chapter_sizes[..spine_len], 198 207 &mut meta_buf, 199 208 ); 200 209 k.write_app_subdir(dir, cache::META_FILE, &meta_buf[..meta_len])?; 201 210 202 - self.chapters_cached = true; 211 + self.epub.chapters_cached = true; 203 212 log::info!("epub: cache complete"); 204 213 Ok(false) 205 214 } ··· 214 223 k: &mut KernelHandle<'_>, 215 224 ch: usize, 216 225 ) -> crate::error::Result<()> { 217 - if ch >= self.spine.len() || self.ch_cached[ch] { 226 + if ch >= self.epub.spine.len() || self.epub.ch_cached[ch] { 218 227 return Ok(()); 219 228 } 220 229 221 - let dir_buf = self.cache_dir; 230 + let dir_buf = self.epub.cache_dir; 222 231 let dir = cache::dir_name_str(&dir_buf); 223 232 let (nb, nl) = self.name_copy(); 224 233 let epub_name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); 225 - let entry_idx = self.spine.items[ch] as usize; 226 - let entry = *self.zip.entry(entry_idx); 234 + let entry_idx = self.epub.spine.items[ch] as usize; 235 + let entry = *self.epub.zip.entry(entry_idx); 227 236 let ch_file = cache::chapter_file_name(ch as u16); 228 237 let ch_str = cache::chapter_file_str(&ch_file); 229 238 ··· 244 253 .await 245 254 .map_err(|msg| Error::from(msg).with_source("epub_cache_chapter_async: stream"))?; 246 255 247 - self.chapter_sizes[ch] = text_size; 248 - self.ch_cached[ch] = true; 256 + self.epub.chapter_sizes[ch] = text_size; 257 + self.epub.ch_cached[ch] = true; 249 258 250 259 log::info!( 251 260 "epub: cached ch{}/{} = {} bytes", 252 261 ch, 253 - self.spine.len(), 262 + self.epub.spine.len(), 254 263 text_size 255 264 ); 256 265 Ok(()) ··· 260 269 self.reset_paging(); 261 270 // force reload; ch_cache may hold a different chapter's data 262 271 // with the same byte count (try_cache_chapter only checks len) 263 - self.ch_cache = Vec::new(); 264 - let ch = self.chapter as usize; 272 + self.epub.ch_cache = Vec::new(); 273 + let ch = self.epub.chapter as usize; 265 274 self.file_size = if ch < cache::MAX_CACHE_CHAPTERS { 266 - self.chapter_sizes[ch] 275 + self.epub.chapter_sizes[ch] 267 276 } else { 268 277 0 269 278 }; 270 279 log::info!( 271 280 "epub: index chapter {}/{} ({} bytes cached text)", 272 - self.chapter + 1, 273 - self.spine.len(), 281 + self.epub.chapter + 1, 282 + self.epub.spine.len(), 274 283 self.file_size, 275 284 ); 276 285 } 277 286 278 287 pub(super) fn try_cache_chapter(&mut self, k: &mut KernelHandle<'_>) -> bool { 279 - if !self.is_epub || !self.chapters_cached { 288 + if !self.is_epub || !self.epub.chapters_cached { 280 289 return false; 281 290 } 282 291 283 - let ch = self.chapter as usize; 292 + let ch = self.epub.chapter as usize; 284 293 let ch_size = if ch < cache::MAX_CACHE_CHAPTERS { 285 - self.chapter_sizes[ch] as usize 294 + self.epub.chapter_sizes[ch] as usize 286 295 } else { 287 296 return false; 288 297 }; 289 298 290 299 if ch_size == 0 || ch_size > CHAPTER_CACHE_MAX { 291 - self.ch_cache = Vec::new(); 300 + self.epub.ch_cache = Vec::new(); 292 301 return false; 293 302 } 294 303 295 - if self.ch_cache.len() == ch_size { 304 + if self.epub.ch_cache.len() == ch_size { 296 305 log::info!("chapter cache: reusing {} bytes in RAM", ch_size); 297 306 return true; 298 307 } 299 308 300 - self.ch_cache = Vec::new(); 301 - if self.ch_cache.try_reserve_exact(ch_size).is_err() { 309 + self.epub.ch_cache = Vec::new(); 310 + if self.epub.ch_cache.try_reserve_exact(ch_size).is_err() { 302 311 log::info!("chapter cache: OOM for {} bytes", ch_size); 303 312 return false; 304 313 } 305 - self.ch_cache.resize(ch_size, 0); 314 + self.epub.ch_cache.resize(ch_size, 0); 306 315 307 - let dir_buf = self.cache_dir; 316 + let dir_buf = self.epub.cache_dir; 308 317 let dir = cache::dir_name_str(&dir_buf); 309 - let ch_file = cache::chapter_file_name(self.chapter); 318 + let ch_file = cache::chapter_file_name(self.epub.chapter); 310 319 let ch_str = cache::chapter_file_str(&ch_file); 311 320 312 321 let mut pos = 0usize; ··· 316 325 dir, 317 326 ch_str, 318 327 pos as u32, 319 - &mut self.ch_cache[pos..pos + chunk], 328 + &mut self.epub.ch_cache[pos..pos + chunk], 320 329 ) { 321 330 Ok(n) if n > 0 => pos += n, 322 331 Ok(_) => break, 323 332 Err(e) => { 324 333 log::info!("chapter cache: SD read failed at {}: {}", pos, e); 325 - self.ch_cache = Vec::new(); 334 + self.epub.ch_cache = Vec::new(); 326 335 return false; 327 336 } 328 337 } ··· 330 339 331 340 log::info!( 332 341 "chapter cache: loaded ch{} ({} bytes) into RAM", 333 - self.chapter, 342 + self.epub.chapter, 334 343 ch_size, 335 344 ); 336 345 true ··· 339 348 // run one step of background caching; async because CacheChapter 340 349 // awaits epub_cache_chapter_async which yields during deflate 341 350 pub(super) async fn bg_cache_step(&mut self, k: &mut KernelHandle<'_>) { 342 - match self.bg_cache { 351 + match self.epub.bg_cache { 343 352 BgCacheState::CacheChapter => { 344 - let spine_len = self.spine.len(); 353 + let spine_len = self.epub.spine.len(); 345 354 346 355 // skip chapters already cached 347 - while (self.cache_chapter as usize) < spine_len 348 - && self.ch_cached[self.cache_chapter as usize] 356 + while (self.epub.cache_chapter as usize) < spine_len 357 + && self.epub.ch_cached[self.epub.cache_chapter as usize] 349 358 { 350 - self.cache_chapter += 1; 359 + self.epub.cache_chapter += 1; 351 360 } 352 361 353 362 // priority: cache chapters adjacent to reading position 354 363 // before continuing the sequential scan; forward/backward 355 364 // nav stays instant 356 - let reading_ch = self.chapter as usize; 365 + let reading_ch = self.epub.chapter as usize; 357 366 for &adj in &[reading_ch + 1, reading_ch.saturating_sub(1)] { 358 - if adj < spine_len && adj != reading_ch && !self.ch_cached[adj] { 367 + if adj < spine_len && adj != reading_ch && !self.epub.ch_cached[adj] { 359 368 log::info!( 360 369 "epub: priority cache ch{} (adjacent to ch{})", 361 370 adj, ··· 367 376 } 368 377 } 369 378 370 - let ch = self.cache_chapter as usize; 379 + let ch = self.epub.cache_chapter as usize; 371 380 if ch >= spine_len { 372 381 let _ = self.epub_finish_cache(k); 373 - self.img_cache_ch = self.chapter; 374 - self.img_cache_offset = 0; 375 - self.img_scan_wrapped = false; 376 - self.bg_cache = BgCacheState::CacheImage; 382 + self.epub.img_cache_ch = self.epub.chapter; 383 + self.epub.img_cache_offset = 0; 384 + self.epub.img_scan_wrapped = false; 385 + self.epub.bg_cache = BgCacheState::CacheImage; 377 386 return; 378 387 } 379 388 380 389 match self.epub_cache_chapter_async(k, ch).await { 381 390 Ok(()) => { 382 - self.cache_chapter += 1; 391 + self.epub.cache_chapter += 1; 383 392 // try nearby image dispatch before next chapter 384 393 if self.try_dispatch_nearby_image(k) { 385 - self.bg_cache = BgCacheState::WaitNearbyImage; 394 + self.epub.bg_cache = BgCacheState::WaitNearbyImage; 386 395 } 387 396 // else stay in CacheChapter 388 397 } 389 398 Err(e) => { 390 399 log::warn!("bg: ch{} failed: {}, skipping", ch, e); 391 - self.cache_chapter += 1; 400 + self.epub.cache_chapter += 1; 392 401 } 393 402 } 394 403 } ··· 399 408 if self.try_dispatch_nearby_image(k) { 400 409 // stay in WaitNearbyImage 401 410 } else { 402 - self.bg_cache = BgCacheState::CacheChapter; 411 + self.epub.bg_cache = BgCacheState::CacheChapter; 403 412 } 404 413 } 405 414 Ok(None) => {} 406 415 Err(e) => { 407 416 log::warn!("bg: nearby image error: {}, continuing", e); 408 - self.bg_cache = BgCacheState::CacheChapter; 417 + self.epub.bg_cache = BgCacheState::CacheChapter; 409 418 } 410 419 } 411 420 } ··· 415 424 // worker busy: dispatched a small image, wait 416 425 // worker idle: decoded inline, scan next tick 417 426 if !work_queue::is_idle() { 418 - self.bg_cache = BgCacheState::WaitImage; 427 + self.epub.bg_cache = BgCacheState::WaitImage; 419 428 } 420 429 } 421 - Ok(false) => self.bg_cache = BgCacheState::Idle, 430 + Ok(false) => self.epub.bg_cache = BgCacheState::Idle, 422 431 Err(e) => { 423 432 log::warn!("bg: image error: {}, continuing", e); 424 433 // stay in CacheImage; next tick scans for the next one ··· 426 435 } 427 436 } 428 437 BgCacheState::WaitImage => match self.epub_recv_image_result(k) { 429 - Ok(Some(_)) => self.bg_cache = BgCacheState::CacheImage, 438 + Ok(Some(_)) => self.epub.bg_cache = BgCacheState::CacheImage, 430 439 Ok(None) => {} 431 440 Err(e) => { 432 441 log::warn!("bg: image recv error: {}", e); 433 - self.bg_cache = BgCacheState::CacheImage; 442 + self.epub.bg_cache = BgCacheState::CacheImage; 434 443 } 435 444 }, 436 445 BgCacheState::Idle => {}
+53 -51
src/apps/reader/images.rs
··· 41 41 self.page_img = None; 42 42 self.fullscreen_img = false; 43 43 44 - if !self.is_epub || self.spine.is_empty() { 44 + if !self.is_epub || self.epub.spine.is_empty() { 45 45 return; 46 46 } 47 47 48 48 { 49 49 let mut has_img = false; 50 50 let mut has_text = false; 51 - for i in 0..self.line_count { 52 - if self.lines[i].is_image() { 53 - if self.lines[i].is_image_origin() { 51 + for i in 0..self.pg.line_count { 52 + if self.pg.lines[i].is_image() { 53 + if self.pg.lines[i].is_image_origin() { 54 54 has_img = true; 55 55 } 56 - } else if self.lines[i].len > 0 { 56 + } else if self.pg.lines[i].len > 0 { 57 57 has_text = true; 58 58 } 59 59 } ··· 63 63 // copy src path to a local buf to avoid borrowing self.buf below 64 64 let mut src_buf = [0u8; 128]; 65 65 let mut src_len = 0usize; 66 - for i in 0..self.line_count { 67 - if self.lines[i].is_image_origin() { 68 - let start = self.lines[i].start as usize; 69 - let len = self.lines[i].len as usize; 70 - if start + len <= self.buf_len { 66 + for i in 0..self.pg.line_count { 67 + if self.pg.lines[i].is_image_origin() { 68 + let start = self.pg.lines[i].start as usize; 69 + let len = self.pg.lines[i].len as usize; 70 + if start + len <= self.pg.buf_len { 71 71 let n = len.min(src_buf.len()); 72 - src_buf[..n].copy_from_slice(&self.buf[start..start + n]); 72 + src_buf[..n].copy_from_slice(&self.pg.buf[start..start + n]); 73 73 src_len = n; 74 74 } 75 75 break; ··· 87 87 88 88 log::info!("reader: decoding image: {}", src_str); 89 89 90 - let ch_zip_idx = self.spine.items[self.chapter as usize] as usize; 91 - let ch_path = self.zip.entry_name(ch_zip_idx); 90 + let ch_zip_idx = self.epub.spine.items[self.epub.chapter as usize] as usize; 91 + let ch_path = self.epub.zip.entry_name(ch_zip_idx); 92 92 let ch_dir = ch_path.rsplit_once('/').map(|(d, _)| d).unwrap_or(""); 93 93 94 94 let mut path_buf = [0u8; 512]; ··· 98 98 Err(_) => return, 99 99 }; 100 100 101 - let dir_buf = self.cache_dir; 101 + let dir_buf = self.epub.cache_dir; 102 102 let dir = cache::dir_name_str(&dir_buf); 103 103 let img_name = img_cache_name(cache::fnv1a(full_path.as_bytes())); 104 104 let img_file = img_cache_str(&img_name); ··· 128 128 } 129 129 130 130 let zip_idx = match self 131 + .epub 131 132 .zip 132 133 .find(full_path) 133 - .or_else(|| self.zip.find_icase(full_path)) 134 + .or_else(|| self.epub.zip.find_icase(full_path)) 134 135 { 135 136 Some(idx) => idx, 136 137 None => { ··· 139 140 } 140 141 }; 141 142 142 - let entry = *self.zip.entry(zip_idx); 143 + let entry = *self.epub.zip.entry(zip_idx); 143 144 let (nb, nl) = self.name_copy(); 144 145 let epub_name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); 145 146 ··· 256 257 // OOM fallback: release chapter cache and retry 257 258 let result = match result { 258 259 Ok(img) => Ok(img), 259 - Err(e) if !self.ch_cache.is_empty() => { 260 + Err(e) if !self.epub.ch_cache.is_empty() => { 260 261 log::info!( 261 262 "reader: decode failed ({}), releasing {} KB chapter cache and retrying", 262 263 e, 263 - self.ch_cache.len() / 1024, 264 + self.epub.ch_cache.len() / 1024, 264 265 ); 265 - self.ch_cache = Vec::new(); 266 + self.epub.ch_cache = Vec::new(); 266 267 do_decode(k) 267 268 } 268 269 Err(e) => Err(e), ··· 299 300 ch: usize, 300 301 start_offset: usize, 301 302 ) -> crate::error::Result<ScanResult> { 302 - if ch >= cache::MAX_CACHE_CHAPTERS || !self.ch_cached[ch] { 303 + if ch >= cache::MAX_CACHE_CHAPTERS || !self.epub.ch_cached[ch] { 303 304 return Ok(ScanResult::NoneFound); 304 305 } 305 - let ch_size = self.chapter_sizes[ch] as usize; 306 + let ch_size = self.epub.chapter_sizes[ch] as usize; 306 307 if ch_size == 0 { 307 308 return Ok(ScanResult::NoneFound); 308 309 } 309 310 310 - self.prefetch_page = NO_PREFETCH; 311 + self.pg.prefetch_page = NO_PREFETCH; 311 312 312 - let dir_buf = self.cache_dir; 313 + let dir_buf = self.epub.cache_dir; 313 314 let dir = cache::dir_name_str(&dir_buf); 314 315 let (nb, nl) = self.name_copy(); 315 316 let epub_name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); ··· 324 325 dir, 325 326 ch_str, 326 327 offset as u32, 327 - &mut self.prefetch[..read_len], 328 + &mut self.pg.prefetch[..read_len], 328 329 )?; 329 330 if n == 0 { 330 331 break; ··· 332 333 333 334 let mut i = 0; 334 335 while i + 2 < n { 335 - if self.prefetch[i] != MARKER || self.prefetch[i + 1] != IMG_REF { 336 + if self.pg.prefetch[i] != MARKER || self.pg.prefetch[i + 1] != IMG_REF { 336 337 i += 1; 337 338 continue; 338 339 } 339 340 340 - let path_len = self.prefetch[i + 2] as usize; 341 + let path_len = self.pg.prefetch[i + 2] as usize; 341 342 let path_start = i + 3; 342 343 if path_len == 0 || path_start + path_len > n { 343 344 i += 1; ··· 346 347 347 348 let mut src_buf = [0u8; 128]; 348 349 let src_n = path_len.min(src_buf.len()); 349 - src_buf[..src_n].copy_from_slice(&self.prefetch[path_start..path_start + src_n]); 350 + src_buf[..src_n].copy_from_slice(&self.pg.prefetch[path_start..path_start + src_n]); 350 351 let src_str = match core::str::from_utf8(&src_buf[..src_n]) { 351 352 Ok(s) if !s.is_empty() => s, 352 353 _ => { ··· 357 358 358 359 let mut path_buf = [0u8; 512]; 359 360 let plen = { 360 - let ch_zip_idx = self.spine.items[ch] as usize; 361 - let ch_path = self.zip.entry_name(ch_zip_idx); 361 + let ch_zip_idx = self.epub.spine.items[ch] as usize; 362 + let ch_path = self.epub.zip.entry_name(ch_zip_idx); 362 363 let ch_dir = ch_path.rsplit_once('/').map(|(d, _)| d).unwrap_or(""); 363 364 epub::resolve_path(ch_dir, src_str, &mut path_buf) 364 365 }; ··· 392 393 } 393 394 394 395 let zip_idx = match self 396 + .epub 395 397 .zip 396 398 .find(full_path) 397 - .or_else(|| self.zip.find_icase(full_path)) 399 + .or_else(|| self.epub.zip.find_icase(full_path)) 398 400 { 399 401 Some(idx) => idx, 400 402 None => { ··· 404 406 } 405 407 }; 406 408 407 - let entry = *self.zip.entry(zip_idx); 409 + let entry = *self.epub.zip.entry(zip_idx); 408 410 409 411 // large images: decode via streaming SD reads on main loop 410 412 if entry.uncomp_size > PRECACHE_IMG_MAX { ··· 450 452 } 451 453 452 454 // small images: extract to memory for worker dispatch 453 - let data = match super::extract_zip_entry(k, epub_name, &self.zip, zip_idx) { 455 + let data = match super::extract_zip_entry(k, epub_name, &self.epub.zip, zip_idx) { 454 456 Ok(d) => d, 455 457 Err(e) => { 456 458 log::warn!("precache: extract failed: {}", e); ··· 469 471 max_w: TEXT_W as u16, 470 472 max_h: TEXT_AREA_H, 471 473 }; 472 - if work_queue::submit(self.work_gen, task) { 474 + if work_queue::submit(self.epub.work_gen, task) { 473 475 return Ok(ScanResult::Dispatched { 474 476 resume_offset: resume, 475 477 }); ··· 492 494 } 493 495 494 496 // background image scanner: iterates across all chapters starting 495 - // from self.img_cache_ch / self.img_cache_offset, wrapping around 497 + // from self.epub.img_cache_ch / self.epub.img_cache_offset, wrapping around 496 498 // to cover chapters before the reading position 497 499 pub(super) fn epub_find_and_dispatch_image( 498 500 &mut self, 499 501 k: &mut KernelHandle<'_>, 500 502 ) -> crate::error::Result<bool> { 501 - let spine_len = self.spine.len(); 503 + let spine_len = self.epub.spine.len(); 502 504 503 - while (self.img_cache_ch as usize) < spine_len { 504 - if self.img_scan_wrapped && self.img_cache_ch >= self.chapter { 505 + while (self.epub.img_cache_ch as usize) < spine_len { 506 + if self.epub.img_scan_wrapped && self.epub.img_cache_ch >= self.epub.chapter { 505 507 break; 506 508 } 507 509 508 - let ch = self.img_cache_ch as usize; 509 - let start = self.img_cache_offset as usize; 510 + let ch = self.epub.img_cache_ch as usize; 511 + let start = self.epub.img_cache_offset as usize; 510 512 511 513 match self.scan_chapter_for_image(k, ch, start)? { 512 514 ScanResult::Dispatched { resume_offset } 513 515 | ScanResult::DecodedInline { resume_offset } => { 514 - self.img_cache_offset = resume_offset; 516 + self.epub.img_cache_offset = resume_offset; 515 517 return Ok(true); 516 518 } 517 519 ScanResult::NoneFound => { 518 - self.img_cache_ch += 1; 519 - self.img_cache_offset = 0; 520 + self.epub.img_cache_ch += 1; 521 + self.epub.img_cache_offset = 0; 520 522 } 521 523 } 522 524 } 523 525 524 526 // wrap around: if we started mid-book, scan chapters before the start 525 - if !self.img_scan_wrapped && self.chapter > 0 { 527 + if !self.epub.img_scan_wrapped && self.epub.chapter > 0 { 526 528 log::info!( 527 529 "precache: wrapping image scan to ch0 (started at ch{})", 528 - self.chapter, 530 + self.epub.chapter, 529 531 ); 530 - self.img_cache_ch = 0; 531 - self.img_cache_offset = 0; 532 - self.img_scan_wrapped = true; 532 + self.epub.img_cache_ch = 0; 533 + self.epub.img_cache_offset = 0; 534 + self.epub.img_scan_wrapped = true; 533 535 return Ok(true); 534 536 } 535 537 ··· 550 552 551 553 match result.outcome { 552 554 work_queue::WorkOutcome::ImageReady { path_hash, image } => { 553 - let dir_buf = self.cache_dir; 555 + let dir_buf = self.epub.cache_dir; 554 556 let dir = cache::dir_name_str(&dir_buf); 555 557 let img_name = img_cache_name(path_hash); 556 558 let img_file = img_cache_str(&img_name); ··· 590 592 591 593 // dispatch one uncached image from chapters near the current position 592 594 pub(super) fn try_dispatch_nearby_image(&mut self, k: &mut KernelHandle<'_>) -> bool { 593 - let r = self.chapter as usize; 594 - let spine_len = self.spine.len(); 595 + let r = self.epub.chapter as usize; 596 + let spine_len = self.epub.spine.len(); 595 597 for &ch in &[r, r + 1, r.saturating_sub(1), r + 2, r.saturating_sub(2)] { 596 - if ch < spine_len && self.ch_cached[ch] { 598 + if ch < spine_len && self.epub.ch_cached[ch] { 597 599 if self.dispatch_one_image_in_chapter(k, ch) { 598 600 return true; 599 601 }
+247 -212
src/apps/reader/mod.rs
··· 1 - mod epub_pipeline; 1 + mod epubs; 2 2 mod images; 3 3 mod paging; 4 4 ··· 162 162 } 163 163 } 164 164 165 - impl Default for ReaderApp { 166 - fn default() -> Self { 167 - Self::new() 168 - } 169 - } 170 - 171 - pub struct ReaderApp { 172 - pub(super) filename: [u8; 32], 173 - pub(super) filename_len: usize, 174 - pub(super) title: [u8; 96], 175 - pub(super) title_len: usize, 176 - pub(super) file_size: u32, 177 - 165 + // page index, content buffer, and read-ahead state 166 + pub(super) struct PageState { 178 167 pub(super) offsets: [u32; MAX_PAGES], 179 168 pub(super) total_pages: usize, 180 169 pub(super) fully_indexed: bool, ··· 188 177 pub(super) prefetch: [u8; PAGE_BUF], 189 178 pub(super) prefetch_len: usize, 190 179 pub(super) prefetch_page: usize, 180 + } 191 181 192 - pub(super) state: State, 193 - pub(super) error: Option<Error>, 194 - pub(super) show_position: bool, 182 + impl PageState { 183 + pub(super) const fn new() -> Self { 184 + Self { 185 + offsets: [0u32; MAX_PAGES], 186 + total_pages: 0, 187 + fully_indexed: false, 188 + page: 0, 189 + buf: [0u8; PAGE_BUF], 190 + buf_len: 0, 191 + lines: [LineSpan::EMPTY; LINES_PER_PAGE], 192 + line_count: 0, 193 + prefetch: [0u8; PAGE_BUF], 194 + prefetch_len: 0, 195 + prefetch_page: NO_PREFETCH, 196 + } 197 + } 198 + } 195 199 196 - pub(super) is_epub: bool, 200 + // epub-specific state: zip index, metadata, spine, toc, chapter 201 + // cache, background cache progress, image cache scan position 202 + pub(super) struct EpubState { 197 203 pub(super) zip: ZipIndex, 198 204 pub(super) meta: EpubMeta, 199 205 pub(super) spine: EpubSpine, 200 206 pub(super) chapter: u16, 201 - pub(super) goto_last_page: bool, 202 - pub(super) restore_offset: Option<u32>, 203 207 204 208 pub(super) cache_dir: [u8; 8], 205 - pub(super) epub_name_hash: u32, 206 - pub(super) epub_file_size: u32, 209 + pub(super) name_hash: u32, 210 + pub(super) archive_size: u32, 207 211 pub(super) chapter_sizes: [u32; cache::MAX_CACHE_CHAPTERS], 208 212 pub(super) chapters_cached: bool, 209 213 pub(super) cache_chapter: u16, 210 - pub(super) img_cache_ch: u16, 211 - pub(super) img_cache_offset: u32, 212 - pub(super) img_scan_wrapped: bool, 214 + pub(super) ch_cached: [bool; cache::MAX_CACHE_CHAPTERS], 215 + pub(super) ch_cache: Vec<u8>, 213 216 214 217 pub(super) bg_cache: BgCacheState, 215 - pub(super) ch_cached: [bool; cache::MAX_CACHE_CHAPTERS], 216 218 pub(super) work_gen: u16, 217 219 218 - pub(super) ch_cache: Vec<u8>, 219 - pub(super) page_img: Option<DecodedImage>, 220 - pub(super) fullscreen_img: bool, 221 - pub(super) defer_image_decode: bool, 220 + pub(super) img_cache_ch: u16, 221 + pub(super) img_cache_offset: u32, 222 + pub(super) img_scan_wrapped: bool, 223 + 222 224 pub(super) toc: EpubToc, 223 225 pub(super) toc_source: Option<TocSource>, 224 226 pub(super) toc_selected: usize, 225 227 pub(super) toc_scroll: usize, 228 + } 229 + 230 + impl EpubState { 231 + pub(super) const fn new() -> Self { 232 + Self { 233 + zip: ZipIndex::new(), 234 + meta: EpubMeta::new(), 235 + spine: EpubSpine::new(), 236 + chapter: 0, 237 + cache_dir: [0u8; 8], 238 + name_hash: 0, 239 + archive_size: 0, 240 + chapter_sizes: [0u32; cache::MAX_CACHE_CHAPTERS], 241 + chapters_cached: false, 242 + cache_chapter: 0, 243 + ch_cached: [false; cache::MAX_CACHE_CHAPTERS], 244 + ch_cache: Vec::new(), 245 + bg_cache: BgCacheState::Idle, 246 + work_gen: 0, 247 + img_cache_ch: 0, 248 + img_cache_offset: 0, 249 + img_scan_wrapped: false, 250 + toc: EpubToc::new(), 251 + toc_source: None, 252 + toc_selected: 0, 253 + toc_scroll: 0, 254 + } 255 + } 256 + } 257 + 258 + impl Default for ReaderApp { 259 + fn default() -> Self { 260 + Self::new() 261 + } 262 + } 263 + 264 + pub struct ReaderApp { 265 + pub(super) filename: [u8; 32], 266 + pub(super) filename_len: usize, 267 + pub(super) title: [u8; 96], 268 + pub(super) title_len: usize, 269 + pub(super) file_size: u32, 270 + 271 + pub(super) pg: PageState, 272 + pub(super) epub: EpubState, 273 + 274 + pub(super) state: State, 275 + pub(super) error: Option<Error>, 276 + pub(super) show_position: bool, 277 + 278 + pub(super) is_epub: bool, 279 + pub(super) goto_last_page: bool, 280 + pub(super) restore_offset: Option<u32>, 281 + 282 + pub(super) page_img: Option<DecodedImage>, 283 + pub(super) fullscreen_img: bool, 284 + pub(super) defer_image_decode: bool, 226 285 227 286 pub(super) fonts: Option<fonts::FontSet>, 228 287 pub(super) font_line_h: u16, ··· 246 305 title_len: 0, 247 306 file_size: 0, 248 307 249 - offsets: [0u32; MAX_PAGES], 250 - total_pages: 0, 251 - fully_indexed: false, 252 - 253 - page: 0, 254 - buf: [0u8; PAGE_BUF], 255 - buf_len: 0, 256 - lines: [LineSpan::EMPTY; LINES_PER_PAGE], 257 - line_count: 0, 258 - 259 - prefetch: [0u8; PAGE_BUF], 260 - prefetch_len: 0, 261 - prefetch_page: NO_PREFETCH, 308 + pg: PageState::new(), 309 + epub: EpubState::new(), 262 310 263 311 state: State::NeedPage, 264 312 error: None, 265 313 show_position: false, 266 314 267 315 is_epub: false, 268 - zip: ZipIndex::new(), 269 - meta: EpubMeta::new(), 270 - spine: EpubSpine::new(), 271 - chapter: 0, 272 316 goto_last_page: false, 273 317 restore_offset: None, 274 318 275 - cache_dir: [0u8; 8], 276 - epub_name_hash: 0, 277 - epub_file_size: 0, 278 - chapter_sizes: [0u32; cache::MAX_CACHE_CHAPTERS], 279 - chapters_cached: false, 280 - cache_chapter: 0, 281 - img_cache_ch: 0, 282 - img_cache_offset: 0, 283 - img_scan_wrapped: false, 284 - 285 - bg_cache: BgCacheState::Idle, 286 - ch_cached: [false; cache::MAX_CACHE_CHAPTERS], 287 - work_gen: 0, 288 - 289 - ch_cache: Vec::new(), 290 - 291 319 page_img: None, 292 320 fullscreen_img: false, 293 321 defer_image_decode: false, 294 - 295 - toc: EpubToc::new(), 296 - toc_source: None, 297 - toc_selected: 0, 298 - toc_scroll: 0, 299 322 300 323 fonts: None, 301 324 font_line_h: LINE_H, ··· 324 347 } 325 348 326 349 pub fn has_bg_work(&self) -> bool { 327 - self.is_epub && self.bg_cache != BgCacheState::Idle 350 + self.is_epub && self.epub.bg_cache != BgCacheState::Idle 328 351 } 329 352 330 353 pub(super) fn cached_chapter_count(&self) -> usize { 331 - let n = self.spine.len().min(cache::MAX_CACHE_CHAPTERS); 332 - self.ch_cached[..n].iter().filter(|&&c| c).count() 354 + let n = self.epub.spine.len().min(cache::MAX_CACHE_CHAPTERS); 355 + self.epub.ch_cached[..n].iter().filter(|&&c| c).count() 333 356 } 334 357 335 358 // update the kernel loading indicator with current caching progress 336 359 fn set_cache_loading(&self, ctx: &mut AppContext) { 337 360 let cached = self.cached_chapter_count(); 338 - let total = self.spine.len(); 361 + let total = self.epub.spine.len(); 339 362 let mut lbuf = StackFmt::<28>::new(); 340 363 if matches!( 341 - self.bg_cache, 364 + self.epub.bg_cache, 342 365 BgCacheState::CacheChapter | BgCacheState::WaitNearbyImage 343 366 ) && cached < total 344 367 { ··· 358 381 // chapter caching is async and only runs during active background, 359 382 // so this only handles the sync image recv states 360 383 pub fn bg_work_tick(&mut self, k: &mut KernelHandle<'_>) { 361 - match self.bg_cache { 384 + match self.epub.bg_cache { 362 385 BgCacheState::WaitNearbyImage => match self.epub_recv_image_result(k) { 363 386 Ok(Some(_)) => { 364 387 if !self.try_dispatch_nearby_image(k) { 365 - self.bg_cache = BgCacheState::CacheChapter; 388 + self.epub.bg_cache = BgCacheState::CacheChapter; 366 389 } 367 390 } 368 391 Ok(None) => {} 369 392 Err(e) => { 370 393 log::warn!("bg: nearby image error (suspended): {}", e); 371 - self.bg_cache = BgCacheState::CacheChapter; 394 + self.epub.bg_cache = BgCacheState::CacheChapter; 372 395 } 373 396 }, 374 397 BgCacheState::WaitImage => match self.epub_recv_image_result(k) { 375 - Ok(Some(_)) => self.bg_cache = BgCacheState::CacheImage, 398 + Ok(Some(_)) => self.epub.bg_cache = BgCacheState::CacheImage, 376 399 Ok(None) => {} 377 400 Err(e) => { 378 401 log::warn!("bg: image recv error (suspended): {}", e); 379 - self.bg_cache = BgCacheState::CacheImage; 402 + self.epub.bg_cache = BgCacheState::CacheImage; 380 403 } 381 404 }, 382 405 _ => {} ··· 394 417 ); 395 418 n += 1; 396 419 397 - if self.is_epub && self.spine.len() > 1 { 420 + if self.is_epub && self.epub.spine.len() > 1 { 398 421 self.qa_buf[n] = QuickAction::trigger(QA_PREV_CHAPTER, "Prev Ch", "<<<"); 399 422 n += 1; 400 423 self.qa_buf[n] = QuickAction::trigger(QA_NEXT_CHAPTER, "Next Ch", ">>>"); 401 424 n += 1; 402 425 } 403 426 404 - if self.is_epub && !self.toc.is_empty() { 427 + if self.is_epub && !self.epub.toc.is_empty() { 405 428 self.qa_buf[n] = QuickAction::trigger(QA_TOC, "Contents", "Open"); 406 429 n += 1; 407 430 } ··· 446 469 if self.state == State::Ready { 447 470 bm.save( 448 471 &self.filename[..self.filename_len], 449 - self.offsets[self.page], 450 - self.chapter, 472 + self.pg.offsets[self.pg.page], 473 + self.epub.chapter, 451 474 ); 452 475 } 453 476 } ··· 460 483 slot.chapter, 461 484 slot.filename_str(), 462 485 ); 463 - self.chapter = slot.chapter; 486 + self.epub.chapter = slot.chapter; 464 487 self.restore_offset = if slot.byte_offset > 0 { 465 488 Some(slot.byte_offset) 466 489 } else { ··· 481 504 } 482 505 483 506 fn progress_pct(&self) -> u8 { 484 - if self.is_epub && !self.spine.is_empty() { 485 - let spine_len = self.spine.len() as u64; 486 - let ch = self.chapter as u64; 507 + if self.is_epub && !self.epub.spine.is_empty() { 508 + let spine_len = self.epub.spine.len() as u64; 509 + let ch = self.epub.chapter as u64; 487 510 488 - if ch + 1 >= spine_len && self.fully_indexed && self.page + 1 >= self.total_pages { 511 + if ch + 1 >= spine_len 512 + && self.pg.fully_indexed 513 + && self.pg.page + 1 >= self.pg.total_pages 514 + { 489 515 return 100; 490 516 } 491 517 492 518 let in_ch = if self.file_size == 0 { 493 519 0u64 494 520 } else { 495 - let pos = self.offsets[self.page] as u64; 521 + let pos = self.pg.offsets[self.pg.page] as u64; 496 522 let size = self.file_size as u64; 497 523 ((pos * 100) / size).min(100) 498 524 }; ··· 504 530 if self.file_size == 0 { 505 531 return 100; 506 532 } 507 - if self.fully_indexed && self.page + 1 >= self.total_pages { 533 + if self.pg.fully_indexed && self.pg.page + 1 >= self.pg.total_pages { 508 534 return 100; 509 535 } 510 - let pos = self.offsets[self.page] as u64; 536 + let pos = self.pg.offsets[self.pg.page] as u64; 511 537 let size = self.file_size as u64; 512 538 ((pos * 100) / size).min(100) as u8 513 539 } ··· 592 618 // Bump to a new work-queue generation and drain stale work 593 619 // from any previous book (covers the case where on_enter is 594 620 // called without a preceding on_exit, e.g. Replace transition). 595 - self.work_gen = work_queue::reset(); 596 - self.bg_cache = BgCacheState::Idle; 597 - self.ch_cached = [false; cache::MAX_CACHE_CHAPTERS]; 598 - self.img_scan_wrapped = false; 621 + self.epub.work_gen = work_queue::reset(); 622 + self.epub.bg_cache = BgCacheState::Idle; 623 + self.epub.ch_cached = [false; cache::MAX_CACHE_CHAPTERS]; 624 + self.epub.img_scan_wrapped = false; 599 625 600 626 self.is_epub = epub::is_epub_filename(self.name()); 601 627 self.rebuild_quick_actions(); 602 628 self.reset_paging(); 603 - self.ch_cache = Vec::new(); 629 + self.epub.ch_cache = Vec::new(); 604 630 self.file_size = 0; 605 - self.chapter = 0; 631 + self.epub.chapter = 0; 606 632 self.error = None; 607 633 self.show_position = false; 608 634 self.defer_image_decode = true; ··· 624 650 // doesn't write stale results after we switch books. 625 651 if self.is_epub { 626 652 work_queue::reset(); 627 - self.bg_cache = BgCacheState::Idle; 653 + self.epub.bg_cache = BgCacheState::Idle; 628 654 } 629 655 630 - self.line_count = 0; 631 - self.buf_len = 0; 632 - self.prefetch_page = NO_PREFETCH; 633 - self.prefetch_len = 0; 656 + self.pg.line_count = 0; 657 + self.pg.buf_len = 0; 658 + self.pg.prefetch_page = NO_PREFETCH; 659 + self.pg.prefetch_len = 0; 634 660 self.restore_offset = None; 635 661 self.show_position = false; 636 - self.ch_cache = Vec::new(); 662 + self.epub.ch_cache = Vec::new(); 637 663 self.page_img = None; 638 664 639 665 if self.is_epub { 640 - self.toc.clear(); 641 - self.toc_source = None; 666 + self.epub.toc.clear(); 667 + self.epub.toc_source = None; 642 668 } 643 669 } 644 670 ··· 651 677 // Restore our generation so the worker considers in-flight 652 678 // results current again (another app may have submitted work 653 679 // under a different generation while we were suspended). 654 - if self.work_gen != 0 { 655 - work_queue::set_active_generation(self.work_gen); 680 + if self.epub.work_gen != 0 { 681 + work_queue::set_active_generation(self.epub.work_gen); 656 682 } 657 683 658 684 let font_changed = self.book_font_size_idx != self.applied_font_idx; 659 685 self.apply_font_metrics(); 660 686 if font_changed { 661 687 self.reset_paging(); 662 - if self.is_epub && self.chapters_cached { 688 + if self.is_epub && self.epub.chapters_cached { 663 689 self.state = State::NeedIndex; 664 690 } else { 665 691 self.state = State::NeedPage; ··· 677 703 let _ = k.write_app_data(RECENT_FILE, &self.filename[..self.filename_len]); 678 704 679 705 if self.is_epub { 680 - self.zip.clear(); 681 - self.meta = EpubMeta::new(); 682 - self.spine = EpubSpine::new(); 683 - self.chapters_cached = false; 706 + self.epub.zip.clear(); 707 + self.epub.meta = EpubMeta::new(); 708 + self.epub.spine = EpubSpine::new(); 709 + self.epub.chapters_cached = false; 684 710 self.goto_last_page = false; 685 711 self.state = State::NeedInit; 686 712 ctx.set_loading(LOADING_REGION, "Loading", 10); ··· 720 746 }, 721 747 722 748 State::NeedToc => { 723 - if let Some(source) = self.toc_source.take() { 749 + if let Some(source) = self.epub.toc_source.take() { 724 750 let (nb, nl) = self.name_copy(); 725 751 let name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); 726 752 let toc_idx = source.zip_index(); 727 753 728 754 let mut toc_dir_buf = [0u8; 256]; 729 755 let toc_dir_len = { 730 - let toc_path = self.zip.entry_name(toc_idx); 756 + let toc_path = self.epub.zip.entry_name(toc_idx); 731 757 let dir = toc_path.rsplit_once('/').map(|(d, _)| d).unwrap_or(""); 732 758 let n = dir.len().min(toc_dir_buf.len()); 733 759 toc_dir_buf[..n].copy_from_slice(dir.as_bytes()); ··· 736 762 let toc_dir = 737 763 core::str::from_utf8(&toc_dir_buf[..toc_dir_len]).unwrap_or(""); 738 764 739 - match extract_zip_entry(k, name, &self.zip, toc_idx) { 765 + match extract_zip_entry(k, name, &self.epub.zip, toc_idx) { 740 766 Ok(toc_data) => { 741 767 epub::parse_toc( 742 768 source, 743 769 &toc_data, 744 770 toc_dir, 745 - &self.spine, 746 - &self.zip, 747 - &mut self.toc, 771 + &self.epub.spine, 772 + &self.epub.zip, 773 + &mut self.epub.toc, 748 774 ); 749 - log::info!("epub: TOC has {} entries", self.toc.len()); 775 + log::info!("epub: TOC has {} entries", self.epub.toc.len()); 750 776 } 751 777 Err(_e) => { 752 778 log::warn!("epub: failed to read TOC"); ··· 767 793 // cache the current chapter; async version yields 768 794 // during deflate so the scheduler's select can 769 795 // interrupt if the user presses back 770 - let ch = self.chapter as usize; 796 + let ch = self.epub.chapter as usize; 771 797 match self.epub_cache_chapter_async(k, ch).await { 772 798 Ok(()) => { 773 - self.chapters_cached = true; 774 - self.cache_chapter = 0; 799 + self.epub.chapters_cached = true; 800 + self.epub.cache_chapter = 0; 775 801 776 802 // eagerly dispatch nearby images to 777 803 // the worker so they decode while the 778 804 // user reads the first page 779 805 if self.try_dispatch_nearby_image(k) { 780 - self.bg_cache = BgCacheState::WaitNearbyImage; 806 + self.epub.bg_cache = BgCacheState::WaitNearbyImage; 781 807 } else { 782 - self.bg_cache = BgCacheState::CacheChapter; 808 + self.epub.bg_cache = BgCacheState::CacheChapter; 783 809 } 784 810 785 811 self.state = State::NeedIndex; ··· 808 834 // indexing (it may not be if background caching 809 835 // hasn't reached it yet) 810 836 if self.is_epub 811 - && self.chapters_cached 812 - && !self.ch_cached[self.chapter as usize] 837 + && self.epub.chapters_cached 838 + && !self.epub.ch_cached[self.epub.chapter as usize] 813 839 { 814 840 // async version yields during deflate so the 815 841 // scheduler's select can interrupt on input 816 842 if let Err(e) = self 817 - .epub_cache_chapter_async(k, self.chapter as usize) 843 + .epub_cache_chapter_async(k, self.epub.chapter as usize) 818 844 .await 819 845 { 820 846 self.error = Some(e); ··· 857 883 858 884 State::NeedPage => { 859 885 if let Some(target_off) = self.restore_offset.take() { 860 - self.page = 0; 886 + self.pg.page = 0; 861 887 loop { 862 888 match self.load_and_prefetch(k) { 863 889 Ok(()) => {} ··· 869 895 break; 870 896 } 871 897 } 872 - if self.page + 1 >= self.total_pages { 898 + if self.pg.page + 1 >= self.pg.total_pages { 873 899 break; 874 900 } 875 - if self.offsets[self.page + 1] > target_off { 901 + if self.pg.offsets[self.pg.page + 1] > target_off { 876 902 break; 877 903 } 878 - self.page += 1; 904 + self.pg.page += 1; 879 905 } 880 906 if self.state != State::Error { 881 907 self.defer_image_decode = false; ··· 916 942 if matches!( 917 943 self.state, 918 944 State::Ready | State::ShowToc | State::NeedIndex | State::NeedPage 919 - ) && self.bg_cache != BgCacheState::Idle 945 + ) && self.epub.bg_cache != BgCacheState::Idle 920 946 { 921 947 // ensure caching indicator is visible (covers resume 922 948 // and the transition from initial load to bg caching) ··· 924 950 self.set_cache_loading(ctx); 925 951 } 926 952 let prev_count = self.cached_chapter_count(); 927 - let prev_bg = self.bg_cache; 953 + let prev_bg = self.epub.bg_cache; 928 954 self.bg_cache_step(k).await; 929 - if self.bg_cache == BgCacheState::Idle { 955 + if self.epub.bg_cache == BgCacheState::Idle { 930 956 ctx.clear_loading(); 931 - } else if self.cached_chapter_count() != prev_count || self.bg_cache != prev_bg { 957 + } else if self.cached_chapter_count() != prev_count || self.epub.bg_cache != prev_bg { 932 958 self.set_cache_loading(ctx); 933 959 } 934 960 } ··· 943 969 return Transition::None; 944 970 } 945 971 ActionEvent::Press(Action::Next) | ActionEvent::Repeat(Action::Next) => { 946 - let len = self.toc.len(); 972 + let len = self.epub.toc.len(); 947 973 if len > 0 { 948 - if self.toc_selected + 1 < len { 949 - self.toc_selected += 1; 974 + if self.epub.toc_selected + 1 < len { 975 + self.epub.toc_selected += 1; 950 976 } else { 951 - self.toc_selected = 0; 952 - self.toc_scroll = 0; 977 + self.epub.toc_selected = 0; 978 + self.epub.toc_scroll = 0; 953 979 } 954 980 let vis = (TEXT_AREA_H / self.font_line_h) as usize; 955 - if self.toc_selected >= self.toc_scroll + vis { 956 - self.toc_scroll = self.toc_selected + 1 - vis; 981 + if self.epub.toc_selected >= self.epub.toc_scroll + vis { 982 + self.epub.toc_scroll = self.epub.toc_selected + 1 - vis; 957 983 } 958 984 ctx.mark_dirty(PAGE_REGION); 959 985 } 960 986 return Transition::None; 961 987 } 962 988 ActionEvent::Press(Action::Prev) | ActionEvent::Repeat(Action::Prev) => { 963 - let len = self.toc.len(); 989 + let len = self.epub.toc.len(); 964 990 if len > 0 { 965 - if self.toc_selected > 0 { 966 - self.toc_selected -= 1; 991 + if self.epub.toc_selected > 0 { 992 + self.epub.toc_selected -= 1; 967 993 } else { 968 - self.toc_selected = len - 1; 994 + self.epub.toc_selected = len - 1; 969 995 let vis = (TEXT_AREA_H / self.font_line_h) as usize; 970 - if self.toc_selected >= vis { 971 - self.toc_scroll = self.toc_selected + 1 - vis; 996 + if self.epub.toc_selected >= vis { 997 + self.epub.toc_scroll = self.epub.toc_selected + 1 - vis; 972 998 } 973 999 } 974 - if self.toc_selected < self.toc_scroll { 975 - self.toc_scroll = self.toc_selected; 1000 + if self.epub.toc_selected < self.epub.toc_scroll { 1001 + self.epub.toc_scroll = self.epub.toc_selected; 976 1002 } 977 1003 ctx.mark_dirty(PAGE_REGION); 978 1004 } 979 1005 return Transition::None; 980 1006 } 981 1007 ActionEvent::Press(Action::Select) | ActionEvent::Press(Action::NextJump) => { 982 - let entry = &self.toc.entries[self.toc_selected]; 1008 + let entry = &self.epub.toc.entries[self.epub.toc_selected]; 983 1009 if entry.spine_idx != 0xFFFF { 984 1010 log::info!( 985 1011 "toc: jumping to \"{}\" -> spine {}", 986 1012 entry.title_str(), 987 1013 entry.spine_idx 988 1014 ); 989 - self.chapter = entry.spine_idx; 990 - self.page = 0; 1015 + self.epub.chapter = entry.spine_idx; 1016 + self.pg.page = 0; 991 1017 self.goto_last_page = false; 992 1018 self.state = State::NeedIndex; 993 1019 ctx.mark_dirty(PAGE_REGION); ··· 1075 1101 fn on_quick_trigger(&mut self, id: u8, ctx: &mut AppContext) { 1076 1102 match id { 1077 1103 QA_PREV_CHAPTER => { 1078 - if self.is_epub && self.chapter > 0 { 1079 - self.chapter -= 1; 1104 + if self.is_epub && self.epub.chapter > 0 { 1105 + self.epub.chapter -= 1; 1080 1106 self.goto_last_page = false; 1081 1107 self.state = State::NeedIndex; 1082 1108 } 1083 1109 } 1084 1110 QA_NEXT_CHAPTER => { 1085 - if self.is_epub && (self.chapter as usize + 1) < self.spine.len() { 1086 - self.chapter += 1; 1111 + if self.is_epub && (self.epub.chapter as usize + 1) < self.epub.spine.len() { 1112 + self.epub.chapter += 1; 1087 1113 self.goto_last_page = false; 1088 1114 self.state = State::NeedIndex; 1089 1115 } 1090 1116 } 1091 1117 QA_TOC => { 1092 - if self.is_epub && !self.toc.is_empty() { 1093 - log::info!("toc: opening ({} entries)", self.toc.len()); 1094 - self.toc_selected = 0; 1095 - self.toc_scroll = 0; 1096 - for i in 0..self.toc.len() { 1097 - if self.toc.entries[i].spine_idx == self.chapter { 1098 - self.toc_selected = i; 1118 + if self.is_epub && !self.epub.toc.is_empty() { 1119 + log::info!("toc: opening ({} entries)", self.epub.toc.len()); 1120 + self.epub.toc_selected = 0; 1121 + self.epub.toc_scroll = 0; 1122 + for i in 0..self.epub.toc.len() { 1123 + if self.epub.toc.entries[i].spine_idx == self.epub.chapter { 1124 + self.epub.toc_selected = i; 1099 1125 let vis = (TEXT_AREA_H / self.font_line_h) as usize; 1100 - if self.toc_selected >= vis { 1101 - self.toc_scroll = self.toc_selected + 1 - vis; 1126 + if self.epub.toc_selected >= vis { 1127 + self.epub.toc_scroll = self.epub.toc_selected + 1 - vis; 1102 1128 } 1103 1129 break; 1104 1130 } ··· 1116 1142 self.book_font_size_idx = value; 1117 1143 self.apply_font_metrics(); 1118 1144 if self.state == State::Ready { 1119 - if self.is_epub && self.chapters_cached { 1145 + if self.is_epub && self.epub.chapters_cached { 1120 1146 self.state = State::NeedIndex; 1121 1147 } else { 1122 1148 self.state = State::NeedPage; ··· 1155 1181 1156 1182 if self.state == State::ShowToc { 1157 1183 draw_chrome_text(strip, STATUS_REGION, "Contents", Alignment::CenterRight, cf); 1158 - } else if self.is_epub && !self.spine.is_empty() { 1184 + } else if self.is_epub && !self.epub.spine.is_empty() { 1159 1185 let mut sbuf = StackFmt::<40>::new(); 1160 - if self.spine.len() > 1 { 1161 - if self.fully_indexed { 1186 + if self.epub.spine.len() > 1 { 1187 + if self.pg.fully_indexed { 1162 1188 let _ = write!( 1163 1189 sbuf, 1164 1190 "Ch{}/{} {}/{}", 1165 - self.chapter + 1, 1166 - self.spine.len(), 1167 - self.page + 1, 1168 - self.total_pages 1191 + self.epub.chapter + 1, 1192 + self.epub.spine.len(), 1193 + self.pg.page + 1, 1194 + self.pg.total_pages 1169 1195 ); 1170 1196 } else { 1171 1197 let _ = write!( 1172 1198 sbuf, 1173 1199 "Ch{}/{} p{}", 1174 - self.chapter + 1, 1175 - self.spine.len(), 1176 - self.page + 1 1200 + self.epub.chapter + 1, 1201 + self.epub.spine.len(), 1202 + self.pg.page + 1 1177 1203 ); 1178 1204 } 1179 - } else if self.fully_indexed { 1180 - let _ = write!(sbuf, "{}/{}", self.page + 1, self.total_pages); 1205 + } else if self.pg.fully_indexed { 1206 + let _ = write!(sbuf, "{}/{}", self.pg.page + 1, self.pg.total_pages); 1181 1207 } else { 1182 - let _ = write!(sbuf, "p{}", self.page + 1); 1208 + let _ = write!(sbuf, "p{}", self.pg.page + 1); 1183 1209 } 1184 - if self.bg_cache != BgCacheState::Idle { 1210 + if self.epub.bg_cache != BgCacheState::Idle { 1185 1211 let cached = self.cached_chapter_count(); 1186 - let total = self.spine.len(); 1212 + let total = self.epub.spine.len(); 1187 1213 if cached < total { 1188 1214 let _ = write!(sbuf, " [{}/{}]", cached, total); 1189 1215 } else { ··· 1199 1225 ); 1200 1226 } else if self.file_size > 0 { 1201 1227 let mut sbuf = StackFmt::<24>::new(); 1202 - if self.fully_indexed { 1203 - let _ = write!(sbuf, "{}/{}", self.page + 1, self.total_pages); 1228 + if self.pg.fully_indexed { 1229 + let _ = write!(sbuf, "{}/{}", self.pg.page + 1, self.pg.total_pages); 1204 1230 } else { 1205 - let _ = write!(sbuf, "{} | {}%", self.page + 1, self.progress_pct()); 1231 + let _ = write!(sbuf, "p{}", self.pg.page + 1); 1206 1232 } 1207 1233 draw_chrome_text( 1208 1234 strip, ··· 1234 1260 } 1235 1261 1236 1262 if self.state == State::ShowToc { 1237 - let toc_len = self.toc.len(); 1263 + let toc_len = self.epub.toc.len(); 1238 1264 if self.fonts.is_some() { 1239 1265 let font = fonts::body_font(self.book_font_size_idx); 1240 1266 let line_h = font.line_height as i32; 1241 1267 let ascent = font.ascent as i32; 1242 1268 let vis_max = (TEXT_AREA_H / font.line_height) as usize; 1243 - let visible = vis_max.min(toc_len.saturating_sub(self.toc_scroll)); 1269 + let visible = vis_max.min(toc_len.saturating_sub(self.epub.toc_scroll)); 1244 1270 for i in 0..visible { 1245 - let idx = self.toc_scroll + i; 1246 - let entry = &self.toc.entries[idx]; 1271 + let idx = self.epub.toc_scroll + i; 1272 + let entry = &self.epub.toc.entries[idx]; 1247 1273 let y_top = TEXT_Y as i32 + i as i32 * line_h; 1248 1274 let baseline = y_top + ascent; 1249 - let selected = idx == self.toc_selected; 1275 + let selected = idx == self.epub.toc_selected; 1250 1276 1251 1277 if selected { 1252 1278 Rectangle::new( ··· 1264 1290 BinaryColor::On 1265 1291 }; 1266 1292 let mut cx = MARGIN as i32; 1267 - if entry.spine_idx != 0xFFFF && entry.spine_idx == self.chapter { 1293 + if entry.spine_idx != 0xFFFF && entry.spine_idx == self.epub.chapter { 1268 1294 cx += font.draw_char_fg(strip, '>', fg, cx, baseline) as i32; 1269 1295 cx += font.draw_char_fg(strip, ' ', fg, cx, baseline) as i32; 1270 1296 } ··· 1273 1299 } else { 1274 1300 let style = MonoTextStyle::new(&FONT_6X13, BinaryColor::On); 1275 1301 let vis_max = (TEXT_AREA_H / LINE_H) as usize; 1276 - let visible = vis_max.min(toc_len.saturating_sub(self.toc_scroll)); 1302 + let visible = vis_max.min(toc_len.saturating_sub(self.epub.toc_scroll)); 1277 1303 for i in 0..visible { 1278 - let idx = self.toc_scroll + i; 1279 - let entry = &self.toc.entries[idx]; 1304 + let idx = self.epub.toc_scroll + i; 1305 + let entry = &self.epub.toc.entries[idx]; 1280 1306 let y = TEXT_Y as i32 + i as i32 * LINE_H as i32 + LINE_H as i32; 1281 - let marker = if idx == self.toc_selected { "> " } else { " " }; 1307 + let marker = if idx == self.epub.toc_selected { 1308 + "> " 1309 + } else { 1310 + " " 1311 + }; 1282 1312 Text::new(marker, Point::new(0, y), style) 1283 1313 .draw(strip) 1284 1314 .unwrap(); ··· 1313 1343 } 1314 1344 } else { 1315 1345 let mut img_rendered = false; 1316 - for i in 0..self.line_count { 1317 - let span = self.lines[i]; 1346 + for i in 0..self.pg.line_count { 1347 + let span = &self.pg.lines[i]; 1318 1348 1319 1349 if span.is_image() { 1320 1350 if span.is_image_origin() && !img_rendered { ··· 1353 1383 let baseline = TEXT_Y as i32 + i as i32 * line_h + ascent; 1354 1384 let x_indent = INDENT_PX as i32 * span.indent as i32; 1355 1385 1356 - let line = &self.buf[start..end]; 1386 + let line = &self.pg.buf[start..end]; 1357 1387 let mut cx = MARGIN as i32 + x_indent; 1358 1388 let mut sty = span.style(); 1359 1389 let mut j = 0usize; ··· 1393 1423 } 1394 1424 } else { 1395 1425 let style = MonoTextStyle::new(&FONT_6X13, BinaryColor::On); 1396 - for i in 0..self.line_count { 1397 - let span = self.lines[i]; 1426 + for i in 0..self.pg.line_count { 1427 + let span = self.pg.lines[i]; 1398 1428 let start = span.start as usize; 1399 1429 let end = start + span.len as usize; 1400 - let text = core::str::from_utf8(&self.buf[start..end]).unwrap_or(""); 1430 + let text = core::str::from_utf8(&self.pg.buf[start..end]).unwrap_or(""); 1401 1431 let y = TEXT_Y as i32 + i as i32 * LINE_H as i32 + LINE_H as i32; 1402 1432 Text::new(text, Point::new(MARGIN as i32, y), style) 1403 1433 .draw(strip) ··· 1424 1454 && POSITION_OVERLAY.intersects(strip.logical_window()) 1425 1455 { 1426 1456 let mut pbuf = StackFmt::<48>::new(); 1427 - if self.is_epub && self.spine.len() > 1 { 1428 - if self.fully_indexed { 1457 + if self.is_epub && self.epub.spine.len() > 1 { 1458 + if self.pg.fully_indexed { 1429 1459 let _ = write!( 1430 1460 pbuf, 1431 1461 "Ch {}/{} Page {}/{}", 1432 - self.chapter + 1, 1433 - self.spine.len(), 1434 - self.page + 1, 1435 - self.total_pages 1462 + self.epub.chapter + 1, 1463 + self.epub.spine.len(), 1464 + self.pg.page + 1, 1465 + self.pg.total_pages 1436 1466 ); 1437 1467 } else { 1438 1468 let _ = write!( 1439 1469 pbuf, 1440 1470 "Ch {}/{} Page {}", 1441 - self.chapter + 1, 1442 - self.spine.len(), 1443 - self.page + 1 1471 + self.epub.chapter + 1, 1472 + self.epub.spine.len(), 1473 + self.pg.page + 1 1444 1474 ); 1445 1475 } 1446 - } else if self.fully_indexed { 1447 - let _ = write!(pbuf, "Page {}/{}", self.page + 1, self.total_pages); 1476 + } else if self.pg.fully_indexed { 1477 + let _ = write!(pbuf, "Page {}/{}", self.pg.page + 1, self.pg.total_pages); 1448 1478 } else { 1449 - let _ = write!(pbuf, "Page {} ({}%)", self.page + 1, self.progress_pct()); 1479 + let _ = write!( 1480 + pbuf, 1481 + "Page {} ({}%)", 1482 + self.pg.page + 1, 1483 + self.progress_pct() 1484 + ); 1450 1485 } 1451 1486 1452 1487 POSITION_OVERLAY
+104 -104
src/apps/reader/paging.rs
··· 20 20 21 21 if let Some(fs) = fonts_copy { 22 22 let (c, count) = 23 - wrap_proportional(&self.buf, n, &fs, &mut self.lines, self.max_lines, TEXT_W); 24 - self.line_count = count; 23 + wrap_proportional(&self.pg.buf, n, &fs, &mut self.pg.lines, self.max_lines, TEXT_W); 24 + self.pg.line_count = count; 25 25 c 26 26 } else { 27 27 self.wrap_monospace(n) ··· 32 32 use super::CHARS_PER_LINE; 33 33 34 34 let max = self.max_lines; 35 - self.line_count = 0; 35 + self.pg.line_count = 0; 36 36 let mut col: usize = 0; 37 37 let mut line_start: usize = 0; 38 38 39 39 for i in 0..n { 40 - let b = self.buf[i]; 40 + let b = self.pg.buf[i]; 41 41 match b { 42 42 b'\r' => {} 43 43 b'\n' => { 44 - let end = trim_trailing_cr(&self.buf, line_start, i); 44 + let end = trim_trailing_cr(&self.pg.buf, line_start, i); 45 45 self.push_line(line_start, end); 46 46 line_start = i + 1; 47 47 col = 0; 48 - if self.line_count >= max { 48 + if self.pg.line_count >= max { 49 49 return line_start; 50 50 } 51 51 } ··· 55 55 self.push_line(line_start, i + 1); 56 56 line_start = i + 1; 57 57 col = 0; 58 - if self.line_count >= max { 58 + if self.pg.line_count >= max { 59 59 return line_start; 60 60 } 61 61 } ··· 63 63 } 64 64 } 65 65 66 - if line_start < n && self.line_count < max { 67 - let end = trim_trailing_cr(&self.buf, line_start, n); 66 + if line_start < n && self.pg.line_count < max { 67 + let end = trim_trailing_cr(&self.pg.buf, line_start, n); 68 68 self.push_line(line_start, end); 69 69 } 70 70 ··· 72 72 } 73 73 74 74 pub(super) fn push_line(&mut self, start: usize, end: usize) { 75 - if self.line_count < LINES_PER_PAGE { 76 - self.lines[self.line_count] = LineSpan { 75 + if self.pg.line_count < LINES_PER_PAGE { 76 + self.pg.lines[self.pg.line_count] = LineSpan { 77 77 start: start as u16, 78 78 len: (end - start) as u16, 79 79 flags: 0, 80 80 indent: 0, 81 81 }; 82 - self.line_count += 1; 82 + self.pg.line_count += 1; 83 83 } 84 84 } 85 85 86 86 pub(super) fn reset_paging(&mut self) { 87 - self.page = 0; 88 - self.offsets[0] = 0; 89 - self.total_pages = 1; 90 - self.fully_indexed = false; 91 - self.buf_len = 0; 92 - self.line_count = 0; 93 - self.prefetch_page = NO_PREFETCH; 94 - self.prefetch_len = 0; 87 + self.pg.page = 0; 88 + self.pg.offsets[0] = 0; 89 + self.pg.total_pages = 1; 90 + self.pg.fully_indexed = false; 91 + self.pg.buf_len = 0; 92 + self.pg.line_count = 0; 93 + self.pg.prefetch_page = NO_PREFETCH; 94 + self.pg.prefetch_len = 0; 95 95 self.page_img = None; 96 96 self.fullscreen_img = false; 97 97 } ··· 100 100 &mut self, 101 101 k: &mut KernelHandle<'_>, 102 102 ) -> crate::error::Result<()> { 103 - if !self.ch_cache.is_empty() { 104 - let start = (self.offsets[self.page] as usize).min(self.ch_cache.len()); 105 - let end = (start + PAGE_BUF).min(self.ch_cache.len()); 103 + if !self.epub.ch_cache.is_empty() { 104 + let start = (self.pg.offsets[self.pg.page] as usize).min(self.epub.ch_cache.len()); 105 + let end = (start + PAGE_BUF).min(self.epub.ch_cache.len()); 106 106 let n = end - start; 107 107 if n > 0 { 108 - self.buf[..n].copy_from_slice(&self.ch_cache[start..end]); 108 + self.pg.buf[..n].copy_from_slice(&self.epub.ch_cache[start..end]); 109 109 } 110 - self.buf_len = n; 111 - self.prefetch_page = NO_PREFETCH; 112 - self.prefetch_len = 0; 110 + self.pg.buf_len = n; 111 + self.pg.prefetch_page = NO_PREFETCH; 112 + self.pg.prefetch_len = 0; 113 113 self.wrap_lines_counted(n); 114 114 self.decode_page_images(k); 115 115 return Ok(()); ··· 118 118 let (nb, nl) = self.name_copy(); 119 119 let name = core::str::from_utf8(&nb[..nl]).unwrap_or(""); 120 120 121 - if self.prefetch_page == self.page { 122 - core::mem::swap(&mut self.buf, &mut self.prefetch); 123 - self.buf_len = self.prefetch_len; 124 - self.prefetch_page = NO_PREFETCH; 125 - self.prefetch_len = 0; 126 - } else if self.is_epub && self.chapters_cached { 127 - let dir_buf = self.cache_dir; 121 + if self.pg.prefetch_page == self.pg.page { 122 + core::mem::swap(&mut self.pg.buf, &mut self.pg.prefetch); 123 + self.pg.buf_len = self.pg.prefetch_len; 124 + self.pg.prefetch_page = NO_PREFETCH; 125 + self.pg.prefetch_len = 0; 126 + } else if self.is_epub && self.epub.chapters_cached { 127 + let dir_buf = self.epub.cache_dir; 128 128 let dir = cache::dir_name_str(&dir_buf); 129 - let ch_file = cache::chapter_file_name(self.chapter); 129 + let ch_file = cache::chapter_file_name(self.epub.chapter); 130 130 let ch_str = cache::chapter_file_str(&ch_file); 131 - let n = k.read_app_subdir_chunk(dir, ch_str, self.offsets[self.page], &mut self.buf)?; 132 - self.buf_len = n; 131 + let n = k.read_app_subdir_chunk(dir, ch_str, self.pg.offsets[self.pg.page], &mut self.pg.buf)?; 132 + self.pg.buf_len = n; 133 133 } else if self.file_size == 0 { 134 - let (size, n) = k.read_file_start(name, &mut self.buf)?; 134 + let (size, n) = k.read_file_start(name, &mut self.pg.buf)?; 135 135 self.file_size = size; 136 - self.buf_len = n; 136 + self.pg.buf_len = n; 137 137 log::info!("reader: opened {} ({} bytes)", name, size); 138 138 139 139 if size == 0 { 140 - self.fully_indexed = true; 141 - self.line_count = 0; 140 + self.pg.fully_indexed = true; 141 + self.pg.line_count = 0; 142 142 return Ok(()); 143 143 } 144 144 } else { 145 - let n = k.read_chunk(name, self.offsets[self.page], &mut self.buf)?; 146 - self.buf_len = n; 145 + let n = k.read_chunk(name, self.pg.offsets[self.pg.page], &mut self.pg.buf)?; 146 + self.pg.buf_len = n; 147 147 } 148 148 149 - let consumed = self.wrap_lines_counted(self.buf_len); 150 - let next_offset = self.offsets[self.page] + consumed as u32; 149 + let consumed = self.wrap_lines_counted(self.pg.buf_len); 150 + let next_offset = self.pg.offsets[self.pg.page] + consumed as u32; 151 151 152 - if self.page + 1 >= self.total_pages && !self.fully_indexed { 153 - if self.line_count >= self.max_lines && next_offset < self.file_size { 154 - if self.total_pages < MAX_PAGES { 155 - self.offsets[self.total_pages] = next_offset; 156 - self.total_pages += 1; 152 + if self.pg.page + 1 >= self.pg.total_pages && !self.pg.fully_indexed { 153 + if self.pg.line_count >= self.max_lines && next_offset < self.file_size { 154 + if self.pg.total_pages < MAX_PAGES { 155 + self.pg.offsets[self.pg.total_pages] = next_offset; 156 + self.pg.total_pages += 1; 157 157 } else { 158 - self.fully_indexed = true; 158 + self.pg.fully_indexed = true; 159 159 } 160 160 } else { 161 - self.fully_indexed = true; 161 + self.pg.fully_indexed = true; 162 162 } 163 163 } 164 164 165 - if self.page + 1 < self.total_pages { 166 - let pf_offset = self.offsets[self.page + 1]; 167 - let pf_result = if self.is_epub && self.chapters_cached { 168 - let dir_buf = self.cache_dir; 165 + if self.pg.page + 1 < self.pg.total_pages { 166 + let pf_offset = self.pg.offsets[self.pg.page + 1]; 167 + let pf_result = if self.is_epub && self.epub.chapters_cached { 168 + let dir_buf = self.epub.cache_dir; 169 169 let dir = cache::dir_name_str(&dir_buf); 170 - let ch_file = cache::chapter_file_name(self.chapter); 170 + let ch_file = cache::chapter_file_name(self.epub.chapter); 171 171 let ch_str = cache::chapter_file_str(&ch_file); 172 - k.read_app_subdir_chunk(dir, ch_str, pf_offset, &mut self.prefetch) 172 + k.read_app_subdir_chunk(dir, ch_str, pf_offset, &mut self.pg.prefetch) 173 173 } else { 174 - k.read_chunk(name, pf_offset, &mut self.prefetch) 174 + k.read_chunk(name, pf_offset, &mut self.pg.prefetch) 175 175 }; 176 176 match pf_result { 177 177 Ok(n) => { 178 - self.prefetch_len = n; 179 - self.prefetch_page = self.page + 1; 178 + self.pg.prefetch_len = n; 179 + self.pg.prefetch_page = self.pg.page + 1; 180 180 } 181 181 Err(_) => { 182 - self.prefetch_page = NO_PREFETCH; 183 - self.prefetch_len = 0; 182 + self.pg.prefetch_page = NO_PREFETCH; 183 + self.pg.prefetch_len = 0; 184 184 } 185 185 } 186 186 } else { 187 - self.prefetch_page = NO_PREFETCH; 188 - self.prefetch_len = 0; 187 + self.pg.prefetch_page = NO_PREFETCH; 188 + self.pg.prefetch_len = 0; 189 189 } 190 190 191 191 self.decode_page_images(k); ··· 193 193 } 194 194 195 195 pub(super) fn preindex_all_pages(&mut self) { 196 - if self.ch_cache.is_empty() { 196 + if self.epub.ch_cache.is_empty() { 197 197 return; 198 198 } 199 199 200 - let total = self.ch_cache.len(); 201 - self.offsets[0] = 0; 202 - self.total_pages = 1; 200 + let total = self.epub.ch_cache.len(); 201 + self.pg.offsets[0] = 0; 202 + self.pg.total_pages = 1; 203 203 204 204 let mut offset = 0usize; 205 - while offset < total && self.total_pages < MAX_PAGES { 205 + while offset < total && self.pg.total_pages < MAX_PAGES { 206 206 let end = (offset + PAGE_BUF).min(total); 207 207 let n = end - offset; 208 - self.buf[..n].copy_from_slice(&self.ch_cache[offset..end]); 209 - self.buf_len = n; 208 + self.pg.buf[..n].copy_from_slice(&self.epub.ch_cache[offset..end]); 209 + self.pg.buf_len = n; 210 210 211 211 let consumed = self.wrap_lines_counted(n); 212 212 let next_offset = offset + consumed; 213 213 214 - if self.line_count >= self.max_lines && next_offset < total { 215 - self.offsets[self.total_pages] = next_offset as u32; 216 - self.total_pages += 1; 214 + if self.pg.line_count >= self.max_lines && next_offset < total { 215 + self.pg.offsets[self.pg.total_pages] = next_offset as u32; 216 + self.pg.total_pages += 1; 217 217 offset = next_offset; 218 218 } else { 219 219 break; 220 220 } 221 221 } 222 222 223 - self.fully_indexed = true; 224 - log::info!("chapter pre-indexed: {} pages", self.total_pages); 223 + self.pg.fully_indexed = true; 224 + log::info!("chapter pre-indexed: {} pages", self.pg.total_pages); 225 225 } 226 226 227 227 pub(super) fn scan_to_last_page( 228 228 &mut self, 229 229 k: &mut KernelHandle<'_>, 230 230 ) -> crate::error::Result<()> { 231 - while !self.fully_indexed && self.total_pages < MAX_PAGES { 232 - self.page = self.total_pages - 1; 231 + while !self.pg.fully_indexed && self.pg.total_pages < MAX_PAGES { 232 + self.pg.page = self.pg.total_pages - 1; 233 233 self.load_and_prefetch(k)?; 234 - if self.page + 1 < self.total_pages { 235 - self.page += 1; 234 + if self.pg.page + 1 < self.pg.total_pages { 235 + self.pg.page += 1; 236 236 } else { 237 237 break; 238 238 } 239 239 } 240 - if self.total_pages > 0 { 241 - self.page = self.total_pages - 1; 240 + if self.pg.total_pages > 0 { 241 + self.pg.page = self.pg.total_pages - 1; 242 242 } 243 - self.prefetch_page = NO_PREFETCH; 243 + self.pg.prefetch_page = NO_PREFETCH; 244 244 self.load_and_prefetch(k) 245 245 } 246 246 ··· 249 249 return false; 250 250 } 251 251 252 - if self.page + 1 < self.total_pages { 253 - self.page += 1; 252 + if self.pg.page + 1 < self.pg.total_pages { 253 + self.pg.page += 1; 254 254 self.state = State::NeedPage; 255 255 return true; 256 256 } 257 257 258 - if self.is_epub && self.fully_indexed && (self.chapter as usize + 1) < self.spine.len() { 259 - self.chapter += 1; 258 + if self.is_epub && self.pg.fully_indexed && (self.epub.chapter as usize + 1) < self.epub.spine.len() { 259 + self.epub.chapter += 1; 260 260 self.goto_last_page = false; 261 261 self.state = State::NeedIndex; 262 262 return true; ··· 270 270 return false; 271 271 } 272 272 273 - if self.page > 0 { 274 - self.page -= 1; 273 + if self.pg.page > 0 { 274 + self.pg.page -= 1; 275 275 self.state = State::NeedPage; 276 276 return true; 277 277 } 278 278 279 - if self.is_epub && self.chapter > 0 { 280 - self.chapter -= 1; 279 + if self.is_epub && self.epub.chapter > 0 { 280 + self.epub.chapter -= 1; 281 281 self.goto_last_page = true; 282 282 self.state = State::NeedIndex; 283 283 return true; ··· 292 292 return false; 293 293 } 294 294 if self.is_epub { 295 - if (self.chapter as usize + 1) < self.spine.len() { 296 - self.chapter += 1; 295 + if (self.epub.chapter as usize + 1) < self.epub.spine.len() { 296 + self.epub.chapter += 1; 297 297 self.goto_last_page = false; 298 298 self.state = State::NeedIndex; 299 299 return true; 300 300 } 301 301 } else { 302 - let last = if self.total_pages > 0 { 303 - self.total_pages - 1 302 + let last = if self.pg.total_pages > 0 { 303 + self.pg.total_pages - 1 304 304 } else { 305 305 0 306 306 }; 307 - let target = (self.page + 10).min(last); 308 - if target != self.page { 309 - self.page = target; 307 + let target = (self.pg.page + 10).min(last); 308 + if target != self.pg.page { 309 + self.pg.page = target; 310 310 self.state = State::NeedPage; 311 311 return true; 312 312 } ··· 320 320 return false; 321 321 } 322 322 if self.is_epub { 323 - if self.chapter > 0 { 324 - self.chapter -= 1; 323 + if self.epub.chapter > 0 { 324 + self.epub.chapter -= 1; 325 325 self.goto_last_page = false; 326 326 self.state = State::NeedIndex; 327 327 return true; 328 328 } 329 329 } else { 330 - let target = self.page.saturating_sub(10); 331 - if target != self.page { 332 - self.page = target; 330 + let target = self.pg.page.saturating_sub(10); 331 + if target != self.pg.page { 332 + self.pg.page = target; 333 333 self.state = State::NeedPage; 334 334 return true; 335 335 }