163 lines
6.4 KiB
Dart
163 lines
6.4 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 에서 (화면을 막지 않게). 줄 세우기 · 시간 제한 ·
|
|
/// 멈춤은 [QueuedHwpDoc] 가 한다 (요청마다). 라이브러리는 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'];
|
|
|
|
/// 이 Dart 가 기대하는 C ABI 의 판 (tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION 과 같아야 함).
|
|
/// 3: 요청별 멈춤 (export 에 요청 번호 · jj_rhwp_cancel(번호) · jj_rhwp_cancel_all)
|
|
static const abiVersion = 3;
|
|
|
|
/// 꼭 있어야 하는 C 함수
|
|
static const requiredSymbols = [
|
|
'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_cancel_all', //
|
|
'jj_rhwp_close', 'jj_rhwp_free',
|
|
];
|
|
|
|
static bool? _available;
|
|
|
|
/// 라이브러리를 읽을 수 있고 ABI 가 맞는지 (아니면 다른 앱으로 연다). 처음 HWP 를 열 때 한 번 본다.
|
|
/// 관리자 지시 (10/20, 28 지적): 옛 .so 에 새 인수를 넘기면 힙이 망가지므로, 기호와 판이 맞을 때만
|
|
static bool get available => _available ??= () {
|
|
try {
|
|
final lib = DynamicLibrary.open(libName);
|
|
return abiCompatible(
|
|
has: lib.providesSymbol, abi: () => lib.lookupFunction<Int32 Function(), int Function()>('jj_rhwp_abi_version')());
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}();
|
|
|
|
/// [requiredSymbols] 가 모두 있고 [abi] 가 [abiVersion] 인지 (시험용으로 따로 - 기호를 찾다 · 부르다 실패하면 false)
|
|
static bool abiCompatible({required bool Function(String name) has, required int Function() abi}) {
|
|
try {
|
|
if (!requiredSymbols.every(has)) return false;
|
|
return abi() == abiVersion;
|
|
} 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 QueuedHwpDoc(_RhwpNative(handle, pages));
|
|
}
|
|
}
|
|
|
|
class _RhwpNative implements HwpNative {
|
|
final int _handle;
|
|
@override
|
|
final int pageCount;
|
|
var _closed = false;
|
|
_RhwpNative(this._handle, this.pageCount);
|
|
|
|
@override
|
|
Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out) {
|
|
final h = _handle;
|
|
return Isolate.run(() => _export(h, req, from, to, fontDirs, out));
|
|
}
|
|
|
|
/// 줄을 서지 않고 바로 (다른 isolate 의 바꾸기가 다음 쪽에서 끝난다)
|
|
@override
|
|
void cancel(int req) {
|
|
if (!_closed) _cancel(_handle, req);
|
|
}
|
|
|
|
@override
|
|
void cancelAll() {
|
|
if (!_closed) _cancelAll(_handle);
|
|
}
|
|
|
|
/// [QueuedHwpDoc.close] 가 도는 바꾸기가 끝난 뒤에 부른다
|
|
@override
|
|
Future<void> close() 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>, Uint64, Int32, Int32, Pointer<Utf8>, Pointer<Utf8>, Pointer<Int32>, Pointer<Pointer<Utf8>>);
|
|
typedef _ExportD = int Function(Pointer<Void>, int, int, int, Pointer<Utf8>, Pointer<Utf8>, Pointer<Int32>, Pointer<Pointer<Utf8>>);
|
|
typedef _CancelC = Void Function(Pointer<Void>, Uint64);
|
|
typedef _CancelD = void Function(Pointer<Void>, int);
|
|
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 req, 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), req, 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, int req) =>
|
|
_lib().lookupFunction<_CancelC, _CancelD>('jj_rhwp_cancel')(Pointer<Void>.fromAddress(handle), req);
|
|
|
|
void _cancelAll(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_cancel_all')(Pointer<Void>.fromAddress(handle));
|
|
|
|
void _close(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_close')(Pointer<Void>.fromAddress(handle));
|