JJ_MKVMaker/lib/platform/android/hwp_pdf_ffi.dart

140 lines
5.2 KiB
Dart

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 에서 (화면을 막지 않게), 한 문서는 한 번에 하나씩.
/// 라이브러리는 HWP 를 처음 열 때 읽는다 (앱을 켤 때 읽지 않음).
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<HwpPdfDoc> open(String path, {String? password}) async {
final (handle, code, err) = await Isolate.run(() => _open(path, password ?? ''));
if (handle == 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? '');
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<void> _queue = Future.value();
_RhwpDoc(this._handle, this.pageCount);
Future<T> _serial<T>(Future<T> Function() f) {
final run = _queue.then((_) => f());
_queue = run.then((_) {}, onError: (Object _) {});
return run;
}
@override
Future<void> exportPdf(int from, int to, String out, {List<String> fontDirs = const []}) => _serial(() async {
if (_closed) throw const HwpPdfException(HwpErrorKind.closed);
final h = _handle, dirs = fontDirs.join('\n');
final (code, err) = await Isolate.run(() => _export(h, from, to, dirs, out));
if (code != 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? '');
});
/// 지금 바꾸는 것을 멈춘다 (줄을 서지 않고 바로 - 다른 isolate 의 바꾸기가 다음 쪽에서 끝난다)
@override
void cancel() {
if (!_closed) _cancel(_handle);
}
@override
Future<void> close() => _serial(() async {
if (_closed) return;
_closed = true;
_close(_handle);
});
}
// ───────── C 함수 (isolate 안에서 부른다 - 핸들은 주소 숫자로 넘긴다) ─────────
typedef _OpenC = Pointer<Void> Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Int32>, Pointer<Pointer<Utf8>>);
typedef _CountC = Int32 Function(Pointer<Void>);
typedef _CountD = int Function(Pointer<Void>);
typedef _ExportC = Int32 Function(Pointer<Void>, Int32, Int32, Pointer<Utf8>, Pointer<Utf8>, Pointer<Int32>, Pointer<Pointer<Utf8>>);
typedef _ExportD = int Function(Pointer<Void>, int, int, Pointer<Utf8>, Pointer<Utf8>, Pointer<Int32>, Pointer<Pointer<Utf8>>);
typedef _VoidC = Void Function(Pointer<Void>);
typedef _VoidD = void Function(Pointer<Void>);
typedef _FreeC = Void Function(Pointer<Utf8>);
typedef _FreeD = void Function(Pointer<Utf8>);
DynamicLibrary _lib() => DynamicLibrary.open(RhwpEngine.libName);
/// 돌려받은 오류 글을 읽고 푼다
String? _takeError(DynamicLibrary lib, Pointer<Pointer<Utf8>> 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, 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 code = calloc<Int32>();
final err = calloc<Pointer<Utf8>>();
try {
final doc = open(a, b, code, err);
return (doc.address, code.value, _takeError(lib, err));
} finally {
calloc.free(a);
calloc.free(b);
calloc.free(code);
calloc.free(err);
}
}
int _pageCount(int handle) => _lib().lookupFunction<_CountC, _CountD>('jj_rhwp_page_count')(Pointer<Void>.fromAddress(handle));
(int, 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 code = calloc<Int32>();
final err = calloc<Pointer<Utf8>>();
try {
final r = export(Pointer<Void>.fromAddress(handle), from, to, d, o, code, err);
final e = _takeError(lib, err);
return r == 0 ? (0, null) : (code.value == 0 ? 9 : code.value, e);
} finally {
calloc.free(d);
calloc.free(o);
calloc.free(code);
calloc.free(err);
}
}
void _cancel(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_cancel')(Pointer<Void>.fromAddress(handle));
void _close(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_close')(Pointer<Void>.fromAddress(handle));