From ced0ae9e5d5c89840bff85f98c8e9975368e54e4 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:43:20 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20Android=20HWP=20=C2=B7=20HWPX=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=201=EB=8B=A8=EA=B3=84=20(=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=A7=80=EC=8B=9C=2010/20):=20rhwp=20v0.8.7=20(MIT,=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=201a76570=20=EA=B3=A0=EC=A0=95)=20=EB=A5=BC?= =?UTF-8?q?=20=EC=96=87=EC=9D=80=20C=20ABI=20(tool/rhwp=5Fffi)=20=EC=99=80?= =?UTF-8?q?=20=ED=95=A8=EA=BB=98=20NDK=20=EB=A1=9C=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?(tool/build=5Frhwp=5Fandroid.sh=20-=20git=20sparse=20=EB=B0=9B?= =?UTF-8?q?=EA=B8=B0=20=C2=B7=20Rust=201.93.1=20=C2=B7=20Cargo.lock=20--lo?= =?UTF-8?q?cked=20=C2=B7=2016KB=20=EC=A0=95=EB=A0=AC=20=C2=B7=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20/jj=20=EB=A1=9C=20=EB=B0=94=EA=BF=88)=20=E2=86=92?= =?UTF-8?q?=20APK=20=EC=9D=98=20libjj=5Frhwp.so=20=EB=A5=BC=20dart:ffi=20?= =?UTF-8?q?=EB=A1=9C=20=C2=B7=20=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?HwpSource:=20=EB=B3=B4=EB=8A=94=20=EC=AA=BD=EC=9D=98=205?= =?UTF-8?q?=EC=AA=BD=20=EB=AC=B6=EC=9D=8C=EB=A7=8C=20PDF=20=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=94=EA=BE=B8=EA=B3=A0=20=EB=8B=A4=EC=9D=8C=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=EC=9D=84=20=EB=AF=B8=EB=A6=AC,=20=EB=AC=B6=EC=9D=8C?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=203=EB=B6=84=20=EC=A0=9C=ED=95=9C,=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=ED=95=9C=20=EB=AC=B6=EC=9D=8C=EC=9D=80=20?= =?UTF-8?q?=EA=B7=B8=20=EC=AA=BD=EC=97=90=20=EC=9D=B4=EC=9C=A0=20=C2=B7=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EB=B3=B4=EB=A9=B4=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=C2=B7=20=EB=8B=AB=EC=9C=BC=EB=A9=B4=20=EB=A9=88=EC=B6=A4=20?= =?UTF-8?q?=C2=B7=20=ED=8C=8C=EC=9D=BC=20=ED=83=90=EC=83=89=EA=B8=B0:=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=BC=B0=EC=9D=84=20=EB=95=8C=20=EC=95=B1=20?= =?UTF-8?q?=EC=95=88=EC=97=90=EC=84=9C,=20=EC=97=B4=EC=A7=80=20=EB=AA=BB?= =?UTF-8?q?=ED=95=98=EB=A9=B4=20=EC=9D=B4=EC=9C=A0=EB=A5=BC=20=EC=95=8C?= =?UTF-8?q?=EB=A6=AC=EA=B3=A0=20=EB=8B=A4=EB=A5=B8=20=EC=95=B1=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=C2=B7=20=EB=AC=B8=EC=84=9C=EC=9D=98=20=EA=B0=80?= =?UTF-8?q?=EB=A1=9C=20=EC=AA=BD=EC=9D=80=20=EC=9E=90=EB=8F=99=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=20=EB=82=98=EB=88=84=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=8C=20(=EB=82=B4=20=ED=8C=90=EB=8B=A8)=20=C2=B7=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=84=A0=EC=8A=A4=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=C2=B7=20Android=20=EA=B3=A0=EC=A7=80=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20hwp=5Fsource=5Ftest=20(=EA=B0=80=EC=A7=9C=20?= =?UTF-8?q?=EC=97=94=EC=A7=84,=20Windows=20=ED=86=B5=ED=95=A9)=20(?= =?UTF-8?q?=EA=B2=80=ED=86=A0=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- android/app/build.gradle.kts | 5 +- android/app/third_party_libs.txt | 1 + assets/l10n/en.json | 4 + assets/l10n/ja.json | 4 + assets/l10n/zh-Hans.json | 4 + assets/licenses/RHWP_LICENSES.txt | 278 +++ .../licenses/THIRD_PARTY_NOTICES_ANDROID.txt | 4 + integration_test/hwp_source_test.dart | 134 ++ lib/core/hwp_source.dart | 131 ++ lib/platform/android/hwp_pdf_ffi.dart | 126 ++ lib/ui/explorer_page.dart | 43 + lib/ui/license_texts.dart | 2 + lib/ui/reader_page.dart | 4 +- tool/build_rhwp_android.sh | 95 + tool/rhwp_ffi/Cargo.lock | 1928 +++++++++++++++++ tool/rhwp_ffi/Cargo.toml | 20 + tool/rhwp_ffi/rust-toolchain.toml | 5 + tool/rhwp_ffi/src/lib.rs | 137 ++ 18 files changed, 2923 insertions(+), 2 deletions(-) create mode 100644 assets/licenses/RHWP_LICENSES.txt create mode 100644 integration_test/hwp_source_test.dart create mode 100644 lib/core/hwp_source.dart create mode 100644 lib/platform/android/hwp_pdf_ffi.dart create mode 100644 tool/build_rhwp_android.sh create mode 100644 tool/rhwp_ffi/Cargo.lock create mode 100644 tool/rhwp_ffi/Cargo.toml create mode 100644 tool/rhwp_ffi/rust-toolchain.toml create mode 100644 tool/rhwp_ffi/src/lib.rs diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2583013..d22ec95 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -67,9 +67,12 @@ android { sourceSets["main"].jniLibs.srcDir("../../third_party/tor/android") // 86: 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일 - 7z · RAR 풀기): tool/build_7zz_android.sh (공식 소스, SHA-256 고정) sourceSets["main"].jniLibs.srcDir("../../third_party/7zip/android") + // 관리자 지시 (10/20): HWP · HWPX → PDF (libjj_rhwp.so - dart:ffi 로 읽는 라이브러리): tool/build_rhwp_android.sh (rhwp v0.8.7, 커밋 고정) + sourceSets["main"].jniLibs.srcDir("../../third_party/rhwp/android") // 195: third_party 의 실행 파일은 third_party_libs.txt 에 적은 것만 (공유 폴더에 다른 판이 있어도 섞이지 않게) val thirdPartyLibs = file("third_party_libs.txt").readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }.toSet() - listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android", "../../third_party/7zip/android").forEach { dir -> + listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android", "../../third_party/7zip/android", + "../../third_party/rhwp/android").forEach { dir -> fileTree(dir).matching { include("**/*.so") }.forEach { so -> if (so.name !in thirdPartyLibs) packaging.jniLibs.excludes += "**/${so.name}" } diff --git a/android/app/third_party_libs.txt b/android/app/third_party_libs.txt index c591af0..62c352d 100644 --- a/android/app/third_party_libs.txt +++ b/android/app/third_party_libs.txt @@ -7,3 +7,4 @@ libsdcli.so libsdcli_v80.so libtor.so lib7zz.so +libjj_rhwp.so diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 8078b98..de42852 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -545,6 +545,7 @@ "{0}: 작업 중이라 옮기지 않았습니다": "{0}: not moved because it is being processed", "{0}: 저장하지 않았습니다": "{0}: not saved", "{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: moves the {1} checked videos to {2}", + "{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다": "Couldn't convert pages {0}-{1} within {2} s", "{0}개": "{0}", "{0}개 ZIP 으로 묶기": "Compress {0} items to ZIP", "{0}개 · {1}": "{0} · {1}", @@ -1024,6 +1025,7 @@ "단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "Shortcuts - Total Commander: F5 copy · F6 move · F7 new folder · F8 delete · Insert/Space mark · Ctrl+R refresh. Others: Ctrl+C · X · V (copy · cut · paste) · Ctrl+A mark all · F5 refresh", "단축키 해제": "Unregister hotkey", "닫기": "Close", + "닫힌 문서입니다": "The document is closed", "닫힘 (실행 안 함)": "Closed (not run)", "담아 두고 붙여넣기 (기본)": "Hold and paste (default)", "답이 없습니다": "No answer", @@ -1786,6 +1788,7 @@ "앱 안 브라우저의 쿠키 (로그인) · 사이트 데이터 · 캐시 · 방문 기록과, 다운로드용으로 내보낸 쿠키 파일을 지웁니다. 사이트에 다시 로그인해야 합니다. 즐겨찾기는 그대로입니다.": "Deletes the in-app browser's cookies (logins), site data, cache and history, plus the cookie file exported for downloads. You'll need to log in to sites again. Bookmarks are kept.", "앱 안 웹 브라우저 · 즐겨찾기 · 동영상 다운로드": "In-app web browser · bookmarks · video downloads", "앱 안 웹 브라우저: [건너뛰기] 를 대신 누르고, 건너뛸 수 없는 광고는 소리를 끄고 빨리 넘깁니다 (넘기는 동안 화면 위쪽에 \"광고 건너뛰는 중…\")": "In-app web browser: presses [Skip] for you, and mutes and fast-forwards ads that can't be skipped (\"Skipping ad…\" shows at the top while skipping)", + "앱 안에서 열지 못해 다른 앱으로 엽니다: {0}": "Couldn't open it in the app, opening with another app: {0}", "앱 지우기": "Uninstall app", "앱 휴지통에 {0}일 두었다가 지웁니다 (탐색기 [휴지통] 에서 되살리기).": "Kept in the app trash for {0} days, then deleted (restore from [Trash] in the explorer).", "앱에 들어 있는 것 (기본)": "Built into the app (default)", @@ -1825,6 +1828,7 @@ "엔진": "Engine", "여기까지 범위 고르기": "Select range up to here", "여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "Drop here to add to the Make MKV video list", + "여는 중: {0}": "Opening: {0}", "여러 개를 골랐을 때": "When several are selected", "여러 동영상을 MKV 로 만들 때 동시에 처리할 개수 (재인코딩은 CPU 를 많이 씁니다)": "How many to process at once when making several videos into MKV (re-encoding uses a lot of CPU)", "여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다": "Too many wrong tries. You can try again in {0} s", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index ccb57d8..09393b3 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -545,6 +545,7 @@ "{0}: 작업 중이라 옮기지 않았습니다": "{0}: 作業中のため移動しませんでした", "{0}: 저장하지 않았습니다": "{0}: 保存しませんでした", "{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: チェックした動画 {1}個を {2} に移動します", + "{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다": "{0}~{1} ページを {2} 秒以内に変換できませんでした", "{0}개": "{0}個", "{0}개 ZIP 으로 묶기": "{0} 件を ZIP にまとめる", "{0}개 · {1}": "{0}個・{1}", @@ -1024,6 +1025,7 @@ "단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "ショートカット - Total Commander: F5 コピー · F6 移動 · F7 新しいフォルダー · F8 削除 · Insert/Space マーク · Ctrl+R 更新。その他: Ctrl+C · X · V (コピー · 切り取り · 貼り付け) · Ctrl+A すべてマーク · F5 更新", "단축키 해제": "ショートカット解除", "닫기": "閉じる", + "닫힌 문서입니다": "文書は閉じられています", "닫힘 (실행 안 함)": "閉じた (実行しない)", "담아 두고 붙여넣기 (기본)": "保持して貼り付け (既定)", "답이 없습니다": "応答がありません", @@ -1786,6 +1788,7 @@ "앱 안 브라우저의 쿠키 (로그인) · 사이트 데이터 · 캐시 · 방문 기록과, 다운로드용으로 내보낸 쿠키 파일을 지웁니다. 사이트에 다시 로그인해야 합니다. 즐겨찾기는 그대로입니다.": "アプリ内ブラウザの Cookie (ログイン)・サイトデータ・キャッシュ・閲覧履歴と、ダウンロード用に書き出した Cookie ファイルを削除します。サイトへの再ログインが必要です。お気に入りはそのままです。", "앱 안 웹 브라우저 · 즐겨찾기 · 동영상 다운로드": "アプリ内 Web ブラウザー・ブックマーク・動画ダウンロード", "앱 안 웹 브라우저: [건너뛰기] 를 대신 누르고, 건너뛸 수 없는 광고는 소리를 끄고 빨리 넘깁니다 (넘기는 동안 화면 위쪽에 \"광고 건너뛰는 중…\")": "アプリ内ウェブブラウザー:[スキップ]を代わりに押し、スキップできない広告は音を消して早送りします(スキップ中は画面上部に「広告をスキップ中…」)", + "앱 안에서 열지 못해 다른 앱으로 엽니다: {0}": "アプリ内で開けないため、他のアプリで開きます: {0}", "앱 지우기": "アプリを削除", "앱 휴지통에 {0}일 두었다가 지웁니다 (탐색기 [휴지통] 에서 되살리기).": "アプリのゴミ箱に {0} 日間置いてから削除します (エクスプローラーの [ゴミ箱] から復元できます)。", "앱에 들어 있는 것 (기본)": "アプリに内蔵 (既定)", @@ -1825,6 +1828,7 @@ "엔진": "エンジン", "여기까지 범위 고르기": "ここまで範囲選択", "여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "ここにドロップすると MKV 作成の動画リストに追加します", + "여는 중: {0}": "開いています: {0}", "여러 개를 골랐을 때": "複数選んだとき", "여러 동영상을 MKV 로 만들 때 동시에 처리할 개수 (재인코딩은 CPU 를 많이 씁니다)": "複数の動画を MKV にするとき同時に処理する数(再エンコードは CPU を多く使います)", "여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다": "何度も間違えたため {0} 秒後に再入力できます", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index faec53e..bd1f8ff 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -545,6 +545,7 @@ "{0}: 작업 중이라 옮기지 않았습니다": "{0}: 正在处理,未移动", "{0}: 저장하지 않았습니다": "{0}:未保存", "{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: 将勾选的 {1} 个视频移到 {2}", + "{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다": "无法在 {2} 秒内转换第 {0}~{1} 页", "{0}개": "{0} 个", "{0}개 ZIP 으로 묶기": "将 {0} 项压缩为 ZIP", "{0}개 · {1}": "{0} 个 · {1}", @@ -1024,6 +1025,7 @@ "단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "快捷键 - Total Commander: F5 复制 · F6 移动 · F7 新建文件夹 · F8 删除 · Insert/Space 标记 · Ctrl+R 刷新。其他: Ctrl+C · X · V (复制 · 剪切 · 粘贴) · Ctrl+A 全部标记 · F5 刷新", "단축키 해제": "注销快捷键", "닫기": "关闭", + "닫힌 문서입니다": "文档已关闭", "닫힘 (실행 안 함)": "已关闭 (未执行)", "담아 두고 붙여넣기 (기본)": "暂存后粘贴 (默认)", "답이 없습니다": "没有应答", @@ -1786,6 +1788,7 @@ "앱 안 브라우저의 쿠키 (로그인) · 사이트 데이터 · 캐시 · 방문 기록과, 다운로드용으로 내보낸 쿠키 파일을 지웁니다. 사이트에 다시 로그인해야 합니다. 즐겨찾기는 그대로입니다.": "删除应用内浏览器的 Cookie (登录) · 网站数据 · 缓存 · 浏览记录,以及为下载导出的 Cookie 文件。需要重新登录网站。收藏保持不变。", "앱 안 웹 브라우저 · 즐겨찾기 · 동영상 다운로드": "应用内浏览器 · 书签 · 视频下载", "앱 안 웹 브라우저: [건너뛰기] 를 대신 누르고, 건너뛸 수 없는 광고는 소리를 끄고 빨리 넘깁니다 (넘기는 동안 화면 위쪽에 \"광고 건너뛰는 중…\")": "应用内网页浏览器: 代为点击[跳过],无法跳过的广告会静音并快进(跳过时屏幕顶部显示“正在跳过广告…”)", + "앱 안에서 열지 못해 다른 앱으로 엽니다: {0}": "无法在应用内打开,改用其他应用打开: {0}", "앱 지우기": "卸载应用", "앱 휴지통에 {0}일 두었다가 지웁니다 (탐색기 [휴지통] 에서 되살리기).": "在应用回收站保留 {0} 天后删除 (可在资源管理器的 [回收站] 中还原)。", "앱에 들어 있는 것 (기본)": "应用内置 (默认)", @@ -1825,6 +1828,7 @@ "엔진": "引擎", "여기까지 범위 고르기": "选择到此处的范围", "여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "拖放到此处即可加入制作 MKV 的视频列表", + "여는 중: {0}": "正在打开: {0}", "여러 개를 골랐을 때": "选择多个时", "여러 동영상을 MKV 로 만들 때 동시에 처리할 개수 (재인코딩은 CPU 를 많이 씁니다)": "将多个视频制作成 MKV 时同时处理的数量(重新编码会占用大量 CPU)", "여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다": "错误次数过多,{0} 秒后可再次输入", diff --git a/assets/licenses/RHWP_LICENSES.txt b/assets/licenses/RHWP_LICENSES.txt new file mode 100644 index 0000000..2e94b57 --- /dev/null +++ b/assets/licenses/RHWP_LICENSES.txt @@ -0,0 +1,278 @@ +rhwp v0.8.7 (https://github.com/edwardkim/rhwp, commit 1a76570e833917d15817415a53c09ad61ab3203f) +Android: libjj_rhwp.so (HWP / HWPX -> PDF), built by tool/build_rhwp_android.sh with tool/rhwp_ffi + +MIT License + +Copyright (c) 2025-2026 Edward Kim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------------------------------------------------------------- +rhwp THIRD_PARTY_LICENSES.md +--------------------------------------------------------------------- + +# Third-Party Licenses + +rhwp 프로젝트가 사용하는 서드파티 라이브러리 및 리소스의 라이선스 목록이다. + +기준 파일: + +- `Cargo.toml` / `Cargo.lock` (`rhwp` v0.8.7) +- `rhwp-studio/package.json` / `rhwp-studio/package-lock.json` +- `rhwp-chrome/package.json` / `rhwp-chrome/package-lock.json` +- `rhwp-firefox/package.json` / `rhwp-firefox/package-lock.json` +- `rhwp-vscode/package.json` / `rhwp-vscode/package-lock.json` +- `assets/fonts/FONTS.md` + +--- + +## Rust 크레이트 + +### 직접 의존성 + +| 크레이트 | 버전 | 라이선스 | 저장소 | 비고 | +|---------|------|---------|--------|------| +| aes | 0.9.2 | MIT OR Apache-2.0 | RustCrypto/block-ciphers | HWPX 암호화 | +| base64 | 0.23.1 | MIT OR Apache-2.0 | marshallpierce/rust-base64 | Base64 인코딩 | +| blake3 | 1.8.6 | CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception | BLAKE3-team/BLAKE3 | 해시/진단 | +| byteorder | 1.5.0 | Unlicense OR MIT | BurntSushi/byteorder | 바이너리 endian 처리 | +| cbc | 0.2.1 | MIT OR Apache-2.0 | RustCrypto/block-modes | HWPX 암호화 모드 | +| cfb | 0.14.0 | MIT | mdsteele/rust-cfb | OLE Compound File | +| cipher | 0.5.2 | MIT OR Apache-2.0 | RustCrypto/traits | 암호 trait | +| codepage | 0.1.2 | Apache-2.0 OR MIT | hsivonen/codepage | 코드페이지 처리 | +| console_error_panic_hook | 0.1.7 | Apache-2.0/MIT | rustwasm/console_error_panic_hook | WASM panic hook | +| crc32fast | 1.5.0 | MIT OR Apache-2.0 | srijs/rust-crc32fast | HWP3 암호 키 검증 | +| des | 0.9.0 | MIT OR Apache-2.0 | RustCrypto/block-ciphers | HWP3 암호화 | +| ed25519-dalek | 2.2.0 | BSD-3-Clause | dalek-cryptography/curve25519-dalek | capsule 서명 | +| embedded-io | 0.7.1 | MIT OR Apache-2.0 | rust-embedded/embedded-hal | IO trait | +| encoding_rs | 0.8.35 | (Apache-2.0 OR MIT) AND BSD-3-Clause | hsivonen/encoding_rs | 문자 인코딩 | +| flate2 | 1.1.9 | MIT OR Apache-2.0 | rust-lang/flate2-rs | 압축/해제 | +| getrandom | 0.4.3 | MIT OR Apache-2.0 | rust-random/getrandom | 서명 키 난수 생성 | +| hmac | 0.13.0 | MIT OR Apache-2.0 | RustCrypto/MACs | HWPX 무결성 | +| image | 0.25.10 | MIT OR Apache-2.0 | image-rs/image | BMP/JPEG/PNG 디코딩 | +| js-sys | 0.3.102 | MIT OR Apache-2.0 | rustwasm/wasm-bindgen | WASM JS interop | +| paste | 1.0.15 | MIT OR Apache-2.0 | dtolnay/paste | 매크로 보조 | +| pbkdf2 | 0.13.0 | MIT OR Apache-2.0 | RustCrypto/password-hashes | HWPX 키 파생 | +| pcx | 0.2.5 | MIT OR Apache-2.0 OR WTFPL | kryptan/pcx | PCX 이미지 디코딩 | +| pdf-writer | 0.12.1 | MIT OR Apache-2.0 | typst/pdf-writer | PDF 출력 | +| quick-xml | 0.41.0 | MIT | tafia/quick-xml | HWPX XML 파싱 | +| resvg | 0.47.0 | Apache-2.0 OR MIT | linebender/resvg | native-skia SVG raster (optional) | +| roxmltree | 0.21.1 | MIT OR Apache-2.0 | RazrFalcon/roxmltree | XML 검증·정규화 | +| serde | 1.0.229 | MIT OR Apache-2.0 | serde-rs/serde | 직렬화 | +| serde_json | 1.0.151 | MIT OR Apache-2.0 | serde-rs/json | JSON 직렬화 | +| sha1 | 0.11.0 | MIT OR Apache-2.0 | RustCrypto/hashes | HWPX 암호 키 파생 | +| sha2 | 0.11.0 | MIT OR Apache-2.0 | RustCrypto/hashes | 서명·CAS 해시 | +| skia-safe | 0.99.0 | MIT | rust-skia/rust-skia | native-skia PNG/PDF backend (optional) | +| snafu | 0.9.2 | MIT OR Apache-2.0 | shepmaster/snafu | 에러 처리 | +| strum | 0.28.0 | MIT | Peternator7/strum | enum derive | +| subsecond | 0.7.10 | MIT OR Apache-2.0 | DioxusLabs/dioxus | 개발 핫패치 (optional) | +| subsetter | 0.2.6 | MIT OR Apache-2.0 | typst/subsetter | 폰트 subset | +| svg2pdf | 0.13.0 | MIT OR Apache-2.0 | typst/svg2pdf | SVG → PDF | +| svgtypes | 0.16.1 | Apache-2.0 OR MIT | linebender/svgtypes | SVG 값 파싱 | +| ttf-parser | 0.25.1 | MIT OR Apache-2.0 | harfbuzz/ttf-parser | 폰트 파싱 | +| unicode-properties | 0.1.4 | MIT OR Apache-2.0 | unicode-rs/unicode-properties | 유니코드 문자 분류 | +| unicode-segmentation | 1.13.3 | MIT OR Apache-2.0 | unicode-rs/unicode-segmentation | grapheme/word segmentation | +| unicode-width | 0.2.2 | MIT OR Apache-2.0 | unicode-rs/unicode-width | 문자 폭 계산 | +| usvg | 0.45.1 | Apache-2.0 OR MIT | linebender/resvg | SVG 파싱 | +| wasm-bindgen | 0.2.125 | MIT OR Apache-2.0 | rustwasm/wasm-bindgen | WASM binding | +| wasm-bindgen-test | 0.3.75 | MIT OR Apache-2.0 | rustwasm/wasm-bindgen | WASM 테스트 | +| web-sys | 0.3.102 | MIT OR Apache-2.0 | rustwasm/wasm-bindgen | Web API binding | +| zip | 8.6.0 | MIT | zip-rs/zip2 | HWPX ZIP 처리 | + +### 전체 Rust 의존성 요약 + +`cargo metadata --locked` 기준 package entry는 247개이며, 그중 registry 또는 Git +source를 가진 외부 Rust 크레이트는 243개다. 나머지 4개는 현재 workspace 패키지다. + +주요 라이선스군: + +- MIT / Apache-2.0 dual license 계열 +- MIT 단독 +- Apache-2.0 OR MIT +- BSD-2-Clause / BSD-3-Clause +- Zlib +- Unlicense +- 0BSD +- ISC +- Unicode-DFS-2016 +- CC0-1.0 / Apache-2.0 WITH LLVM-exception (`blake3`) +- WTFPL 대체 선택지 포함 (`pcx`, MIT/Apache 선택 가능) + +> 모든 Rust 의존성은 MIT, Apache-2.0, BSD, Zlib, Unlicense, 0BSD, ISC 등 permissive license 계열이거나 +> permissive option을 포함하며, rhwp의 MIT 라이선스와 호환된다. + +--- + +## npm 패키지 + +### npm 배포 패키지 + +| 패키지 | 버전 | 라이선스 | 용도 | +|--------|------|---------|------| +| @rhwp/core | 0.8.6 | MIT | WASM parser/renderer API | +| @rhwp/editor | 0.8.6 | MIT | iframe 기반 웹 에디터 wrapper | + +### rhwp-studio + +| 패키지 | Lock 버전 | 라이선스 | 용도 | +|--------|-----------|---------|------| +| canvaskit-wasm | 0.42.0 | BSD-3-Clause | CanvasKit 렌더링 backend | +| @noble/hashes | 2.4.0 | MIT | 렌더 리소스 키와 문서 digest | +| @types/chrome | 0.2.5 | MIT | Chrome API 타입 | +| pixelmatch | 7.2.0 | ISC | 시각 diff 픽셀 비교 | +| pngjs | 7.0.0 | MIT | 시각 diff PNG 처리 | +| puppeteer-core | 25.5.0 | Apache-2.0 | E2E 테스트 / CDP 연결 | +| typescript | 7.0.2 | Apache-2.0 | TypeScript 컴파일 | +| vite | 8.2.1 | MIT | 개발 서버 + 빌드 | +| vite-plugin-pwa | 1.3.0 | MIT | PWA 빌드 보조 | +| workbox-window | 7.4.1 | MIT | Service worker runtime 보조 | + +### rhwp-chrome / rhwp-firefox + +| 패키지 | Lock 버전 | 라이선스 | 용도 | +|--------|-----------|---------|------| +| typescript | 7.0.2 | Apache-2.0 | TypeScript 컴파일 | +| vite | 8.2.1 | MIT | 확장 번들 빌드 | + +### rhwp-vscode + +| 패키지 | Lock 버전 | 라이선스 | 용도 | +|--------|-----------|---------|------| +| @types/node | 18.15.13 | MIT | Node.js 타입 | +| @types/vscode | 1.82.0 | MIT | VS Code API 타입 | +| @typescript/native | 7.0.2 | Apache-2.0 | TypeScript native preview 컴파일러 | +| copy-webpack-plugin | 14.0.0 | MIT | Webpack 파일 복사 | +| null-loader | 4.0.1 | MIT | Webpack 로더 | +| ts-loader | 9.6.2 | MIT | Webpack TypeScript 로더 | +| typescript | 6.0.2 | Apache-2.0 | TypeScript 컴파일 | +| webpack | 5.109.2 | MIT | 번들러 | +| webpack-cli | 7.2.2 | MIT | Webpack CLI | +| @noble/hashes | 2.4.0 | MIT | 문서 digest | +| canvaskit-wasm | 0.42.0 | BSD-3-Clause | 자동 선택 CanvasKit backend | + +### rhwp-shared + +`rhwp-shared`는 현재 별도 외부 npm 의존성이 없는 shared source package다. + +--- + +## 웹 폰트 및 폰트 리소스 + +`assets/fonts/`에 포함된 canonical 폰트 목록이다. 저작권 보호가 필요한 한컴/Microsoft 폰트는 Git에 +포함하지 않으며, 세부 파일 목록과 폴백 관계는 `assets/fonts/FONTS.md`를 참조한다. + +| 폰트/리소스 | 라이선스 | 출처 | 비고 | +|-------------|---------|------|------| +| Pretendard (9종) | SIL Open Font License 1.1 | github.com/orioncactus/pretendard | Sans-serif fallback | +| Noto Serif KR (2종) | SIL Open Font License 1.1 | Google Fonts | Serif fallback | +| Noto Sans KR (3종) | SIL Open Font License 1.1 | Google Fonts | Sans-serif fallback | +| Nanum Myeongjo (3종) | SIL Open Font License 1.1 | Google Fonts | Serif fallback | +| Nanum Gothic (3종) | SIL Open Font License 1.1 | Google Fonts | Sans-serif fallback | +| Nanum Gothic Coding (2종) | SIL Open Font License 1.1 | Google Fonts | Monospace fallback | +| Gowun Batang (2종) | SIL Open Font License 1.1 | Google Fonts | Serif fallback | +| Gowun Dodum | SIL Open Font License 1.1 | Google Fonts | Sans-serif fallback | +| D2 Coding (2종) | SIL Open Font License 1.1 | NAVER | Monospace fallback | +| Spoqa Han Sans | SIL Open Font License 1.1 | github.com/spoqa/spoqa-han-sans | Sans-serif fallback | +| Source Han Serif K Old Hangul subset | SIL Open Font License 1.1 | Adobe / Google | 옛한글 PUA fallback, `SourceHanSerifK-OFL.txt` 동봉 | +| Latin Modern Math | GUST Font License | GUST / TeX ecosystem | 수식 fallback | +| Cafe24 써라운드 | 카페24 무료 배포 | fonts.cafe24.com | 장식체 | +| Cafe24 슈퍼매직 | 카페24 무료 배포 | fonts.cafe24.com | 장식체 | +| 행복고딕 (Happiness Sans, 4종) | 무료 배포 | 행복나눔재단 | Sans-serif fallback | + +--- + +## 도구 + +| 도구 | 라이선스 | 용도 | +|------|---------|------| +| Docker | Apache-2.0 | WASM 빌드 환경 | +| wasm-pack | MIT OR Apache-2.0 | WASM 패키징 | +| Chrome DevTools Protocol | BSD-3-Clause | E2E 테스트 / 브라우저 자동화 | +| GitHub Actions | GitHub Terms | CI/CD | +| npm Trusted Publishing / OIDC | npm Terms | npm 배포 | + +--- + +## 직접 포팅한 알고리즘 + +### Volexity hwp-extract + +- 출처: [volexity/hwp-extract](https://github.com/volexity/hwp-extract), commit + `e5f8b5e1590dee973630666e687e919fa70da2e2` +- 참조 파일: `src/hwp_extract/encrypt.py` +- 적용 위치: `src/parser/crypto.rs`의 HWP5 EncryptVersion 4 키 파생 및 비트 단위 + AES-CFB 복호화 +- 라이선스: BSD-3-Clause + +Modified BSD License + +_Copyright © `2024`, `Volexity, Inc`_ + +_All rights reserved._ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the `Volexity, Inc` nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL `Volexity, Inc` BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## 참조한 오픈소스 프로젝트 (스펙·설계 참조) + +rhwp는 아래 프로젝트들의 **코드를 직접 복사하지 않으며**, 공개된 스펙 정보(enum 값·속성 기본값·태그 이름·검증 규칙 등)만 참조한다. +Apache-2.0 라이선스 고지 의무는 본 문서, 관련 기술 문서, 그리고 각 참조 파일의 헤더 주석으로 충족한다. + +| 프로젝트 | 라이선스 | 참조 범위 | rhwp 위치 | +|---------|---------|----------|-----------| +| [hancom-io/hwpx-owpml-model](https://github.com/hancom-io/hwpx-owpml-model) | Apache-2.0 © 2022 Hancom Inc. | HWPX enum 정의, 속성 기본값, 태그 전체 집합, canonical 속성·자식 순서 | `src/serializer/hwpx/canonical_defaults.rs`, `src/serializer/hwpx/{header,table,shape,picture}.rs`, `mydocs/tech/hwpx_hancom_reference.md` | +| [hancom-io/dvc](https://github.com/hancom-io/dvc) | Apache-2.0 © 2022 Hancom Inc. | HWPX 검증 규칙 JSON 스키마, errorCode 체계 | `mydocs/tech/hwpx_dvc_reference.md` | + +--- + +## 라이선스 호환성 + +rhwp는 **MIT 라이선스**로 배포된다. + +- MIT, Apache-2.0, BSD, Zlib, Unlicense, 0BSD, ISC, CC0 — MIT와 호환되는 permissive license 계열 +- `encoding_rs`의 BSD-3-Clause 조항은 고지 의무를 요구하며, 이 문서로 충족 +- `pcx`는 WTFPL 대체 선택지를 포함하지만 MIT/Apache-2.0 선택지가 있어 rhwp MIT 배포와 호환 +- 오픈 폰트는 SIL OFL 1.1 또는 GUST Font License 등 재배포 가능한 라이선스만 Git에 포함 +- 저작권 보호 대상 폰트(한컴, Microsoft 등)는 Git에 포함하지 않으며 `ttfs/FONTS.md`, `assets/fonts/FONTS.md`에서 목록과 폴백만 관리 + +--- + +*이 문서는 `Cargo.toml`, `Cargo.lock`, 각 `package.json`/`package-lock.json`, `assets/fonts/FONTS.md` 기준으로 현행화되었으며, 의존성 업데이트 시 함께 갱신해야 한다.* diff --git a/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt b/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt index 08c2e25..9dc6fbf 100644 --- a/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt +++ b/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt @@ -58,6 +58,10 @@ JJ_MKVMaker (Android) - 포함된 오픈 소스 구성 요소 및 라이선스 (이 앱은 RAR 을 만들지 않습니다). 공식 소스 (https://www.7-zip.org/a/7z2501-src.7z) 를 고치지 않고 Android NDK 로 빌드 (빌드 방법: 소스의 tool/build_7zz_android.sh). 따로 실행하는 프로그램이며, 대응 소스는 위 주소와 이 앱 판의 태그에 있습니다. 원문: 앱 안 "오픈 소스 라이선스" 의 7-Zip (7ZIP_LICENSE.txt) · GNU LGPL 2.1 +- rhwp v0.8.7 (libjj_rhwp.so - HWP · HWPX 를 PDF 로 바꿔 보기) MIT https://github.com/edwardkim/rhwp + Copyright (c) 2025-2026 Edward Kim. 커밋 1a76570 을 고치지 않고, 얇은 C 함수 묶음 (소스의 tool/rhwp_ffi) 과 함께 Android NDK 로 + 빌드 (빌드 방법: 소스의 tool/build_rhwp_android.sh). 함께 묶인 Rust crate 들 (MIT · Apache-2.0 등) 의 목록과 원문: + 앱 안 "오픈 소스 라이선스" 의 rhwp (RHWP_LICENSES.txt) - stable-diffusion.cpp · ggml (libsdcli.so 라는 이름의 실행 파일) MIT https://github.com/leejet/stable-diffusion.cpp 커밋 228c707 을 고치지 않고 Android NDK 로 빌드 (AI 그림 · 해상도 올리기, 빌드 방법: 소스의 tool/build_sdcpp_android.sh) 함께 묶인 구성 요소: ggml (MIT) · libwebp · libwebm (BSD-3) · Oniguruma · Darts-clone (BSD-2) · utf8proc · nlohmann/json · diff --git a/integration_test/hwp_source_test.dart b/integration_test/hwp_source_test.dart new file mode 100644 index 0000000..d292a66 --- /dev/null +++ b/integration_test/hwp_source_test.dart @@ -0,0 +1,134 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image/image.dart' as img; +import 'package:integration_test/integration_test.dart'; +import 'package:jj_mkvmaker/core/hwp_source.dart'; +import 'package:jj_mkvmaker/core/reader_sources.dart'; +import 'package:path/path.dart' as p; +import 'package:pdfrx/pdfrx.dart'; + +import 'support/temp_dir.dart'; + +/// 쪽마다 그림 한 장인 진짜 PDF 를 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다 +class _FakeEngine implements HwpPdfEngine { + final int pages; + final String tempDir; + final calls = <(int, int)>[]; + var failNext = 0; + Completer? hold; + _FakeEngine(this.pages, this.tempDir); + + @override + Future open(String path, {String? password}) async { + if (password == 'bad') throw const HwpPdfException('암호가 틀림'); + return _FakeDoc(this); + } +} + +class _FakeDoc implements HwpPdfDoc { + final _FakeEngine e; + var closed = false; + _FakeDoc(this.e); + @override + int get pageCount => e.pages; + + @override + Future exportPdf(int from, int to, String out, {List fontDirs = const []}) async { + e.calls.add((from, to)); + // 끝나지 않는 변환 (시간 제한 시험): 풀려도 아무것도 쓰지 않는다 (시험이 끝난 뒤 폴더에 쓰지 않게) + if (e.hold != null) { + await e.hold!.future; + throw const HwpPdfException('늦게 끝남'); + } + if (e.failNext > 0) { + e.failNext--; + throw const HwpPdfException('렌더 실패'); + } + final docs = []; + for (var i = from; i < to; i++) { + final png = p.join(e.tempDir, 'pg$i.png'); + // 쪽마다 폭이 달라 (100 + 쪽 번호) 어느 쪽이 그려졌는지 크기로 안다 + File(png).writeAsBytesSync(img.encodePng(img.Image(width: 100 + i, height: 140))); + docs.add(await imagePageDocument(png, tempDir: e.tempDir, sourceName: 'jj_fake_$i')); + } + final outDoc = await PdfDocument.createNew(sourceName: 'jj_fake_out_$from'); + outDoc.pages = [for (final d in docs) d.pages.first]; + File(out).writeAsBytesSync(await outDoc.encodePdf()); + await outDoc.dispose(); + for (final d in docs) { + await d.dispose(); + } + } + + @override + Future close() async => closed = true; +} + +/// 관리자 지시 (10/20): HWP · HWPX 보기 - 보는 쪽의 묶음만 PDF 로 바꾸고 다음 묶음을 미리 만든다 · 시간 제한 · 실패하면 다시 · 닫기. +/// (rhwp 대신 가짜 엔진, PDF 를 그리는 PDFium 이 필요해 Windows 통합 시험) +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('보는 쪽의 묶음 (5쪽) 만 바꾸고 다음 묶음을 미리 · 끝 묶음은 남은 쪽만', (tester) async { + final dir = testTempDir('jj_hwpsrc_'); + final doc = File(p.join(dir.path, 'a.hwp'))..writeAsStringSync('x'); + final engine = _FakeEngine(12, dir.path); + final src = await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, chunk: 5)); + expect(src!.length, 12); + expect(src.pageName(0), '1 / 12'); + final first = await tester.runAsync(() => src.load(0, maxWidth: 200)); + expect(first!.image, isNotNull); + first.dispose(); + await tester.runAsync(() => Future.delayed(const Duration(seconds: 2))); + expect(engine.calls, [(0, 5), (5, 10)], reason: '첫 묶음 + 다음 묶음 미리'); + // 끝 쪽: 남은 2쪽만 (11번째 = 폭 110) + final last = await tester.runAsync(() => src.load(11, maxWidth: 200)); + expect(last!.image, isNotNull); + last.dispose(); + expect(engine.calls.last, (10, 12)); + // 이미 만든 묶음은 다시 만들지 않는다 + final again = await tester.runAsync(() => src.load(3, maxWidth: 200)); + again!.dispose(); + expect(engine.calls.where((c) => c == (0, 5)), hasLength(1)); + await tester.runAsync(src.dispose); + expect(Directory(src.work.path).existsSync(), isFalse, reason: '닫으면 임시 PDF 를 지움'); + }); + + testWidgets('묶음이 실패하면 그 쪽에 이유, 다시 보면 다시 만든다 · 시간 제한 · 열기 실패', (tester) async { + final dir = testTempDir('jj_hwpsrc_'); + final doc = File(p.join(dir.path, 'b.hwpx'))..writeAsStringSync('x'); + final engine = _FakeEngine(3, dir.path)..failNext = 1; + final src = await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, chunk: 5, + timeout: const Duration(seconds: 2))); + Object? err; + await tester.runAsync(() => src!.load(0, maxWidth: 200).then((r) => r.dispose(), onError: (Object e) { + err = e; + })); + expect('$err', contains('렌더 실패')); + final ok = await tester.runAsync(() => src!.load(0, maxWidth: 200)); + expect(ok!.image, isNotNull, reason: '실패한 묶음은 다시'); + ok.dispose(); + await tester.runAsync(src!.dispose); + + // 시간 제한: 끝나지 않는 변환 + final slow = _FakeEngine(3, dir.path)..hold = Completer(); + final s2 = await tester.runAsync(() => HwpSource.open(doc.path, engine: slow, tempDir: dir.path, timeout: const Duration(seconds: 1))); + Object? err2; + await tester.runAsync(() => s2!.load(1, maxWidth: 200).then((r) => r.dispose(), onError: (Object e) { + err2 = e; + })); + expect(err2, isA()); + expect('$err2', contains('1~3')); + slow.hold!.complete(); + await tester.runAsync(s2!.dispose); + + // 열기 실패 (암호 등) 는 그대로 알린다 - 파일 탐색기는 다른 앱으로 연다 + Object? err3; + await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, password: 'bad').then((_) {}, onError: (Object e) { + err3 = e; + })); + expect('$err3', contains('암호')); + }); +} diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart new file mode 100644 index 0000000..8b5c0cf --- /dev/null +++ b/lib/core/hwp_source.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math' as math; + +import 'package:path/path.dart' as p; +import 'package:pdfrx/pdfrx.dart'; + +import 'reader_sources.dart'; +import 'vfs.dart'; +import '../l10n/tr.dart'; + +/// HWP · HWPX 확장자 (앱 안에서 PDF 로 바꿔 보기 - Android, rhwp) +const hwpExtensions = ['hwp', 'hwpx']; + +/// HWP · HWPX → PDF 엔진 (Android: APK 의 libjj_rhwp.so - lib/platform/android/hwp_pdf_ffi.dart, 시험: 가짜) +abstract class HwpPdfEngine { + /// 문서 열기 (암호가 있으면 [password]). 실패하면 [HwpPdfException] + Future open(String path, {String? password}); +} + +/// 연 문서: 쪽 수와 쪽 범위 → PDF +abstract class HwpPdfDoc { + int get pageCount; + + /// [from] 부터 [to] 앞까지 (0 부터) 를 PDF 파일 [out] 으로. [fontDirs] 는 글꼴을 찾을 폴더들 + Future exportPdf(int from, int to, String out, {List fontDirs = const []}); + Future close(); +} + +class HwpPdfException implements Exception { + final String message; + const HwpPdfException(this.message); + @override + String toString() => message; +} + +/// 관리자 지시 (10/20): HWP · HWPX 를 보기 화면으로 - 큰 문서 (편람 383쪽은 PC 에서도 14분) 를 통째로 바꾸지 않고, +/// 보는 쪽이 든 [chunk] 쪽 묶음만 PDF 로 바꿔 보이면서 다음 묶음을 뒤에서 미리 만든다. 묶음마다 [timeout], +/// 닫으면 (dispose) 더 만들지 않는다. 바꾸는 중의 쪽은 보기 화면이 기다리는 표시를 보이고, 실패한 묶음은 그 쪽에 이유가 보인다. +class HwpSource extends ReaderSource { + final String path; + final HwpPdfDoc doc; + final Directory work; + final List fontDirs; + final int chunk; + final Duration timeout; + @override + final String title; + + final _chunks = >{}; + final _opened = []; + var _disposed = false; + + HwpSource._(this.path, this.doc, this.work, this.title, {required this.fontDirs, required this.chunk, required this.timeout}); + + /// 문서를 연다 (WebDAV 등은 [tempDir] 로 받은 뒤). 열지 못하면 [HwpPdfException] + static Future open(String path, + {required HwpPdfEngine engine, + required String tempDir, + String? password, + List fontDirs = const [], + int chunk = 5, + Duration timeout = const Duration(minutes: 3)}) async { + final local = await vLocalCopy(path, tempDir); + final doc = await engine.open(local, password: password); + final work = Directory(p.join(tempDir, 'jj_hwp_${DateTime.now().microsecondsSinceEpoch}'))..createSync(recursive: true); + return HwpSource._(path, doc, work, vBasename(path), fontDirs: fontDirs, chunk: chunk, timeout: timeout); + } + + @override + int get length => doc.pageCount; + @override + String pageName(int i) => '${i + 1} / $length'; + + /// [k] 번째 묶음의 PDF (한 번만 만든다 - 실패하면 다음에 다시) + Future _chunk(int k) => _chunks[k] ??= _make(k).onError((e, st) { + _chunks.remove(k); + Error.throwWithStackTrace(e, st); + }); + + Future _make(int k) async { + if (_disposed) throw StateError(tr('닫힌 문서입니다')); + final from = k * chunk, to = math.min(from + chunk, length); + final out = p.join(work.path, 'p$k.pdf'); + try { + await doc.exportPdf(from, to, out, fontDirs: fontDirs).timeout(timeout); + } on TimeoutException { + throw HwpPdfException(trf('{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다', [from + 1, to, timeout.inSeconds])); + } + if (_disposed) throw StateError(tr('닫힌 문서입니다')); + await pdfrxFlutterInitialize(); + final pdf = await PdfDocument.openFile(out); + if (_disposed) { + await pdf.dispose(); + throw StateError(tr('닫힌 문서입니다')); + } + _opened.add(pdf); + return pdf; + } + + @override + Future load(int i, {required int maxWidth}) async { + final k = i ~/ chunk; + final pdf = await _chunk(k); + // 다음 묶음을 미리 (넘겨 볼 때 기다리지 않게) - 실패해도 여기서는 알리지 않는다 (그 쪽을 볼 때 보임) + if ((k + 1) * chunk < length) unawaited(_chunk(k + 1).then((_) {}, onError: (Object _) {})); + final page = pdf.pages[i - k * chunk]; + final w = maxWidth.toDouble(); + final h = w * page.height / page.width; + final image = await page.render(fullWidth: w, fullHeight: h, backgroundColor: 0xffffffff); + if (image == null) return const ReaderImage(); + try { + return ReaderImage(image: await image.createImage()); + } finally { + image.dispose(); + } + } + + @override + Future dispose() async { + _disposed = true; + for (final d in _opened) { + await d.dispose(); + } + _opened.clear(); + await doc.close(); // 바꾸는 중이면 그것이 끝난 뒤에 닫힌다 (엔진이 한 번에 하나씩) + try { + if (work.existsSync()) work.deleteSync(recursive: true); + } catch (_) {} + } +} diff --git a/lib/platform/android/hwp_pdf_ffi.dart b/lib/platform/android/hwp_pdf_ffi.dart new file mode 100644 index 0000000..6864502 --- /dev/null +++ b/lib/platform/android/hwp_pdf_ffi.dart @@ -0,0 +1,126 @@ +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)); diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index ad4924b..0a54c84 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -25,6 +25,7 @@ import '../core/ftp.dart' show FtpCertException, FtpRegistry; import '../core/smb.dart' show SmbRegistry; import '../core/webdav.dart' show DavRegistry; import '../platform/android/android_storage.dart'; +import '../platform/android/hwp_pdf_ffi.dart' show RhwpEngine; import 'app_actions.dart'; import 'explorer_look.dart'; import 'key_hint.dart'; @@ -50,6 +51,7 @@ import '../app/component_store.dart'; import '../app/components.dart'; import 'zip_page.dart'; import '../core/reader_sources.dart'; +import '../core/hwp_source.dart'; import '../l10n/tr.dart'; import 'explorer_bookmarks.dart'; import 'explorer_search.dart'; @@ -1672,6 +1674,11 @@ class _ExplorerPageState extends State { if (ext == 'pdf') return _viewPdf(path); if (zipExtensions.contains(ext)) return _openZip(path, pane); } + // 관리자 지시 (10/20): Android 의 HWP · HWPX 는 앱 안에서 PDF 로 바꿔 본다 (rhwp - APK 에 들어 있음, 보기 컴포넌트를 켰을 때). + // 열지 못하면 (암호 · 깨진 파일 · 라이브러리 없음) 이유를 알리고 예전처럼 다른 앱으로 + if (Platform.isAndroid && c.settings.components.contains('viewer') && hwpExtensions.contains(extOf(path)) && RhwpEngine.available) { + if (await _viewHwp(path)) return; + } // 문서 미리보기 (Windows: 설치한 변환기로 PDF 로 바꿔 봄) if (c.settings.components.contains('docs') && Platform.isWindows && AppComponent.docExtensions.contains(extOf(path))) { @@ -1783,6 +1790,42 @@ class _ExplorerPageState extends State { await openReader(context, c, src); } + /// 관리자 지시 (10/20): HWP · HWPX → 보기 화면 (보는 쪽부터 몇 쪽씩 PDF 로 바꿈 - [HwpSource]). 열지 못하면 false + Future _viewHwp(String path) async { + final temp = await c.services.storage.tempDirectory(); + if (!mounted) return true; + final dialog = showDialog( + context: context, + barrierDismissible: false, + builder: (_) => AlertDialog( + content: Row(children: [ + const CircularProgressIndicator(), + const SizedBox(width: 16), + Expanded(child: Text(trf('여는 중: {0}', [vBasename(path)]))), + ]), + ), + ); + HwpSource? src; + Object? error; + try { + src = await HwpSource.open(path, engine: RhwpEngine(), tempDir: temp, fontDirs: RhwpEngine.fontDirs); + } catch (e) { + error = e; + } + if (mounted) Navigator.of(context).pop(); + await dialog; + if (src == null) { + _snack(trf('앱 안에서 열지 못해 다른 앱으로 엽니다: {0}', [error])); + return false; + } + if (!mounted) { + await src.dispose(); + return true; + } + await openReader(context, c, src); + return true; + } + Future _viewPdf(String path) async { final temp = await c.services.storage.tempDirectory(); PdfSource src; diff --git a/lib/ui/license_texts.dart b/lib/ui/license_texts.dart index 0084bea..2b193fa 100644 --- a/lib/ui/license_texts.dart +++ b/lib/ui/license_texts.dart @@ -40,6 +40,8 @@ List<(List, String)> get _licenseAssets => [ (['smbj 0.15.0 · asn-one · Bouncy Castle · SLF4J · MBassador (Android SMB)'], 'assets/licenses/SMBJ_LICENSES.txt'), // Tor 브라우저: tor 와 함께 묶인 Libevent · OpenSSL · zlib · zstd (공식 번들의 docs) (['Tor 0.4.9.13 (Libevent · OpenSSL · zlib · Zstandard)'], 'assets/licenses/TOR_LICENSES.txt'), + // 관리자 지시 (10/20): Android 의 HWP · HWPX 보기 (libjj_rhwp.so - rhwp MIT 와 함께 묶인 Rust crate 들) + if (Platform.isAndroid) (['rhwp 0.8.7 (HWP · HWPX → PDF)'], 'assets/licenses/RHWP_LICENSES.txt'), // 175: 함께 빌드한 네이티브 라이브러리 (Dart 쪽 패키지 라이선스와 따로) ([tr('whisper.cpp · ggml (whisper_ggml 안)')], 'assets/licenses/WHISPER_CPP_LICENSE.txt'), (['ONNX Runtime 1.15.1 (Microsoft)'], 'assets/licenses/ONNXRUNTIME_LICENSES.txt'), diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 497168c..71c12c0 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -7,6 +7,7 @@ import 'package:wakelock_plus/wakelock_plus.dart'; import '../app/app_controller.dart'; import '../core/reader_sources.dart'; +import '../core/hwp_source.dart' show HwpSource; import '../core/vfs.dart'; import '../l10n/tr.dart'; import 'ai_image_page.dart'; @@ -159,7 +160,8 @@ class _ReaderPageState extends State { if (d == null) return false; Size turned(Size s) => _turns.isOdd ? Size(s.height, s.width) : s; return autoSplitsPage(turned(d), [for (final e in _dims.entries) if (e.key != page) turned(e.value)], - bundle: src is! ImageFilesSource); + // 관리자 지시 (10/20) · 내 판단: HWP · HWPX 문서의 가로 쪽 (가로 표 등) 은 만화 두 쪽 스캔이 아니라 나누지 않는다 + bundle: src is! ImageFilesSource && src is! HwpSource); } /// 223: 나눠 본 장에서 바로 한 장으로 (설정을 "끄기" 로) diff --git a/tool/build_rhwp_android.sh b/tool/build_rhwp_android.sh new file mode 100644 index 0000000..b92ab5b --- /dev/null +++ b/tool/build_rhwp_android.sh @@ -0,0 +1,95 @@ +#!/bin/sh +# 관리자 지시 (10/20): Android (arm64) 에서 HWP · HWPX 를 PDF 로 바꿔 앱의 보기 화면으로 연다 - rhwp (Rust, MIT) 를 +# tool/rhwp_ffi (얇은 C ABI) 와 함께 NDK 로 빌드해 third_party/rhwp/android/arm64-v8a/libjj_rhwp.so 로 둔다 (rsync · 7zz 와 같은 방식 - +# APK 의 jniLibs 로 들어가 앱이 dart:ffi 로 읽는다). +# 소스: https://github.com/edwardkim/rhwp v0.8.7 = 커밋 1a76570 (고정 - git 커밋 해시가 내용의 SHA-1 지문이다, 고치지 않음). +# 저장소가 크므로 (예제 문서 · PDF 로 4GB 넘음) 빌드에 쓰는 폴더만 받는다 (git sparse, 깊이 1). +# 라이선스: rhwp MIT (LICENSE) · 의존 crate 들 (THIRD_PARTY_LICENSES.md) - 둘 다 third_party/rhwp 에 함께 둔다. +# 도구: Rust 1.93.1 (rhwp 의 rust-toolchain.toml 과 같음, tools/rust - RUSTUP_HOME · CARGO_HOME) + Android NDK clang (링커). +# Windows 에서는 호스트 쪽 (빌드 스크립트) 을 MSVC 로 링크하므로 tools/VSBuildTools 의 vcvars64 환경을 읽어 온다. +# 16KB 쪽 기기 (Android 15+) 에서도 읽히게 -z max-page-size=16384, 다시 만들어도 같은 파일이 되게 경로를 /jj 로 바꿔 적는다. +# 의존 crate 판은 tool/rhwp_ffi/Cargo.lock 으로 고정 (--locked). +# 사용법 (Git Bash): sh tool/build_rhwp_android.sh - Android NDK 가 필요 (ANDROID_NDK 또는 tools/android-sdk/ndk/*) +# 다시 만들어지는지 보기: JJ_RHWP_CHECK=1 sh tool/build_rhwp_android.sh (깨끗한 target 으로 두 번 빌드해 해시를 견줌) +set -e +APP=$(cd "$(dirname "$0")/.." && pwd) +TOOLS=$(cd "$APP/.." && pwd)/tools +VER=0.8.7 +REV=1a76570e833917d15817415a53c09ad61ab3203f +NDK_ROOT=${ANDROID_NDK:-$(ls -d "$TOOLS"/android-sdk/ndk/* | sort | tail -1)} +case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) HOST=windows-x86_64; EXE=.exe; CMD=.cmd ;; Darwin) HOST=darwin-x86_64; EXE=; CMD= ;; *) HOST=linux-x86_64; EXE=; CMD= ;; esac +TC=$NDK_ROOT/toolchains/llvm/prebuilt/$HOST/bin +export RUSTUP_HOME=${RUSTUP_HOME:-$TOOLS/rust/rustup} +export CARGO_HOME=${CARGO_HOME:-$TOOLS/rust/cargo} +export PATH="$CARGO_HOME/bin:$PATH" + +# Windows: 호스트 링커 (MSVC) 환경 +if [ "$HOST" = windows-x86_64 ] && [ -z "$VCINSTALLDIR" ]; then + VC=$(cygpath -w "$TOOLS/VSBuildTools/VC/Auxiliary/Build/vcvars64.bat") + ENVF=$(mktemp) + # cmd 에 따옴표를 넘기면 Git Bash 가 바꿔 버려, 부를 것을 .cmd 파일로 적어 그것을 돌린다 + BAT=$(mktemp --suffix=.cmd) + printf '@call "%s" >nul\r\n@set\r\n' "$VC" > "$BAT" + cmd //c "$(cygpath -w "$BAT")" | tr -d '\r' > "$ENVF" + rm -f "$BAT" + while IFS='=' read -r k v; do + case "$k" in + Path|PATH) PATH="$(cygpath -u -p "$v"):$PATH" ;; + LIB|LIBPATH|INCLUDE|VCINSTALLDIR|VCToolsInstallDir|WindowsSdkDir|WindowsSDKVersion|UCRTVersion|UniversalCRTSdkDir) export "$k=$v" ;; + esac + done < "$ENVF" + rm -f "$ENVF" + export PATH + [ -n "$VCINSTALLDIR" ] || { echo "vcvars64 환경을 읽지 못함: $VC"; exit 1; } +fi + +# 작업 폴더: tools 아래 (빌드 캐시가 수 GB - 시스템 임시 폴더 (C:) 가 모자라 시험이 실패했다, 10/20). 다시 빌드할 때 이어 쓴다 +WORK=${JJ_RHWP_WORK:-$TOOLS/src/rhwp/build} +mkdir -p "$WORK" +SRC=$WORK/rhwp +if [ "$(git -C "$SRC" rev-parse HEAD 2>/dev/null)" != "$REV" ]; then + rm -rf "$SRC" + git init -q "$SRC" + git -C "$SRC" remote add origin https://github.com/edwardkim/rhwp.git + git -C "$SRC" sparse-checkout init --no-cone + printf '%s\n' '/*' '!/samples/' '!/pdf/' '!/mydocs/' '!/saved/' '!/tests/' '!/gym/' '!/rhwp-*/' '!/npm/' '!/typescript/' \ + '!/tools/' '!/fuzz/' '!/scripts/' '/saved/blank2010.hwp' '/mydocs/manual/' '/mydocs/tech/' > "$SRC/.git/info/sparse-checkout" + git -C "$SRC" fetch -q --depth 1 --filter=blob:none origin "$REV" + git -C "$SRC" checkout -q FETCH_HEAD +fi +[ "$(git -C "$SRC" rev-parse HEAD)" = "$REV" ] || { echo "rhwp 커밋이 다름"; exit 1; } +[ -z "$(git -C "$SRC" status --porcelain)" ] || { echo "rhwp 소스가 바뀌어 있음"; exit 1; } + +rm -rf "$WORK/ffi" +cp -r "$APP/tool/rhwp_ffi" "$WORK/ffi" +# 처음 한 번: 의존 판을 rhwp 의 Cargo.lock 그대로 이어받아 tool/rhwp_ffi/Cargo.lock 을 만든다 (그 뒤로는 --locked) +LOCKED=--locked +if [ ! -f "$APP/tool/rhwp_ffi/Cargo.lock" ]; then + cp "$SRC/Cargo.lock" "$WORK/ffi/Cargo.lock" + LOCKED= +fi +export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TC/aarch64-linux-android24-clang$CMD" +export CC_aarch64_linux_android="$TC/aarch64-linux-android24-clang$CMD" +export AR_aarch64_linux_android="$TC/llvm-ar$EXE" +W=$WORK +[ "$HOST" = windows-x86_64 ] && W=$(cygpath -m "$WORK") +export RUSTFLAGS="-C link-arg=-Wl,-z,max-page-size=16384 -C link-arg=-Wl,--build-id=none --remap-path-prefix=$W=/jj --remap-path-prefix=$(cygpath -m "$CARGO_HOME" 2>/dev/null || echo "$CARGO_HOME")=/cargo" +export JJ_RHWP_VERSION=$VER +build() { + cargo build -q --release $LOCKED --target aarch64-linux-android --manifest-path "$WORK/ffi/Cargo.toml" --target-dir "$1" + "$TC/llvm-strip$EXE" -o "$2" "$1/aarch64-linux-android/release/libjj_rhwp.so" +} +OUT="$APP/third_party/rhwp/android/arm64-v8a" +mkdir -p "$OUT" +build "$WORK/target" "$OUT/libjj_rhwp.so" +[ -n "$LOCKED" ] || cp "$WORK/ffi/Cargo.lock" "$APP/tool/rhwp_ffi/Cargo.lock" +cp "$SRC/LICENSE" "$OUT/../LICENSE.txt" +cp "$SRC/THIRD_PARTY_LICENSES.md" "$OUT/../THIRD_PARTY_LICENSES.md" +if [ -n "$JJ_RHWP_CHECK" ]; then + rm -rf "$WORK/target2" + build "$WORK/target2" "$WORK/again.so" + A=$(sha256sum < "$OUT/libjj_rhwp.so"); B=$(sha256sum < "$WORK/again.so") + [ "$A" = "$B" ] && echo "다시 만들어도 같음" || { echo "다시 만들면 다름: $A / $B"; exit 1; } +fi +ls -l "$OUT/libjj_rhwp.so" +sha256sum "$OUT/libjj_rhwp.so" diff --git a/tool/rhwp_ffi/Cargo.lock b/tool/rhwp_ffi/Cargo.lock new file mode 100644 index 0000000..d89770d --- /dev/null +++ b/tool/rhwp_ffi/Cargo.lock @@ -0,0 +1,1928 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", +] + +[[package]] +name = "argon2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "blake2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfb" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f13298d9178622b7916ec5d01d470425c04273c8ed07e87b1c38b34c4f4612b" +dependencies = [ + "fnv", + "uuid", + "web-time", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer", + "crypto-common", + "inout", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "codepage" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdff162541cd8b79de82e2edcc7eff3a8c2a6dc3d75152636028f96d93de3b26" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "ctutils", + "typenum", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "gif 0.14.2", + "moxcms", + "num-traits", + "png 0.18.1", + "tiff", + "zune-core 0.5.3", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jj_rhwp" +version = "0.1.0" +dependencies = [ + "rhwp", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common", + "rand_core", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "crypto-common", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8", + "shake", + "signature", +] + +[[package]] +name = "ml-kem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8", + "rand_core", + "sha3", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "password-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +dependencies = [ + "phc", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pcx" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa08d8cdb97709f15a9cf1bc0edd439a96e8c1d05687272b430b7a3cd8a64b" +dependencies = [ + "byteorder", +] + +[[package]] +name = "pdf-writer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df03c7d216de06f93f398ef06f1385a60f2c597bb96f8195c8d98e08a26b1d5" +dependencies = [ + "bitflags 2.13.1", + "itoa", + "memchr", + "ryu", +] + +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" +dependencies = [ + "cpufeatures", + "universal-hash", +] + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rhwp" +version = "0.8.7" +dependencies = [ + "aes", + "argon2", + "base64 0.23.1", + "blake3", + "byteorder", + "cbc", + "cfb", + "chacha20poly1305", + "cipher", + "codepage", + "crc32fast", + "des", + "ed25519-dalek", + "embedded-io", + "encoding_rs", + "flate2", + "getrandom", + "hmac", + "image", + "js-sys", + "ml-dsa", + "ml-kem", + "paste", + "pbkdf2", + "pcx", + "pdf-writer", + "quick-xml", + "rand_core", + "rhwp-contracts", + "rhwp-ooxml-chart", + "rhwp-password-crypto", + "roxmltree 0.21.1", + "rustybuzz", + "serde", + "serde_json", + "sha1", + "sha2", + "snafu", + "strum", + "subsetter", + "svg2pdf", + "svgtypes 0.16.1", + "ttf-parser", + "unicode-properties", + "unicode-segmentation", + "unicode-width", + "usvg", + "wasm-bindgen", + "web-sys", + "zeroize", + "zip", +] + +[[package]] +name = "rhwp-contracts" +version = "0.1.0" +dependencies = [ + "serde_json", +] + +[[package]] +name = "rhwp-ooxml-chart" +version = "0.1.0" +dependencies = [ + "quick-xml", +] + +[[package]] +name = "rhwp-password-crypto" +version = "0.1.0" +dependencies = [ + "aes", + "base64 0.23.1", + "cbc", + "crc32fast", + "des", + "flate2", + "getrandom", + "hmac", + "pbkdf2", + "quick-xml", + "roxmltree 0.21.1", + "sha1", + "sha2", + "zip", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "snafu" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45cb604038abb7b926b679887b3226d8d0f23874b66623625a0454be425a4b7" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287f59010008f0d7cf5e3b03196d666c1acc46c8d3e9cf34c28a1a7157601e72" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subsetter" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38803281d1c23166c5ebcb455439a5d2afe711cc909cf88af72448c297756ad6" +dependencies = [ + "kurbo 0.13.1", + "rustc-hash", + "skrifa", + "write-fonts", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg2pdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50dc062439cc1a396181059c80932a6e6bd731b130e674c597c0c8874b6df22" +dependencies = [ + "fontdb", + "image", + "log", + "miniz_oxide 0.8.9", + "once_cell", + "pdf-writer", + "resvg", + "siphasher", + "subsetter", + "tiny-skia", + "ttf-parser", + "usvg", +] + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher", +] + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.1", + "siphasher", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common", + "ctutils", +] + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "write-fonts" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" +dependencies = [ + "font-types", + "indexmap", + "kurbo 0.13.1", + "log", + "read-fonts", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core 0.5.3", +] diff --git a/tool/rhwp_ffi/Cargo.toml b/tool/rhwp_ffi/Cargo.toml new file mode 100644 index 0000000..74150e7 --- /dev/null +++ b/tool/rhwp_ffi/Cargo.toml @@ -0,0 +1,20 @@ +# JJ_MKVMaker: HWP · HWPX → PDF (rhwp) 의 C ABI - tool/build_rhwp_android.sh 가 rhwp 소스 (커밋 고정) 옆에 두고 빌드한다 +[package] +name = "jj_rhwp" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# 빌드 스크립트가 ../rhwp 에 rhwp v0.8.7 (커밋 1a76570) 을 받아 둔다 +rhwp = { path = "../rhwp", default-features = false } + +[profile.release] +opt-level = 3 +lto = "thin" +codegen-units = 1 +strip = true diff --git a/tool/rhwp_ffi/rust-toolchain.toml b/tool/rhwp_ffi/rust-toolchain.toml new file mode 100644 index 0000000..f0e07e1 --- /dev/null +++ b/tool/rhwp_ffi/rust-toolchain.toml @@ -0,0 +1,5 @@ +# rhwp v0.8.7 과 같은 판 (rhwp 의 rust-toolchain.toml) +[toolchain] +channel = "1.93.1" +targets = ["aarch64-linux-android"] +profile = "minimal" diff --git a/tool/rhwp_ffi/src/lib.rs b/tool/rhwp_ffi/src/lib.rs new file mode 100644 index 0000000..f801f96 --- /dev/null +++ b/tool/rhwp_ffi/src/lib.rs @@ -0,0 +1,137 @@ +//! JJ_MKVMaker: HWP · HWPX 를 PDF 로 바꾸는 얇은 C ABI (rhwp, MIT - https://github.com/edwardkim/rhwp). +//! 관리자 지시 (10/20): Android 에서 HWP · HWPX 를 앱의 보기 화면 (PDF) 으로 연다 - 문서를 한 번 열고, 쪽 범위마다 PDF 를 만든다 +//! (큰 문서는 앞쪽부터 몇 쪽씩 보이면서 뒤를 이어 만든다). Dart 는 dart:ffi 로 부른다 (lib/platform/android/hwp_pdf.dart). +//! 오류 글 · 돌려준 글은 jj_rhwp_free 로 푼다. 어느 함수도 panic 을 C 쪽으로 넘기지 않는다. + +use std::ffi::{c_char, c_int, CStr, CString}; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::path::PathBuf; +use std::ptr::null_mut; + +use rhwp::document_core::DocumentCore; +use rhwp::renderer::pdf::PdfExportOptions; + +/// 연 문서 +pub struct JjRhwpDoc { + core: DocumentCore, +} + +fn text(p: *const c_char) -> Option { + if p.is_null() { + return None; + } + Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()) +} + +fn set_err(err: *mut *mut c_char, msg: &str) { + if err.is_null() { + return; + } + let c = CString::new(msg.replace('\0', " ")).unwrap_or_default(); + unsafe { *err = c.into_raw() }; +} + +fn guarded(err: *mut *mut c_char, fail: T, f: impl FnOnce() -> Result) -> T { + match catch_unwind(AssertUnwindSafe(f)) { + Ok(Ok(v)) => v, + Ok(Err(e)) => { + set_err(err, &e); + fail + } + Err(p) => { + let msg = p + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| p.downcast_ref::().cloned()) + .unwrap_or_else(|| "panic".to_string()); + set_err(err, &format!("rhwp panic: {msg}")); + fail + } + } +} + +/// 판 (예: "0.8.7") - 라이선스 화면 · 작업 기록용. jj_rhwp_free 로 푼다 +#[no_mangle] +pub extern "C" fn jj_rhwp_version() -> *mut c_char { + CString::new(option_env!("JJ_RHWP_VERSION").unwrap_or("?")).unwrap_or_default().into_raw() +} + +/// 문서 열기 (password 는 null 이나 "" 이면 없음). 실패하면 null 과 err +#[no_mangle] +pub extern "C" fn jj_rhwp_open(path: *const c_char, password: *const c_char, err: *mut *mut c_char) -> *mut JjRhwpDoc { + guarded(err, null_mut(), || { + let path = text(path).ok_or("경로 없음")?; + let data = std::fs::read(&path).map_err(|e| e.to_string())?; + let core = match text(password).filter(|p| !p.is_empty()) { + Some(p) => DocumentCore::from_bytes_with_password(&data, p.as_bytes()), + None => DocumentCore::from_bytes(&data), + } + .map_err(|e| e.to_string())?; + Ok(Box::into_raw(Box::new(JjRhwpDoc { core }))) + }) +} + +/// 쪽 수 (doc 이 null 이면 -1) +#[no_mangle] +pub extern "C" fn jj_rhwp_page_count(doc: *const JjRhwpDoc) -> c_int { + if doc.is_null() { + return -1; + } + let doc = unsafe { &*doc }; + catch_unwind(AssertUnwindSafe(|| doc.core.page_count() as c_int)).unwrap_or(-1) +} + +/// [from, to) 쪽 (0 부터) 을 PDF 파일 out 으로. font_dirs 는 줄바꿈 (\n) 으로 나눈 글꼴 폴더들 (Android 는 시스템 글꼴을 +/// 스스로 찾지 않으므로 /system/fonts 와 앱이 받은 한글 글꼴 폴더를 넘긴다). 성공 0, 실패 -1 과 err +#[no_mangle] +pub extern "C" fn jj_rhwp_export_pdf( + doc: *const JjRhwpDoc, + from: c_int, + to: c_int, + font_dirs: *const c_char, + out: *const c_char, + err: *mut *mut c_char, +) -> c_int { + guarded(err, -1, || { + if doc.is_null() { + return Err("문서 없음".into()); + } + let doc = unsafe { &*doc }; + let count = doc.core.page_count() as c_int; + if from < 0 || to > count || from >= to { + return Err(format!("쪽 범위가 맞지 않음: {from}..{to} / {count}")); + } + let out = text(out).ok_or("저장할 곳 없음")?; + let mut options = PdfExportOptions::default(); + options.font_paths = text(font_dirs) + .unwrap_or_default() + .split('\n') + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + .collect(); + let pages: Vec = (from as u32..to as u32).collect(); + let bytes = doc.core.render_pages_pdf_native_with_options(&pages, &options).map_err(|e| e.to_string())?; + // 다 쓴 뒤에 이름을 바꾼다 (보기 화면이 반쪽 파일을 읽지 않게) + let part = format!("{out}.part"); + std::fs::write(&part, &bytes).map_err(|e| e.to_string())?; + std::fs::rename(&part, &out).map_err(|e| e.to_string())?; + Ok(0) + }) +} + +/// 문서 닫기 +#[no_mangle] +pub extern "C" fn jj_rhwp_close(doc: *mut JjRhwpDoc) { + if !doc.is_null() { + let _ = catch_unwind(AssertUnwindSafe(|| drop(unsafe { Box::from_raw(doc) }))); + } +} + +/// 이 라이브러리가 돌려준 글 (err · 판) 풀기 +#[no_mangle] +pub extern "C" fn jj_rhwp_free(s: *mut c_char) { + if !s.is_null() { + drop(unsafe { CString::from_raw(s) }); + } +}