1/** 2 * FB2 to PDF Worker (via Pyodide + PyMuPDF) 3 * PyMuPDF has native FB2 (FictionBook) support, providing high-quality conversion! 4 * 5 * Note: FB2 format is primarily used for Cyrillic text (Russian, Ukrainian, etc.) 6 * PyMuPDF's built-in fonts support Cyrillic characters, so no additional font download is needed. 7 * 8 * Uses PyMuPDF's native convert_to_pdf() for optimal file size and text preservation. 9 */ 10 11import { loadPyodide } from '/pymupdf-wasm/pyodide.js'; 12 13let pyodide = null; 14let initPromise = null; 15 16async function init() { 17 if (pyodide) return pyodide; 18 19 self.postMessage({ type: 'status', message: 'Loading Python environment...' }); 20 21 pyodide = await loadPyodide({ 22 indexURL: '/pymupdf-wasm/', 23 fullStdLib: false 24 }); 25 26 self.postMessage({ type: 'status', message: 'Installing dependencies...' }); 27 28 const install = async (url) => { 29 await pyodide.loadPackage(url); 30 }; 31 32 const basePath = '/pymupdf-wasm/'; 33 await install(basePath + 'numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl'); 34 await install(basePath + 'pymupdf-1.26.3-cp313-none-pyodide_2025_0_wasm32.whl'); 35 36 self.postMessage({ type: 'status', message: 'Initializing converter...' }); 37 38 pyodide.runPython(` 39import pymupdf 40import gc 41import zipfile 42import io 43 44def convert_fb2_to_pdf(input_obj, quality='medium'): 45 """ 46 Convert FB2 (FictionBook) to PDF using PyMuPDF's native conversion. 47 48 This method preserves text as vector content (not images), resulting in: 49 - Much smaller file sizes 50 - Searchable/selectable text 51 - Better quality at any zoom level 52 53 Supports both .fb2 and .fb2.zip files. 54 """ 55 if hasattr(input_obj, "to_py"): 56 input_bytes = bytes(input_obj.to_py()) 57 else: 58 input_bytes = bytes(input_obj) 59 60 # Check if it's a zipped FB2 61 fb2_bytes = input_bytes 62 try: 63 with zipfile.ZipFile(io.BytesIO(input_bytes)) as zf: 64 fb2_files = [name for name in zf.namelist() if name.lower().endswith('.fb2')] 65 if fb2_files: 66 fb2_bytes = zf.read(fb2_files[0]) 67 except: 68 pass 69 70 # Open FB2 with PyMuPDF 71 fb2_doc = pymupdf.open(stream=fb2_bytes, filetype="fb2") 72 73 # Use PyMuPDF's native PDF conversion - preserves text, much smaller file size 74 pdf_bytes = fb2_doc.convert_to_pdf() 75 76 fb2_doc.close() 77 gc.collect() 78 79 # Optionally optimize the PDF 80 pdf_doc = pymupdf.open("pdf", pdf_bytes) 81 82 # Save with compression and garbage collection 83 optimized_bytes = pdf_doc.tobytes( 84 garbage=4, # Maximum garbage collection 85 deflate=True, # Compress streams 86 clean=True # Clean up redundant objects 87 ) 88 89 pdf_doc.close() 90 gc.collect() 91 92 return optimized_bytes 93 `); 94 95 return pyodide; 96} 97 98self.onmessage = async (event) => { 99 const { type, id, data } = event.data; 100 101 try { 102 if (type === 'init') { 103 if (!initPromise) initPromise = init(); 104 await initPromise; 105 self.postMessage({ id, type: 'init-complete' }); 106 return; 107 } 108 109 if (type === 'convert') { 110 if (!pyodide) { 111 if (!initPromise) initPromise = init(); 112 await initPromise; 113 } 114 115 const { file, quality = 'medium' } = data; 116 const arrayBuffer = await file.arrayBuffer(); 117 const inputBytes = new Uint8Array(arrayBuffer); 118 119 self.postMessage({ type: 'status', message: 'Converting FB2 to PDF...' }); 120 121 const convertFunc = pyodide.globals.get('convert_fb2_to_pdf'); 122 const resultProxy = convertFunc(inputBytes, quality); 123 const resultBytes = resultProxy.toJs(); 124 resultProxy.destroy(); 125 126 const resultBlob = new Blob([resultBytes], { type: 'application/pdf' }); 127 128 self.postMessage({ 129 id, 130 type: 'convert-complete', 131 result: resultBlob 132 }); 133 } 134 135 } catch (error) { 136 console.error('Worker error:', error); 137 self.postMessage({ 138 id, 139 type: 'error', 140 error: error.message || String(error) 141 }); 142 } 143};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.