import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; import '../../core/hwp_source.dart'; /// 관리자 지시 (10/20): Android 의 HWP · HWPX → PDF - APK 에 든 libjj_rhwp.so (rhwp v0.8.7, MIT - tool/build_rhwp_android.sh · /// tool/rhwp_ffi) 를 dart:ffi 로 부른다. 여는 것 · 바꾸는 것은 다른 isolate 에서 (화면을 막지 않게), 한 문서는 한 번에 하나씩. class RhwpEngine implements HwpPdfEngine { static const libName = 'libjj_rhwp.so'; /// 글꼴을 찾을 폴더 (Android 는 rhwp 가 시스템 글꼴을 스스로 찾지 않는다 - 한글은 Noto Sans/Serif CJK KR 로 대신 그림). /// 관리자 판단 (10/20): 한글 글꼴 컴포넌트를 받으면 그 폴더를 앞에 더한다 (아직 없음) static const fontDirs = ['/system/fonts']; /// 라이브러리를 읽을 수 있는지 (없으면 다른 앱으로 연다) static bool get available { try { DynamicLibrary.open(libName); return true; } catch (_) { return false; } } @override Future open(String path, {String? password}) async { final (handle, err) = await Isolate.run(() => _open(path, password ?? '')); if (handle == 0) throw HwpPdfException(err ?? 'rhwp'); final pages = await Isolate.run(() => _pageCount(handle)); return _RhwpDoc(handle, pages); } } class _RhwpDoc implements HwpPdfDoc { final int _handle; @override final int pageCount; var _closed = false; /// 한 번에 하나씩 (rhwp 문서는 여러 스레드에서 함께 쓰지 않는다) Future _queue = Future.value(); _RhwpDoc(this._handle, this.pageCount); Future _serial(Future Function() f) { final run = _queue.then((_) => f()); _queue = run.then((_) {}, onError: (Object _) {}); return run; } @override Future exportPdf(int from, int to, String out, {List fontDirs = const []}) => _serial(() async { if (_closed) throw const HwpPdfException('closed'); final h = _handle, dirs = fontDirs.join('\n'); final err = await Isolate.run(() => _export(h, from, to, dirs, out)); if (err != null) throw HwpPdfException(err); }); @override Future close() => _serial(() async { if (_closed) return; _closed = true; _close(_handle); }); } // ───────── C 함수 (isolate 안에서 부른다 - 핸들은 주소 숫자로 넘긴다) ───────── typedef _OpenC = Pointer Function(Pointer, Pointer, Pointer>); typedef _CountC = Int32 Function(Pointer); typedef _CountD = int Function(Pointer); typedef _ExportC = Int32 Function(Pointer, Int32, Int32, Pointer, Pointer, Pointer>); typedef _ExportD = int Function(Pointer, int, int, Pointer, Pointer, Pointer>); typedef _CloseC = Void Function(Pointer); typedef _CloseD = void Function(Pointer); typedef _FreeC = Void Function(Pointer); typedef _FreeD = void Function(Pointer); DynamicLibrary _lib() => DynamicLibrary.open(RhwpEngine.libName); /// 돌려받은 오류 글을 읽고 푼다 String? _takeError(DynamicLibrary lib, Pointer> err) { final p = err.value; if (p == nullptr) return null; final s = p.toDartString(); lib.lookupFunction<_FreeC, _FreeD>('jj_rhwp_free')(p); return s; } (int, String?) _open(String path, String password) { final lib = _lib(); final open = lib.lookupFunction<_OpenC, _OpenC>('jj_rhwp_open'); final a = path.toNativeUtf8(), b = password.toNativeUtf8(); final err = calloc>(); try { final doc = open(a, b, err); return (doc.address, _takeError(lib, err)); } finally { calloc.free(a); calloc.free(b); calloc.free(err); } } int _pageCount(int handle) => _lib().lookupFunction<_CountC, _CountD>('jj_rhwp_page_count')(Pointer.fromAddress(handle)); String? _export(int handle, int from, int to, String fontDirs, String out) { final lib = _lib(); final export = lib.lookupFunction<_ExportC, _ExportD>('jj_rhwp_export_pdf'); final d = fontDirs.toNativeUtf8(), o = out.toNativeUtf8(); final err = calloc>(); try { final r = export(Pointer.fromAddress(handle), from, to, d, o, err); final e = _takeError(lib, err); return r == 0 ? null : (e ?? 'rhwp'); } finally { calloc.free(d); calloc.free(o); calloc.free(err); } } void _close(int handle) => _lib().lookupFunction<_CloseC, _CloseD>('jj_rhwp_close')(Pointer.fromAddress(handle));